@legioncodeinc/hive 0.1.0

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 (190) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +262 -0
  3. package/dist/cli-commands.d.ts +41 -0
  4. package/dist/cli-commands.js +76 -0
  5. package/dist/cli-commands.js.map +1 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +46 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/daemon/dashboard/app.js +118 -0
  10. package/dist/daemon/dashboard/host.d.ts +90 -0
  11. package/dist/daemon/dashboard/host.js +172 -0
  12. package/dist/daemon/dashboard/host.js.map +1 -0
  13. package/dist/daemon/dashboard/web-assets.d.ts +88 -0
  14. package/dist/daemon/dashboard/web-assets.js +195 -0
  15. package/dist/daemon/dashboard/web-assets.js.map +1 -0
  16. package/dist/daemon/fleet-status.d.ts +15 -0
  17. package/dist/daemon/fleet-status.js +56 -0
  18. package/dist/daemon/fleet-status.js.map +1 -0
  19. package/dist/daemon/gate.d.ts +55 -0
  20. package/dist/daemon/gate.js +128 -0
  21. package/dist/daemon/gate.js.map +1 -0
  22. package/dist/daemon/proxy.d.ts +38 -0
  23. package/dist/daemon/proxy.js +126 -0
  24. package/dist/daemon/proxy.js.map +1 -0
  25. package/dist/daemon/registry.d.ts +31 -0
  26. package/dist/daemon/registry.js +120 -0
  27. package/dist/daemon/registry.js.map +1 -0
  28. package/dist/daemon/server.d.ts +41 -0
  29. package/dist/daemon/server.js +126 -0
  30. package/dist/daemon/server.js.map +1 -0
  31. package/dist/daemon/setup-auth.d.ts +37 -0
  32. package/dist/daemon/setup-auth.js +51 -0
  33. package/dist/daemon/setup-auth.js.map +1 -0
  34. package/dist/daemon/telemetry-proxy.d.ts +47 -0
  35. package/dist/daemon/telemetry-proxy.js +63 -0
  36. package/dist/daemon/telemetry-proxy.js.map +1 -0
  37. package/dist/dashboard/contracts.d.ts +235 -0
  38. package/dist/dashboard/contracts.js +43 -0
  39. package/dist/dashboard/contracts.js.map +1 -0
  40. package/dist/dashboard/web/app.d.ts +44 -0
  41. package/dist/dashboard/web/app.js +179 -0
  42. package/dist/dashboard/web/app.js.map +1 -0
  43. package/dist/dashboard/web/boot-route.d.ts +23 -0
  44. package/dist/dashboard/web/boot-route.js +29 -0
  45. package/dist/dashboard/web/boot-route.js.map +1 -0
  46. package/dist/dashboard/web/build-graph-button.d.ts +46 -0
  47. package/dist/dashboard/web/build-graph-button.js +81 -0
  48. package/dist/dashboard/web/build-graph-button.js.map +1 -0
  49. package/dist/dashboard/web/buzzing-screen.d.ts +30 -0
  50. package/dist/dashboard/web/buzzing-screen.js +135 -0
  51. package/dist/dashboard/web/buzzing-screen.js.map +1 -0
  52. package/dist/dashboard/web/folder-picker.d.ts +54 -0
  53. package/dist/dashboard/web/folder-picker.js +125 -0
  54. package/dist/dashboard/web/folder-picker.js.map +1 -0
  55. package/dist/dashboard/web/graph-layout.d.ts +101 -0
  56. package/dist/dashboard/web/graph-layout.js +0 -0
  57. package/dist/dashboard/web/graph-layout.js.map +1 -0
  58. package/dist/dashboard/web/harness-strip.d.ts +38 -0
  59. package/dist/dashboard/web/harness-strip.js +65 -0
  60. package/dist/dashboard/web/harness-strip.js.map +1 -0
  61. package/dist/dashboard/web/health-rail.d.ts +26 -0
  62. package/dist/dashboard/web/health-rail.js +61 -0
  63. package/dist/dashboard/web/health-rail.js.map +1 -0
  64. package/dist/dashboard/web/main.d.ts +22 -0
  65. package/dist/dashboard/web/main.js +51 -0
  66. package/dist/dashboard/web/main.js.map +1 -0
  67. package/dist/dashboard/web/needs-project.d.ts +51 -0
  68. package/dist/dashboard/web/needs-project.js +69 -0
  69. package/dist/dashboard/web/needs-project.js.map +1 -0
  70. package/dist/dashboard/web/page-frame.d.ts +95 -0
  71. package/dist/dashboard/web/page-frame.js +98 -0
  72. package/dist/dashboard/web/page-frame.js.map +1 -0
  73. package/dist/dashboard/web/pages/coming-soon.d.ts +23 -0
  74. package/dist/dashboard/web/pages/coming-soon.js +19 -0
  75. package/dist/dashboard/web/pages/coming-soon.js.map +1 -0
  76. package/dist/dashboard/web/pages/dashboard.d.ts +32 -0
  77. package/dist/dashboard/web/pages/dashboard.js +205 -0
  78. package/dist/dashboard/web/pages/dashboard.js.map +1 -0
  79. package/dist/dashboard/web/pages/graph.d.ts +72 -0
  80. package/dist/dashboard/web/pages/graph.js +362 -0
  81. package/dist/dashboard/web/pages/graph.js.map +1 -0
  82. package/dist/dashboard/web/pages/harnesses.d.ts +65 -0
  83. package/dist/dashboard/web/pages/harnesses.js +210 -0
  84. package/dist/dashboard/web/pages/harnesses.js.map +1 -0
  85. package/dist/dashboard/web/pages/health.d.ts +22 -0
  86. package/dist/dashboard/web/pages/health.js +91 -0
  87. package/dist/dashboard/web/pages/health.js.map +1 -0
  88. package/dist/dashboard/web/pages/lifecycle-panel.d.ts +60 -0
  89. package/dist/dashboard/web/pages/lifecycle-panel.js +187 -0
  90. package/dist/dashboard/web/pages/lifecycle-panel.js.map +1 -0
  91. package/dist/dashboard/web/pages/logs.d.ts +55 -0
  92. package/dist/dashboard/web/pages/logs.js +311 -0
  93. package/dist/dashboard/web/pages/logs.js.map +1 -0
  94. package/dist/dashboard/web/pages/memories.d.ts +35 -0
  95. package/dist/dashboard/web/pages/memories.js +439 -0
  96. package/dist/dashboard/web/pages/memories.js.map +1 -0
  97. package/dist/dashboard/web/pages/projects.d.ts +38 -0
  98. package/dist/dashboard/web/pages/projects.js +325 -0
  99. package/dist/dashboard/web/pages/projects.js.map +1 -0
  100. package/dist/dashboard/web/pages/roi-chart.d.ts +52 -0
  101. package/dist/dashboard/web/pages/roi-chart.js +133 -0
  102. package/dist/dashboard/web/pages/roi-chart.js.map +1 -0
  103. package/dist/dashboard/web/pages/roi.d.ts +93 -0
  104. package/dist/dashboard/web/pages/roi.js +375 -0
  105. package/dist/dashboard/web/pages/roi.js.map +1 -0
  106. package/dist/dashboard/web/pages/settings.d.ts +103 -0
  107. package/dist/dashboard/web/pages/settings.js +420 -0
  108. package/dist/dashboard/web/pages/settings.js.map +1 -0
  109. package/dist/dashboard/web/pages/sync.d.ts +59 -0
  110. package/dist/dashboard/web/pages/sync.js +280 -0
  111. package/dist/dashboard/web/pages/sync.js.map +1 -0
  112. package/dist/dashboard/web/panels.d.ts +178 -0
  113. package/dist/dashboard/web/panels.js +471 -0
  114. package/dist/dashboard/web/panels.js.map +1 -0
  115. package/dist/dashboard/web/primitives.d.ts +80 -0
  116. package/dist/dashboard/web/primitives.js +208 -0
  117. package/dist/dashboard/web/primitives.js.map +1 -0
  118. package/dist/dashboard/web/readiness-splash.d.ts +39 -0
  119. package/dist/dashboard/web/readiness-splash.js +201 -0
  120. package/dist/dashboard/web/readiness-splash.js.map +1 -0
  121. package/dist/dashboard/web/registry.d.ts +96 -0
  122. package/dist/dashboard/web/registry.js +99 -0
  123. package/dist/dashboard/web/registry.js.map +1 -0
  124. package/dist/dashboard/web/router.d.ts +48 -0
  125. package/dist/dashboard/web/router.js +76 -0
  126. package/dist/dashboard/web/router.js.map +1 -0
  127. package/dist/dashboard/web/scope-context.d.ts +155 -0
  128. package/dist/dashboard/web/scope-context.js +266 -0
  129. package/dist/dashboard/web/scope-context.js.map +1 -0
  130. package/dist/dashboard/web/service-icons.d.ts +33 -0
  131. package/dist/dashboard/web/service-icons.js +80 -0
  132. package/dist/dashboard/web/service-icons.js.map +1 -0
  133. package/dist/dashboard/web/setup-gate.d.ts +116 -0
  134. package/dist/dashboard/web/setup-gate.js +272 -0
  135. package/dist/dashboard/web/setup-gate.js.map +1 -0
  136. package/dist/dashboard/web/sidebar.d.ts +63 -0
  137. package/dist/dashboard/web/sidebar.js +91 -0
  138. package/dist/dashboard/web/sidebar.js.map +1 -0
  139. package/dist/dashboard/web/use-fleet-telemetry.d.ts +111 -0
  140. package/dist/dashboard/web/use-fleet-telemetry.js +278 -0
  141. package/dist/dashboard/web/use-fleet-telemetry.js.map +1 -0
  142. package/dist/dashboard/web/wire.d.ts +1937 -0
  143. package/dist/dashboard/web/wire.js +1837 -0
  144. package/dist/dashboard/web/wire.js.map +1 -0
  145. package/dist/errors.d.ts +5 -0
  146. package/dist/errors.js +11 -0
  147. package/dist/errors.js.map +1 -0
  148. package/dist/install/registry.d.ts +35 -0
  149. package/dist/install/registry.js +110 -0
  150. package/dist/install/registry.js.map +1 -0
  151. package/dist/lock.d.ts +10 -0
  152. package/dist/lock.js +87 -0
  153. package/dist/lock.js.map +1 -0
  154. package/dist/service/commands.d.ts +15 -0
  155. package/dist/service/commands.js +49 -0
  156. package/dist/service/commands.js.map +1 -0
  157. package/dist/service/index.d.ts +38 -0
  158. package/dist/service/index.js +174 -0
  159. package/dist/service/index.js.map +1 -0
  160. package/dist/service/platform.d.ts +37 -0
  161. package/dist/service/platform.js +67 -0
  162. package/dist/service/platform.js.map +1 -0
  163. package/dist/service/templates.d.ts +10 -0
  164. package/dist/service/templates.js +120 -0
  165. package/dist/service/templates.js.map +1 -0
  166. package/dist/shared/constants.d.ts +15 -0
  167. package/dist/shared/constants.js +19 -0
  168. package/dist/shared/constants.js.map +1 -0
  169. package/dist/shared/daemon-routing.d.ts +11 -0
  170. package/dist/shared/daemon-routing.js +38 -0
  171. package/dist/shared/daemon-routing.js.map +1 -0
  172. package/dist/shared/fleet-readiness.d.ts +18 -0
  173. package/dist/shared/fleet-readiness.js +9 -0
  174. package/dist/shared/fleet-readiness.js.map +1 -0
  175. package/dist/shared/fleet-telemetry.d.ts +68 -0
  176. package/dist/shared/fleet-telemetry.js +61 -0
  177. package/dist/shared/fleet-telemetry.js.map +1 -0
  178. package/dist/shared/lifecycle-flags.d.ts +34 -0
  179. package/dist/shared/lifecycle-flags.js +39 -0
  180. package/dist/shared/lifecycle-flags.js.map +1 -0
  181. package/dist/shared/memory-types.d.ts +55 -0
  182. package/dist/shared/memory-types.js +75 -0
  183. package/dist/shared/memory-types.js.map +1 -0
  184. package/dist/shared/service-status.d.ts +82 -0
  185. package/dist/shared/service-status.js +97 -0
  186. package/dist/shared/service-status.js.map +1 -0
  187. package/dist/telemetry/emit.d.ts +197 -0
  188. package/dist/telemetry/emit.js +196 -0
  189. package/dist/telemetry/emit.js.map +1 -0
  190. package/package.json +50 -0
@@ -0,0 +1,187 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * PRD-058d — the LIFECYCLE HEALTH panel on the Memories page (the operator surface for the four
4
+ * lifecycle engines: recency/activation 058a, conflicts 058b, stale-references 058c, calibration 058e).
5
+ *
6
+ * Renders, off the SHARED `wire` (never `createWireClient`), inside the existing Memories page:
7
+ * - a store-level HEALTH summary: the aggregate health badge `H`, a freshness read, the open-conflict
8
+ * count, the stale-ref count, and the calibration ECE (US-55d.2.1);
9
+ * - the conflict QUEUE with a per-conflict RESOLVE action that calls the 058b
10
+ * `POST /api/memories/conflicts/:id/resolve` endpoint and POLLS to convergence (US-55d.2.2 —
11
+ * never a single immediate read-back, the DeepLake eventual-consistency rule);
12
+ * - the stale-reference list + count (US-55d.2.1);
13
+ * - the calibration view: the ECE/Brier + a reliability diagram from `GET /api/memories/calibration`
14
+ * (the 058e introspection payload, US-55d.2.4).
15
+ *
16
+ * It REUSES the PRD-040 memories-page data-fetching (the same `wire`) and the PRD-029 degradation
17
+ * states (loading / empty / degraded). A term whose producing engine is OFF renders as INERT (the
18
+ * honest empty state), NOT as an error — calibration dormant shows "calibration dormant", an empty
19
+ * conflict queue shows "no open conflicts", and the health scalar degrades to the live terms (an
20
+ * install with every engine off reads `H = 1`, not a phantom demotion).
21
+ *
22
+ * Security: every value renders as ESCAPED React text (never `dangerouslySetInnerHTML`); no
23
+ * token/secret rides any list/badge/diagram line (the wire shapes carry none by construction). Every
24
+ * visual value is an existing `var(--…)` DS token.
25
+ */
26
+ import React from "react";
27
+ import { Badge, Button } from "../primitives.js";
28
+ import { EMPTY_CALIBRATION } from "../wire.js";
29
+ /** The poll-to-convergence budget for a resolve read-back (mirrors the page's re-read recipe). */
30
+ const RESOLVE_POLL_ATTEMPTS = 6;
31
+ const RESOLVE_POLL_DELAY_MS = 150;
32
+ /** Sleep `ms` (the poll-until-convergence delay). */
33
+ function sleep(ms) {
34
+ return new Promise((resolve) => setTimeout(resolve, ms));
35
+ }
36
+ /**
37
+ * Call a lifecycle wire method GUARDED against a thin/older wire that does not implement it: if the
38
+ * method is absent (or throws), degrade to `fallback` rather than crashing the panel. This mirrors the
39
+ * wire layer's "every read degrades to a safe value, never a throw into React" posture so a page that
40
+ * mounts the panel with a non-lifecycle wire (an out-of-scope suite, an older daemon shape) renders the
41
+ * inert state instead of an unhandled rejection. Pure delegation otherwise.
42
+ */
43
+ async function readLifecycle(wire, method, fallback, ...args) {
44
+ const fn = wire[method];
45
+ if (typeof fn !== "function")
46
+ return fallback;
47
+ try {
48
+ return (await fn.apply(wire, args)) ?? fallback;
49
+ }
50
+ catch {
51
+ return fallback;
52
+ }
53
+ }
54
+ /** The shared surface card style (matches the page's panel rhythm). */
55
+ const SURFACE = {
56
+ padding: 16,
57
+ background: "var(--bg-surface)",
58
+ border: "1px solid var(--border-default)",
59
+ borderRadius: "var(--radius-lg)",
60
+ };
61
+ /** A muted mono label → value stat tile for the health summary row. */
62
+ function Stat({ label, value }) {
63
+ return (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 2, minWidth: 96 }, children: [_jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: 10, color: "var(--text-tertiary)", textTransform: "uppercase", letterSpacing: "0.07em" }, children: label }), _jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: 16, fontWeight: 600, color: "var(--text-primary)" }, children: value })] }));
64
+ }
65
+ /** The health badge tone by band: ≥0.66 verified, ≥0.33 honey, else warning. Pure. */
66
+ export function healthTone(h) {
67
+ if (h >= 0.66)
68
+ return "verified";
69
+ if (h >= 0.33)
70
+ return "honey";
71
+ return "warning";
72
+ }
73
+ /**
74
+ * Assemble the store-level health scalar `H = A · C · (1 − σ) · κ` for the BADGE (058d read-side
75
+ * projection). Inputs come from the already-emitted aggregates: `freshness` (A), the calibration
76
+ * `1 − ece` proxy for trust (C — when calibration is dormant, identity 1), the stale-ref fraction
77
+ * (σ), and the open-conflict presence as a soft κ proxy. Each absent/dormant term is its IDENTITY,
78
+ * so the badge degrades to the live terms. Pure; bounded `[0,1]`.
79
+ */
80
+ export function assembleStoreHealth(inputs) {
81
+ const clamp = (x, fallback) => {
82
+ if (x === undefined || !Number.isFinite(x))
83
+ return fallback;
84
+ return Math.min(1, Math.max(0, x));
85
+ };
86
+ return clamp(inputs.freshness, 1) * clamp(inputs.calibrationConfidence, 1) * (1 - clamp(inputs.staleness, 0)) * clamp(inputs.kappa, 1);
87
+ }
88
+ /**
89
+ * Render the freshness (`A`) stat value: the bounded score when a read-side activation/freshness
90
+ * aggregate is available, else the inert dash (the wire carries no per-store `A` today, so freshness
91
+ * is dormant, NOT calibrated confidence mislabeled). Kept as a small pure helper so the freshness term
92
+ * threads cleanly the day the wire surfaces an `A` aggregate. Pure.
93
+ */
94
+ export function freshnessLabel(freshness) {
95
+ return freshness !== undefined && Number.isFinite(freshness) ? Math.min(1, Math.max(0, freshness)).toFixed(2) : "—";
96
+ }
97
+ /** One reliability-diagram row rendered as a horizontal bar (predicted vs the diagonal). */
98
+ function ReliabilityRow({ lower, upper, accuracy }) {
99
+ const pct = Math.round(Math.min(1, Math.max(0, accuracy)) * 100);
100
+ return (_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [_jsxs("span", { style: { fontFamily: "var(--font-mono)", fontSize: 10, color: "var(--text-tertiary)", minWidth: 64 }, children: [lower.toFixed(2), "\u2013", upper.toFixed(2)] }), _jsx("div", { style: { flex: 1, height: 8, background: "var(--bg-subtle)", borderRadius: 4, overflow: "hidden" }, children: _jsx("div", { style: { width: `${pct}%`, height: "100%", background: "var(--honey)" } }) }), _jsxs("span", { style: { fontFamily: "var(--font-mono)", fontSize: 10, color: "var(--text-secondary)", minWidth: 32, textAlign: "right" }, children: [pct, "%"] })] }));
101
+ }
102
+ /** The conflict resolve verdict choices the dashboard offers (mirrors the daemon's safe-default order). */
103
+ const RESOLVE_VERDICTS = ["review", "supersede", "keep-both"];
104
+ /** One conflict row with an inline resolve control (calls the 058b endpoint, polls to convergence). */
105
+ function ConflictRow({ conflict, onResolve, }) {
106
+ const [verdict, setVerdict] = React.useState("review");
107
+ const [winner, setWinner] = React.useState(conflict.memoryAId);
108
+ const [busy, setBusy] = React.useState(false);
109
+ const submit = async () => {
110
+ if (busy)
111
+ return;
112
+ setBusy(true);
113
+ // supersede REQUIRES a winner (the daemon 400s otherwise); the picker defaults to side A.
114
+ await onResolve(conflict.id, verdict, verdict === "supersede" ? winner : "");
115
+ setBusy(false);
116
+ };
117
+ return (_jsxs("div", { "data-testid": "conflict-row", "data-conflict-id": conflict.id, style: { display: "flex", flexDirection: "column", gap: 8, padding: "10px 12px", background: "var(--bg-elevated)", border: "1px solid var(--border-default)", borderRadius: "var(--radius-md)" }, children: [_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }, children: [_jsx(Badge, { tone: "warning", mono: true, dot: true, children: conflict.status || "open" }), _jsxs("span", { style: { fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--text-secondary)", wordBreak: "break-all" }, children: [conflict.memoryAId, " \u21C4 ", conflict.memoryBId] })] }), _jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }, children: [_jsx("select", { "aria-label": "verdict", value: verdict, onChange: (e) => setVerdict(e.target.value), style: { height: 32, padding: "0 8px", background: "var(--bg-surface)", border: "1px solid var(--border-default)", borderRadius: "var(--radius-md)", color: "var(--text-primary)", fontFamily: "var(--font-mono)", fontSize: 12 }, children: RESOLVE_VERDICTS.map((v) => (_jsx("option", { value: v, children: v }, v))) }), verdict === "supersede" && (_jsxs("select", { "aria-label": "winner", value: winner, onChange: (e) => setWinner(e.target.value), style: { height: 32, padding: "0 8px", background: "var(--bg-surface)", border: "1px solid var(--border-default)", borderRadius: "var(--radius-md)", color: "var(--text-primary)", fontFamily: "var(--font-mono)", fontSize: 12, maxWidth: 180 }, children: [_jsxs("option", { value: conflict.memoryAId, children: ["winner: ", conflict.memoryAId] }), _jsxs("option", { value: conflict.memoryBId, children: ["winner: ", conflict.memoryBId] })] })), _jsx(Button, { variant: "secondary", size: "sm", onClick: () => void submit(), disabled: busy, children: busy ? "…" : "Resolve" })] })] }));
118
+ }
119
+ /**
120
+ * The Lifecycle HEALTH panel (058d). Hydrates the conflict queue, the stale-ref list, and the
121
+ * calibration introspection on mount; resolves a conflict through the 058b endpoint and POLLS the
122
+ * queue to convergence (the resolved conflict drops out of the `open` list). Every read degrades to
123
+ * an honest empty/inert state on failure (PRD-029), never a throw.
124
+ */
125
+ export function LifecycleHealthPanel({ wire }) {
126
+ const [conflicts, setConflicts] = React.useState([]);
127
+ const [staleRefs, setStaleRefs] = React.useState([]);
128
+ const [calibration, setCalibration] = React.useState(EMPTY_CALIBRATION);
129
+ const [hydrated, setHydrated] = React.useState(false);
130
+ const reloadConflicts = React.useCallback(async () => {
131
+ const rows = await readLifecycle(wire, "lifecycleConflicts", [], "open");
132
+ setConflicts(rows);
133
+ return rows;
134
+ }, [wire]);
135
+ // US-55d.2.1: hydrate the three lifecycle reads on mount (the same `wire` the page already uses).
136
+ // Each call is GUARDED against a thin/older wire that does not implement the method (degrade to the
137
+ // empty value, never an unhandled rejection — the wire layer's "degrade, never throw" posture).
138
+ React.useEffect(() => {
139
+ let alive = true;
140
+ void (async () => {
141
+ const [c, s, cal] = await Promise.all([
142
+ readLifecycle(wire, "lifecycleConflicts", [], "open"),
143
+ readLifecycle(wire, "lifecycleStaleRefs", []),
144
+ readLifecycle(wire, "calibration", EMPTY_CALIBRATION),
145
+ ]);
146
+ if (!alive)
147
+ return;
148
+ setConflicts(c);
149
+ setStaleRefs(s);
150
+ setCalibration(cal);
151
+ setHydrated(true);
152
+ })();
153
+ return () => {
154
+ alive = false;
155
+ };
156
+ }, [wire]);
157
+ // US-55d.2.2: resolve through the 058b endpoint, then POLL the open queue to convergence (the
158
+ // resolved conflict drops out). Never a single immediate read-back — the DeepLake consistency rule.
159
+ const onResolve = React.useCallback(async (id, verdict, winnerId) => {
160
+ const ok = typeof wire.resolveConflict === "function"
161
+ ? await wire.resolveConflict(id, { verdict, ...(winnerId !== "" ? { winnerId } : {}) })
162
+ : false;
163
+ if (!ok) {
164
+ // Honest failure: re-read so the row reflects the persisted (unchanged) state, no optimistic drop.
165
+ await reloadConflicts();
166
+ return;
167
+ }
168
+ for (let attempt = 0; attempt < RESOLVE_POLL_ATTEMPTS; attempt += 1) {
169
+ const rows = await reloadConflicts();
170
+ if (!rows.some((c) => c.id === id))
171
+ return; // converged: the resolved conflict left the open queue.
172
+ await sleep(RESOLVE_POLL_DELAY_MS);
173
+ }
174
+ }, [wire, reloadConflicts]);
175
+ // The store-level health badge `H = A · C · (1 − σ) · κ` (058d read-side projection). Each dormant
176
+ // term is its IDENTITY. The wire feeds this panel conflicts (κ), stale refs (σ), and calibration (C)
177
+ // only; it carries NO per-store freshness/activation aggregate (A), so A is genuinely UNAVAILABLE
178
+ // here: it stays `undefined` (→ identity 1 in `assembleStoreHealth`) and the freshness Stat renders
179
+ // INERT ("—") rather than mislabeling C as freshness. C (calibrated confidence) gets its OWN row.
180
+ const freshness = undefined; // A: no read-side store freshness aggregate on the wire (058d).
181
+ const staleness = staleRefs.length > 0 ? Math.min(1, staleRefs.length / Math.max(staleRefs.length, 8)) : 0;
182
+ const calibrationConfidence = calibration.identity ? 1 : Math.max(0, 1 - calibration.ece);
183
+ const kappa = conflicts.length > 0 ? 0.5 : 1;
184
+ const health = assembleStoreHealth({ freshness, calibrationConfidence, staleness, kappa });
185
+ return (_jsxs("div", { "data-testid": "lifecycle-panel", style: { ...SURFACE, display: "flex", flexDirection: "column", gap: 16 }, children: [_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [_jsx("div", { style: { fontSize: 14, fontWeight: 600, color: "var(--text-primary)" }, children: "Memory health" }), _jsx(Badge, { tone: healthTone(health), mono: true, dot: true, children: _jsxs("span", { "data-testid": "health-badge", children: ["H ", health.toFixed(2)] }) })] }), _jsxs("div", { style: { display: "flex", gap: 18, flexWrap: "wrap" }, children: [_jsx(Stat, { label: "freshness", value: freshnessLabel(freshness) }), _jsx(Stat, { label: "calibrated confidence", value: calibration.identity ? "—" : calibrationConfidence.toFixed(2) }), _jsx(Stat, { label: "open conflicts", value: String(conflicts.length) }), _jsx(Stat, { label: "stale refs", value: String(staleRefs.length) }), _jsx(Stat, { label: "ECE", value: calibration.identity ? "dormant" : calibration.ece.toFixed(3) })] }), _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [_jsxs("div", { style: { fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--text-tertiary)", textTransform: "uppercase", letterSpacing: "0.07em" }, children: ["Conflicts (", conflicts.length, ")"] }), !hydrated ? (_jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--text-tertiary)" }, children: "loading\u2026" })) : conflicts.length === 0 ? (_jsx("span", { "data-testid": "conflicts-empty", style: { fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--text-tertiary)" }, children: "no open conflicts." })) : (conflicts.map((c) => _jsx(ConflictRow, { conflict: c, onResolve: onResolve }, c.id)))] }), _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: [_jsxs("div", { style: { fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--text-tertiary)", textTransform: "uppercase", letterSpacing: "0.07em" }, children: ["Stale references (", staleRefs.length, ")"] }), !hydrated ? (_jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--text-tertiary)" }, children: "loading\u2026" })) : staleRefs.length === 0 ? (_jsx("span", { "data-testid": "stale-refs-empty", style: { fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--text-tertiary)" }, children: "no stale references." })) : (staleRefs.map((r) => (_jsxs("div", { "data-testid": "stale-ref-row", style: { display: "flex", gap: 8, fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--text-secondary)" }, children: [_jsx("span", { style: { color: "var(--honey)", wordBreak: "break-all" }, children: r.memoryId }), _jsx("span", { style: { color: "var(--text-tertiary)" }, children: r.staleRefs.length > 0 ? r.staleRefs.join(", ") : "(refs unrecorded)" })] }, r.memoryId))))] }), _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [_jsx("div", { style: { fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--text-tertiary)", textTransform: "uppercase", letterSpacing: "0.07em" }, children: "Calibration" }), calibration.identity ? (_jsx("span", { "data-testid": "calibration-dormant", style: { fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--text-tertiary)" }, children: "calibration dormant \u2014 confidence weighting (c) stays 0 until the curve is fit." })) : (_jsxs("div", { "data-testid": "calibration-view", style: { display: "flex", flexDirection: "column", gap: 8 }, children: [_jsxs("div", { style: { display: "flex", gap: 18, flexWrap: "wrap" }, children: [_jsx(Stat, { label: "ECE", value: calibration.ece.toFixed(3) }), _jsx(Stat, { label: "Brier", value: calibration.brier.toFixed(3) }), _jsx(Stat, { label: "samples", value: String(calibration.nSamples) })] }), _jsx("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: calibration.reliabilityDiagram.map((b) => (_jsx(ReliabilityRow, { lower: b.lower, upper: b.upper, accuracy: b.accuracy }, `${b.lower}-${b.upper}`))) })] }))] })] }));
186
+ }
187
+ //# sourceMappingURL=lifecycle-panel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle-panel.js","sourceRoot":"","sources":["../../../../src/dashboard/web/pages/lifecycle-panel.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The LOGS page — PRD-043 (durable history + live tail + browsable Turns drill-down).
3
+ *
4
+ * Mounted at the PRD-037 `#/logs` slot (replacing the `ComingSoon` placeholder). ONE page, three
5
+ * mutually-referential surfaces over the INJECTED `wire` (never `createWireClient`):
6
+ *
7
+ * 043b — REQUEST-LOG view (the default tab):
8
+ * · LIVE TAIL (collapsible, on TOP — OQ-2 stacked) reusing the existing `/api/logs/stream` SSE
9
+ * follow (`wire.logsStream`, shared with the Sync activity feed — D-2, not re-implemented).
10
+ * · HISTORY TABLE (below) fed by the durable `/api/logs/history` (`wire.logsHistory`), with
11
+ * filter controls (time range · status/level incl. a `5xx` class · path exact/prefix ·
12
+ * harness/org) that refetch page one, plus cursor pagination ("load more", no dup/gap rows).
13
+ * · The live tail + history SHARE ONE `LogRow` renderer so they never drift (D-1). They are kept
14
+ * visually SEPARATE and NOT de-duped across each other (D-1 / OQ-3).
15
+ *
16
+ * 043c — TURNS view (a tab on the SAME page):
17
+ * · A browsable list of captured turns (newest first) read from DeepLake via the existing
18
+ * `fetchSessionsView` path (`wire.turnsHistory` → `/api/diagnostics/sessions`, NOT SQLite — D-2),
19
+ * with cursor pagination, and DRILL-DOWN to a single turn's metadata + back.
20
+ * · Every user-facing string says "Turns"/"turn" (PRD-035a — grep-proven, AC-4). METADATA ONLY:
21
+ * never a transcript/body/JSONB/secret (D-4 / AC-5).
22
+ *
23
+ * Status renders as a LEVEL via existing Badge tones (2xx ok / 4xx warn / 5xx critical) — no new
24
+ * color ramp (D-4). Every visual value is an existing DS token + an existing primitive
25
+ * (`Badge`/`Button`/`Panel`/`PageFrame`). NO new dependency, NO CDN React, NO secret on screen (D-5).
26
+ */
27
+ import React from "react";
28
+ import { type BadgeTone } from "../primitives.js";
29
+ import type { PageProps } from "../page-frame.js";
30
+ import { type LogRecordWire } from "../wire.js";
31
+ /** Map an HTTP status code to a DS Badge tone — the log-level color language (D-4, no new ramp). */
32
+ export declare function statusTone(status: number): BadgeTone;
33
+ /**
34
+ * One request-log row (D-1 — the SINGLE renderer shared by the live tail + the history table, so
35
+ * the two never drift). Columns: time · method + path · status (as a level badge) · duration ·
36
+ * harness/org. Every field comes from a secret-free `LogRecordWire` (D-5) — no header/token/body.
37
+ */
38
+ export declare function LogRow({ record, live }: {
39
+ record: LogRecordWire;
40
+ live?: boolean;
41
+ }): React.JSX.Element;
42
+ /**
43
+ * Append one SSE-tailed record to the live buffer (newest FIRST), capped (D-1 — shares the LogRow
44
+ * renderer with the history table). Pure + exported so the page test drives the follow handler
45
+ * deterministically (inject a record, assert it lands) without a live EventSource.
46
+ */
47
+ export declare function appendLiveRecord(buffer: readonly LogRecordWire[], record: LogRecordWire): readonly LogRecordWire[];
48
+ /**
49
+ * The Logs page (043b + 043c). Hydrates the durable history + the live tail (request log) and the
50
+ * browsable Turns history (DeepLake) over the shared `wire`. A tab switches between the two
51
+ * surfaces; within Requests, the live tail (collapsible, top) + the filtered/paginated history
52
+ * (below) stack. All hydration is fetch-on-mount + explicit refetch (filters/load-more) — the live
53
+ * tail follows the SSE stream (no poll). Loading/empty/error states are explicit everywhere.
54
+ */
55
+ export declare function LogsPage({ wire, daemonUp }: PageProps): React.JSX.Element;
@@ -0,0 +1,311 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * The LOGS page — PRD-043 (durable history + live tail + browsable Turns drill-down).
4
+ *
5
+ * Mounted at the PRD-037 `#/logs` slot (replacing the `ComingSoon` placeholder). ONE page, three
6
+ * mutually-referential surfaces over the INJECTED `wire` (never `createWireClient`):
7
+ *
8
+ * 043b — REQUEST-LOG view (the default tab):
9
+ * · LIVE TAIL (collapsible, on TOP — OQ-2 stacked) reusing the existing `/api/logs/stream` SSE
10
+ * follow (`wire.logsStream`, shared with the Sync activity feed — D-2, not re-implemented).
11
+ * · HISTORY TABLE (below) fed by the durable `/api/logs/history` (`wire.logsHistory`), with
12
+ * filter controls (time range · status/level incl. a `5xx` class · path exact/prefix ·
13
+ * harness/org) that refetch page one, plus cursor pagination ("load more", no dup/gap rows).
14
+ * · The live tail + history SHARE ONE `LogRow` renderer so they never drift (D-1). They are kept
15
+ * visually SEPARATE and NOT de-duped across each other (D-1 / OQ-3).
16
+ *
17
+ * 043c — TURNS view (a tab on the SAME page):
18
+ * · A browsable list of captured turns (newest first) read from DeepLake via the existing
19
+ * `fetchSessionsView` path (`wire.turnsHistory` → `/api/diagnostics/sessions`, NOT SQLite — D-2),
20
+ * with cursor pagination, and DRILL-DOWN to a single turn's metadata + back.
21
+ * · Every user-facing string says "Turns"/"turn" (PRD-035a — grep-proven, AC-4). METADATA ONLY:
22
+ * never a transcript/body/JSONB/secret (D-4 / AC-5).
23
+ *
24
+ * Status renders as a LEVEL via existing Badge tones (2xx ok / 4xx warn / 5xx critical) — no new
25
+ * color ramp (D-4). Every visual value is an existing DS token + an existing primitive
26
+ * (`Badge`/`Button`/`Panel`/`PageFrame`). NO new dependency, NO CDN React, NO secret on screen (D-5).
27
+ */
28
+ import React from "react";
29
+ import { Badge, Button, Input } from "../primitives.js";
30
+ import { Panel } from "../panels.js";
31
+ import { PageFrame } from "../page-frame.js";
32
+ import { EMPTY_LOGS_HISTORY, } from "../wire.js";
33
+ /** How many live-tail rows the collapsible feed keeps (the SSE-followed buffer cap). */
34
+ const MAX_LIVE_LINES = 30;
35
+ /** The default history page size the table requests (the daemon clamps to MAX_HISTORY_LIMIT). */
36
+ const HISTORY_PAGE_SIZE = 50;
37
+ /** The default turns page size the Turns list requests. */
38
+ const TURNS_PAGE_SIZE = 50;
39
+ // ─────────────────────────────────────────────────────────────────────────────
40
+ // Status → DS tone (D-4): 2xx ok · 4xx warn · 5xx critical · other neutral.
41
+ // ─────────────────────────────────────────────────────────────────────────────
42
+ /** Map an HTTP status code to a DS Badge tone — the log-level color language (D-4, no new ramp). */
43
+ export function statusTone(status) {
44
+ if (status >= 500)
45
+ return "critical";
46
+ if (status >= 400)
47
+ return "warning";
48
+ if (status >= 200 && status < 300)
49
+ return "verified";
50
+ return "neutral";
51
+ }
52
+ /** Short, secret-free time-of-day from an ISO timestamp (HH:MM:SS), matching `formatLogLine`. */
53
+ function shortTime(iso) {
54
+ return (iso || "").slice(11, 19) || (iso || "").slice(0, 8);
55
+ }
56
+ // ─────────────────────────────────────────────────────────────────────────────
57
+ // The SHARED row renderer (D-1) — used by BOTH the live tail and the history table.
58
+ // ─────────────────────────────────────────────────────────────────────────────
59
+ /**
60
+ * One request-log row (D-1 — the SINGLE renderer shared by the live tail + the history table, so
61
+ * the two never drift). Columns: time · method + path · status (as a level badge) · duration ·
62
+ * harness/org. Every field comes from a secret-free `LogRecordWire` (D-5) — no header/token/body.
63
+ */
64
+ export function LogRow({ record, live }) {
65
+ const status = record.status;
66
+ const org = record.org ?? "";
67
+ const duration = record.durationMs;
68
+ return (_jsxs("div", { "data-testid": "log-row", style: {
69
+ display: "grid",
70
+ gridTemplateColumns: "76px 64px 1fr auto auto auto",
71
+ alignItems: "center",
72
+ gap: 12,
73
+ padding: "8px 6px",
74
+ borderTop: "1px solid var(--border-subtle)",
75
+ }, children: [_jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: 12, color: live ? "var(--text-primary)" : "var(--text-tertiary)" }, children: shortTime(record.time) }), _jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: 12, fontWeight: 600, color: "var(--text-secondary)" }, children: record.method }), _jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: 13, color: "var(--text-primary)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, title: record.path, children: record.path }), _jsx(Badge, { tone: statusTone(status), mono: true, children: status > 0 ? status : "—" }), _jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--text-tertiary)", textAlign: "right", minWidth: 48 }, children: typeof duration === "number" ? `${Math.round(duration)}ms` : "" }), _jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--text-tertiary)", textAlign: "right", minWidth: 60 }, children: org })] }));
76
+ }
77
+ /** The table header row (mirrors the LogRow grid so columns line up). */
78
+ function LogTableHeader() {
79
+ const cell = (label, align = "left") => (_jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: 10, textTransform: "uppercase", letterSpacing: "0.06em", color: "var(--text-tertiary)", textAlign: align }, children: label }));
80
+ return (_jsxs("div", { style: { display: "grid", gridTemplateColumns: "76px 64px 1fr auto auto auto", alignItems: "center", gap: 12, padding: "4px 6px 8px" }, children: [cell("time"), cell("method"), cell("path"), cell("status"), cell("dur", "right"), cell("org", "right")] }));
81
+ }
82
+ /** The empty filter state — an unfiltered newest page. */
83
+ const EMPTY_FILTERS = { since: "", until: "", status: "", path: "", org: "" };
84
+ /** Map the editable filter state to the wire filters (omitting empties so the daemon defaults). */
85
+ function toWireFilters(state) {
86
+ const f = { limit: HISTORY_PAGE_SIZE };
87
+ if (state.since !== "")
88
+ f.since = state.since;
89
+ if (state.until !== "")
90
+ f.until = state.until;
91
+ if (state.status !== "")
92
+ f.status = state.status;
93
+ if (state.path !== "")
94
+ f.path = state.path;
95
+ if (state.org !== "")
96
+ f.org = state.org;
97
+ return f;
98
+ }
99
+ /** A labeled filter input (mono, compact) reusing the DS `Input` primitive. */
100
+ function FilterField({ label, value, placeholder, onChange, testid, }) {
101
+ return (_jsxs("label", { style: { display: "flex", flexDirection: "column", gap: 4, minWidth: 130, flex: "1 1 130px" }, children: [_jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: 10, textTransform: "uppercase", letterSpacing: "0.06em", color: "var(--text-tertiary)" }, children: label }), _jsx(Input, { mono: true, size: "sm", value: value, placeholder: placeholder, onChange: (e) => onChange(e.target.value), "data-testid": testid })] }));
102
+ }
103
+ /** The filter bar (FR-2): time range · status/level · path · harness/org + Apply / Clear. */
104
+ function FilterBar({ draft, onChangeField, onApply, onClear, }) {
105
+ return (_jsx(Panel, { title: "Filters", eyebrow: "time \u00B7 status \u00B7 path \u00B7 harness", children: _jsxs("div", { style: { display: "flex", flexWrap: "wrap", gap: 12, alignItems: "flex-end" }, children: [_jsx(FilterField, { label: "since (ISO)", value: draft.since, placeholder: "2026-06-22T00:00:00Z", onChange: (v) => onChangeField("since", v), testid: "filter-since" }), _jsx(FilterField, { label: "until (ISO)", value: draft.until, placeholder: "2026-06-22T23:59:59Z", onChange: (v) => onChangeField("until", v), testid: "filter-until" }), _jsx(FilterField, { label: "status / level", value: draft.status, placeholder: "5xx or 404", onChange: (v) => onChangeField("status", v), testid: "filter-status" }), _jsx(FilterField, { label: "path", value: draft.path, placeholder: "/api/memories", onChange: (v) => onChangeField("path", v), testid: "filter-path" }), _jsx(FilterField, { label: "harness / org", value: draft.org, placeholder: "org id", onChange: (v) => onChangeField("org", v), testid: "filter-org" }), _jsxs("div", { style: { display: "flex", gap: 8 }, children: [_jsx(Button, { variant: "primary", size: "sm", "data-testid": "filter-apply", onClick: onApply, children: "Apply" }), _jsx(Button, { variant: "ghost", size: "sm", "data-testid": "filter-clear", onClick: onClear, children: "Clear" })] })] }) }));
106
+ }
107
+ // ─────────────────────────────────────────────────────────────────────────────
108
+ // 043b — the live tail (collapsible, on TOP — OQ-2 stacked) + the history table.
109
+ // ─────────────────────────────────────────────────────────────────────────────
110
+ /**
111
+ * Append one SSE-tailed record to the live buffer (newest FIRST), capped (D-1 — shares the LogRow
112
+ * renderer with the history table). Pure + exported so the page test drives the follow handler
113
+ * deterministically (inject a record, assert it lands) without a live EventSource.
114
+ */
115
+ export function appendLiveRecord(buffer, record) {
116
+ const next = [record, ...buffer];
117
+ return next.length > MAX_LIVE_LINES ? next.slice(0, MAX_LIVE_LINES) : next;
118
+ }
119
+ /** The collapsible live-tail section (reuses the SSE follow; newest first; shares LogRow). */
120
+ function LiveTail({ records, open, onToggle }) {
121
+ return (_jsx(Panel, { title: "Live tail", eyebrow: `${records.length} streaming`, right: _jsx("button", { type: "button", "data-testid": "live-toggle", "aria-expanded": open, onClick: onToggle, style: { background: "transparent", border: "1px solid var(--border-default)", borderRadius: "var(--radius-md)", color: "var(--text-secondary)", fontFamily: "var(--font-mono)", fontSize: 11, padding: "4px 10px", cursor: "pointer" }, children: open ? "collapse" : "expand" }), children: open ? (records.length === 0 ? (_jsx("div", { "data-testid": "live-empty", style: { padding: "12px 4px", fontSize: 13, color: "var(--text-tertiary)", fontFamily: "var(--font-mono)" }, children: "waiting for daemon activity\u2026" })) : (_jsx("div", { "data-testid": "live-list", children: records.map((r, i) => (_jsx(LogRow, { record: r, live: true }, `${r.time}-${r.path}-${i}`))) }))) : (_jsx("div", { style: { padding: "4px", fontSize: 12, color: "var(--text-tertiary)", fontFamily: "var(--font-mono)" }, children: "live tail collapsed" })) }));
122
+ }
123
+ /** The durable history table (FR-1/FR-3): filtered, paginated, newest first; shares LogRow. */
124
+ function HistoryTable({ page, loading, error, onLoadMore, }) {
125
+ return (_jsx(Panel, { title: "History", eyebrow: page.persistent ? `${page.count} on this page` : "history unavailable", children: error ? (_jsx("div", { "data-testid": "history-error", style: { padding: "12px 4px", fontSize: 13, color: "var(--severity-critical)", fontFamily: "var(--font-mono)" }, children: "Could not load log history." })) : loading && page.records.length === 0 ? (_jsx("div", { "data-testid": "history-loading", style: { padding: "12px 4px", fontSize: 13, color: "var(--text-tertiary)", fontFamily: "var(--font-mono)" }, children: "loading\u2026" })) : !page.persistent ? (_jsx("div", { "data-testid": "history-unavailable", style: { padding: "12px 4px", fontSize: 13, color: "var(--text-tertiary)" }, children: "Durable log history is unavailable on this daemon. Live tail above still streams current activity." })) : page.records.length === 0 ? (_jsx("div", { "data-testid": "history-empty", style: { padding: "12px 4px", fontSize: 13, color: "var(--text-tertiary)" }, children: "No logs match these filters." })) : (_jsxs(_Fragment, { children: [_jsx(LogTableHeader, {}), _jsx("div", { "data-testid": "history-list", children: page.records.map((r, i) => (_jsx(LogRow, { record: r }, `${r.time}-${r.path}-${r.status}-${i}`))) }), page.nextCursor !== null && (_jsx("div", { style: { display: "flex", justifyContent: "center", marginTop: 12 }, children: _jsx(Button, { variant: "secondary", size: "sm", disabled: loading, "data-testid": "history-load-more", onClick: onLoadMore, children: loading ? "loading…" : "Load more" }) }))] })) }));
126
+ }
127
+ // ─────────────────────────────────────────────────────────────────────────────
128
+ // 043c — the Turns section: browsable list + drill-down (labeled "Turns" — AC-4).
129
+ // ─────────────────────────────────────────────────────────────────────────────
130
+ /** A presentation-safe key/value row in the turn-detail panel (METADATA ONLY — D-4 / AC-5). */
131
+ function TurnDetailRow({ label, children }) {
132
+ return (_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12, padding: "8px 6px", borderTop: "1px solid var(--border-subtle)" }, children: [_jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--text-tertiary)", minWidth: 120 }, children: label }), _jsx("span", { style: { flex: 1 } }), _jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: 13, color: "var(--text-primary)", textAlign: "right", wordBreak: "break-word" }, children: children })] }));
133
+ }
134
+ /**
135
+ * One turn's drill-down detail (FR-2 — harness, project, timestamp, event count, status, turn id),
136
+ * with a Back control. METADATA ONLY: it renders NO transcript/body/JSONB/secret (D-4 / AC-5).
137
+ */
138
+ function TurnDetail({ turn, onBack }) {
139
+ return (_jsx(Panel, { title: "Turn detail", eyebrow: turn.sessionId, right: _jsx(Button, { variant: "ghost", size: "sm", "data-testid": "turn-back", onClick: onBack, children: "\u2039 Back" }), children: _jsxs("div", { "data-testid": "turn-detail", children: [_jsx(TurnDetailRow, { label: "turn id", children: turn.sessionId || "—" }), _jsx(TurnDetailRow, { label: "project", children: turn.project || "—" }), _jsx(TurnDetailRow, { label: "timestamp", children: turn.startedAt || "—" }), _jsx(TurnDetailRow, { label: "event count", children: turn.eventCount }), _jsx(TurnDetailRow, { label: "status", children: _jsx(Badge, { tone: turn.status === "summarized" ? "verified" : "neutral", mono: true, children: turn.status }) })] }) }));
140
+ }
141
+ /** One row in the Turns list (click opens the drill-down detail). */
142
+ function TurnRow({ turn, onOpen }) {
143
+ return (_jsxs("button", { type: "button", "data-testid": `turn-row-${turn.sessionId}`, onClick: () => onOpen(turn), style: {
144
+ display: "grid",
145
+ gridTemplateColumns: "96px 1fr auto auto",
146
+ alignItems: "center",
147
+ gap: 12,
148
+ width: "100%",
149
+ padding: "10px 6px",
150
+ borderTop: "1px solid var(--border-subtle)",
151
+ background: "transparent",
152
+ border: "none",
153
+ borderTopColor: "var(--border-subtle)",
154
+ cursor: "pointer",
155
+ textAlign: "left",
156
+ }, children: [_jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: 13, fontWeight: 600, color: "var(--honey)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: turn.sessionId }), _jsx("span", { style: { fontSize: 13, color: "var(--text-secondary)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: turn.project }), _jsxs("span", { style: { fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--text-tertiary)" }, children: [turn.startedAt, turn.eventCount > 0 ? ` · ${turn.eventCount}e` : ""] }), _jsx(Badge, { tone: turn.status === "summarized" ? "verified" : "neutral", mono: true, children: turn.status })] }));
157
+ }
158
+ /** The Turns list (FR-1 — captured turns, newest first, labeled "Turns") + cursor pagination. */
159
+ function TurnsList({ turns, loading, error, hasMore, onOpen, onLoadMore, }) {
160
+ return (_jsx(Panel, { title: "Turns", eyebrow: `${turns.length} captured`, children: error ? (_jsx("div", { "data-testid": "turns-error", style: { padding: "12px 4px", fontSize: 13, color: "var(--severity-critical)", fontFamily: "var(--font-mono)" }, children: "Could not load turns." })) : loading && turns.length === 0 ? (_jsx("div", { "data-testid": "turns-loading", style: { padding: "12px 4px", fontSize: 13, color: "var(--text-tertiary)", fontFamily: "var(--font-mono)" }, children: "loading\u2026" })) : turns.length === 0 ? (_jsx("div", { "data-testid": "turns-empty", style: { padding: "12px 4px", fontSize: 13, color: "var(--text-tertiary)" }, children: "No turns captured yet." })) : (_jsxs(_Fragment, { children: [_jsx("div", { "data-testid": "turns-list", children: turns.map((t, i) => (_jsx(TurnRow, { turn: t, onOpen: onOpen }, t.sessionId || i))) }), hasMore && (_jsx("div", { style: { display: "flex", justifyContent: "center", marginTop: 12 }, children: _jsx(Button, { variant: "secondary", size: "sm", disabled: loading, "data-testid": "turns-load-more", onClick: onLoadMore, children: loading ? "loading…" : "Load more" }) }))] })) }));
161
+ }
162
+ // ─────────────────────────────────────────────────────────────────────────────
163
+ // The routed page.
164
+ // ─────────────────────────────────────────────────────────────────────────────
165
+ /** A tab button for the Requests / Turns switch (mono pill, honey when active — mirrors Sync). */
166
+ function TabButton({ label, active, onClick, testid }) {
167
+ return (_jsx("button", { type: "button", "data-testid": testid, "aria-pressed": active, onClick: onClick, style: {
168
+ display: "inline-flex",
169
+ alignItems: "center",
170
+ gap: 8,
171
+ height: 34,
172
+ padding: "0 14px",
173
+ background: active ? "var(--honey-subtle)" : "var(--bg-elevated)",
174
+ border: `1px solid ${active ? "var(--honey-border)" : "var(--border-default)"}`,
175
+ borderRadius: "var(--radius-full)",
176
+ color: active ? "var(--honey)" : "var(--text-secondary)",
177
+ fontFamily: "var(--font-sans)",
178
+ fontSize: 13,
179
+ fontWeight: 600,
180
+ cursor: "pointer",
181
+ }, children: label }));
182
+ }
183
+ /**
184
+ * The Logs page (043b + 043c). Hydrates the durable history + the live tail (request log) and the
185
+ * browsable Turns history (DeepLake) over the shared `wire`. A tab switches between the two
186
+ * surfaces; within Requests, the live tail (collapsible, top) + the filtered/paginated history
187
+ * (below) stack. All hydration is fetch-on-mount + explicit refetch (filters/load-more) — the live
188
+ * tail follows the SSE stream (no poll). Loading/empty/error states are explicit everywhere.
189
+ */
190
+ export function LogsPage({ wire, daemonUp }) {
191
+ const [tab, setTab] = React.useState("requests");
192
+ // ── 043b: the request-log history (durable) + filters + pagination. ──
193
+ const [history, setHistory] = React.useState(EMPTY_LOGS_HISTORY);
194
+ const [appliedFilters, setAppliedFilters] = React.useState(EMPTY_FILTERS);
195
+ const [draftFilters, setDraftFilters] = React.useState(EMPTY_FILTERS);
196
+ const [historyLoading, setHistoryLoading] = React.useState(false);
197
+ const [historyError, setHistoryError] = React.useState(false);
198
+ // ── 043b: the live tail (SSE follow — no poll). ──
199
+ const [liveRecords, setLiveRecords] = React.useState([]);
200
+ const [liveOpen, setLiveOpen] = React.useState(true);
201
+ // ── 043c: the browsable Turns history + drill-down. ──
202
+ const [turns, setTurns] = React.useState([]);
203
+ const [turnsCursor, setTurnsCursor] = React.useState(null);
204
+ const [turnsLoading, setTurnsLoading] = React.useState(false);
205
+ const [turnsError, setTurnsError] = React.useState(false);
206
+ const [selectedTurn, setSelectedTurn] = React.useState(null);
207
+ // Fetch page ONE of the history for a given filter set (replaces the current page).
208
+ const loadHistoryFirstPage = React.useCallback(async (filters) => {
209
+ setHistoryLoading(true);
210
+ setHistoryError(false);
211
+ try {
212
+ const page = await wire.logsHistory(toWireFilters(filters));
213
+ setHistory(page);
214
+ }
215
+ catch {
216
+ setHistoryError(true);
217
+ }
218
+ finally {
219
+ setHistoryLoading(false);
220
+ }
221
+ }, [wire]);
222
+ // Append the next OLDER history page via the cursor (no dup/gap — the daemon pages on id).
223
+ const loadHistoryMore = React.useCallback(async () => {
224
+ if (history.nextCursor === null)
225
+ return;
226
+ setHistoryLoading(true);
227
+ try {
228
+ const next = await wire.logsHistory({ ...toWireFilters(appliedFilters), cursor: history.nextCursor });
229
+ setHistory((cur) => ({
230
+ records: [...cur.records, ...next.records],
231
+ count: cur.count + next.count,
232
+ nextCursor: next.nextCursor,
233
+ persistent: next.persistent,
234
+ }));
235
+ }
236
+ catch {
237
+ setHistoryError(true);
238
+ }
239
+ finally {
240
+ setHistoryLoading(false);
241
+ }
242
+ }, [wire, appliedFilters, history.nextCursor]);
243
+ // Fetch page ONE of the turns history (replaces the list).
244
+ const loadTurnsFirstPage = React.useCallback(async () => {
245
+ setTurnsLoading(true);
246
+ setTurnsError(false);
247
+ try {
248
+ const page = await wire.turnsHistory({ limit: TURNS_PAGE_SIZE });
249
+ setTurns(page.sessions);
250
+ setTurnsCursor(page.nextCursor);
251
+ }
252
+ catch {
253
+ setTurnsError(true);
254
+ }
255
+ finally {
256
+ setTurnsLoading(false);
257
+ }
258
+ }, [wire]);
259
+ // Append the next OLDER turns page via the cursor.
260
+ const loadTurnsMore = React.useCallback(async () => {
261
+ if (turnsCursor === null)
262
+ return;
263
+ setTurnsLoading(true);
264
+ try {
265
+ const next = await wire.turnsHistory({ limit: TURNS_PAGE_SIZE, cursor: turnsCursor });
266
+ setTurns((cur) => [...cur, ...next.sessions]);
267
+ setTurnsCursor(next.nextCursor);
268
+ }
269
+ catch {
270
+ setTurnsError(true);
271
+ }
272
+ finally {
273
+ setTurnsLoading(false);
274
+ }
275
+ }, [wire, turnsCursor]);
276
+ // On mount: load the first history page + the first turns page (both surfaces are browsable).
277
+ React.useEffect(() => {
278
+ void loadHistoryFirstPage(EMPTY_FILTERS);
279
+ void loadTurnsFirstPage();
280
+ }, [loadHistoryFirstPage, loadTurnsFirstPage]);
281
+ // The live tail FOLLOWS the SSE stream (D-2 — reuse `wire.logsStream`, no poll). In a non-browser
282
+ // env (jsdom test) `logsStream` is an inert no-op, so the tail degrades to empty and the test
283
+ // drives the handler directly. The EventSource is closed on unmount via the returned unsubscribe.
284
+ React.useEffect(() => {
285
+ let alive = true;
286
+ const unsubscribe = wire.logsStream((record) => {
287
+ if (!alive)
288
+ return;
289
+ setLiveRecords((buf) => appendLiveRecord(buf, record));
290
+ });
291
+ return () => {
292
+ alive = false;
293
+ unsubscribe();
294
+ };
295
+ }, [wire]);
296
+ const onApplyFilters = React.useCallback(() => {
297
+ setAppliedFilters(draftFilters);
298
+ void loadHistoryFirstPage(draftFilters);
299
+ }, [draftFilters, loadHistoryFirstPage]);
300
+ const onClearFilters = React.useCallback(() => {
301
+ setDraftFilters(EMPTY_FILTERS);
302
+ setAppliedFilters(EMPTY_FILTERS);
303
+ void loadHistoryFirstPage(EMPTY_FILTERS);
304
+ }, [loadHistoryFirstPage]);
305
+ const onChangeField = React.useCallback((key, value) => {
306
+ setDraftFilters((cur) => ({ ...cur, [key]: value }));
307
+ }, []);
308
+ const turnsHasMore = turnsCursor !== null;
309
+ return (_jsx(PageFrame, { title: "Logs", eyebrow: daemonUp ? "request log · turns" : "daemon offline", right: _jsxs("div", { style: { display: "flex", gap: 8 }, children: [_jsx(TabButton, { label: "Requests", active: tab === "requests", onClick: () => setTab("requests"), testid: "tab-requests" }), _jsx(TabButton, { label: "Turns", active: tab === "turns", onClick: () => setTab("turns"), testid: "tab-turns" })] }), children: tab === "requests" ? (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, children: [_jsx(LiveTail, { records: liveRecords, open: liveOpen, onToggle: () => setLiveOpen((o) => !o) }), _jsx(FilterBar, { draft: draftFilters, onChangeField: onChangeField, onApply: onApplyFilters, onClear: onClearFilters }), _jsx(HistoryTable, { page: history, loading: historyLoading, error: historyError, onLoadMore: () => void loadHistoryMore() })] })) : selectedTurn !== null ? (_jsx(TurnDetail, { turn: selectedTurn, onBack: () => setSelectedTurn(null) })) : (_jsx(TurnsList, { turns: turns, loading: turnsLoading, error: turnsError, hasMore: turnsHasMore, onOpen: (t) => setSelectedTurn(t), onLoadMore: () => void loadTurnsMore() })) }));
310
+ }
311
+ //# sourceMappingURL=logs.js.map