@theihtisham/agent-shadow-brain 7.0.0 → 7.1.3

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 (130) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +47 -385
  3. package/dist/activity-cli.d.ts +3 -0
  4. package/dist/activity-cli.d.ts.map +1 -0
  5. package/dist/activity-cli.js +78 -0
  6. package/dist/activity-cli.js.map +1 -0
  7. package/dist/adapters/index.d.ts +2 -1
  8. package/dist/adapters/index.d.ts.map +1 -1
  9. package/dist/adapters/index.js +3 -2
  10. package/dist/adapters/index.js.map +1 -1
  11. package/dist/brain/activity-journal.d.ts +82 -0
  12. package/dist/brain/activity-journal.d.ts.map +1 -0
  13. package/dist/brain/activity-journal.js +314 -0
  14. package/dist/brain/activity-journal.js.map +1 -0
  15. package/dist/brain/activity-reporting.d.ts +14 -0
  16. package/dist/brain/activity-reporting.d.ts.map +1 -0
  17. package/dist/brain/activity-reporting.js +17 -0
  18. package/dist/brain/activity-reporting.js.map +1 -0
  19. package/dist/brain/auto-update.js +1 -1
  20. package/dist/brain/brain-portability.js +1 -1
  21. package/dist/brain/client-integrations.d.ts +61 -0
  22. package/dist/brain/client-integrations.d.ts.map +1 -0
  23. package/dist/brain/client-integrations.js +98 -0
  24. package/dist/brain/client-integrations.js.map +1 -0
  25. package/dist/brain/context-assembly.d.ts +76 -0
  26. package/dist/brain/context-assembly.d.ts.map +1 -0
  27. package/dist/brain/context-assembly.js +305 -0
  28. package/dist/brain/context-assembly.js.map +1 -0
  29. package/dist/brain/feature-settings.d.ts +58 -0
  30. package/dist/brain/feature-settings.d.ts.map +1 -0
  31. package/dist/brain/feature-settings.js +241 -0
  32. package/dist/brain/feature-settings.js.map +1 -0
  33. package/dist/brain/global-brain.d.ts +7 -0
  34. package/dist/brain/global-brain.d.ts.map +1 -1
  35. package/dist/brain/global-brain.js +19 -4
  36. package/dist/brain/global-brain.js.map +1 -1
  37. package/dist/brain/mcp-server.d.ts +23 -1
  38. package/dist/brain/mcp-server.d.ts.map +1 -1
  39. package/dist/brain/mcp-server.js +224 -24
  40. package/dist/brain/mcp-server.js.map +1 -1
  41. package/dist/brain/mcp-stdio.d.ts +12 -0
  42. package/dist/brain/mcp-stdio.d.ts.map +1 -0
  43. package/dist/brain/mcp-stdio.js +134 -0
  44. package/dist/brain/mcp-stdio.js.map +1 -0
  45. package/dist/brain/memory-annotations.d.ts +97 -0
  46. package/dist/brain/memory-annotations.d.ts.map +1 -0
  47. package/dist/brain/memory-annotations.js +388 -0
  48. package/dist/brain/memory-annotations.js.map +1 -0
  49. package/dist/brain/memory-graph.d.ts.map +1 -1
  50. package/dist/brain/memory-graph.js +12 -23
  51. package/dist/brain/memory-graph.js.map +1 -1
  52. package/dist/brain/memory-sources.d.ts +8 -0
  53. package/dist/brain/memory-sources.d.ts.map +1 -0
  54. package/dist/brain/memory-sources.js +27 -0
  55. package/dist/brain/memory-sources.js.map +1 -0
  56. package/dist/brain/orchestrator.js +1 -1
  57. package/dist/brain/subconscious.d.ts +21 -24
  58. package/dist/brain/subconscious.d.ts.map +1 -1
  59. package/dist/brain/subconscious.js +82 -218
  60. package/dist/brain/subconscious.js.map +1 -1
  61. package/dist/brain/task-workflow.d.ts +22 -0
  62. package/dist/brain/task-workflow.d.ts.map +1 -0
  63. package/dist/brain/task-workflow.js +182 -0
  64. package/dist/brain/task-workflow.js.map +1 -0
  65. package/dist/brain/workflow-skills.d.ts +86 -0
  66. package/dist/brain/workflow-skills.d.ts.map +1 -0
  67. package/dist/brain/workflow-skills.js +514 -0
  68. package/dist/brain/workflow-skills.js.map +1 -0
  69. package/dist/cli.js +21 -2
  70. package/dist/cli.js.map +1 -1
  71. package/dist/context-cli.d.ts +3 -0
  72. package/dist/context-cli.d.ts.map +1 -0
  73. package/dist/context-cli.js +56 -0
  74. package/dist/context-cli.js.map +1 -0
  75. package/dist/curation-cli.d.ts +4 -0
  76. package/dist/curation-cli.d.ts.map +1 -0
  77. package/dist/curation-cli.js +118 -0
  78. package/dist/curation-cli.js.map +1 -0
  79. package/dist/dashboard/live-workspace-html.d.ts +2 -0
  80. package/dist/dashboard/live-workspace-html.d.ts.map +1 -0
  81. package/dist/dashboard/live-workspace-html.js +893 -0
  82. package/dist/dashboard/live-workspace-html.js.map +1 -0
  83. package/dist/dashboard/server.d.ts +1 -0
  84. package/dist/dashboard/server.d.ts.map +1 -1
  85. package/dist/dashboard/server.js +53 -18
  86. package/dist/dashboard/server.js.map +1 -1
  87. package/dist/dashboard/v6-dashboard-html.d.ts +1 -1
  88. package/dist/dashboard/v6-dashboard-html.d.ts.map +1 -1
  89. package/dist/dashboard/v6-dashboard-html.js +7 -29
  90. package/dist/dashboard/v6-dashboard-html.js.map +1 -1
  91. package/dist/dashboard/workspace-api.d.ts +16 -0
  92. package/dist/dashboard/workspace-api.d.ts.map +1 -0
  93. package/dist/dashboard/workspace-api.js +309 -0
  94. package/dist/dashboard/workspace-api.js.map +1 -0
  95. package/dist/dashboard/workspace-html.d.ts +2 -0
  96. package/dist/dashboard/workspace-html.d.ts.map +1 -0
  97. package/dist/dashboard/workspace-html.js +2132 -0
  98. package/dist/dashboard/workspace-html.js.map +1 -0
  99. package/dist/index.d.ts +14 -0
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +10 -0
  102. package/dist/index.js.map +1 -1
  103. package/dist/integration-cli.d.ts +3 -0
  104. package/dist/integration-cli.d.ts.map +1 -0
  105. package/dist/integration-cli.js +14 -0
  106. package/dist/integration-cli.js.map +1 -0
  107. package/dist/types.d.ts +12 -3
  108. package/dist/types.d.ts.map +1 -1
  109. package/dist/workflow-cli.d.ts +3 -0
  110. package/dist/workflow-cli.d.ts.map +1 -0
  111. package/dist/workflow-cli.js +100 -0
  112. package/dist/workflow-cli.js.map +1 -0
  113. package/docs/WORKFLOW_INTEGRATION.md +105 -0
  114. package/docs/WORKSPACE.md +111 -0
  115. package/docs/assets/context-flow-v7.2.png +0 -0
  116. package/docs/assets/live-v7.1.3.png +0 -0
  117. package/docs/assets/memory-detail-v7.1.3.png +0 -0
  118. package/docs/assets/overview-v7.1.3.png +0 -0
  119. package/docs/assets/workspace-graph-v7.2.png +0 -0
  120. package/docs/assets/workspace-overview-v7.2.png +0 -0
  121. package/docs/launch/RELEASE_NOTES_v7.1.0.md +32 -0
  122. package/docs/launch/RELEASE_NOTES_v7.1.3.md +40 -0
  123. package/docs/launch/RELEASE_NOTES_v7.2.0.md +24 -0
  124. package/package.json +112 -207
  125. package/skills/shadow-debug/SKILL.md +16 -0
  126. package/skills/shadow-implement/SKILL.md +16 -0
  127. package/skills/shadow-release/SKILL.md +16 -0
  128. package/skills/shadow-research/SKILL.md +12 -0
  129. package/skills/shadow-review/SKILL.md +14 -0
  130. package/skills/shadow-workflow/SKILL.md +31 -0
@@ -0,0 +1,893 @@
1
+ // Local, read-only operations console for the Shadow Brain event journal.
2
+ export const LIVE_WORKSPACE_HTML = String.raw `<!doctype html>
3
+ <html lang="en" data-motion="system">
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <meta name="color-scheme" content="dark">
8
+ <title>Live operations · Agent Shadow Brain</title>
9
+ <script>
10
+ try {
11
+ if (localStorage.getItem('shadow-brain:workspace:motion') === 'reduce') document.documentElement.dataset.motion = 'reduce';
12
+ } catch {}
13
+ </script>
14
+ <style>
15
+ :root {
16
+ color-scheme: dark;
17
+ --canvas: #101923; --header: #131e2a; --surface: #162230; --inset: #111d29; --hover: #213345;
18
+ --line: #314153; --ink: #edf5fb; --muted: #aabed1; --control: #738ba1; --focus: #9bdff3;
19
+ --cyan: #86dbe3; --cyan-ink: #102f38; --cyan-tint: #1d3b46;
20
+ --amber: #f5ce91; --amber-tint: #3a3027; --rose: #ffacba; --rose-tint: #432936;
21
+ --node: #203b4c; --graph-line: #567589; --grid: #2c4153;
22
+ --ease: 190ms cubic-bezier(.2,.75,.25,1); --radius: 9px;
23
+ font: 15px/1.5 Inter, "Segoe UI", system-ui, sans-serif;
24
+ }
25
+ * { box-sizing: border-box; }
26
+ [hidden] { display: none !important; }
27
+ body { margin: 0; background: var(--canvas); color: var(--ink); }
28
+ h1,h2,h3,p,ul,dl,dd { margin: 0; }
29
+ h1,h2,h3 { line-height: 1.22; text-wrap: balance; overflow-wrap: anywhere; }
30
+ h1 { font-size: 2rem; letter-spacing: -.035em; font-weight: 710; }
31
+ h2 { font-size: 1.1rem; font-weight: 680; letter-spacing: -.013em; }
32
+ h3 { font-size: .94rem; font-weight: 650; }
33
+ p { overflow-wrap: anywhere; max-width: 69ch; }
34
+ a { color: var(--cyan); text-decoration: none; }
35
+ a:hover { text-decoration: underline; }
36
+ :is(button,a,select,summary,[tabindex]):focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
37
+ .muted { color: var(--muted); }
38
+ .small { font-size: .86rem; }
39
+ .mono { font: .82rem/1.45 "Cascadia Code", Consolas, monospace; overflow-wrap: anywhere; }
40
+ .icon { width: 18px; height: 18px; flex: none; vertical-align: middle; }
41
+ .icon.compact { width: 15px; height: 15px; }
42
+ .skip { position: fixed; top: 7px; left: 8px; z-index: 3; transform: translateY(-150%); }
43
+ .skip:focus { transform: none; }
44
+ .topbar { background: var(--header); border-bottom: 1px solid var(--line); }
45
+ .top-inner { max-width: 1560px; margin: 0 auto; min-height: 59px; padding: 9px 27px; display: flex; align-items: center; gap: 22px; }
46
+ .brand { display: inline-flex; align-items: center; gap: 9px; color: var(--ink); font-weight: 690; white-space: nowrap; }
47
+ .brand:hover { text-decoration: none; }
48
+ .brand-mark { display: grid; place-items: center; width: 34px; height: 34px; background: var(--cyan-tint); color: var(--cyan); border-radius: 7px; }
49
+ .main-nav { display: flex; align-items: center; gap: 7px; }
50
+ .main-nav a { display: inline-flex; min-height: 34px; align-items: center; padding: 5px 9px; border-radius: 5px; color: var(--muted); font-size: .88rem; }
51
+ .main-nav a[aria-current="page"] { color: var(--cyan); background: var(--cyan-tint); font-weight: 650; }
52
+ .top-state { display: flex; align-items: center; gap: 10px; margin-left: auto; }
53
+ .tag { display: inline-flex; align-items: center; gap: 6px; font-size: .77rem; font-weight: 620; line-height: 1.25; padding: 4px 7px; border-radius: 5px; background: var(--hover); color: var(--muted); max-width: 100%; overflow-wrap: anywhere; }
54
+ .tag.ok { background: var(--cyan-tint); color: var(--cyan); }
55
+ .tag.warn { background: var(--amber-tint); color: var(--amber); }
56
+ .tag.bad { background: var(--rose-tint); color: var(--rose); }
57
+ .dot { display: inline-block; width: 7px; height: 7px; background: currentColor; border-radius: 50%; flex: none; }
58
+ main { max-width: 1560px; padding: 26px 27px 44px; margin: 0 auto; min-width: 0; }
59
+ .head { display: flex; justify-content: space-between; align-items: start; gap: 15px; margin-bottom: 17px; }
60
+ .head p { margin-top: 6px; }
61
+ .controls { display: flex; flex-wrap: wrap; justify-content: end; gap: 7px; align-items: end; }
62
+ .button,select {
63
+ color: var(--ink); background: var(--surface); border: 1px solid var(--control); border-radius: 6px;
64
+ min-height: 36px; padding: 6px 10px; font: inherit; font-size: .85rem; font-weight: 650; transition: background var(--ease), border-color var(--ease);
65
+ }
66
+ .button { display: inline-flex; align-items: center; justify-content: center; gap: 6px; cursor: pointer; line-height: 1.2; }
67
+ .button:hover, select:hover { background: var(--hover); text-decoration: none; }
68
+ .button:disabled { opacity: .56; cursor: default; }
69
+ .button.primary { border-color: var(--cyan); color: var(--cyan-ink); background: var(--cyan); }
70
+ .button.primary:hover { background: #b3ebee; }
71
+ .button.quiet { background: transparent; border-color: transparent; color: var(--cyan); }
72
+ .button.quiet:hover, .button[aria-pressed="true"] { background: var(--cyan-tint); }
73
+ .label { display: grid; gap: 3px; min-width: 0; font-size: .79rem; color: var(--muted); font-weight: 630; }
74
+ .notice { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 10px; padding: 9px 12px; min-height: 40px; border-radius: 6px; border: 1px solid var(--line); background: var(--inset); color: var(--muted); font-size: .84rem; margin-bottom: 14px; }
75
+ .notice.error { background: var(--rose-tint); border-color: transparent; color: var(--rose); }
76
+ .stats { display: grid; grid-template-columns: repeat(5,minmax(0,1fr)); border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); margin-bottom: 15px; }
77
+ .stat { display: grid; gap: 4px; min-width: 0; padding: 13px 15px; }
78
+ .stat + .stat { border-left: 1px solid var(--line); }
79
+ .stat dt { color: var(--muted); font-size: .82rem; font-weight: 590; }
80
+ .stat dd { font-size: 1.37rem; font-variant-numeric: tabular-nums; font-weight: 710; line-height: 1.15; overflow-wrap: anywhere; }
81
+ .stat .danger { color: var(--rose); }
82
+ .panel { border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); min-width: 0; overflow: hidden; }
83
+ .panel-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; min-height: 55px; padding: 11px 14px; border-bottom: 1px solid var(--line); }
84
+ .panel-head p { margin-top: 3px; }
85
+ .panel-body { padding: 12px 14px; min-width: 0; }
86
+ .blank { min-height: 112px; display: grid; align-content: start; gap: 6px; padding: 17px 14px; color: var(--muted); }
87
+ .blank h3 { color: var(--ink); }
88
+ .blank .icon { color: var(--cyan); margin-bottom: 3px; }
89
+ .ops-grid { display: grid; grid-template-columns: minmax(190px,.72fr) minmax(0,2.55fr) minmax(233px,.95fr); gap: 14px; margin-bottom: 15px; align-items: stretch; }
90
+ .list { list-style: none; padding: 0; }
91
+ .list > li + li { border-top: 1px solid var(--line); }
92
+ .session-list, .inspect-body { max-height: 445px; overflow: auto; }
93
+ .session-row { width: 100%; border: 0; background: transparent; color: var(--ink); display: grid; grid-template-columns: 29px minmax(0,1fr); align-items: start; text-align: left; gap: 8px; padding: 11px 12px; font: inherit; cursor: pointer; }
94
+ .session-row:hover, .session-row[aria-pressed="true"] { background: var(--hover); }
95
+ .session-row[aria-pressed="true"] .session-glyph { background: var(--cyan-tint); color: var(--cyan); }
96
+ .session-glyph { width: 29px; height: 29px; border-radius: 6px; background: var(--inset); color: var(--muted); display: grid; place-items: center; }
97
+ .session-label { display: block; font-weight: 630; overflow-wrap: anywhere; }
98
+ .session-detail { display: grid; gap: 4px; justify-items: start; min-width: 0; }
99
+ .network-body { min-height: 405px; position: relative; display: grid; align-items: center; background: var(--inset); }
100
+ #network-svg { display: block; width: 100%; height: 405px; }
101
+ .topology-ring { fill: none; stroke: var(--graph-line); stroke-width: 1; stroke-opacity: .29; stroke-dasharray: 2 5; }
102
+ .network-edge { fill: none; stroke: var(--graph-line); stroke-width: 1.2; opacity: .62; transition: opacity var(--ease), stroke var(--ease); }
103
+ .network-edge.active { stroke: var(--cyan); stroke-width: 1.8; opacity: .9; }
104
+ .network-edge.dimmed { opacity: .3; }
105
+ .network-edge.reported { stroke: var(--amber); stroke-dasharray: 3 4; }
106
+ .network-node { cursor: pointer; color: var(--muted); }
107
+ .network-node .node-surface { fill: var(--node); stroke: var(--graph-line); stroke-width: 1.25; }
108
+ .network-node .node-indicator { fill: currentColor; }
109
+ .network-node.live { color: var(--cyan); }
110
+ .network-node.reported { color: var(--amber); }
111
+ .network-node.failed { color: var(--rose); }
112
+ .network-node:is(:hover,:focus-visible,[aria-pressed="true"]) .node-surface { stroke: currentColor; stroke-width: 2.4; }
113
+ .network-node:focus-visible { outline: none; }
114
+ .network-node text, .network-core text { fill: var(--ink); font-size: 12px; font-weight: 650; pointer-events: none; }
115
+ .network-node .node-subtitle, .network-core .node-subtitle { fill: var(--muted); font-size: 10px; font-weight: 550; }
116
+ .network-core circle { fill: var(--cyan-tint); stroke: var(--cyan); stroke-width: 1.4; }
117
+ .network-core .core-inner { fill: var(--surface); stroke: var(--graph-line); }
118
+ .pulse { pointer-events: none; fill: var(--cyan); }
119
+ .pulse.reported { fill: var(--amber); }
120
+ .pulse.failed { fill: var(--rose); }
121
+ .network-caption { padding: 8px 13px; border-top: 1px solid var(--line); display: flex; flex-wrap: wrap; gap: 5px 12px; color: var(--muted); font-size: .78rem; }
122
+ .network-caption span { display: inline-flex; align-items: center; gap: 5px; }
123
+ .legend-line { width: 17px; border-top: 2px solid var(--cyan); }
124
+ .legend-line.reported { border-color: var(--amber); border-top-style: dashed; }
125
+ .inspect-body .panel-body { display: grid; gap: 10px; }
126
+ .facts { display: grid; gap: 7px; }
127
+ .facts div { display: flex; align-items: start; justify-content: space-between; gap: 8px; }
128
+ .facts dt { color: var(--muted); font-size: .83rem; }
129
+ .facts dd { text-align: right; font-size: .83rem; overflow-wrap: anywhere; }
130
+ .stages { display: flex; flex-wrap: wrap; gap: 4px; }
131
+ .stage { border: 1px solid var(--line); color: var(--muted); border-radius: 4px; padding: 4px 6px; font-size: .78rem; }
132
+ .stage.seen { border-color: var(--amber); color: var(--amber); }
133
+ .stage.current { background: var(--amber-tint); font-weight: 680; }
134
+ .charts { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 14px; margin-bottom: 15px; }
135
+ .chart-svg { width: 100%; height: 150px; display: block; }
136
+ .chart-grid { stroke: var(--line); stroke-width: 1; }
137
+ .chart-label { fill: var(--muted); font-size: 11px; }
138
+ .chart-bar { fill: var(--cyan); }
139
+ .chart-error { fill: var(--rose); }
140
+ .chart-duration { stroke: var(--amber); fill: none; stroke-width: 2; }
141
+ .chart-point { fill: var(--amber); }
142
+ .chart-data summary { cursor: pointer; color: var(--cyan); }
143
+ .chart-data table { border-collapse: collapse; width: 100%; margin-top: 6px; font-size: .82rem; }
144
+ .chart-data th,.chart-data td { text-align: left; padding: 4px 7px; border-bottom: 1px solid var(--line); }
145
+ .events { max-height: 550px; overflow: auto; }
146
+ .event-row { display: grid; grid-template-columns: 105px minmax(135px,.7fr) minmax(0,1.2fr) auto; gap: 9px; align-items: start; padding: 9px 14px; }
147
+ .event-row.new { animation: arrive var(--ease) both; }
148
+ .event-meta { overflow-wrap: anywhere; }
149
+ .event-title { font-weight: 630; overflow-wrap: anywhere; }
150
+ .event-link { border: 0; background: transparent; color: var(--ink); padding: 0; text-align: left; font: inherit; cursor: pointer; }
151
+ .event-link:hover, .event-link[aria-pressed="true"] { color: var(--cyan); text-decoration: underline; }
152
+ .event-row .tag { justify-self: end; }
153
+ @keyframes arrive { from { background: var(--cyan-tint); opacity: .8; } to { background: transparent; opacity: 1; } }
154
+ html[data-motion="reduce"] *, html[data-motion="reduce"] *::before, html[data-motion="reduce"] *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
155
+ @media (prefers-reduced-motion: reduce) {
156
+ *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
157
+ }
158
+ @media (max-width: 1150px) {
159
+ .ops-grid { grid-template-columns: minmax(175px,.7fr) minmax(0,2.3fr); }
160
+ .inspect-panel { grid-column: 1 / -1; }
161
+ .inspect-body { max-height: 300px; }
162
+ }
163
+ @media (max-width: 750px) {
164
+ :root { font-size: 14px; }
165
+ .top-inner,main { padding-left: 12px; padding-right: 12px; }
166
+ .top-inner { gap: 9px; flex-wrap: wrap; }
167
+ .main-nav { gap: 2px; }
168
+ .top-version { display: none; }
169
+ .head { flex-wrap: wrap; }
170
+ .controls { justify-content: start; }
171
+ .stats { grid-template-columns: repeat(2,minmax(0,1fr)); }
172
+ .stat + .stat { border-left: 0; }
173
+ .stat:nth-child(even) { border-left: 1px solid var(--line); }
174
+ .stat:nth-child(n+3) { border-top: 1px solid var(--line); }
175
+ .stat:last-child { grid-column: 1 / -1; }
176
+ .ops-grid,.charts { grid-template-columns: minmax(0,1fr); }
177
+ .network-panel { grid-row: 1; }
178
+ .network-body { min-height: 280px; }
179
+ #network-svg { height: 280px; }
180
+ .session-list,.inspect-body { max-height: 320px; }
181
+ .event-row { grid-template-columns: minmax(0,1fr) auto; }
182
+ .event-row .event-meta { grid-column: 1; }
183
+ .event-row .tag { grid-column: 2; grid-row: 1; }
184
+ .event-row .event-title { grid-column: 1 / -1; }
185
+ }
186
+ </style>
187
+ </head>
188
+ <body>
189
+ <a class="skip button" href="#live-main">Skip to content</a>
190
+ <header class="topbar">
191
+ <div class="top-inner">
192
+ <a class="brand" href="/" aria-label="Agent Shadow Brain workspace"><span class="brand-mark" data-icon="brand"></span><span>Agent Shadow Brain</span></a>
193
+ <nav class="main-nav" aria-label="Navigation"><a href="/">Workspace</a><a href="/live" aria-current="page">Live</a></nav>
194
+ <div class="top-state"><span class="small muted mono top-version">7.1.3</span><span id="demo-tag" class="tag warn" hidden>Synthetic demo</span><span id="stream-tag" class="tag"><span class="dot"></span><span id="stream-label">Connecting</span></span></div>
195
+ </div>
196
+ </header>
197
+ <main id="live-main" tabindex="-1">
198
+ <div class="head">
199
+ <div><h1>Live operations</h1><p class="muted small">Follow recorded agent sessions, tool calls, and reported task phases.</p></div>
200
+ <div class="controls" aria-label="Live view controls">
201
+ <label for="time-window" class="label">Window<select id="time-window"><option value="1">1 minute</option><option value="5">5 minutes</option><option value="15" selected>15 minutes</option><option value="60">1 hour</option></select></label>
202
+ <button class="button" type="button" id="motion-control" aria-pressed="false"><span data-icon="motion"></span>Reduce motion</button>
203
+ <button class="button" type="button" id="refresh"><span data-icon="refresh"></span>Refresh</button>
204
+ <button class="button primary" type="button" id="pause"><span data-icon="pause"></span><span id="pause-label">Pause stream</span></button>
205
+ </div>
206
+ </div>
207
+ <div class="notice" id="stream-notice" role="status">Connecting to the local event journal…</div>
208
+ <dl class="stats" id="live-stats" aria-label="Tool metrics in the selected window"></dl>
209
+ <div class="ops-grid">
210
+ <section class="panel">
211
+ <div class="panel-head"><h2>Sessions</h2><span id="session-count" class="tag">0</span></div>
212
+ <div id="sessions" class="session-list"></div>
213
+ </section>
214
+ <section class="panel network-panel">
215
+ <div class="panel-head"><div><h2>Activity network</h2><p class="small muted" id="network-count">Recorded session activity</p></div><span id="network-status" class="tag"></span></div>
216
+ <div class="network-body"><svg id="network-svg" viewBox="0 0 720 405" role="group" aria-label="Activity network. Use arrow keys to navigate sessions."></svg></div>
217
+ <div class="network-caption"><span><i class="legend-line"></i>Measured activity</span><span><i class="legend-line reported"></i>Host-reported activity</span></div>
218
+ </section>
219
+ <section class="panel inspect-panel">
220
+ <div class="panel-head"><h2>Session details</h2></div><div id="inspection" class="inspect-body"></div>
221
+ </section>
222
+ </div>
223
+ <div class="charts">
224
+ <section class="panel"><div class="panel-head"><div><h2>Tool throughput</h2><p class="small muted">Recorded tool results per interval</p></div></div><div class="panel-body" id="throughput"></div></section>
225
+ <section class="panel"><div class="panel-head"><div><h2>Tool duration</h2><p class="small muted">Median measured duration per interval</p></div></div><div class="panel-body" id="duration"></div></section>
226
+ </div>
227
+ <section class="panel">
228
+ <div class="panel-head"><div><h2>Event timeline</h2><p class="small muted" id="timeline-scope">All sessions</p></div><div class="controls">
229
+ <label class="label" for="event-filter">Event source<select id="event-filter"><option value="">All sources</option><option value="measured">Measured</option><option value="reported">Reported</option></select></label>
230
+ <button class="button quiet" type="button" id="clear-session" hidden>Show all sessions</button></div>
231
+ </div><div id="events" class="events"></div>
232
+ </section>
233
+ </main>
234
+ <script>
235
+ 'use strict';
236
+ const $ = (id) => document.getElementById(id);
237
+ const glyphs = {
238
+ brand: '<path d="M12 5 5 9v6l7 4 7-4V9l-7-4v7m-7-3 7 3 7-3m-7 3v7"/><circle cx="12" cy="12" r="1.5" fill="currentColor"/>',
239
+ motion: '<path d="M3 8h14M5 12h16M3 16h12"/><circle cx="19" cy="8" r="1.4"/><circle cx="17" cy="16" r="1.4"/>',
240
+ refresh: '<path d="M19.5 9a8 8 0 1 0 .3 5.5M20 4v5h-5"/>',
241
+ pause: '<path d="M9 5v14m6-14v14"/>',
242
+ play: '<path d="m8 5 11 7-11 7z"/>',
243
+ agent: '<rect x="4.5" y="7" width="15" height="12" rx="3"/><path d="M12 7V4m-8 9H2m20 0h-2m-11-.5h.02m5.98 0H15m-5 3h4"/>',
244
+ activity: '<path d="M3 12h4l3-7 4 14 3-7h4"/>',
245
+ warning: '<path d="m12 3 10 17H2L12 3zm0 6v4m0 3h.01"/>'
246
+ };
247
+ const phases = { understand: 'Understand', plan: 'Plan', work: 'Work', verify: 'Verify' };
248
+ const transports = { mcp: 'MCP', cli: 'CLI', host: 'Host' };
249
+ const outcomes = { success: 'Succeeded', error: 'Error', cancelled: 'Cancelled' };
250
+ const names = { codex: 'Codex', 'claude-code': 'Claude Code', opencode: 'OpenCode', deepseek: 'DeepSeek', 'deepseek-harness': 'DeepSeek Harness' };
251
+ const esc = (value) => String(value ?? '').replace(/[&<>"']/g, (char) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[char]));
252
+ const icon = (name, compact = false) => '<svg aria-hidden="true" class="icon' + (compact ? ' compact' : '') + '" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.65" stroke-linecap="round" stroke-linejoin="round">' + (glyphs[name] || '') + '</svg>';
253
+ const clip = (value, length) => value.length > length ? value.slice(0, length - 1) + '…' : value;
254
+ const number = (value) => Number(value).toLocaleString();
255
+ const clock = new Intl.DateTimeFormat(undefined, { hour: 'numeric', minute: '2-digit', second: '2-digit' });
256
+ const tick = new Intl.DateTimeFormat(undefined, { hour: 'numeric', minute: '2-digit' });
257
+ const tag = (label, kind = '') => '<span class="tag ' + kind + '">' + esc(label) + '</span>';
258
+ const MAX_EVENTS = 500;
259
+ const ACTIVE_AGE_MS = 120_000;
260
+ const motionPreference = matchMedia('(prefers-reduced-motion: reduce)');
261
+ const state = {
262
+ events: [], journalId: null, cursor: null, retained: 0, dropped: 0, gap: false, reset: false,
263
+ loaded: false, paused: false, pending: false, controller: null, problem: '', checkedAt: 0, recording: null, delivery: null, demo: false,
264
+ selected: '', selectedEvent: '', renderKey: '', model: null, newIds: new Set()
265
+ };
266
+
267
+ document.querySelectorAll('[data-icon]').forEach((element) => { element.innerHTML = icon(element.dataset.icon); });
268
+
269
+ function labelAgent(value, transport) {
270
+ if (value) return Object.hasOwn(names, value) ? names[value] : value;
271
+ return (transports[transport] || 'Tool') + ' session';
272
+ }
273
+
274
+ function clockText(value, formatter = clock) {
275
+ const time = typeof value === 'number' ? value : Date.parse(value);
276
+ return Number.isFinite(time) ? formatter.format(time) : '';
277
+ }
278
+
279
+ function timeMarkup(value) {
280
+ const time = Date.parse(value);
281
+ if (!Number.isFinite(time)) return '';
282
+ return '<time class="mono muted" datetime="' + esc(value) + '" title="' + esc(new Date(time).toLocaleString()) + '">' + esc(clockText(time)) + '</time>';
283
+ }
284
+
285
+ function durationText(value) {
286
+ if (!Number.isFinite(value)) return '—';
287
+ if (value >= 1000) return (value / 1000).toLocaleString(undefined, { maximumFractionDigits: 2 }) + ' s';
288
+ if (value > 0 && value < .01) return '<0.01 ms';
289
+ return value.toLocaleString(undefined, { maximumFractionDigits: value < 1 ? 2 : 1 }) + ' ms';
290
+ }
291
+
292
+ function median(values) {
293
+ if (!values.length) return null;
294
+ const sorted = [...values].sort((a, b) => a - b);
295
+ const middle = Math.floor(sorted.length / 2);
296
+ return sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
297
+ }
298
+
299
+ function blank(image, title, detail) {
300
+ return '<div class="blank">' + icon(image) + '<h3>' + esc(title) + '</h3><p class="small">' + esc(detail) + '</p></div>';
301
+ }
302
+
303
+ function sessionKey(event) {
304
+ return JSON.stringify([event.projectId || '', event.sessionId || '', event.sessionId ? '' : event.agent || '', event.sessionId ? '' : event.transport]);
305
+ }
306
+
307
+ function spanKey(event) {
308
+ return JSON.stringify([sessionKey(event), event.correlationId]);
309
+ }
310
+
311
+ function describeEvent(event) {
312
+ const tool = event.tool || 'Tool';
313
+ switch (event.kind) {
314
+ case 'session-start': return 'Session started';
315
+ case 'session-end': return 'Session ended';
316
+ case 'phase': return 'Reported phase · ' + (phases[event.phase] || 'Unknown');
317
+ case 'tool-start': return tool + ' started';
318
+ case 'tool-end': return tool + ' · ' + (outcomes[event.outcome] || 'Result recorded');
319
+ default: return 'Journal event';
320
+ }
321
+ }
322
+
323
+ function eventClass(event) {
324
+ if (event.outcome === 'error' || event.outcome === 'cancelled') return 'failed';
325
+ return event.source === 'reported' ? 'reported' : 'live';
326
+ }
327
+
328
+ function buildModel(now, minutes) {
329
+ const start = now - minutes * 60_000;
330
+ const measuredStarts = new Map();
331
+ const matched = [];
332
+ const sessions = new Map();
333
+ const ended = new Set();
334
+ const visible = [];
335
+ let unmatchedEnds = 0;
336
+ for (const event of state.events) {
337
+ const timestamp = Date.parse(event.timestamp);
338
+ if (!Number.isFinite(timestamp) || timestamp > now) continue;
339
+ const key = sessionKey(event);
340
+ if (event.kind === 'session-end') ended.add(key);
341
+ if (event.kind === 'session-start') ended.delete(key);
342
+ if (event.source === 'measured' && event.correlationId) {
343
+ const correlation = spanKey(event);
344
+ if (event.kind === 'tool-start') measuredStarts.set(correlation, event);
345
+ if (event.kind === 'tool-end') {
346
+ const began = measuredStarts.get(correlation);
347
+ measuredStarts.delete(correlation);
348
+ if (timestamp >= start) {
349
+ if (began) matched.push(event);
350
+ else unmatchedEnds++;
351
+ }
352
+ }
353
+ }
354
+ if (timestamp < start) continue;
355
+ visible.push(event);
356
+ const session = sessions.get(key) || {
357
+ key, project: event.projectId || '', sessionId: event.sessionId || '', agent: '', transport: event.transport,
358
+ events: [], phases: new Map(), transports: new Set(), results: [], pending: [], stale: 0, latest: null, ended: false
359
+ };
360
+ if (event.agent) session.agent = event.agent;
361
+ if (transports[event.transport]) session.transports.add(transports[event.transport]);
362
+ session.events.push(event);
363
+ session.latest = event;
364
+ if (event.kind === 'phase' && event.source === 'reported' && phases[event.phase]) session.phases.set(event.phase, event);
365
+ sessions.set(key, session);
366
+ }
367
+ matched.forEach((event) => sessions.get(sessionKey(event))?.results.push(event));
368
+ let pending = 0;
369
+ let stale = 0;
370
+ for (const event of measuredStarts.values()) {
371
+ const key = sessionKey(event);
372
+ const session = sessions.get(key);
373
+ if (Date.parse(event.timestamp) < start || !session) continue;
374
+ if (ended.has(key) || now - Date.parse(event.timestamp) > ACTIVE_AGE_MS) {
375
+ session.stale++;
376
+ stale++;
377
+ } else {
378
+ session.pending.push(event);
379
+ pending++;
380
+ }
381
+ }
382
+ for (const session of sessions.values()) session.ended = ended.has(session.key);
383
+ const ordered = [...sessions.values()].sort((a, b) => b.latest.sequence - a.latest.sequence);
384
+ const errors = matched.filter((event) => event.outcome === 'error').length;
385
+ const durations = matched.map((event) => event.durationMs).filter(Number.isFinite);
386
+ return { now, start, minutes, visible, sessions: ordered, matched, pending, stale, errors, unmatchedEnds, median: median(durations) };
387
+ }
388
+
389
+ function sessionStatus(session) {
390
+ if (session.pending.length) return ['Awaiting tool result', 'warn'];
391
+ if (session.ended) return ['Ended', ''];
392
+ if (session.latest?.outcome === 'error') return ['Latest result error', 'bad'];
393
+ if (session.stale) return ['Result unavailable', 'warn'];
394
+ if (session.latest?.source === 'reported') return ['Phase or status reported', 'warn'];
395
+ return ['Activity recorded', 'ok'];
396
+ }
397
+
398
+ function sessionTransports(session) {
399
+ return [...session.transports].sort().join(' · ');
400
+ }
401
+
402
+ function renderStats(model) {
403
+ const cells = [
404
+ ['Observed sessions', model.sessions.length], ['Matched tool results', model.matched.length],
405
+ ['Awaiting result', model.pending], ['Tool errors', model.errors], ['Median duration', durationText(model.median)]
406
+ ];
407
+ $('live-stats').innerHTML = cells.map(([label, value], index) => '<div class="stat"><dt>' + label + '</dt><dd' +
408
+ (index === 3 && value ? ' class="danger"' : '') + '>' + esc(state.loaded ? typeof value === 'number' ? number(value) : value : '—') + '</dd></div>').join('');
409
+ }
410
+
411
+ function renderSessions(model) {
412
+ $('session-count').textContent = number(model.sessions.length);
413
+ if (!model.sessions.length) {
414
+ $('sessions').innerHTML = blank('agent', state.loaded ? 'No sessions in this window' : 'Loading sessions',
415
+ state.loaded ? 'Sessions appear when Shadow Brain records a supported action.' : 'Reading retained journal events.');
416
+ return;
417
+ }
418
+ $('sessions').innerHTML = '<ul class="list">' + model.sessions.map((session) => {
419
+ const [status, kind] = sessionStatus(session);
420
+ return '<li><button class="session-row" type="button" data-session="' + esc(session.key) + '" aria-pressed="' + (session.key === state.selected) +
421
+ '"><span class="session-glyph">' + icon('agent', true) + '</span><span class="session-detail"><span class="session-label">' +
422
+ esc(labelAgent(session.agent, session.transport)) + '</span><span class="small muted">' +
423
+ esc([session.project, sessionTransports(session)].filter(Boolean).join(' · ')) + '</span>' + tag(status, kind) + '</span></button></li>';
424
+ }).join('') + '</ul>';
425
+ }
426
+
427
+ function layoutNetwork(sessions) {
428
+ const width = 720;
429
+ const height = 405;
430
+ const core = { x: width / 2, y: height / 2 };
431
+ const ordered = [...sessions].sort((a, b) => a.key.localeCompare(b.key));
432
+ const phase = ordered.length <= 2 ? 0 : Math.PI / 4;
433
+ return { core, nodes: ordered.map((session, index) => {
434
+ const angle = index * Math.PI * 2 / ordered.length + phase;
435
+ const point = { x: core.x + Math.cos(angle) * 202, y: core.y + Math.sin(angle) * 113 };
436
+ const satellites = session.events.slice(-12).map((event, i) => {
437
+ const turn = (i % 6 - 2.5) * .27;
438
+ const ray = angle + turn;
439
+ const distance = i < 6 ? 76 : 96;
440
+ return { event, x: point.x + Math.cos(ray) * distance, y: point.y + Math.sin(ray) * distance * .72 };
441
+ });
442
+ return { session, point, satellites };
443
+ }) };
444
+ }
445
+
446
+ function pointPath(a, b, control = 0) {
447
+ const x = (a.x + b.x) / 2;
448
+ const y = (a.y + b.y) / 2 - control;
449
+ return 'M' + a.x.toFixed(1) + ' ' + a.y.toFixed(1) + ' Q' + x.toFixed(1) + ' ' + y.toFixed(1) + ' ' +
450
+ b.x.toFixed(1) + ' ' + b.y.toFixed(1);
451
+ }
452
+
453
+ function motionAllowed() {
454
+ return document.documentElement.dataset.motion !== 'reduce' && !motionPreference.matches && !state.paused && !document.hidden;
455
+ }
456
+
457
+ function pulse(path, tone, index) {
458
+ const delay = (index % 5) * .085;
459
+ return '<circle class="pulse ' + tone + '" r="3.9" opacity="0" data-delay="' + delay +
460
+ '"><animate attributeName="opacity" values="0;1;1;0" keyTimes="0;.13;.68;1" dur=".95s" begin="indefinite"/>' +
461
+ '<animateMotion path="' + path + '" dur=".95s" begin="indefinite"/></circle>';
462
+ }
463
+
464
+ function renderNetwork(model, animate) {
465
+ const svg = $('network-svg');
466
+ const selectedSession = model.sessions.find((session) => session.key === state.selected);
467
+ const mapSessions = model.sessions.slice(0, 10);
468
+ if (selectedSession && !mapSessions.includes(selectedSession)) mapSessions[mapSessions.length - 1] = selectedSession;
469
+ const { core, nodes } = layoutNetwork(mapSessions);
470
+ const displayedEvents = nodes.reduce((sum, node) => sum + node.satellites.length, 0);
471
+ $('network-count').textContent = displayedEvents ? number(displayedEvents) + ' recent events across ' + number(mapSessions.length) + ' displayed sessions' :
472
+ 'Recorded session activity';
473
+ const omitted = model.sessions.length - mapSessions.length;
474
+ $('network-status').textContent = omitted > 0 ? number(omitted) + ' more in Sessions' : state.paused ? 'Paused' : '';
475
+ $('network-status').hidden = !omitted && !state.paused;
476
+ const first = state.selected || nodes[0]?.session.key;
477
+ const primary = [];
478
+ const links = [];
479
+ const satellites = [];
480
+ const pulses = [];
481
+ const firstEvent = state.selectedEvent || nodes[0]?.satellites[0]?.event.id;
482
+ for (const { session, point, satellites: events } of nodes) {
483
+ const selected = session.key === state.selected;
484
+ const tone = session.pending.length ? 'live' : eventClass(session.latest);
485
+ const source = session.latest.source === 'reported' ? ' reported' : '';
486
+ links.push('<path class="network-edge' + source + (selected ? ' active' : state.selected ? ' dimmed' : '') +
487
+ '" d="' + pointPath(core, point, 10) + '"/>');
488
+ for (const item of events) {
489
+ const event = item.event;
490
+ const eventTone = eventClass(event);
491
+ const path = pointPath(point, item, 4);
492
+ links.push('<path class="network-edge' + (eventTone === 'reported' ? ' reported' : '') +
493
+ (event.id === state.selectedEvent ? ' active' : state.selected && !selected ? ' dimmed' : '') + '" d="' + path + '"/>');
494
+ if (animate && state.newIds.has(event.id)) {
495
+ pulses.push(pulse(pointPath(core, point, 10), eventTone, pulses.length));
496
+ pulses.push(pulse(path, eventTone, pulses.length));
497
+ }
498
+ const eventLabel = describeEvent(event) + ' · ' + labelAgent(session.agent, session.transport);
499
+ satellites.push('<g class="network-node ' + eventTone + '" data-live-event="' + esc(event.id) + '" role="button" tabindex="' +
500
+ (event.id === firstEvent ? 0 : -1) + '" aria-pressed="' + (event.id === state.selectedEvent) + '" aria-label="' + esc(eventLabel) +
501
+ '" transform="translate(' + item.x.toFixed(1) + ' ' + item.y.toFixed(1) + ')"><title>' + esc(eventLabel) +
502
+ '</title><circle r="8" fill="transparent"/><circle class="node-surface" r="5.5"/><circle class="node-indicator" r="2.3"/></g>');
503
+ }
504
+ const label = labelAgent(session.agent, session.transport);
505
+ const [status] = sessionStatus(session);
506
+ const full = [label, session.project, status].filter(Boolean).join(' · ');
507
+ const subtitle = clip(session.project || transports[session.transport] || 'Recorded session', 18);
508
+ primary.push('<g class="network-node ' + tone + '" data-session="' + esc(session.key) + '" role="button" tabindex="' +
509
+ (session.key === first ? 0 : -1) + '" aria-pressed="' + selected + '" aria-label="' + esc(full) + '" transform="translate(' +
510
+ (point.x - 68).toFixed(1) + ' ' + (point.y - 18).toFixed(1) + ')"><title>' + esc(full) +
511
+ '</title><rect class="node-surface" width="136" height="36" rx="6"/><circle class="node-indicator" cx="13" cy="13" r="3.6"/><text x="23" y="16">' +
512
+ esc(clip(label, 17)) + '</text><text class="node-subtitle" x="23" y="29">' + esc(subtitle) + '</text></g>');
513
+ }
514
+ const base = '<defs><pattern id="network-grid" width="16" height="16" patternUnits="userSpaceOnUse"><circle cx="1" cy="1" r=".8" fill="var(--grid)"/></pattern></defs>' +
515
+ '<rect width="720" height="405" fill="url(#network-grid)"/><ellipse class="topology-ring" cx="360" cy="202.5" rx="239" ry="143"/>' +
516
+ '<ellipse class="topology-ring" cx="360" cy="202.5" rx="202" ry="113"/><ellipse class="topology-ring" cx="360" cy="202.5" rx="131" ry="79"/>';
517
+ const hub = '<g class="network-core" aria-label="Local event journal"><circle cx="360" cy="202.5" r="54"/>' +
518
+ '<circle class="core-inner" cx="360" cy="202.5" r="47"/><text text-anchor="middle" x="360" y="201">Shadow Brain</text>' +
519
+ '<text class="node-subtitle" text-anchor="middle" x="360" y="217">Event journal</text></g>';
520
+ svg.innerHTML = base + '<g aria-hidden="true">' + links.join('') + '</g>' + hub + satellites.join('') + primary.join('') +
521
+ '<g id="network-pulses" aria-hidden="true">' + pulses.slice(0, 20).join('') + '</g>';
522
+ svg.querySelectorAll('#network-pulses [data-delay]').forEach((element) => {
523
+ for (const animation of element.children) animation.beginElementAt?.(Number(element.dataset.delay));
524
+ });
525
+ }
526
+
527
+ function fact(label, value) {
528
+ return value ? '<div><dt>' + esc(label) + '</dt><dd>' + esc(value) + '</dd></div>' : '';
529
+ }
530
+
531
+ function renderInspector(model) {
532
+ const session = model.sessions.find((item) => item.key === state.selected);
533
+ const target = $('inspection');
534
+ if (!session) {
535
+ target.innerHTML = blank('activity', 'Select a session or event', 'Inspect the recorded action, phase, and timing details.');
536
+ return;
537
+ }
538
+ const event = session.events.find((item) => item.id === state.selectedEvent);
539
+ const [status, kind] = sessionStatus(session);
540
+ const current = [...session.phases.values()].sort((a, b) => b.sequence - a.sequence)[0];
541
+ const phaseMarkup = Object.entries(phases).map(([key, label]) => {
542
+ const observation = session.phases.get(key);
543
+ return '<span class="stage' + (observation ? ' seen' : '') + (current?.phase === key ? ' current' : '') +
544
+ '"' + (observation ? ' title="' + esc('Reported at ' + clockText(observation.timestamp)) + '"' : '') + '>' + label + '</span>';
545
+ }).join('');
546
+ const eventDetails = event ? '<div><h3>Selected event</h3><p class="small">' + esc(describeEvent(event)) + '</p></div><dl class="facts">' +
547
+ fact('Source', event.source === 'reported' ? 'Host report' : 'Measured') +
548
+ fact('Recorded at', clockText(event.timestamp)) + fact('Transport', transports[event.transport]) +
549
+ fact('Duration', Number.isFinite(event.durationMs) ? durationText(event.durationMs) : '') +
550
+ fact('Sequence', String(event.sequence)) + '</dl>' : '';
551
+ const projectLink = session.project ? '<a class="button quiet" href="/#memories?project=' + esc(encodeURIComponent(session.project)) +
552
+ '">Open project memories</a>' : '';
553
+ target.innerHTML = '<div class="panel-body"><div><h3>' + esc(labelAgent(session.agent, session.transport)) + '</h3>' + tag(status, kind) +
554
+ '</div><dl class="facts">' + fact('Project', session.project || 'Unscoped') + fact('Transports', sessionTransports(session)) +
555
+ fact('Session ID', session.sessionId) + fact('Latest event', clockText(session.latest.timestamp)) +
556
+ fact('Matched results', String(session.results.length)) + fact('Awaiting result', session.pending.length ? String(session.pending.length) : '') +
557
+ fact('Unresolved starts', session.stale ? String(session.stale) : '') + '</dl>' +
558
+ '<div><h3>Task phases</h3>' + (current ? '<p class="small muted">Latest report at ' + esc(clockText(current.timestamp)) +
559
+ '</p><div class="stages">' + phaseMarkup + '</div>' : '<p class="small muted">No phase reports in this window.</p>') + '</div>' +
560
+ eventDetails + projectLink + '</div>';
561
+ }
562
+
563
+ function chartBuckets(model) {
564
+ const size = 10;
565
+ const width = (model.now - model.start) / size;
566
+ const buckets = Array.from({ length: size }, (_, index) => ({ time: model.start + index * width, results: 0, errors: 0, durations: [] }));
567
+ for (const result of model.matched) {
568
+ const position = Math.min(size - 1, Math.max(0, Math.floor((Date.parse(result.timestamp) - model.start) / width)));
569
+ const bucket = buckets[position];
570
+ bucket.results++;
571
+ if (result.outcome === 'error') bucket.errors++;
572
+ if (Number.isFinite(result.durationMs)) bucket.durations.push(result.durationMs);
573
+ }
574
+ return buckets.map(({ durations, ...bucket }) => ({ ...bucket, duration: median(durations) }));
575
+ }
576
+
577
+ function chartData(buckets, type, open, formatter) {
578
+ const timing = type === 'duration';
579
+ const headings = timing ? '<th scope="col">Median duration</th>' : '<th scope="col">Results</th><th scope="col">Errors</th>';
580
+ return '<details class="chart-data"' + (open ? ' open' : '') + '><summary class="small">View chart data</summary>' +
581
+ '<table><thead><tr><th scope="col">Interval start</th>' + headings + '</tr></thead><tbody>' + buckets.map((bucket) => {
582
+ const values = timing ? '<td>' + esc(durationText(bucket.duration)) + '</td>' : '<td>' + number(bucket.results) + '</td><td>' + number(bucket.errors) + '</td>';
583
+ return '<tr><th scope="row">' + esc(clockText(bucket.time, formatter)) + '</th>' + values + '</tr>';
584
+ }).join('') + '</tbody></table></details>';
585
+ }
586
+
587
+ function renderChart(id, buckets) {
588
+ const target = $(id);
589
+ const timing = id === 'duration';
590
+ const open = target.querySelector('details')?.open;
591
+ const formatter = buckets[1].time - buckets[0].time < 60_000 ? clock : tick;
592
+ const values = buckets.map((bucket) => timing ? bucket.duration : bucket.results).filter(Number.isFinite);
593
+ if (timing ? !values.length : !values.some(Boolean)) {
594
+ target.innerHTML = blank('activity', timing ? 'No measured durations in this window' : 'No matched tool results in this window',
595
+ 'Matching start and result events will populate this chart.');
596
+ return;
597
+ }
598
+ const max = Math.max(1, ...values);
599
+ const left = 63;
600
+ const right = 529;
601
+ const baseline = 119;
602
+ const top = 20;
603
+ const step = (right - left) / buckets.length;
604
+ const height = (value) => (baseline - top) * value / max;
605
+ let previous = null;
606
+ const marks = buckets.map((bucket, index) => {
607
+ const center = left + step * (index + .5);
608
+ if (!timing) {
609
+ const regular = bucket.results - bucket.errors;
610
+ const w = step * .65;
611
+ return '<g><title>' + esc(clockText(bucket.time, formatter) + ' · ' + bucket.results + ' results · ' + bucket.errors + ' errors') +
612
+ '</title><rect class="chart-bar" x="' + (center - w / 2) + '" y="' + (baseline - height(regular)) + '" width="' + w +
613
+ '" height="' + height(regular) + '"/><rect class="chart-error" x="' + (center - w / 2) + '" y="' +
614
+ (baseline - height(bucket.results)) + '" width="' + w + '" height="' + height(bucket.errors) + '"/></g>';
615
+ }
616
+ if (!Number.isFinite(bucket.duration)) {
617
+ previous = null;
618
+ return '';
619
+ }
620
+ const y = baseline - height(bucket.duration);
621
+ const line = previous ? '<path class="chart-duration" d="M' + previous.x + ' ' + previous.y + ' L' + center + ' ' + y + '"/>' : '';
622
+ previous = { x: center, y };
623
+ return line + '<circle class="chart-point" cx="' + center + '" cy="' + y + '" r="3.7"><title>' +
624
+ esc(clockText(bucket.time, formatter) + ' · ' + durationText(bucket.duration)) + '</title></circle>';
625
+ }).join('');
626
+ const maximum = timing ? durationText(max) : number(max);
627
+ const axis = [top, (top + baseline) / 2, baseline].map((y) => '<path class="chart-grid" d="M' + left + ' ' + y + 'H' + right + '"/>').join('');
628
+ const captions = [0, 4, 9].map((index) => '<text class="chart-label" x="' + (left + step * (index + .5)) +
629
+ '" y="139" text-anchor="middle">' + esc(clockText(buckets[index].time, formatter)) + '</text>').join('');
630
+ target.innerHTML = '<svg class="chart-svg" aria-hidden="true" viewBox="0 0 548 148">' + axis + marks + captions +
631
+ '<text class="chart-label" x="5" y="24">' + esc(maximum) + '</text><text class="chart-label" x="5" y="123">0</text></svg>' +
632
+ (!timing ? '<p class="small muted"><span style="color:var(--cyan)">■</span> Succeeded or cancelled <span style="color:var(--rose)">■</span> Errors</p>' : '') +
633
+ chartData(buckets, id, open, formatter);
634
+ }
635
+
636
+ function renderEvents(model) {
637
+ const session = model.sessions.find((item) => item.key === state.selected);
638
+ const filter = $('event-filter').value;
639
+ const data = (session ? session.events : model.visible).filter((event) => !filter || event.source === filter);
640
+ $('timeline-scope').textContent = (session ? labelAgent(session.agent, session.transport) + ' session' : 'All sessions') +
641
+ ' · ' + number(data.length) + ' recorded events';
642
+ $('clear-session').hidden = !session;
643
+ const target = $('events');
644
+ if (!data.length) {
645
+ target.innerHTML = blank('activity', 'No matching events', state.loaded ? 'Choose a different window or event source.' : 'Reading retained journal events.');
646
+ return;
647
+ }
648
+ target.innerHTML = '<ul class="list">' + [...data].reverse().slice(0, 150).map((event) => {
649
+ const tone = eventClass(event);
650
+ const source = event.source === 'reported' ? 'Reported' : 'Measured';
651
+ const extra = Number.isFinite(event.durationMs) ? ' · ' + durationText(event.durationMs) : '';
652
+ const meta = [event.agent ? labelAgent(event.agent, event.transport) : transports[event.transport], event.projectId].filter(Boolean).join(' · ');
653
+ return '<li class="event-row' + (state.newIds.has(event.id) ? ' new' : '') + '">' + timeMarkup(event.timestamp) +
654
+ '<span class="event-meta small muted">' + esc(meta) + '</span><div class="event-title"><button class="event-link" type="button" data-live-event="' +
655
+ esc(event.id) + '" aria-pressed="' + (event.id === state.selectedEvent) + '">' + esc(describeEvent(event)) +
656
+ '</button><span class="small muted">' + esc(extra) + '</span></div>' + tag(source, tone === 'reported' ? 'warn' : tone === 'failed' ? 'bad' : 'ok') + '</li>';
657
+ }).join('') + '</ul>' + (data.length > 150 ? '<p class="panel-body small muted">Showing the latest 150 events in this view.</p>' : '');
658
+ }
659
+
660
+ function renderStatus(model) {
661
+ const badge = $('stream-tag');
662
+ const label = state.problem ? 'Disconnected' : !state.loaded ? 'Connecting' : state.paused ? 'Paused' : !state.delivery ? 'Delivery off' : 'Live';
663
+ badge.className = 'tag ' + (state.problem ? 'bad' : state.loaded && state.delivery && !state.paused ? 'ok' : 'warn');
664
+ badge.title = state.checkedAt ? 'Last checked ' + clockText(state.checkedAt) : '';
665
+ $('stream-label').textContent = label;
666
+ $('demo-tag').hidden = !state.demo;
667
+ $('pause-label').textContent = state.paused ? 'Resume stream' : 'Pause stream';
668
+ $('pause').querySelector('[data-icon]').innerHTML = icon(state.paused ? 'play' : 'pause');
669
+ $('pause').disabled = !state.loaded || state.delivery === false;
670
+ $('refresh').disabled = state.pending;
671
+ const messages = [];
672
+ if (state.problem) messages.push(state.problem);
673
+ else if (!state.loaded) messages.push('Connecting to the local event journal…');
674
+ else {
675
+ messages.push(number(model.visible.length) + ' loaded events · Last ' + model.minutes + (model.minutes === 1 ? ' minute' : ' minutes'));
676
+ if (state.demo) messages.push('Scripted local activity.');
677
+ if (state.recording === false) messages.push('Activity recording is off.');
678
+ if (state.delivery === false) messages.push('Live delivery is off.');
679
+ if (state.paused) messages.push('Stream paused.');
680
+ const oldest = Date.parse(state.events[0]?.timestamp);
681
+ const incomplete = state.gap || (state.dropped > 0 || state.retained > state.events.length) &&
682
+ (!Number.isFinite(oldest) || oldest > model.start);
683
+ if (incomplete) messages.push('The window may be partial because older events are outside the loaded journal.');
684
+ if (state.reset) messages.push('The journal restarted.');
685
+ if (model.unmatchedEnds || model.stale) messages.push('Unmatched spans are excluded from measured result metrics.');
686
+ }
687
+ const notice = $('stream-notice');
688
+ notice.className = 'notice' + (state.problem ? ' error' : '');
689
+ const settings = state.loaded && (state.recording === false || state.delivery === false);
690
+ const markup = messages.map((message) => '<span>' + esc(message) + '</span>').join('') +
691
+ (settings ? '<a class="button quiet" href="/#features">Open Features</a>' : '');
692
+ if (notice.innerHTML !== markup) notice.innerHTML = markup;
693
+ }
694
+
695
+ function focusKey() {
696
+ const active = document.activeElement;
697
+ const value = active?.dataset.session || active?.dataset.liveEvent;
698
+ if (!value) return null;
699
+ const section = active.closest('#network-svg,#sessions,#events');
700
+ return section ? { section, key: active.dataset.session ? 'data-session' : 'data-live-event', value } : null;
701
+ }
702
+
703
+ function render(force = false, animate = false) {
704
+ const now = Math.max(state.checkedAt || Date.now(), Date.parse(state.events.at(-1)?.timestamp) || 0);
705
+ const minutes = Number($('time-window').value);
706
+ const model = buildModel(now, minutes);
707
+ state.model = model;
708
+ renderStatus(model);
709
+ const key = [state.loaded, state.journalId, state.cursor, Math.floor(now / 15000), minutes].join('|');
710
+ if (!force && key === state.renderKey) return;
711
+ state.renderKey = key;
712
+ if (!model.sessions.some((session) => session.key === state.selected)) state.selected = '';
713
+ if (!model.visible.some((event) => event.id === state.selectedEvent)) state.selectedEvent = '';
714
+ const focus = focusKey();
715
+ const scrolls = ['sessions', 'inspection', 'events'].map((id) => [$(id), $(id).scrollTop]);
716
+ renderStats(model);
717
+ renderSessions(model);
718
+ renderNetwork(model, animate && motionAllowed());
719
+ renderInspector(model);
720
+ const buckets = chartBuckets(model);
721
+ renderChart('throughput', buckets);
722
+ renderChart('duration', buckets);
723
+ renderEvents(model);
724
+ for (const [target, scroll] of scrolls) target.scrollTop = scroll;
725
+ if (focus) focus.section.querySelector('[' + focus.key + '="' + CSS.escape(focus.value) + '"]')?.focus({ preventScroll: true });
726
+ state.newIds.clear();
727
+ }
728
+
729
+ async function loadPage(signal) {
730
+ const query = new URLSearchParams({ limit: '200' });
731
+ if (state.cursor !== null) query.set('cursor', String(state.cursor));
732
+ if (state.journalId) query.set('journalId', state.journalId);
733
+ const response = await fetch('/api/workspace/telemetry?' + query, { cache: 'no-store', headers: { Accept: 'application/json' }, signal });
734
+ const data = await response.json().catch(() => null);
735
+ if (!response.ok || !Array.isArray(data?.events) || !Number.isSafeInteger(data.nextAfterSequence ?? data.cursor)) {
736
+ throw new Error(data?.error || 'The local event journal could not be read.');
737
+ }
738
+ return data;
739
+ }
740
+
741
+ function acceptPage(page, animate) {
742
+ if (page.liveEnabled === false) {
743
+ state.events = [];
744
+ state.journalId = null;
745
+ state.cursor = null;
746
+ state.retained = 0;
747
+ state.dropped = 0;
748
+ state.gap = false;
749
+ state.reset = false;
750
+ state.recording = page.recordingEnabled;
751
+ state.delivery = false;
752
+ state.demo = page.demo === true;
753
+ state.checkedAt = Date.parse(page.checkedAt) || Date.now();
754
+ state.loaded = true;
755
+ state.newIds.clear();
756
+ return;
757
+ }
758
+ const reset = page.reset || (state.journalId && page.journalId !== state.journalId);
759
+ state.reset = !!reset;
760
+ if (reset) {
761
+ state.events = [];
762
+ state.gap = false;
763
+ }
764
+ if (page.gap) state.gap = true;
765
+ const merged = new Map(state.events.map((event) => [event.sequence, event]));
766
+ for (const event of page.events) {
767
+ if (!Number.isSafeInteger(event.sequence) || typeof event.id !== 'string' || !Number.isFinite(Date.parse(event.timestamp))) continue;
768
+ if (animate && !merged.has(event.sequence)) state.newIds.add(event.id);
769
+ merged.set(event.sequence, event);
770
+ }
771
+ state.events = [...merged.values()].sort((a, b) => a.sequence - b.sequence).slice(-MAX_EVENTS);
772
+ state.journalId = page.journalId;
773
+ state.cursor = page.nextAfterSequence ?? page.cursor;
774
+ state.retained = page.retainedCount;
775
+ state.dropped = page.droppedCount;
776
+ state.recording = page.recordingEnabled;
777
+ state.delivery = page.liveEnabled;
778
+ state.demo = page.demo === true;
779
+ state.checkedAt = Date.parse(page.checkedAt) || Date.now();
780
+ state.loaded = true;
781
+ }
782
+
783
+ async function poll(manual = false) {
784
+ if (state.pending || (!manual && (state.paused || state.delivery === false || document.hidden))) return;
785
+ state.pending = true;
786
+ $('refresh').disabled = true;
787
+ const controller = new AbortController();
788
+ state.controller = controller;
789
+ const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(12000)]);
790
+ const animate = state.loaded && state.delivery !== false && motionAllowed();
791
+ try {
792
+ for (let i = 0; i < 3; i++) {
793
+ const page = await loadPage(signal);
794
+ if (signal.aborted) return;
795
+ acceptPage(page, animate);
796
+ if (!page.hasMore) break;
797
+ }
798
+ state.problem = '';
799
+ } catch (error) {
800
+ if (!controller.signal.aborted) state.problem = error.message || 'The event journal is unavailable.';
801
+ } finally {
802
+ state.pending = false;
803
+ state.controller = null;
804
+ render(manual, animate);
805
+ }
806
+ }
807
+
808
+ function chooseSession(key) {
809
+ state.selected = key;
810
+ state.selectedEvent = '';
811
+ render(true);
812
+ }
813
+
814
+ function chooseEvent(id) {
815
+ const event = state.model?.visible.find((item) => item.id === id);
816
+ if (!event) return;
817
+ state.selected = sessionKey(event);
818
+ state.selectedEvent = id;
819
+ render(true);
820
+ }
821
+
822
+ function keyboardNetwork(event) {
823
+ const target = event.target.closest('[data-session],[data-live-event]');
824
+ if (!target) return;
825
+ const eventKey = target.dataset.liveEvent;
826
+ const attr = eventKey ? 'data-live-event' : 'data-session';
827
+ const items = [...$('network-svg').querySelectorAll('[' + attr + ']')];
828
+ const index = items.indexOf(target);
829
+ const activate = (item) => {
830
+ if (eventKey) chooseEvent(item.dataset.liveEvent);
831
+ else chooseSession(item.dataset.session);
832
+ };
833
+ if (event.key === 'Enter' || event.key === ' ') {
834
+ event.preventDefault();
835
+ activate(target);
836
+ return;
837
+ }
838
+ const next = { ArrowLeft: index - 1, ArrowUp: index - 1, ArrowRight: index + 1, ArrowDown: index + 1, Home: 0, End: items.length - 1 }[event.key];
839
+ if (!Number.isInteger(next)) return;
840
+ event.preventDefault();
841
+ const selected = items[(next + items.length) % items.length];
842
+ const value = eventKey ? selected.dataset.liveEvent : selected.dataset.session;
843
+ activate(selected);
844
+ $('network-svg').querySelector('[' + attr + '="' + CSS.escape(value) + '"]')?.focus({ preventScroll: true });
845
+ }
846
+
847
+ function syncMotion() {
848
+ const reduced = document.documentElement.dataset.motion === 'reduce' || motionPreference.matches;
849
+ $('motion-control').setAttribute('aria-pressed', String(reduced));
850
+ $('motion-control').title = motionPreference.matches ? 'Your system preference reduces motion' : '';
851
+ if (reduced) $('network-pulses')?.replaceChildren();
852
+ }
853
+
854
+ document.addEventListener('click', (event) => {
855
+ const target = event.target.closest('[data-session],[data-live-event]');
856
+ if (target?.dataset.session) chooseSession(target.dataset.session);
857
+ else if (target?.dataset.liveEvent) chooseEvent(target.dataset.liveEvent);
858
+ });
859
+ $('network-svg').addEventListener('keydown', keyboardNetwork);
860
+ $('time-window').addEventListener('change', () => render(true));
861
+ $('event-filter').addEventListener('change', () => renderEvents(state.model));
862
+ $('clear-session').addEventListener('click', () => chooseSession(''));
863
+ $('refresh').addEventListener('click', () => { void poll(true); });
864
+ $('pause').addEventListener('click', () => {
865
+ state.paused = !state.paused;
866
+ if (state.paused) {
867
+ state.controller?.abort();
868
+ $('network-pulses')?.replaceChildren();
869
+ render(true);
870
+ } else {
871
+ render(true);
872
+ void poll();
873
+ }
874
+ });
875
+ $('motion-control').addEventListener('click', () => {
876
+ const value = document.documentElement.dataset.motion === 'reduce' ? 'system' : 'reduce';
877
+ document.documentElement.dataset.motion = value;
878
+ try { localStorage.setItem('shadow-brain:workspace:motion', value); } catch {}
879
+ syncMotion();
880
+ });
881
+ motionPreference.addEventListener('change', syncMotion);
882
+ document.addEventListener('visibilitychange', () => {
883
+ if (document.hidden) $('network-pulses')?.replaceChildren();
884
+ else void poll();
885
+ });
886
+ syncMotion();
887
+ render(true);
888
+ void poll(true);
889
+ setInterval(() => { void poll(); }, 1900);
890
+ </script>
891
+ </body>
892
+ </html>`;
893
+ //# sourceMappingURL=live-workspace-html.js.map