@ericsanchezok/synergy-workbench 0.0.0-stage → 3.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 (153) hide show
  1. package/AGENTS.md +30 -0
  2. package/README.md +16 -2
  3. package/dist/agents.js +11 -0
  4. package/dist/cli-adapter.js +8 -0
  5. package/dist/component.js +65 -0
  6. package/dist/config-schema.js +265 -0
  7. package/dist/execution/activity.js +78 -0
  8. package/dist/execution/content.js +184 -0
  9. package/dist/execution/json-sections.js +231 -0
  10. package/dist/execution/presentation.js +79 -0
  11. package/dist/execution/preview.js +13 -0
  12. package/dist/execution/process.js +125 -0
  13. package/dist/execution/projection.js +327 -0
  14. package/dist/execution/query.js +18 -0
  15. package/dist/execution/routes.js +189 -0
  16. package/dist/execution/schema.js +322 -0
  17. package/dist/execution/service.js +1993 -0
  18. package/dist/features.js +22 -0
  19. package/dist/http.js +65 -0
  20. package/dist/performance/analysis.js +330 -0
  21. package/dist/performance/catalog.js +391 -0
  22. package/dist/performance/dashboard.js +483 -0
  23. package/dist/performance/error.js +15 -0
  24. package/dist/performance/index.js +6 -0
  25. package/dist/performance/inflight.js +43 -0
  26. package/dist/performance/projection.js +138 -0
  27. package/dist/performance/prompt/base.txt +38 -0
  28. package/dist/performance/prompt/builder.js +21 -0
  29. package/dist/performance/routes/performance-route.js +439 -0
  30. package/dist/performance/schema.js +628 -0
  31. package/dist/performance/timeline.js +98 -0
  32. package/dist/performance/trace-detail.js +91 -0
  33. package/dist/presentation/service.js +270 -0
  34. package/dist/presentation/tools/workbench-view.js +65 -0
  35. package/dist/presentation/tools.js +10 -0
  36. package/dist/project/directories.js +363 -0
  37. package/dist/project/git-health.js +498 -0
  38. package/dist/project/ide.js +66 -0
  39. package/dist/project/migration.js +22 -0
  40. package/dist/project/routes/directories.js +89 -0
  41. package/dist/project/routes/git.js +30 -0
  42. package/dist/project/routes/task-defaults.js +42 -0
  43. package/dist/project/session-health.js +9 -0
  44. package/dist/project/startup.js +28 -0
  45. package/dist/project/task-defaults.js +68 -0
  46. package/dist/project/tools/session-control.js +664 -0
  47. package/dist/project/tools/session-control.txt +60 -0
  48. package/dist/project/tools/worktree-archive.js +76 -0
  49. package/dist/project/tools/worktree-enter.js +175 -0
  50. package/dist/project/tools/worktree-leave.js +119 -0
  51. package/dist/project/tools/worktree-list.js +52 -0
  52. package/dist/project/tools.js +26 -0
  53. package/dist/project/vcs-branch-watcher.js +76 -0
  54. package/dist/project/vcs.js +114 -0
  55. package/dist/project/worktree-janitor.js +140 -0
  56. package/dist/project/worktrees.js +98 -0
  57. package/dist/push/bridge.js +171 -0
  58. package/dist/push/index.js +4 -0
  59. package/dist/push/routes/push.js +224 -0
  60. package/dist/push/service.js +93 -0
  61. package/dist/push/store.js +121 -0
  62. package/dist/push/types.js +58 -0
  63. package/dist/review/git.js +226 -0
  64. package/dist/review/routes.js +45 -0
  65. package/dist/review/schema.js +32 -0
  66. package/dist/review/state.js +79 -0
  67. package/dist/stats/aggregator.js +252 -0
  68. package/dist/stats/canonical.js +89 -0
  69. package/dist/stats/cli/stats.js +302 -0
  70. package/dist/stats/engine.js +176 -0
  71. package/dist/stats/index.js +5 -0
  72. package/dist/stats/rollup.js +460 -0
  73. package/dist/stats/routes/stats.js +94 -0
  74. package/dist/stats/routes/usage.js +83 -0
  75. package/dist/stats/storage.js +86 -0
  76. package/dist/stats/types.js +267 -0
  77. package/dist/worker.js +6 -0
  78. package/package.json +232 -4
  79. package/src/agents.ts +15 -0
  80. package/src/cli-adapter.ts +11 -0
  81. package/src/component.ts +73 -0
  82. package/src/config-schema.ts +289 -0
  83. package/src/execution/activity.ts +83 -0
  84. package/src/execution/content.ts +189 -0
  85. package/src/execution/json-sections.ts +230 -0
  86. package/src/execution/presentation.ts +91 -0
  87. package/src/execution/preview.ts +12 -0
  88. package/src/execution/process.ts +118 -0
  89. package/src/execution/projection.ts +330 -0
  90. package/src/execution/query.ts +19 -0
  91. package/src/execution/routes.ts +324 -0
  92. package/src/execution/schema.ts +336 -0
  93. package/src/execution/service.ts +2149 -0
  94. package/src/features.ts +22 -0
  95. package/src/http.ts +75 -0
  96. package/src/performance/analysis.ts +356 -0
  97. package/src/performance/catalog.ts +902 -0
  98. package/src/performance/dashboard.ts +521 -0
  99. package/src/performance/error.ts +32 -0
  100. package/src/performance/index.ts +6 -0
  101. package/src/performance/inflight.ts +46 -0
  102. package/src/performance/projection.ts +143 -0
  103. package/src/performance/prompt/base.txt +38 -0
  104. package/src/performance/prompt/builder.ts +24 -0
  105. package/src/performance/routes/performance-route.ts +541 -0
  106. package/src/performance/schema.ts +695 -0
  107. package/src/performance/timeline.ts +100 -0
  108. package/src/performance/trace-detail.ts +97 -0
  109. package/src/presentation/service.ts +303 -0
  110. package/src/presentation/tools/workbench-view.ts +75 -0
  111. package/src/presentation/tools.ts +11 -0
  112. package/src/project/directories.ts +391 -0
  113. package/src/project/git-health.ts +577 -0
  114. package/src/project/ide.ts +80 -0
  115. package/src/project/migration.ts +24 -0
  116. package/src/project/routes/directories.ts +124 -0
  117. package/src/project/routes/git.ts +42 -0
  118. package/src/project/routes/task-defaults.ts +50 -0
  119. package/src/project/session-health.ts +10 -0
  120. package/src/project/startup.ts +28 -0
  121. package/src/project/task-defaults.ts +70 -0
  122. package/src/project/tools/session-control.ts +725 -0
  123. package/src/project/tools/session-control.txt +60 -0
  124. package/src/project/tools/worktree-archive.ts +92 -0
  125. package/src/project/tools/worktree-enter.ts +227 -0
  126. package/src/project/tools/worktree-leave.ts +140 -0
  127. package/src/project/tools/worktree-list.ts +68 -0
  128. package/src/project/tools.ts +29 -0
  129. package/src/project/vcs-branch-watcher.ts +81 -0
  130. package/src/project/vcs.ts +124 -0
  131. package/src/project/worktree-janitor.ts +153 -0
  132. package/src/project/worktrees.ts +110 -0
  133. package/src/push/bridge.ts +182 -0
  134. package/src/push/index.ts +4 -0
  135. package/src/push/routes/push.ts +267 -0
  136. package/src/push/service.ts +108 -0
  137. package/src/push/store.ts +120 -0
  138. package/src/push/types.ts +67 -0
  139. package/src/review/git.ts +221 -0
  140. package/src/review/routes.ts +65 -0
  141. package/src/review/schema.ts +34 -0
  142. package/src/review/state.ts +77 -0
  143. package/src/stats/aggregator.ts +277 -0
  144. package/src/stats/canonical.ts +93 -0
  145. package/src/stats/cli/stats.ts +348 -0
  146. package/src/stats/engine.ts +197 -0
  147. package/src/stats/index.ts +5 -0
  148. package/src/stats/rollup.ts +522 -0
  149. package/src/stats/routes/stats.ts +107 -0
  150. package/src/stats/routes/usage.ts +103 -0
  151. package/src/stats/storage.ts +78 -0
  152. package/src/stats/types.ts +351 -0
  153. package/src/worker.ts +7 -0
@@ -0,0 +1,483 @@
1
+ import { ResourceSources } from "@ericsanchezok/synergy-harness/observability/resource-sources";
2
+ import { RuntimeContext } from "@ericsanchezok/synergy-harness/lifecycle/context";
3
+ import { Cortex } from "@ericsanchezok/synergy-harness/cortex";
4
+ import { Diagnostics } from "@ericsanchezok/synergy-harness/observability/diagnostics";
5
+ import { ObservabilityIssues } from "@ericsanchezok/synergy-harness/observability/issues";
6
+ import { ObservabilityStore } from "@ericsanchezok/synergy-harness/observability/store";
7
+ import { SessionManager } from "@ericsanchezok/synergy-harness/session/manager";
8
+ import { SessionMessageCache } from "@ericsanchezok/synergy-harness/session/message-cache";
9
+ import { LLMTurnMemory } from "@ericsanchezok/synergy-harness/session/llm-memory";
10
+ import { parseJson } from "@ericsanchezok/synergy-harness/util/json-parse";
11
+ import { PerformanceProjection } from "./projection.js";
12
+ import { PerformanceSchema } from "./schema.js";
13
+ import { AgentTurn } from "@ericsanchezok/synergy-harness/session/agent-turn";
14
+ import { readRuntimeStats } from "@ericsanchezok/synergy-harness/lifecycle";
15
+ import { PolicyWorker } from "@ericsanchezok/synergy-harness/enforcement/policy-worker";
16
+ import { ProcessRegistry } from "@ericsanchezok/synergy-harness/process/registry";
17
+ import { SessionMemoryPressure } from "@ericsanchezok/synergy-harness/session/memory-pressure";
18
+ export var PerformanceDashboard;
19
+ (function (PerformanceDashboard) {
20
+ const SUMMARY_CACHE_MS = 1000;
21
+ const runtimeState = RuntimeContext.state(() => ({
22
+ cached: undefined,
23
+ labelsCache: new WeakMap(),
24
+ }));
25
+ // Ranked and latest detail can reuse the same row across several projections.
26
+ function metricLabels(row) {
27
+ const instanceState = runtimeState();
28
+ const cachedLabels = instanceState.labelsCache.get(row);
29
+ if (cachedLabels)
30
+ return cachedLabels;
31
+ const labels = parseJson(row.labels_json);
32
+ instanceState.labelsCache.set(row, labels);
33
+ return labels;
34
+ }
35
+ function summary(input = {}) {
36
+ const instanceState = runtimeState();
37
+ const key = `${input.windowMs ?? 300_000}:${input.scopeID ?? ""}`;
38
+ const version = ObservabilityStore.dataVersion();
39
+ const now = Date.now();
40
+ if (instanceState.cached?.key === key &&
41
+ (instanceState.cached.pending ||
42
+ (instanceState.cached.version === version && now - instanceState.cached.at < SUMMARY_CACHE_MS)))
43
+ return instanceState.cached.value;
44
+ const value = computeSummary(input);
45
+ const wrapped = value
46
+ .then((result) => {
47
+ if (instanceState.cached?.value === wrapped) {
48
+ instanceState.cached.pending = false;
49
+ instanceState.cached.at = Date.now();
50
+ }
51
+ return result;
52
+ })
53
+ .catch((error) => {
54
+ const instanceState = runtimeState();
55
+ if (instanceState.cached?.value === wrapped)
56
+ instanceState.cached = undefined;
57
+ throw error;
58
+ });
59
+ instanceState.cached = { key, version, pending: true, at: now, value: wrapped };
60
+ return wrapped;
61
+ }
62
+ PerformanceDashboard.summary = summary;
63
+ async function computeSummary(input = {}) {
64
+ const windowMs = Math.max(1000, Math.min(input.windowMs ?? 300_000, 86_400_000));
65
+ // Storage uses an exclusive upper bound; include observations in the current millisecond.
66
+ const until = Date.now() + 1;
67
+ const since = until - windowMs;
68
+ const filter = { since, until, scopeID: input.scopeID };
69
+ const aggregate = async (name) => (await ObservabilityStore.queryMetricBuckets({
70
+ since,
71
+ until,
72
+ scopeID: input.scopeID,
73
+ names: [name],
74
+ bucketMs: windowMs + 1,
75
+ }))[0];
76
+ const [requests, turnSummary, resourceSummary, counts, toolSums, activeSessions] = await Promise.all([
77
+ aggregate("http.request.duration"),
78
+ aggregate("session.turn.duration"),
79
+ aggregate("frontend.resource.duration"),
80
+ ObservabilityStore.queryMetricCounts(filter),
81
+ ObservabilityStore.queryMetricSums({ ...filter, names: ["tool.execution.count", "tool.execution.error"] }),
82
+ ObservabilityStore.countMetricSessions({ ...filter, since: Math.max(since, until - 300_000) }),
83
+ ]);
84
+ const highlightNames = counts
85
+ .filter((row) => (row.module === "llm" && row.name.endsWith(".duration")) ||
86
+ [
87
+ "http.request.duration",
88
+ "session.turn.duration",
89
+ "tool.execution.duration",
90
+ "storage.operation.duration",
91
+ "library.operation.duration",
92
+ "frontend.resource.duration",
93
+ "frontend.long_task.duration",
94
+ "frontend.web_vital",
95
+ "service.memory.current",
96
+ "service.memory.working_set",
97
+ "service.memory.reclaimable",
98
+ "service.memory.peak",
99
+ ].includes(row.name))
100
+ .map((row) => row.name);
101
+ const metrics = highlightNames.length
102
+ ? await ObservabilityStore.queryMetricHighlights({ ...filter, names: [...new Set(highlightNames)] })
103
+ : [];
104
+ const count = (name) => counts.filter((row) => row.name === name).reduce((sum, row) => sum + row.count, 0);
105
+ const serverFilter = { role: "server", until, limit: 1 };
106
+ const resources = ObservabilityStore.resourceSince(since, { ...serverFilter, newestFirst: true })[0];
107
+ const serverBaseline = ObservabilityStore.resourceSince(since, serverFilter)[0];
108
+ const currentChildRows = resources
109
+ ? ObservabilityStore.resourceSince(resources.time, {
110
+ role: "tool",
111
+ until: resources.time + 1,
112
+ limit: 50_000,
113
+ })
114
+ : [];
115
+ const [serverResources, childResources] = await Promise.all([
116
+ ObservabilityStore.resourceSummary({ since, until, role: "server" }),
117
+ resources
118
+ ? ObservabilityStore.resourceSummary({ since: resources.time, until: resources.time + 1, role: "tool" })
119
+ : undefined,
120
+ ]);
121
+ const childProcesses = rankChildProcesses(currentChildRows);
122
+ const issueCounts = ObservabilityStore.countIssues({ status: "open", scopeID: input.scopeID, since });
123
+ const issues = ObservabilityIssues.list({
124
+ status: "open",
125
+ scopeID: input.scopeID,
126
+ since,
127
+ limit: 20,
128
+ }).map(PerformanceProjection.issue);
129
+ const diagnostics = await Diagnostics.summary().catch(() => undefined);
130
+ const runtimeStats = SessionManager.runtimeStats();
131
+ const agentWorkers = AgentTurn.stats();
132
+ const policyWorkers = PolicyWorker.stats();
133
+ const { toolTasks } = readRuntimeStats();
134
+ const contributedResources = await ResourceSources.read();
135
+ const localProcesses = await ProcessRegistry.resourceStats();
136
+ const controlPlane = SessionMemoryPressure.stats();
137
+ const messageCacheStats = SessionMessageCache.stats();
138
+ const llmTurnStats = LLMTurnMemory.stats();
139
+ const activeLLMTurns = LLMTurnMemory.activeSnapshot(20);
140
+ const cortexTasks = Cortex.list();
141
+ const cortexStats = {
142
+ totalCount: cortexTasks.length,
143
+ byStatus: {
144
+ queued: cortexTasks.filter((task) => task.status === "queued").length,
145
+ running: cortexTasks.filter((task) => task.status === "running").length,
146
+ completed: cortexTasks.filter((task) => task.status === "completed").length,
147
+ error: cortexTasks.filter((task) => task.status === "error").length,
148
+ cancelled: cortexTasks.filter((task) => task.status === "cancelled").length,
149
+ interrupted: cortexTasks.filter((task) => task.status === "interrupted").length,
150
+ },
151
+ retainedPromptChars: cortexTasks.reduce((sum, task) => sum + task.prompt.length, 0),
152
+ retainedOutputChars: cortexTasks.reduce((sum, task) => {
153
+ if (!task.output)
154
+ return sum;
155
+ if (task.output.mode === "summary" || task.output.mode === "final_response")
156
+ return sum + task.output.value.length;
157
+ try {
158
+ return sum + JSON.stringify(task.output.value).length;
159
+ }
160
+ catch {
161
+ return sum;
162
+ }
163
+ }, 0),
164
+ retainedErrorChars: cortexTasks.reduce((sum, task) => sum + (task.error?.length ?? 0), 0),
165
+ retainedProgressToolCount: cortexTasks.reduce((sum, task) => sum + (task.progress?.recentTools?.length ?? 0), 0),
166
+ };
167
+ const http = metrics.filter((row) => row.name === "http.request.duration");
168
+ const turns = metrics.filter((row) => row.name === "session.turn.duration");
169
+ const llm = metrics.filter((row) => row.module === "llm" && row.name.endsWith(".duration"));
170
+ const tools = metrics.filter((row) => row.name === "tool.execution.duration");
171
+ const toolFailures = rankToolFailures(toolSums);
172
+ const storage = metrics.filter((row) => row.name === "storage.operation.duration");
173
+ const library = metrics.filter((row) => row.name === "library.operation.duration");
174
+ const frontendResources = metrics.filter((row) => row.name === "frontend.resource.duration");
175
+ const frontendLongTasks = metrics.filter((row) => row.name === "frontend.long_task.duration");
176
+ const frontendVital = (name) => metrics.find((row) => row.name === "frontend.web_vital" && metricLabels(row).name === name)?.value;
177
+ const criticalIssueCount = issueCounts.critical;
178
+ const score = Math.max(0, 100 - criticalIssueCount * 40 - issueCounts.error * 20 - issueCounts.warning * 8);
179
+ const status = criticalIssueCount > 0 ? "critical" : issueCounts.total > 0 ? "degraded" : "healthy";
180
+ const serverPeakBytes = serverResources?.peakRssBytes ?? undefined;
181
+ const currentServerBytes = resources?.memory_rss_bytes ?? undefined;
182
+ const serverBaselineBytes = serverBaseline?.memory_rss_bytes ?? undefined;
183
+ const latestMetric = (name) => metrics.find((row) => row.name === name)?.value;
184
+ const owners = [
185
+ {
186
+ owner: "control_plane",
187
+ processCount: 1,
188
+ measuredProcessCount: currentServerBytes === undefined ? 0 : 1,
189
+ currentBytes: currentServerBytes,
190
+ peakBytes: serverPeakBytes ?? currentServerBytes,
191
+ baselineBytes: serverBaselineBytes,
192
+ retainedBytes: currentServerBytes === undefined || serverBaselineBytes === undefined
193
+ ? undefined
194
+ : Math.max(0, currentServerBytes - serverBaselineBytes),
195
+ heapUsedBytes: resources?.memory_heap_used_bytes ?? undefined,
196
+ externalBytes: resources?.memory_external_bytes ?? undefined,
197
+ arrayBuffersBytes: resources?.memory_array_buffers_bytes ?? undefined,
198
+ source: "process_api",
199
+ completeness: currentServerBytes === undefined ? "unavailable" : "full",
200
+ attributes: {
201
+ pressure: controlPlane.lastAssessment?.pressure ?? "unknown",
202
+ processPressure: controlPlane.lastAssessment?.processPressure ?? "unknown",
203
+ servicePressure: controlPlane.lastAssessment?.servicePressure ?? "unknown",
204
+ },
205
+ lastRecovery: controlPlane.lastRecovery,
206
+ },
207
+ {
208
+ owner: "agent",
209
+ processCount: agentWorkers.workers,
210
+ measuredProcessCount: agentWorkers.measuredWorkers,
211
+ currentBytes: agentWorkers.rssBytes,
212
+ peakBytes: agentWorkers.peakBytes,
213
+ baselineBytes: agentWorkers.baselineBytes,
214
+ retainedBytes: agentWorkers.retainedBytes,
215
+ heapUsedBytes: agentWorkers.heapUsedBytes,
216
+ externalBytes: agentWorkers.externalBytes,
217
+ arrayBuffersBytes: agentWorkers.arrayBuffersBytes,
218
+ source: "worker_ipc",
219
+ completeness: agentWorkers.workers === 0 || agentWorkers.measuredWorkers === agentWorkers.workers ? "full" : "partial",
220
+ attributes: { active: agentWorkers.active, queued: agentWorkers.queued },
221
+ lastRecovery: agentWorkers.lastRecovery,
222
+ },
223
+ {
224
+ owner: "policy",
225
+ processCount: policyWorkers.workers,
226
+ measuredProcessCount: policyWorkers.measuredWorkers,
227
+ currentBytes: policyWorkers.rssBytes,
228
+ peakBytes: policyWorkers.peakBytes,
229
+ baselineBytes: policyWorkers.baselineBytes,
230
+ retainedBytes: policyWorkers.retainedBytes,
231
+ heapUsedBytes: policyWorkers.heapUsedBytes,
232
+ externalBytes: policyWorkers.externalBytes,
233
+ arrayBuffersBytes: policyWorkers.arrayBuffersBytes,
234
+ source: "worker_ipc",
235
+ completeness: policyWorkers.workers === 0 || policyWorkers.measuredWorkers === policyWorkers.workers ? "full" : "partial",
236
+ attributes: { active: policyWorkers.active, queued: policyWorkers.queued },
237
+ lastRecovery: policyWorkers.lastRecovery,
238
+ },
239
+ ...contributedResources,
240
+ {
241
+ owner: "local_process",
242
+ ...localProcesses,
243
+ source: "process_registry",
244
+ completeness: localProcesses.processCount === 0 || localProcesses.measuredProcessCount === localProcesses.processCount
245
+ ? "full"
246
+ : "partial",
247
+ attributes: {
248
+ stdioOpen: localProcesses.stdio.open,
249
+ stdioDraining: localProcesses.stdio.draining,
250
+ stdioClosed: localProcesses.stdio.closed,
251
+ timedOut: localProcesses.stdio.timedOut,
252
+ drainTimedOut: localProcesses.stdio.drainTimedOut,
253
+ descendantPipeGraceMs: localProcesses.stdio.descendantPipeGraceMs,
254
+ },
255
+ },
256
+ ];
257
+ return PerformanceSchema.DashboardSummary.parse({
258
+ generatedAt: new Date().toISOString(),
259
+ windowMs,
260
+ health: { status, score, openIssueCount: issueCounts.total, criticalIssueCount },
261
+ backend: {
262
+ requestCount: requests?.count ?? 0,
263
+ errorRate: requests ? requests.errors / requests.count : 0,
264
+ p50RequestMs: requests?.p50,
265
+ p95RequestMs: requests?.p95,
266
+ p99RequestMs: requests?.p99,
267
+ activeSessions,
268
+ pendingSessions: diagnostics?.sessions.paused.length ?? 0,
269
+ },
270
+ resources: {
271
+ rssBytes: resources?.memory_rss_bytes ?? undefined,
272
+ heapUsedBytes: resources?.memory_heap_used_bytes ?? undefined,
273
+ heapTotalBytes: resources?.memory_heap_total_bytes ?? undefined,
274
+ externalBytes: resources?.memory_external_bytes ?? undefined,
275
+ arrayBuffersBytes: resources?.memory_array_buffers_bytes ?? undefined,
276
+ cpuUtilizationRatio: resources?.cpu_utilization_ratio ?? undefined,
277
+ eventLoopLagP95Ms: serverResources?.eventLoopLagP95Ms ?? undefined,
278
+ appReadBytes: resources?.app_read_bytes ?? undefined,
279
+ appWrittenBytes: resources?.app_written_bytes ?? undefined,
280
+ appReadOps: resources?.app_read_ops ?? undefined,
281
+ appWriteOps: resources?.app_write_ops ?? undefined,
282
+ childProcessCount: childResources?.count ?? 0,
283
+ measuredChildProcessCount: childResources?.measuredCount ?? 0,
284
+ serviceMemory: resources?.service_memory_source && resources.service_memory_completeness
285
+ ? {
286
+ rssBytes: resources.service_memory_rss_bytes ?? undefined,
287
+ currentBytes: latestMetric("service.memory.current") ?? resources.service_memory_rss_bytes ?? undefined,
288
+ workingSetBytes: latestMetric("service.memory.working_set"),
289
+ reclaimableBytes: latestMetric("service.memory.reclaimable"),
290
+ peakBytes: latestMetric("service.memory.peak"),
291
+ source: resources.service_memory_source,
292
+ completeness: resources.service_memory_completeness,
293
+ }
294
+ : undefined,
295
+ childProcessRssBytes: childResources?.rssBytes ?? 0,
296
+ owners,
297
+ },
298
+ sessions: {
299
+ turnCount: turnSummary?.count ?? 0,
300
+ p95TurnMs: turnSummary?.p95,
301
+ llmCallCount: counts
302
+ .filter((row) => row.module === "llm" && row.name.endsWith(".duration"))
303
+ .reduce((sum, row) => sum + row.count, 0),
304
+ toolCallCount: count("tool.execution.duration"),
305
+ },
306
+ frontend: {
307
+ inpMs: frontendVital("INP"),
308
+ lcpMs: frontendVital("LCP"),
309
+ cls: frontendVital("CLS"),
310
+ fcpMs: frontendVital("FCP"),
311
+ ttfbMs: frontendVital("TTFB"),
312
+ longTaskCount: count("frontend.long_task.duration"),
313
+ resourceP95Ms: resourceSummary?.p95,
314
+ },
315
+ runtime: {
316
+ alive: diagnostics?.lock?.inspection && typeof diagnostics.lock.inspection === "object"
317
+ ? diagnostics.lock.inspection.alive
318
+ : undefined,
319
+ healthy: diagnostics?.lock?.inspection && typeof diagnostics.lock.inspection === "object"
320
+ ? diagnostics.lock.inspection.healthy
321
+ : undefined,
322
+ pid: diagnostics?.lock?.lock && typeof diagnostics.lock.lock === "object"
323
+ ? diagnostics.lock.lock.pid
324
+ : undefined,
325
+ mode: diagnostics?.lock?.lock && typeof diagnostics.lock.lock === "object"
326
+ ? diagnostics.lock.lock.mode
327
+ : undefined,
328
+ mirrorFiles: diagnostics?.traces.files.length ?? 0,
329
+ traceFiles: diagnostics?.traces.files.length ?? 0,
330
+ recentErrors: diagnostics?.traces.recentErrors.length ?? 0,
331
+ pendingSessions: diagnostics?.sessions.paused.length ?? 0,
332
+ sessionRuntimes: runtimeStats,
333
+ execution: {
334
+ agentWorkers,
335
+ policyWorkers,
336
+ toolTasks,
337
+ },
338
+ messageCache: {
339
+ totalBytes: messageCacheStats.totalBytes,
340
+ activeCount: messageCacheStats.activeCount,
341
+ entryCount: messageCacheStats.entryCount,
342
+ hits: messageCacheStats.hits,
343
+ misses: messageCacheStats.misses,
344
+ evictions: messageCacheStats.evictions,
345
+ protectedOverbudget: messageCacheStats.protectedOverbudget,
346
+ entries: messageCacheStats.entries.map((entry) => ({ estimatedBytes: entry.estimatedBytes })),
347
+ truncatedEntryCount: messageCacheStats.truncatedEntryCount,
348
+ },
349
+ llmTurns: {
350
+ ...llmTurnStats,
351
+ turns: activeLLMTurns.map((turn) => ({
352
+ ageMs: turn.ageMs,
353
+ streamActive: turn.streamActive,
354
+ providerID: turn.providerID,
355
+ modelID: turn.modelID,
356
+ historyBeforeBytes: turn.historyBeforeBytes,
357
+ historyAfterBytes: turn.historyAfterBytes,
358
+ requestBytes: turn.requestBytes,
359
+ toolSchemaBytes: turn.toolSchemaBytes,
360
+ outputChars: turn.outputChars,
361
+ toolRawChars: turn.toolRawChars,
362
+ })),
363
+ },
364
+ cortexTasks: {
365
+ totalCount: cortexStats.totalCount,
366
+ queuedCount: cortexStats.byStatus.queued,
367
+ runningCount: cortexStats.byStatus.running,
368
+ completedCount: cortexStats.byStatus.completed,
369
+ errorCount: cortexStats.byStatus.error,
370
+ cancelledCount: cortexStats.byStatus.cancelled,
371
+ interruptedCount: cortexStats.byStatus.interrupted,
372
+ retainedPromptChars: cortexStats.retainedPromptChars,
373
+ retainedOutputChars: cortexStats.retainedOutputChars,
374
+ retainedErrorChars: cortexStats.retainedErrorChars,
375
+ retainedProgressToolCount: cortexStats.retainedProgressToolCount,
376
+ },
377
+ },
378
+ top: {
379
+ slowRoutes: rank(http, "path"),
380
+ slowSessions: rank(turns, "session_id"),
381
+ slowTools: rank(tools, "tool"),
382
+ toolFailures,
383
+ slowProviders: rank(llm, "providerID", "provider", "modelID", "model"),
384
+ slowStorage: rank(storage, "operation"),
385
+ slowLibrary: rank(library, "operation"),
386
+ childProcesses,
387
+ slowFrontend: rank([...frontendResources, ...frontendLongTasks], "routeName", "pathTemplate", "route", "name", "attribution"),
388
+ },
389
+ issues,
390
+ });
391
+ }
392
+ function rank(rows, ...labelKeys) {
393
+ return [...rows]
394
+ .sort((a, b) => b.value - a.value)
395
+ .slice(0, 5)
396
+ .map((row, index) => {
397
+ const label = String(firstLabel(metricLabels(row), labelKeys) ?? row.tool ?? row.session_id ?? row.name);
398
+ return {
399
+ id: `${row.metric_id}-${index}`,
400
+ label,
401
+ module: row.module,
402
+ value: row.value,
403
+ unit: row.unit,
404
+ traceId: row.trace_id ?? undefined,
405
+ sessionID: row.session_id ?? undefined,
406
+ tool: row.tool ?? undefined,
407
+ };
408
+ });
409
+ }
410
+ function rankToolFailures(rows) {
411
+ const tools = new Map();
412
+ for (const row of rows) {
413
+ if (row.name !== "tool.execution.count" && row.name !== "tool.execution.error")
414
+ continue;
415
+ const tool = row.tool ?? stringLabel(metricLabels(row).tool);
416
+ if (!tool)
417
+ continue;
418
+ const entry = tools.get(tool) ?? { callCount: 0, errorCount: 0, categories: new Map() };
419
+ if (row.name === "tool.execution.count") {
420
+ entry.callCount += row.value;
421
+ }
422
+ else {
423
+ entry.errorCount += row.value;
424
+ const errorClass = stringLabel(metricLabels(row).errorName) ?? "UnknownError";
425
+ entry.categories.set(errorClass, (entry.categories.get(errorClass) ?? 0) + row.value);
426
+ }
427
+ tools.set(tool, entry);
428
+ }
429
+ return Array.from(tools, ([tool, entry]) => ({
430
+ tool,
431
+ callCount: entry.callCount,
432
+ errorCount: entry.errorCount,
433
+ errorRate: entry.errorCount / Math.max(entry.callCount, entry.errorCount, 1),
434
+ categories: Array.from(entry.categories, ([errorClass, count]) => ({ errorClass, count }))
435
+ .sort((a, b) => b.count - a.count || a.errorClass.localeCompare(b.errorClass))
436
+ .slice(0, 3),
437
+ }))
438
+ .filter((entry) => entry.errorCount > 0)
439
+ .sort((a, b) => b.errorCount - a.errorCount || b.errorRate - a.errorRate || a.tool.localeCompare(b.tool))
440
+ .slice(0, 5);
441
+ }
442
+ function stringLabel(value) {
443
+ return typeof value === "string" && value.length > 0 ? value : undefined;
444
+ }
445
+ function rankChildProcesses(rows) {
446
+ const latest = new Map();
447
+ for (const row of rows) {
448
+ if (!row.process_role?.startsWith("tool"))
449
+ continue;
450
+ if (row.memory_rss_bytes === undefined || row.memory_rss_bytes === null)
451
+ continue;
452
+ const id = row.process_id ?? (row.pid === undefined || row.pid === null ? undefined : `pid:${row.pid}`);
453
+ if (!id)
454
+ continue;
455
+ const existing = latest.get(id);
456
+ if (!existing || row.time > existing.time)
457
+ latest.set(id, row);
458
+ }
459
+ return Array.from(latest.values())
460
+ .sort((a, b) => (b.memory_rss_bytes ?? 0) - (a.memory_rss_bytes ?? 0))
461
+ .slice(0, 5)
462
+ .map((row, index) => {
463
+ const labels = metricLabels(row);
464
+ return {
465
+ id: `${row.sample_id}-${index}`,
466
+ label: String(labels.command ?? labels.description ?? row.process_id ?? row.pid ?? "tool child process"),
467
+ module: "process",
468
+ value: row.memory_rss_bytes ?? 0,
469
+ unit: "bytes",
470
+ processId: row.process_id ?? undefined,
471
+ pid: row.pid ?? undefined,
472
+ status: row.process_role ?? undefined,
473
+ };
474
+ });
475
+ }
476
+ function firstLabel(labels, keys) {
477
+ for (const key of keys) {
478
+ const value = labels[key];
479
+ if (value !== undefined && value !== null && value !== "")
480
+ return value;
481
+ }
482
+ }
483
+ })(PerformanceDashboard || (PerformanceDashboard = {}));
@@ -0,0 +1,15 @@
1
+ export class PerformanceError extends Error {
2
+ code;
3
+ status;
4
+ data;
5
+ constructor(code, message, status, data = {}) {
6
+ super(message);
7
+ this.code = code;
8
+ this.status = status;
9
+ this.data = data;
10
+ this.name = "PerformanceError";
11
+ }
12
+ toResponse() {
13
+ return { code: this.code, message: this.message, ...this.data };
14
+ }
15
+ }
@@ -0,0 +1,6 @@
1
+ export { PerformanceDashboard } from "./dashboard.js";
2
+ export { PerformanceError } from "./error.js";
3
+ export { PerformanceInflight } from "./inflight.js";
4
+ export { PerformanceSchema } from "./schema.js";
5
+ export { PerformanceTimeline } from "./timeline.js";
6
+ export { PerformanceTraceDetail } from "./trace-detail.js";
@@ -0,0 +1,43 @@
1
+ import { ObservabilityStore } from "@ericsanchezok/synergy-harness/observability/store";
2
+ import { PerformanceSchema } from "./schema.js";
3
+ import { parseJson } from "@ericsanchezok/synergy-harness/util/json-parse";
4
+ export var PerformanceInflight;
5
+ (function (PerformanceInflight) {
6
+ function get(input = {}) {
7
+ const spans = ObservabilityStore.queryInflight(input).map((row) => PerformanceSchema.InflightSpan.parse({
8
+ traceId: row.trace_id,
9
+ correlationId: row.correlation_id ?? undefined,
10
+ spanId: row.span_id,
11
+ parentSpanId: row.parent_span_id ?? undefined,
12
+ kind: row.kind,
13
+ name: row.name,
14
+ module: row.module,
15
+ source: row.source,
16
+ startTime: row.start_time,
17
+ endTime: row.end_time ?? undefined,
18
+ durationMs: row.duration_ms ?? undefined,
19
+ status: row.status,
20
+ lastActivityTime: row.last_activity_time ?? undefined,
21
+ heartbeatTime: row.heartbeat_time ?? undefined,
22
+ heartbeatCount: row.heartbeat_count ?? undefined,
23
+ stalled: row.stalled ? true : undefined,
24
+ errorCode: row.error_code ?? undefined,
25
+ errorMessage: row.error_message ?? undefined,
26
+ scopeID: row.scope_id ?? undefined,
27
+ sessionID: row.session_id ?? undefined,
28
+ messageID: row.message_id ?? undefined,
29
+ callID: row.call_id ?? undefined,
30
+ rid: row.rid ?? undefined,
31
+ processId: row.process_id ?? undefined,
32
+ pid: row.pid ?? undefined,
33
+ tool: row.tool ?? undefined,
34
+ attributes: parseJson(row.attributes_json),
35
+ ageMs: row.age_ms,
36
+ idleMs: row.idle_ms,
37
+ stale: row.stale,
38
+ activeSince: row.last_activity_time ?? row.start_time,
39
+ }));
40
+ return PerformanceSchema.Inflight.parse({ generatedAt: new Date().toISOString(), spans });
41
+ }
42
+ PerformanceInflight.get = get;
43
+ })(PerformanceInflight || (PerformanceInflight = {}));