@ericsanchezok/synergy-workbench 0.0.0-stage → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,103 @@
1
+ import { Hono } from "hono"
2
+ import { describeRoute, resolver, validator } from "hono-openapi"
3
+ import { z } from "zod"
4
+ import { Usage, UsageSchema } from "@ericsanchezok/synergy-harness/usage"
5
+ import { errors } from "@ericsanchezok/synergy-server/server/error"
6
+
7
+ const Query = UsageSchema.Filter.extend({
8
+ includeDescendants: z
9
+ .enum(["true", "false"])
10
+ .default("true")
11
+ .transform((value) => value === "true"),
12
+ from: z.coerce.number().int().nonnegative().safe().optional(),
13
+ to: z.coerce.number().int().nonnegative().safe().optional(),
14
+ })
15
+ const PageQuery = Query.extend({
16
+ cursor: z.string().max(4096).optional(),
17
+ limit: z.coerce.number().int().min(1).max(500).default(100),
18
+ })
19
+ const Clear = z.object({ scope: UsageSchema.Filter, throughRevision: z.number().int().nonnegative() }).strict()
20
+ const Cleared = z.object({
21
+ removed: z.number(),
22
+ activeRetained: z.number(),
23
+ newerRetained: z.number(),
24
+ unattributedRetained: z.number().describe("Owner-level gaps retained because the clear selects a specific run"),
25
+ revision: z.number(),
26
+ })
27
+ const response = (schema: z.ZodType, description: string) => ({
28
+ 200: { description, content: { "application/json": { schema: resolver(schema) } } },
29
+ ...errors(400),
30
+ })
31
+
32
+ export const UsageRoute = () =>
33
+ new Hono()
34
+ .get(
35
+ "/",
36
+ describeRoute({
37
+ operationId: "global.stats.usage",
38
+ summary: "Get canonical usage statistics",
39
+ description:
40
+ "Read retained usage facts with explicit unknowns, billing bases, transport timing, attribution and migration coverage. Time ranges are half-open; request days use the sent timestamp in the returned timezone.",
41
+ responses: response(Usage.Summary, "Canonical usage summary"),
42
+ }),
43
+ validator("query", Query),
44
+ async (c) => {
45
+ try {
46
+ return c.json(await Usage.summary(c.req.valid("query")))
47
+ } catch (error) {
48
+ if (UsageSchema.InvalidQuery.isInstance(error)) return c.json({ message: error.data.message }, 400)
49
+ throw error
50
+ }
51
+ },
52
+ )
53
+ .get(
54
+ "/records",
55
+ describeRoute({
56
+ operationId: "global.stats.usageRecords",
57
+ summary: "Page through retained usage records",
58
+ description:
59
+ "Read compact records without prompts, tool arguments or raw response content. Cursor filters must match the initial request. Each record has a revision; reconnect through the summary endpoint.",
60
+ responses: response(Usage.Page, "Usage records"),
61
+ }),
62
+ validator("query", PageQuery),
63
+ async (c) => {
64
+ const { cursor, limit, ...scope } = c.req.valid("query")
65
+ try {
66
+ return c.json(await Usage.records(scope, { cursor, limit }))
67
+ } catch (error) {
68
+ if (UsageSchema.InvalidQuery.isInstance(error)) return c.json({ message: error.data.message }, 400)
69
+ throw error
70
+ }
71
+ },
72
+ )
73
+ .post(
74
+ "/rebuild",
75
+ describeRoute({
76
+ operationId: "global.stats.usageRebuild",
77
+ summary: "Schedule or resume historical usage capture",
78
+ description:
79
+ "Returns the durable background job. Rebuilding preserves existing facts, original pricing and explicit deletion markers.",
80
+ responses: response(UsageSchema.Rebuild, "Durable rebuild job"),
81
+ }),
82
+ async (c) => c.json(await Usage.rebuild()),
83
+ )
84
+ .delete(
85
+ "/records",
86
+ describeRoute({
87
+ operationId: "global.stats.usageClear",
88
+ summary: "Clear explicitly scoped terminal usage records",
89
+ description:
90
+ "Requires an explicit Scope, session or time range and the summary revision to clear through. Active and newer records are retained. Run-filtered clears also retain unattributed owner-level gaps. Cleared identities cannot be restored by a rebuild.",
91
+ responses: response(Cleared, "Cleared and retained record counts"),
92
+ }),
93
+ validator("json", Clear),
94
+ async (c) => {
95
+ const { scope, throughRevision } = c.req.valid("json")
96
+ try {
97
+ return c.json(await Usage.clear(scope, throughRevision))
98
+ } catch (error) {
99
+ if (UsageSchema.InvalidQuery.isInstance(error)) return c.json({ message: error.data.message }, 400)
100
+ throw error
101
+ }
102
+ },
103
+ )
@@ -0,0 +1,78 @@
1
+ import { Storage } from "@ericsanchezok/synergy-harness/storage/storage"
2
+ import { StoragePath } from "@ericsanchezok/synergy-harness/storage/path"
3
+ import { Identifier } from "@ericsanchezok/synergy-harness/id/id"
4
+ import type { StatsWatermark, SessionDigest, DailyBucket, StatsSnapshot } from "./types"
5
+
6
+ export namespace StatsStorage {
7
+ export async function getWatermark(): Promise<StatsWatermark | undefined> {
8
+ try {
9
+ return await Storage.read<StatsWatermark>(StoragePath.statsWatermark())
10
+ } catch (e) {
11
+ if (e instanceof Storage.NotFoundError) return undefined
12
+ throw e
13
+ }
14
+ }
15
+
16
+ export async function setWatermark(watermark: StatsWatermark): Promise<void> {
17
+ await Storage.write(StoragePath.statsWatermark(), watermark)
18
+ }
19
+
20
+ export async function getDigest(sessionID: string): Promise<SessionDigest | undefined> {
21
+ try {
22
+ return await Storage.read<SessionDigest>(StoragePath.statsDigest(sessionID as Identifier.SessionID))
23
+ } catch (e) {
24
+ if (e instanceof Storage.NotFoundError) return undefined
25
+ throw e
26
+ }
27
+ }
28
+
29
+ export async function setDigest(digest: SessionDigest): Promise<void> {
30
+ await Storage.write(StoragePath.statsDigest(digest.sessionID as Identifier.SessionID), digest)
31
+ }
32
+
33
+ export async function removeDigest(sessionID: string): Promise<void> {
34
+ await Storage.remove(StoragePath.statsDigest(sessionID as Identifier.SessionID))
35
+ }
36
+
37
+ export async function listDigestIDs(): Promise<string[]> {
38
+ return Storage.scan(StoragePath.statsDigestsRoot())
39
+ }
40
+
41
+ export async function getAllDigests(): Promise<SessionDigest[]> {
42
+ const ids = await listDigestIDs()
43
+ if (ids.length === 0) return []
44
+ const keys = ids.map((id) => StoragePath.statsDigest(id as Identifier.SessionID))
45
+ const results = await Storage.readMany<SessionDigest>(keys)
46
+ return results.filter((d): d is SessionDigest => d !== undefined)
47
+ }
48
+
49
+ export async function getDailyBucket(day: string): Promise<DailyBucket | undefined> {
50
+ try {
51
+ return await Storage.read<DailyBucket>(StoragePath.statsDaily(day))
52
+ } catch (e) {
53
+ if (e instanceof Storage.NotFoundError) return undefined
54
+ throw e
55
+ }
56
+ }
57
+
58
+ export async function setDailyBucket(day: string, bucket: DailyBucket): Promise<void> {
59
+ await Storage.write(StoragePath.statsDaily(day), bucket)
60
+ }
61
+
62
+ export async function listDailyKeys(): Promise<string[]> {
63
+ return Storage.scan(StoragePath.statsDailyRoot())
64
+ }
65
+
66
+ export async function getSnapshot(): Promise<StatsSnapshot | undefined> {
67
+ try {
68
+ return await Storage.read<StatsSnapshot>(StoragePath.statsSnapshot())
69
+ } catch (e) {
70
+ if (e instanceof Storage.NotFoundError) return undefined
71
+ throw e
72
+ }
73
+ }
74
+
75
+ export async function setSnapshot(snapshot: StatsSnapshot): Promise<void> {
76
+ await Storage.write(StoragePath.statsSnapshot(), snapshot)
77
+ }
78
+ }
@@ -0,0 +1,351 @@
1
+ import z from "zod"
2
+ import { RolloutAccounting } from "@ericsanchezok/synergy-harness/session/rollout/accounting"
3
+ import { ExecutionPresentation } from "../execution/presentation"
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Shared primitives
7
+ // ---------------------------------------------------------------------------
8
+
9
+ export const TokenBreakdown = z.object({
10
+ input: z.number(),
11
+ output: z.number(),
12
+ reasoning: z.number(),
13
+ cache: z.object({
14
+ read: z.number(),
15
+ write: z.number(),
16
+ }),
17
+ })
18
+ export type TokenBreakdown = z.infer<typeof TokenBreakdown>
19
+
20
+ /** YYYY-MM-DD string */
21
+ export type DayKey = string
22
+
23
+ /** ISO week string like "2026-W17" */
24
+ export type WeekKey = string
25
+
26
+ /** YYYY-MM string */
27
+ export type MonthKey = string
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Dimension 1 — Overview
31
+ // ---------------------------------------------------------------------------
32
+
33
+ export const OverviewStats = z.object({
34
+ totalSessions: z.number(),
35
+ activeSessions: z.number(),
36
+ archivedSessions: z.number(),
37
+ totalMessages: z.number(),
38
+ totalTurns: z.number(),
39
+ totalDays: z.number(),
40
+ longestStreak: z.number(),
41
+ currentStreak: z.number(),
42
+ projectCount: z.number(),
43
+ })
44
+ export type OverviewStats = z.infer<typeof OverviewStats>
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Dimension 2 — Tokens & Cost
48
+ // ---------------------------------------------------------------------------
49
+
50
+ export const TokenCostStats = z.object({
51
+ tokens: TokenBreakdown,
52
+ cost: z.number(),
53
+ accounting: RolloutAccounting.Summary.optional(),
54
+ costPresentation: ExecutionPresentation.Cost.optional(),
55
+ cacheHitRate: z.number(),
56
+ avgCostPerTurn: z.number(),
57
+ avgTokensPerTurn: z.number(),
58
+ dailyCost: z.number(),
59
+ dailyTokens: z.number(),
60
+ })
61
+ export type TokenCostStats = z.infer<typeof TokenCostStats>
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Dimension 3 — By Model (providerID/modelID → usage)
65
+ // ---------------------------------------------------------------------------
66
+
67
+ export const ModelUsage = z.object({
68
+ providerID: z.string(),
69
+ modelID: z.string(),
70
+ messages: z.number(),
71
+ turns: z.number(),
72
+ tokens: TokenBreakdown,
73
+ cost: z.number(),
74
+ accounting: RolloutAccounting.Summary.optional(),
75
+ avgResponseMs: z.number(),
76
+ })
77
+ export type ModelUsage = z.infer<typeof ModelUsage>
78
+
79
+ export const ModelStats = z.object({
80
+ models: z.array(ModelUsage),
81
+ })
82
+ export type ModelStats = z.infer<typeof ModelStats>
83
+
84
+ // ---------------------------------------------------------------------------
85
+ // Dimension 4 — By Agent
86
+ // ---------------------------------------------------------------------------
87
+
88
+ export const AgentUsage = z.object({
89
+ agent: z.string(),
90
+ messages: z.number(),
91
+ sessions: z.number(),
92
+ tokens: TokenBreakdown,
93
+ cost: z.number(),
94
+ accounting: RolloutAccounting.Summary.optional(),
95
+ subagentInvocations: z.number(),
96
+ })
97
+ export type AgentUsage = z.infer<typeof AgentUsage>
98
+
99
+ export const AgentStats = z.object({
100
+ agents: z.array(AgentUsage),
101
+ totalSubagentCalls: z.number(),
102
+ })
103
+ export type AgentStats = z.infer<typeof AgentStats>
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // Dimension 5 — By Tool
107
+ // ---------------------------------------------------------------------------
108
+
109
+ export const ToolUsage = z.object({
110
+ tool: z.string(),
111
+ calls: z.number(),
112
+ successes: z.number(),
113
+ errors: z.number(),
114
+ avgDurationMs: z.number(),
115
+ })
116
+ export type ToolUsage = z.infer<typeof ToolUsage>
117
+
118
+ export const ToolStats = z.object({
119
+ tools: z.array(ToolUsage),
120
+ })
121
+ export type ToolStats = z.infer<typeof ToolStats>
122
+
123
+ // ---------------------------------------------------------------------------
124
+ // Dimension 6 — Code Changes
125
+ // ---------------------------------------------------------------------------
126
+
127
+ export const CodeChangeStats = z.object({
128
+ totalAdditions: z.number(),
129
+ totalDeletions: z.number(),
130
+ totalFiles: z.number(),
131
+ netLines: z.number(),
132
+ dailyAdditions: z.number(),
133
+ dailyDeletions: z.number(),
134
+ })
135
+ export type CodeChangeStats = z.infer<typeof CodeChangeStats>
136
+
137
+ // ---------------------------------------------------------------------------
138
+ // Dimension 7 — Session Lifecycle
139
+ // ---------------------------------------------------------------------------
140
+
141
+ export const SessionLifecycleStats = z.object({
142
+ pinnedCount: z.number(),
143
+ avgTurnsPerSession: z.number(),
144
+ medianTurnsPerSession: z.number(),
145
+ compactionCount: z.number(),
146
+ retryCount: z.number(),
147
+ errorCount: z.number(),
148
+ errorRate: z.number(),
149
+ /** Bucketed session duration: short (<5min), medium (5-30min), long (>30min) */
150
+ durationBuckets: z.object({
151
+ short: z.number(),
152
+ medium: z.number(),
153
+ long: z.number(),
154
+ }),
155
+ })
156
+ export type SessionLifecycleStats = z.infer<typeof SessionLifecycleStats>
157
+
158
+ // ---------------------------------------------------------------------------
159
+ // Dimension 8 — By Channel / Endpoint
160
+ // ---------------------------------------------------------------------------
161
+
162
+ export const ChannelUsage = z.object({
163
+ channel: z.string(),
164
+ sessions: z.number(),
165
+ messages: z.number(),
166
+ })
167
+ export type ChannelUsage = z.infer<typeof ChannelUsage>
168
+
169
+ export const ChannelStats = z.object({
170
+ channels: z.array(ChannelUsage),
171
+ interactiveSessions: z.number(),
172
+ unattendedSessions: z.number(),
173
+ })
174
+ export type ChannelStats = z.infer<typeof ChannelStats>
175
+
176
+ // ---------------------------------------------------------------------------
177
+ // Dimension 9 — Time Series (per-day bucket)
178
+ // ---------------------------------------------------------------------------
179
+
180
+ export const DailyBucket = z.object({
181
+ day: z.string(),
182
+ sessions: z.number(),
183
+ turns: z.number(),
184
+ tokens: TokenBreakdown,
185
+ cost: z.number(),
186
+ accounting: RolloutAccounting.Summary.optional(),
187
+ additions: z.number(),
188
+ deletions: z.number(),
189
+ files: z.number(),
190
+ toolCalls: z.number(),
191
+ errors: z.number(),
192
+ })
193
+ export type DailyBucket = z.infer<typeof DailyBucket>
194
+
195
+ export const HourlyBucket = z.object({
196
+ hour: z.string(),
197
+ turns: z.number(),
198
+ })
199
+ export type HourlyBucket = z.infer<typeof HourlyBucket>
200
+
201
+ export const TimeSeriesStats = z.object({
202
+ days: z.array(DailyBucket),
203
+ hours: z.array(HourlyBucket),
204
+ /** Hour-of-day activity: 24-element array, each = turn count */
205
+ hourlyActivity: z.array(z.number()),
206
+ })
207
+ export type TimeSeriesStats = z.infer<typeof TimeSeriesStats>
208
+
209
+ // ---------------------------------------------------------------------------
210
+ // Composite — Full Stats Snapshot
211
+ // ---------------------------------------------------------------------------
212
+
213
+ export const StatsSnapshot = z.object({
214
+ overview: OverviewStats,
215
+ tokenCost: TokenCostStats,
216
+ models: ModelStats,
217
+ agents: AgentStats,
218
+ tools: ToolStats,
219
+ codeChanges: CodeChangeStats,
220
+ lifecycle: SessionLifecycleStats,
221
+ channels: ChannelStats,
222
+ timeSeries: TimeSeriesStats,
223
+ /** Timestamp when this snapshot was computed */
224
+ computedAt: z.number(),
225
+ /** Watermark: latest session.updated we scanned up to */
226
+ watermark: z.number(),
227
+ })
228
+ export type StatsSnapshot = z.infer<typeof StatsSnapshot>
229
+
230
+ // ---------------------------------------------------------------------------
231
+ // Per-Session Digest (intermediate product for incremental aggregation)
232
+ // ---------------------------------------------------------------------------
233
+
234
+ export const SessionDigest = z.object({
235
+ sessionID: z.string(),
236
+ scopeID: z.string(),
237
+ created: z.number(),
238
+ updated: z.number(),
239
+ rolloutRevision: z.number().int().nonnegative().optional(),
240
+ archived: z.number().optional(),
241
+ pinned: z.boolean(),
242
+ parentID: z.string().optional(),
243
+ endpoint: z
244
+ .object({
245
+ kind: z.string(),
246
+ type: z.string().optional(),
247
+ })
248
+ .optional(),
249
+ interaction: z
250
+ .object({
251
+ mode: z.string(),
252
+ source: z.string().optional(),
253
+ })
254
+ .optional(),
255
+
256
+ turns: z.number(),
257
+ messages: z.number(),
258
+ tokens: TokenBreakdown,
259
+ cost: z.number(),
260
+ accounting: RolloutAccounting.Summary.optional(),
261
+
262
+ /** model key (providerID/modelID) → { messages, tokens, cost, totalResponseMs } */
263
+ modelUsage: z.record(
264
+ z.string(),
265
+ z.object({
266
+ messages: z.number(),
267
+ tokens: TokenBreakdown,
268
+ cost: z.number(),
269
+ accounting: RolloutAccounting.Summary.optional(),
270
+ totalResponseMs: z.number(),
271
+ }),
272
+ ),
273
+
274
+ /** agent → { messages, tokens, cost } */
275
+ agentUsage: z.record(
276
+ z.string(),
277
+ z.object({
278
+ messages: z.number(),
279
+ tokens: TokenBreakdown,
280
+ cost: z.number(),
281
+ accounting: RolloutAccounting.Summary.optional(),
282
+ }),
283
+ ),
284
+
285
+ /** tool → { calls, successes, errors, totalDurationMs } */
286
+ toolUsage: z.record(
287
+ z.string(),
288
+ z.object({
289
+ calls: z.number(),
290
+ successes: z.number(),
291
+ errors: z.number(),
292
+ totalDurationMs: z.number(),
293
+ timedSamples: z.number().int().nonnegative().optional(),
294
+ }),
295
+ ),
296
+
297
+ /** YYYY-MM-DDTHH → turn count */
298
+ hourlyTurns: z.record(z.string(), z.number()),
299
+
300
+ additions: z.number(),
301
+ deletions: z.number(),
302
+ files: z.number(),
303
+
304
+ compactionCount: z.number(),
305
+ retryCount: z.number(),
306
+ errorCount: z.number(),
307
+
308
+ /** Duration in ms (updated - created) */
309
+ durationMs: z.number(),
310
+ })
311
+ export type SessionDigest = z.infer<typeof SessionDigest>
312
+
313
+ export const OperationDigest = SessionDigest.pick({
314
+ scopeID: true,
315
+ created: true,
316
+ updated: true,
317
+ tokens: true,
318
+ cost: true,
319
+ accounting: true,
320
+ modelUsage: true,
321
+ agentUsage: true,
322
+ }).extend({ operationID: z.string(), rolloutRevision: z.number().int().nonnegative(), turns: z.literal(0) })
323
+ export type OperationDigest = z.infer<typeof OperationDigest>
324
+
325
+ // ---------------------------------------------------------------------------
326
+ // Watermark — tracks incremental scan progress
327
+ // ---------------------------------------------------------------------------
328
+
329
+ export const StatsWatermark = z.object({
330
+ /** Last session.updated timestamp we scanned */
331
+ lastUpdated: z.number(),
332
+ /** Set of sessionIDs already digested (to detect deletions) */
333
+ sessionIDs: z.array(z.string()),
334
+ /** Timestamp of last full scan */
335
+ lastFullScanAt: z.number(),
336
+ })
337
+ export type StatsWatermark = z.infer<typeof StatsWatermark>
338
+
339
+ // ---------------------------------------------------------------------------
340
+ // Progress callback
341
+ // ---------------------------------------------------------------------------
342
+
343
+ export const ProgressEvent = z.object({
344
+ phase: z.enum(["scan", "digest", "bucket", "snapshot"]),
345
+ current: z.number(),
346
+ total: z.number(),
347
+ message: z.string().optional(),
348
+ })
349
+ export type ProgressEvent = z.infer<typeof ProgressEvent>
350
+
351
+ export type ProgressCallback = (event: ProgressEvent) => void
package/src/worker.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { version } from "../package.json" with { type: "json" }
2
+ export const metadata = { id: "workbench", version, apiVersion: 1 }
3
+ import { registerConfig } from "./config-schema"
4
+
5
+ export function registerWorker() {
6
+ registerConfig()
7
+ }