@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.
- package/AGENTS.md +30 -0
- package/README.md +16 -2
- package/dist/agents.js +11 -0
- package/dist/cli-adapter.js +8 -0
- package/dist/component.js +65 -0
- package/dist/config-schema.js +265 -0
- package/dist/execution/activity.js +78 -0
- package/dist/execution/content.js +184 -0
- package/dist/execution/json-sections.js +231 -0
- package/dist/execution/presentation.js +79 -0
- package/dist/execution/preview.js +13 -0
- package/dist/execution/process.js +125 -0
- package/dist/execution/projection.js +327 -0
- package/dist/execution/query.js +18 -0
- package/dist/execution/routes.js +189 -0
- package/dist/execution/schema.js +322 -0
- package/dist/execution/service.js +1993 -0
- package/dist/features.js +22 -0
- package/dist/http.js +65 -0
- package/dist/performance/analysis.js +330 -0
- package/dist/performance/catalog.js +391 -0
- package/dist/performance/dashboard.js +483 -0
- package/dist/performance/error.js +15 -0
- package/dist/performance/index.js +6 -0
- package/dist/performance/inflight.js +43 -0
- package/dist/performance/projection.js +138 -0
- package/dist/performance/prompt/base.txt +38 -0
- package/dist/performance/prompt/builder.js +21 -0
- package/dist/performance/routes/performance-route.js +439 -0
- package/dist/performance/schema.js +628 -0
- package/dist/performance/timeline.js +98 -0
- package/dist/performance/trace-detail.js +91 -0
- package/dist/presentation/service.js +270 -0
- package/dist/presentation/tools/workbench-view.js +65 -0
- package/dist/presentation/tools.js +10 -0
- package/dist/project/directories.js +363 -0
- package/dist/project/git-health.js +498 -0
- package/dist/project/ide.js +66 -0
- package/dist/project/migration.js +22 -0
- package/dist/project/routes/directories.js +89 -0
- package/dist/project/routes/git.js +30 -0
- package/dist/project/routes/task-defaults.js +42 -0
- package/dist/project/session-health.js +9 -0
- package/dist/project/startup.js +28 -0
- package/dist/project/task-defaults.js +68 -0
- package/dist/project/tools/session-control.js +664 -0
- package/dist/project/tools/session-control.txt +60 -0
- package/dist/project/tools/worktree-archive.js +76 -0
- package/dist/project/tools/worktree-enter.js +175 -0
- package/dist/project/tools/worktree-leave.js +119 -0
- package/dist/project/tools/worktree-list.js +52 -0
- package/dist/project/tools.js +26 -0
- package/dist/project/vcs-branch-watcher.js +76 -0
- package/dist/project/vcs.js +114 -0
- package/dist/project/worktree-janitor.js +140 -0
- package/dist/project/worktrees.js +98 -0
- package/dist/push/bridge.js +171 -0
- package/dist/push/index.js +4 -0
- package/dist/push/routes/push.js +224 -0
- package/dist/push/service.js +93 -0
- package/dist/push/store.js +121 -0
- package/dist/push/types.js +58 -0
- package/dist/review/git.js +226 -0
- package/dist/review/routes.js +45 -0
- package/dist/review/schema.js +32 -0
- package/dist/review/state.js +79 -0
- package/dist/stats/aggregator.js +252 -0
- package/dist/stats/canonical.js +89 -0
- package/dist/stats/cli/stats.js +302 -0
- package/dist/stats/engine.js +176 -0
- package/dist/stats/index.js +5 -0
- package/dist/stats/rollup.js +460 -0
- package/dist/stats/routes/stats.js +94 -0
- package/dist/stats/routes/usage.js +83 -0
- package/dist/stats/storage.js +86 -0
- package/dist/stats/types.js +267 -0
- package/dist/worker.js +6 -0
- package/package.json +232 -4
- package/src/agents.ts +15 -0
- package/src/cli-adapter.ts +11 -0
- package/src/component.ts +73 -0
- package/src/config-schema.ts +289 -0
- package/src/execution/activity.ts +83 -0
- package/src/execution/content.ts +189 -0
- package/src/execution/json-sections.ts +230 -0
- package/src/execution/presentation.ts +91 -0
- package/src/execution/preview.ts +12 -0
- package/src/execution/process.ts +118 -0
- package/src/execution/projection.ts +330 -0
- package/src/execution/query.ts +19 -0
- package/src/execution/routes.ts +324 -0
- package/src/execution/schema.ts +336 -0
- package/src/execution/service.ts +2149 -0
- package/src/features.ts +22 -0
- package/src/http.ts +75 -0
- package/src/performance/analysis.ts +356 -0
- package/src/performance/catalog.ts +902 -0
- package/src/performance/dashboard.ts +521 -0
- package/src/performance/error.ts +32 -0
- package/src/performance/index.ts +6 -0
- package/src/performance/inflight.ts +46 -0
- package/src/performance/projection.ts +143 -0
- package/src/performance/prompt/base.txt +38 -0
- package/src/performance/prompt/builder.ts +24 -0
- package/src/performance/routes/performance-route.ts +541 -0
- package/src/performance/schema.ts +695 -0
- package/src/performance/timeline.ts +100 -0
- package/src/performance/trace-detail.ts +97 -0
- package/src/presentation/service.ts +303 -0
- package/src/presentation/tools/workbench-view.ts +75 -0
- package/src/presentation/tools.ts +11 -0
- package/src/project/directories.ts +391 -0
- package/src/project/git-health.ts +577 -0
- package/src/project/ide.ts +80 -0
- package/src/project/migration.ts +24 -0
- package/src/project/routes/directories.ts +124 -0
- package/src/project/routes/git.ts +42 -0
- package/src/project/routes/task-defaults.ts +50 -0
- package/src/project/session-health.ts +10 -0
- package/src/project/startup.ts +28 -0
- package/src/project/task-defaults.ts +70 -0
- package/src/project/tools/session-control.ts +725 -0
- package/src/project/tools/session-control.txt +60 -0
- package/src/project/tools/worktree-archive.ts +92 -0
- package/src/project/tools/worktree-enter.ts +227 -0
- package/src/project/tools/worktree-leave.ts +140 -0
- package/src/project/tools/worktree-list.ts +68 -0
- package/src/project/tools.ts +29 -0
- package/src/project/vcs-branch-watcher.ts +81 -0
- package/src/project/vcs.ts +124 -0
- package/src/project/worktree-janitor.ts +153 -0
- package/src/project/worktrees.ts +110 -0
- package/src/push/bridge.ts +182 -0
- package/src/push/index.ts +4 -0
- package/src/push/routes/push.ts +267 -0
- package/src/push/service.ts +108 -0
- package/src/push/store.ts +120 -0
- package/src/push/types.ts +67 -0
- package/src/review/git.ts +221 -0
- package/src/review/routes.ts +65 -0
- package/src/review/schema.ts +34 -0
- package/src/review/state.ts +77 -0
- package/src/stats/aggregator.ts +277 -0
- package/src/stats/canonical.ts +93 -0
- package/src/stats/cli/stats.ts +348 -0
- package/src/stats/engine.ts +197 -0
- package/src/stats/index.ts +5 -0
- package/src/stats/rollup.ts +522 -0
- package/src/stats/routes/stats.ts +107 -0
- package/src/stats/routes/usage.ts +103 -0
- package/src/stats/storage.ts +78 -0
- package/src/stats/types.ts +351 -0
- 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