@jami-studio/core 0.92.26 → 0.92.28

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 (66) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +23 -0
  3. package/corpus/core/package.json +2 -1
  4. package/corpus/core/src/deploy/build.ts +56 -2
  5. package/corpus/core/src/event-bus/bus.ts +138 -140
  6. package/corpus/core/src/event-bus/registry.ts +81 -79
  7. package/corpus/core/src/file-upload/registry.ts +135 -125
  8. package/corpus/core/src/notifications/registry.ts +218 -216
  9. package/corpus/core/src/observability/store.ts +1313 -1321
  10. package/corpus/core/src/private-blob/registry.ts +246 -242
  11. package/corpus/core/src/secrets/register.ts +132 -129
  12. package/corpus/core/src/shared/global-scope.ts +75 -0
  13. package/corpus/core/src/shared/init-memo.ts +94 -0
  14. package/corpus/core/src/sharing/registry.ts +193 -194
  15. package/corpus/core/src/tracking/providers.ts +425 -422
  16. package/corpus/core/src/tracking/registry.ts +102 -102
  17. package/dist/collab/routes.d.ts +1 -1
  18. package/dist/deploy/build.d.ts +23 -0
  19. package/dist/deploy/build.d.ts.map +1 -1
  20. package/dist/deploy/build.js +36 -2
  21. package/dist/deploy/build.js.map +1 -1
  22. package/dist/event-bus/bus.d.ts.map +1 -1
  23. package/dist/event-bus/bus.js +8 -6
  24. package/dist/event-bus/bus.js.map +1 -1
  25. package/dist/event-bus/registry.d.ts.map +1 -1
  26. package/dist/event-bus/registry.js +10 -6
  27. package/dist/event-bus/registry.js.map +1 -1
  28. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  29. package/dist/file-upload/registry.d.ts.map +1 -1
  30. package/dist/file-upload/registry.js +28 -8
  31. package/dist/file-upload/registry.js.map +1 -1
  32. package/dist/notifications/registry.d.ts.map +1 -1
  33. package/dist/notifications/registry.js +6 -5
  34. package/dist/notifications/registry.js.map +1 -1
  35. package/dist/observability/routes.d.ts +5 -5
  36. package/dist/observability/store.d.ts +1 -1
  37. package/dist/observability/store.d.ts.map +1 -1
  38. package/dist/observability/store.js +311 -316
  39. package/dist/observability/store.js.map +1 -1
  40. package/dist/private-blob/registry.d.ts.map +1 -1
  41. package/dist/private-blob/registry.js +18 -13
  42. package/dist/private-blob/registry.js.map +1 -1
  43. package/dist/progress/routes.d.ts +1 -1
  44. package/dist/resources/handlers.d.ts +2 -2
  45. package/dist/secrets/register.d.ts.map +1 -1
  46. package/dist/secrets/register.js +11 -7
  47. package/dist/secrets/register.js.map +1 -1
  48. package/dist/secrets/routes.d.ts +9 -9
  49. package/dist/shared/global-scope.d.ts +55 -0
  50. package/dist/shared/global-scope.d.ts.map +1 -0
  51. package/dist/shared/global-scope.js +70 -0
  52. package/dist/shared/global-scope.js.map +1 -0
  53. package/dist/shared/init-memo.d.ts +34 -0
  54. package/dist/shared/init-memo.d.ts.map +1 -0
  55. package/dist/shared/init-memo.js +80 -0
  56. package/dist/shared/init-memo.js.map +1 -0
  57. package/dist/sharing/registry.d.ts.map +1 -1
  58. package/dist/sharing/registry.js +2 -16
  59. package/dist/sharing/registry.js.map +1 -1
  60. package/dist/tracking/providers.d.ts.map +1 -1
  61. package/dist/tracking/providers.js +11 -9
  62. package/dist/tracking/providers.js.map +1 -1
  63. package/dist/tracking/registry.d.ts.map +1 -1
  64. package/dist/tracking/registry.js +5 -5
  65. package/dist/tracking/registry.js.map +1 -1
  66. package/package.json +2 -1
@@ -9,6 +9,7 @@
9
9
  import { getDbExec, intType, isPostgres, retryOnDdlRace, } from "../db/client.js";
10
10
  import { ensureTableExists, ensureColumnExists, ensureIndexExists, } from "../db/ddl-guard.js";
11
11
  import { isDuplicateColumnError } from "../db/migrations.js";
12
+ import { createInitMemo } from "../shared/init-memo.js";
12
13
  function safeJsonParse(value, fallback) {
13
14
  if (!value)
14
15
  return fallback;
@@ -43,244 +44,238 @@ function withUserFilter(baseWhere, baseArgs, userId) {
43
44
  args: [...baseArgs, userId],
44
45
  };
45
46
  }
46
- let _initPromise;
47
- export async function ensureObservabilityTables() {
48
- if (!_initPromise) {
49
- _initPromise = (async () => {
50
- const client = getDbExec();
51
- const traceSpansCreateSql = `
52
- CREATE TABLE IF NOT EXISTS agent_trace_spans (
53
- id TEXT PRIMARY KEY,
54
- run_id TEXT NOT NULL,
55
- thread_id TEXT,
56
- user_id TEXT,
57
- parent_span_id TEXT,
58
- span_type TEXT NOT NULL,
59
- name TEXT NOT NULL,
60
- input_tokens ${intType()} NOT NULL DEFAULT 0,
61
- output_tokens ${intType()} NOT NULL DEFAULT 0,
62
- cache_read_tokens ${intType()} NOT NULL DEFAULT 0,
63
- cache_write_tokens ${intType()} NOT NULL DEFAULT 0,
64
- cost_cents_x100 ${intType()} NOT NULL DEFAULT 0,
65
- duration_ms ${intType()} NOT NULL DEFAULT 0,
66
- status TEXT NOT NULL DEFAULT 'success',
67
- error_message TEXT,
68
- metadata TEXT,
69
- created_at ${intType()} NOT NULL
70
- )
47
+ // workerd-safe memo: the raw `let _initPromise` pattern wedged every agent
48
+ // chat run on the unified Cloudflare runtime when an early-responding first
49
+ // request created (and froze) the init promise — see shared/init-memo.ts.
50
+ export const ensureObservabilityTables = createInitMemo(async () => {
51
+ const client = getDbExec();
52
+ const traceSpansCreateSql = `
53
+ CREATE TABLE IF NOT EXISTS agent_trace_spans (
54
+ id TEXT PRIMARY KEY,
55
+ run_id TEXT NOT NULL,
56
+ thread_id TEXT,
57
+ user_id TEXT,
58
+ parent_span_id TEXT,
59
+ span_type TEXT NOT NULL,
60
+ name TEXT NOT NULL,
61
+ input_tokens ${intType()} NOT NULL DEFAULT 0,
62
+ output_tokens ${intType()} NOT NULL DEFAULT 0,
63
+ cache_read_tokens ${intType()} NOT NULL DEFAULT 0,
64
+ cache_write_tokens ${intType()} NOT NULL DEFAULT 0,
65
+ cost_cents_x100 ${intType()} NOT NULL DEFAULT 0,
66
+ duration_ms ${intType()} NOT NULL DEFAULT 0,
67
+ status TEXT NOT NULL DEFAULT 'success',
68
+ error_message TEXT,
69
+ metadata TEXT,
70
+ created_at ${intType()} NOT NULL
71
+ )
71
72
  `;
72
- const traceSummariesCreateSql = `
73
- CREATE TABLE IF NOT EXISTS agent_trace_summaries (
74
- run_id TEXT PRIMARY KEY,
75
- thread_id TEXT,
76
- user_id TEXT,
77
- total_spans ${intType()} NOT NULL DEFAULT 0,
78
- llm_calls ${intType()} NOT NULL DEFAULT 0,
79
- tool_calls ${intType()} NOT NULL DEFAULT 0,
80
- successful_tools ${intType()} NOT NULL DEFAULT 0,
81
- failed_tools ${intType()} NOT NULL DEFAULT 0,
82
- total_duration_ms ${intType()} NOT NULL DEFAULT 0,
83
- total_cost_cents_x100 ${intType()} NOT NULL DEFAULT 0,
84
- total_input_tokens ${intType()} NOT NULL DEFAULT 0,
85
- total_output_tokens ${intType()} NOT NULL DEFAULT 0,
86
- model TEXT NOT NULL DEFAULT '',
87
- created_at ${intType()} NOT NULL
88
- )
73
+ const traceSummariesCreateSql = `
74
+ CREATE TABLE IF NOT EXISTS agent_trace_summaries (
75
+ run_id TEXT PRIMARY KEY,
76
+ thread_id TEXT,
77
+ user_id TEXT,
78
+ total_spans ${intType()} NOT NULL DEFAULT 0,
79
+ llm_calls ${intType()} NOT NULL DEFAULT 0,
80
+ tool_calls ${intType()} NOT NULL DEFAULT 0,
81
+ successful_tools ${intType()} NOT NULL DEFAULT 0,
82
+ failed_tools ${intType()} NOT NULL DEFAULT 0,
83
+ total_duration_ms ${intType()} NOT NULL DEFAULT 0,
84
+ total_cost_cents_x100 ${intType()} NOT NULL DEFAULT 0,
85
+ total_input_tokens ${intType()} NOT NULL DEFAULT 0,
86
+ total_output_tokens ${intType()} NOT NULL DEFAULT 0,
87
+ model TEXT NOT NULL DEFAULT '',
88
+ created_at ${intType()} NOT NULL
89
+ )
89
90
  `;
90
- const feedbackCreateSql = `
91
- CREATE TABLE IF NOT EXISTS agent_feedback (
92
- id TEXT PRIMARY KEY,
93
- run_id TEXT,
94
- thread_id TEXT,
95
- message_seq ${intType()},
96
- feedback_type TEXT NOT NULL,
97
- value TEXT NOT NULL DEFAULT '',
98
- user_id TEXT,
99
- created_at ${intType()} NOT NULL
100
- )
91
+ const feedbackCreateSql = `
92
+ CREATE TABLE IF NOT EXISTS agent_feedback (
93
+ id TEXT PRIMARY KEY,
94
+ run_id TEXT,
95
+ thread_id TEXT,
96
+ message_seq ${intType()},
97
+ feedback_type TEXT NOT NULL,
98
+ value TEXT NOT NULL DEFAULT '',
99
+ user_id TEXT,
100
+ created_at ${intType()} NOT NULL
101
+ )
101
102
  `;
102
- const satisfactionScoresCreateSql = `
103
- CREATE TABLE IF NOT EXISTS agent_satisfaction_scores (
104
- id TEXT PRIMARY KEY,
105
- thread_id TEXT NOT NULL,
106
- user_id TEXT,
107
- frustration_score REAL NOT NULL DEFAULT 0,
108
- rephrasing_score REAL NOT NULL DEFAULT 0,
109
- abandonment_score REAL NOT NULL DEFAULT 0,
110
- sentiment_score REAL NOT NULL DEFAULT 0,
111
- length_trend_score REAL NOT NULL DEFAULT 0,
112
- computed_at ${intType()} NOT NULL
113
- )
103
+ const satisfactionScoresCreateSql = `
104
+ CREATE TABLE IF NOT EXISTS agent_satisfaction_scores (
105
+ id TEXT PRIMARY KEY,
106
+ thread_id TEXT NOT NULL,
107
+ user_id TEXT,
108
+ frustration_score REAL NOT NULL DEFAULT 0,
109
+ rephrasing_score REAL NOT NULL DEFAULT 0,
110
+ abandonment_score REAL NOT NULL DEFAULT 0,
111
+ sentiment_score REAL NOT NULL DEFAULT 0,
112
+ length_trend_score REAL NOT NULL DEFAULT 0,
113
+ computed_at ${intType()} NOT NULL
114
+ )
114
115
  `;
115
- const evalsCreateSql = `
116
- CREATE TABLE IF NOT EXISTS agent_evals (
117
- id TEXT PRIMARY KEY,
118
- run_id TEXT NOT NULL,
119
- thread_id TEXT,
120
- user_id TEXT,
121
- eval_type TEXT NOT NULL,
122
- criteria TEXT NOT NULL,
123
- score REAL NOT NULL DEFAULT 0,
124
- reasoning TEXT,
125
- metadata TEXT,
126
- created_at ${intType()} NOT NULL
127
- )
116
+ const evalsCreateSql = `
117
+ CREATE TABLE IF NOT EXISTS agent_evals (
118
+ id TEXT PRIMARY KEY,
119
+ run_id TEXT NOT NULL,
120
+ thread_id TEXT,
121
+ user_id TEXT,
122
+ eval_type TEXT NOT NULL,
123
+ criteria TEXT NOT NULL,
124
+ score REAL NOT NULL DEFAULT 0,
125
+ reasoning TEXT,
126
+ metadata TEXT,
127
+ created_at ${intType()} NOT NULL
128
+ )
128
129
  `;
129
- const evalDatasetsCreateSql = `
130
- CREATE TABLE IF NOT EXISTS agent_eval_datasets (
131
- id TEXT PRIMARY KEY,
132
- name TEXT NOT NULL,
133
- description TEXT NOT NULL DEFAULT '',
134
- entries TEXT NOT NULL DEFAULT '[]',
135
- created_at ${intType()} NOT NULL,
136
- updated_at ${intType()} NOT NULL
137
- )
130
+ const evalDatasetsCreateSql = `
131
+ CREATE TABLE IF NOT EXISTS agent_eval_datasets (
132
+ id TEXT PRIMARY KEY,
133
+ name TEXT NOT NULL,
134
+ description TEXT NOT NULL DEFAULT '',
135
+ entries TEXT NOT NULL DEFAULT '[]',
136
+ created_at ${intType()} NOT NULL,
137
+ updated_at ${intType()} NOT NULL
138
+ )
138
139
  `;
139
- const experimentsCreateSql = `
140
- CREATE TABLE IF NOT EXISTS agent_experiments (
141
- id TEXT PRIMARY KEY,
142
- name TEXT NOT NULL,
143
- status TEXT NOT NULL DEFAULT 'draft',
144
- variants TEXT NOT NULL DEFAULT '[]',
145
- metrics TEXT NOT NULL DEFAULT '[]',
146
- assignment_level TEXT NOT NULL DEFAULT 'user',
147
- started_at ${intType()},
148
- ended_at ${intType()},
149
- created_at ${intType()} NOT NULL,
150
- owner_email TEXT
151
- )
140
+ const experimentsCreateSql = `
141
+ CREATE TABLE IF NOT EXISTS agent_experiments (
142
+ id TEXT PRIMARY KEY,
143
+ name TEXT NOT NULL,
144
+ status TEXT NOT NULL DEFAULT 'draft',
145
+ variants TEXT NOT NULL DEFAULT '[]',
146
+ metrics TEXT NOT NULL DEFAULT '[]',
147
+ assignment_level TEXT NOT NULL DEFAULT 'user',
148
+ started_at ${intType()},
149
+ ended_at ${intType()},
150
+ created_at ${intType()} NOT NULL,
151
+ owner_email TEXT
152
+ )
152
153
  `;
153
- const experimentAssignmentsCreateSql = `
154
- CREATE TABLE IF NOT EXISTS agent_experiment_assignments (
155
- experiment_id TEXT NOT NULL,
156
- user_id TEXT NOT NULL,
157
- variant_id TEXT NOT NULL,
158
- assigned_at ${intType()} NOT NULL,
159
- PRIMARY KEY (experiment_id, user_id)
160
- )
154
+ const experimentAssignmentsCreateSql = `
155
+ CREATE TABLE IF NOT EXISTS agent_experiment_assignments (
156
+ experiment_id TEXT NOT NULL,
157
+ user_id TEXT NOT NULL,
158
+ variant_id TEXT NOT NULL,
159
+ assigned_at ${intType()} NOT NULL,
160
+ PRIMARY KEY (experiment_id, user_id)
161
+ )
161
162
  `;
162
- const experimentResultsCreateSql = `
163
- CREATE TABLE IF NOT EXISTS agent_experiment_results (
164
- id TEXT PRIMARY KEY,
165
- experiment_id TEXT NOT NULL,
166
- variant_id TEXT NOT NULL,
167
- metric TEXT NOT NULL,
168
- value REAL NOT NULL DEFAULT 0,
169
- sample_size ${intType()} NOT NULL DEFAULT 0,
170
- confidence_low REAL NOT NULL DEFAULT 0,
171
- confidence_high REAL NOT NULL DEFAULT 0,
172
- computed_at ${intType()} NOT NULL
173
- )
163
+ const experimentResultsCreateSql = `
164
+ CREATE TABLE IF NOT EXISTS agent_experiment_results (
165
+ id TEXT PRIMARY KEY,
166
+ experiment_id TEXT NOT NULL,
167
+ variant_id TEXT NOT NULL,
168
+ metric TEXT NOT NULL,
169
+ value REAL NOT NULL DEFAULT 0,
170
+ sample_size ${intType()} NOT NULL DEFAULT 0,
171
+ confidence_low REAL NOT NULL DEFAULT 0,
172
+ confidence_high REAL NOT NULL DEFAULT 0,
173
+ computed_at ${intType()} NOT NULL
174
+ )
174
175
  `;
175
- if (isPostgres()) {
176
- // PG guard: probe → guarded DDL → re-probe; skips lock on already-migrated path
177
- await ensureTableExists("agent_trace_spans", traceSpansCreateSql);
178
- await ensureTableExists("agent_trace_summaries", traceSummariesCreateSql);
179
- await ensureTableExists("agent_feedback", feedbackCreateSql);
180
- await ensureTableExists("agent_satisfaction_scores", satisfactionScoresCreateSql);
181
- await ensureTableExists("agent_evals", evalsCreateSql);
182
- await ensureTableExists("agent_eval_datasets", evalDatasetsCreateSql);
183
- await ensureTableExists("agent_experiments", experimentsCreateSql);
184
- await ensureTableExists("agent_experiment_assignments", experimentAssignmentsCreateSql);
185
- await ensureTableExists("agent_experiment_results", experimentResultsCreateSql);
186
- await ensureColumnExists("agent_experiments", "owner_email", `ALTER TABLE agent_experiments ADD COLUMN IF NOT EXISTS owner_email TEXT`);
187
- for (const table of USER_SCOPED_TABLES) {
188
- await ensureColumnExists(table, "user_id", `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS user_id TEXT`);
189
- }
190
- await ensureIndexExists("idx_trace_spans_run", `CREATE INDEX IF NOT EXISTS idx_trace_spans_run ON agent_trace_spans (run_id)`);
191
- await ensureIndexExists("idx_trace_spans_thread", `CREATE INDEX IF NOT EXISTS idx_trace_spans_thread ON agent_trace_spans (thread_id)`);
192
- await ensureIndexExists("idx_trace_spans_created", `CREATE INDEX IF NOT EXISTS idx_trace_spans_created ON agent_trace_spans (created_at)`);
193
- await ensureIndexExists("idx_trace_summaries_created", `CREATE INDEX IF NOT EXISTS idx_trace_summaries_created ON agent_trace_summaries (created_at)`);
194
- await ensureIndexExists("idx_trace_summaries_user", `CREATE INDEX IF NOT EXISTS idx_trace_summaries_user ON agent_trace_summaries (user_id, created_at)`);
195
- await ensureIndexExists("idx_trace_spans_user", `CREATE INDEX IF NOT EXISTS idx_trace_spans_user ON agent_trace_spans (user_id)`);
196
- await ensureIndexExists("idx_feedback_thread", `CREATE INDEX IF NOT EXISTS idx_feedback_thread ON agent_feedback (thread_id)`);
197
- await ensureIndexExists("idx_feedback_created", `CREATE INDEX IF NOT EXISTS idx_feedback_created ON agent_feedback (created_at)`);
198
- await ensureIndexExists("idx_feedback_user", `CREATE INDEX IF NOT EXISTS idx_feedback_user ON agent_feedback (user_id, created_at)`);
199
- await ensureIndexExists("idx_satisfaction_thread", `CREATE INDEX IF NOT EXISTS idx_satisfaction_thread ON agent_satisfaction_scores (thread_id)`);
200
- await ensureIndexExists("idx_satisfaction_user", `CREATE INDEX IF NOT EXISTS idx_satisfaction_user ON agent_satisfaction_scores (user_id, computed_at)`);
201
- await ensureIndexExists("idx_evals_run", `CREATE INDEX IF NOT EXISTS idx_evals_run ON agent_evals (run_id)`);
202
- await ensureIndexExists("idx_evals_created", `CREATE INDEX IF NOT EXISTS idx_evals_created ON agent_evals (created_at)`);
203
- await ensureIndexExists("idx_evals_user", `CREATE INDEX IF NOT EXISTS idx_evals_user ON agent_evals (user_id, created_at)`);
204
- await ensureIndexExists("idx_experiment_results_exp", `CREATE INDEX IF NOT EXISTS idx_experiment_results_exp ON agent_experiment_results (experiment_id)`);
205
- return;
206
- }
207
- // SQLite (local dev): no lock problem — keep the original behaviour.
208
- await retryOnDdlRace(() => client.execute(traceSpansCreateSql));
209
- await retryOnDdlRace(() => client.execute(traceSummariesCreateSql));
210
- await retryOnDdlRace(() => client.execute(feedbackCreateSql));
211
- await retryOnDdlRace(() => client.execute(satisfactionScoresCreateSql));
212
- await retryOnDdlRace(() => client.execute(evalsCreateSql));
213
- await retryOnDdlRace(() => client.execute(evalDatasetsCreateSql));
214
- await retryOnDdlRace(() => client.execute(experimentsCreateSql));
215
- // Additive migration for DBs created before the owner column shipped
216
- // (any pre-existing rows have NULL owner — see `updateExperiment` for
217
- // the migration semantics). Mutations on those rows fall back to the
218
- // standard authentication gate but cannot enforce per-owner scoping
219
- // until they're re-saved.
220
- try {
221
- await client.execute(`ALTER TABLE agent_experiments ADD COLUMN owner_email TEXT`);
222
- }
223
- catch {
224
- // Column already exists — expected after first run.
225
- }
226
- await retryOnDdlRace(() => client.execute(experimentAssignmentsCreateSql));
227
- await retryOnDdlRace(() => client.execute(experimentResultsCreateSql));
228
- // Idempotent column upgrades for DBs created before per-user
229
- // isolation. SQLite has no `ADD COLUMN IF NOT EXISTS`; Postgres
230
- // surfaces "column ... already exists". `isDuplicateColumnError`
231
- // (from db/migrations.ts) recognizes both shapes.
232
- for (const table of USER_SCOPED_TABLES) {
233
- try {
234
- await client.execute(`ALTER TABLE ${table} ADD COLUMN user_id TEXT`);
235
- }
236
- catch (err) {
237
- if (isDuplicateColumnError(err))
238
- continue;
239
- throw err;
240
- }
241
- }
242
- // Indexes for common query patterns
243
- const indexes = [
244
- `CREATE INDEX IF NOT EXISTS idx_trace_spans_run ON agent_trace_spans (run_id)`,
245
- `CREATE INDEX IF NOT EXISTS idx_trace_spans_thread ON agent_trace_spans (thread_id)`,
246
- `CREATE INDEX IF NOT EXISTS idx_trace_spans_created ON agent_trace_spans (created_at)`,
247
- `CREATE INDEX IF NOT EXISTS idx_trace_summaries_created ON agent_trace_summaries (created_at)`,
248
- `CREATE INDEX IF NOT EXISTS idx_trace_summaries_user ON agent_trace_summaries (user_id, created_at)`,
249
- `CREATE INDEX IF NOT EXISTS idx_trace_spans_user ON agent_trace_spans (user_id)`,
250
- `CREATE INDEX IF NOT EXISTS idx_feedback_thread ON agent_feedback (thread_id)`,
251
- `CREATE INDEX IF NOT EXISTS idx_feedback_created ON agent_feedback (created_at)`,
252
- `CREATE INDEX IF NOT EXISTS idx_feedback_user ON agent_feedback (user_id, created_at)`,
253
- `CREATE INDEX IF NOT EXISTS idx_satisfaction_thread ON agent_satisfaction_scores (thread_id)`,
254
- `CREATE INDEX IF NOT EXISTS idx_satisfaction_user ON agent_satisfaction_scores (user_id, computed_at)`,
255
- `CREATE INDEX IF NOT EXISTS idx_evals_run ON agent_evals (run_id)`,
256
- `CREATE INDEX IF NOT EXISTS idx_evals_created ON agent_evals (created_at)`,
257
- `CREATE INDEX IF NOT EXISTS idx_evals_user ON agent_evals (user_id, created_at)`,
258
- `CREATE INDEX IF NOT EXISTS idx_experiment_results_exp ON agent_experiment_results (experiment_id)`,
259
- ];
260
- for (const sql of indexes) {
261
- try {
262
- await client.execute(sql);
263
- }
264
- catch {
265
- // Index might already exist
266
- }
267
- }
268
- })().catch((err) => {
269
- _initPromise = undefined;
176
+ if (isPostgres()) {
177
+ // PG guard: probe → guarded DDL → re-probe; skips lock on already-migrated path
178
+ await ensureTableExists("agent_trace_spans", traceSpansCreateSql);
179
+ await ensureTableExists("agent_trace_summaries", traceSummariesCreateSql);
180
+ await ensureTableExists("agent_feedback", feedbackCreateSql);
181
+ await ensureTableExists("agent_satisfaction_scores", satisfactionScoresCreateSql);
182
+ await ensureTableExists("agent_evals", evalsCreateSql);
183
+ await ensureTableExists("agent_eval_datasets", evalDatasetsCreateSql);
184
+ await ensureTableExists("agent_experiments", experimentsCreateSql);
185
+ await ensureTableExists("agent_experiment_assignments", experimentAssignmentsCreateSql);
186
+ await ensureTableExists("agent_experiment_results", experimentResultsCreateSql);
187
+ await ensureColumnExists("agent_experiments", "owner_email", `ALTER TABLE agent_experiments ADD COLUMN IF NOT EXISTS owner_email TEXT`);
188
+ for (const table of USER_SCOPED_TABLES) {
189
+ await ensureColumnExists(table, "user_id", `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS user_id TEXT`);
190
+ }
191
+ await ensureIndexExists("idx_trace_spans_run", `CREATE INDEX IF NOT EXISTS idx_trace_spans_run ON agent_trace_spans (run_id)`);
192
+ await ensureIndexExists("idx_trace_spans_thread", `CREATE INDEX IF NOT EXISTS idx_trace_spans_thread ON agent_trace_spans (thread_id)`);
193
+ await ensureIndexExists("idx_trace_spans_created", `CREATE INDEX IF NOT EXISTS idx_trace_spans_created ON agent_trace_spans (created_at)`);
194
+ await ensureIndexExists("idx_trace_summaries_created", `CREATE INDEX IF NOT EXISTS idx_trace_summaries_created ON agent_trace_summaries (created_at)`);
195
+ await ensureIndexExists("idx_trace_summaries_user", `CREATE INDEX IF NOT EXISTS idx_trace_summaries_user ON agent_trace_summaries (user_id, created_at)`);
196
+ await ensureIndexExists("idx_trace_spans_user", `CREATE INDEX IF NOT EXISTS idx_trace_spans_user ON agent_trace_spans (user_id)`);
197
+ await ensureIndexExists("idx_feedback_thread", `CREATE INDEX IF NOT EXISTS idx_feedback_thread ON agent_feedback (thread_id)`);
198
+ await ensureIndexExists("idx_feedback_created", `CREATE INDEX IF NOT EXISTS idx_feedback_created ON agent_feedback (created_at)`);
199
+ await ensureIndexExists("idx_feedback_user", `CREATE INDEX IF NOT EXISTS idx_feedback_user ON agent_feedback (user_id, created_at)`);
200
+ await ensureIndexExists("idx_satisfaction_thread", `CREATE INDEX IF NOT EXISTS idx_satisfaction_thread ON agent_satisfaction_scores (thread_id)`);
201
+ await ensureIndexExists("idx_satisfaction_user", `CREATE INDEX IF NOT EXISTS idx_satisfaction_user ON agent_satisfaction_scores (user_id, computed_at)`);
202
+ await ensureIndexExists("idx_evals_run", `CREATE INDEX IF NOT EXISTS idx_evals_run ON agent_evals (run_id)`);
203
+ await ensureIndexExists("idx_evals_created", `CREATE INDEX IF NOT EXISTS idx_evals_created ON agent_evals (created_at)`);
204
+ await ensureIndexExists("idx_evals_user", `CREATE INDEX IF NOT EXISTS idx_evals_user ON agent_evals (user_id, created_at)`);
205
+ await ensureIndexExists("idx_experiment_results_exp", `CREATE INDEX IF NOT EXISTS idx_experiment_results_exp ON agent_experiment_results (experiment_id)`);
206
+ return;
207
+ }
208
+ // SQLite (local dev): no lock problem — keep the original behaviour.
209
+ await retryOnDdlRace(() => client.execute(traceSpansCreateSql));
210
+ await retryOnDdlRace(() => client.execute(traceSummariesCreateSql));
211
+ await retryOnDdlRace(() => client.execute(feedbackCreateSql));
212
+ await retryOnDdlRace(() => client.execute(satisfactionScoresCreateSql));
213
+ await retryOnDdlRace(() => client.execute(evalsCreateSql));
214
+ await retryOnDdlRace(() => client.execute(evalDatasetsCreateSql));
215
+ await retryOnDdlRace(() => client.execute(experimentsCreateSql));
216
+ // Additive migration for DBs created before the owner column shipped
217
+ // (any pre-existing rows have NULL owner — see `updateExperiment` for
218
+ // the migration semantics). Mutations on those rows fall back to the
219
+ // standard authentication gate but cannot enforce per-owner scoping
220
+ // until they're re-saved.
221
+ try {
222
+ await client.execute(`ALTER TABLE agent_experiments ADD COLUMN owner_email TEXT`);
223
+ }
224
+ catch {
225
+ // Column already exists — expected after first run.
226
+ }
227
+ await retryOnDdlRace(() => client.execute(experimentAssignmentsCreateSql));
228
+ await retryOnDdlRace(() => client.execute(experimentResultsCreateSql));
229
+ // Idempotent column upgrades for DBs created before per-user
230
+ // isolation. SQLite has no `ADD COLUMN IF NOT EXISTS`; Postgres
231
+ // surfaces "column ... already exists". `isDuplicateColumnError`
232
+ // (from db/migrations.ts) recognizes both shapes.
233
+ for (const table of USER_SCOPED_TABLES) {
234
+ try {
235
+ await client.execute(`ALTER TABLE ${table} ADD COLUMN user_id TEXT`);
236
+ }
237
+ catch (err) {
238
+ if (isDuplicateColumnError(err))
239
+ continue;
270
240
  throw err;
271
- });
241
+ }
272
242
  }
273
- return _initPromise;
274
- }
243
+ // Indexes for common query patterns
244
+ const indexes = [
245
+ `CREATE INDEX IF NOT EXISTS idx_trace_spans_run ON agent_trace_spans (run_id)`,
246
+ `CREATE INDEX IF NOT EXISTS idx_trace_spans_thread ON agent_trace_spans (thread_id)`,
247
+ `CREATE INDEX IF NOT EXISTS idx_trace_spans_created ON agent_trace_spans (created_at)`,
248
+ `CREATE INDEX IF NOT EXISTS idx_trace_summaries_created ON agent_trace_summaries (created_at)`,
249
+ `CREATE INDEX IF NOT EXISTS idx_trace_summaries_user ON agent_trace_summaries (user_id, created_at)`,
250
+ `CREATE INDEX IF NOT EXISTS idx_trace_spans_user ON agent_trace_spans (user_id)`,
251
+ `CREATE INDEX IF NOT EXISTS idx_feedback_thread ON agent_feedback (thread_id)`,
252
+ `CREATE INDEX IF NOT EXISTS idx_feedback_created ON agent_feedback (created_at)`,
253
+ `CREATE INDEX IF NOT EXISTS idx_feedback_user ON agent_feedback (user_id, created_at)`,
254
+ `CREATE INDEX IF NOT EXISTS idx_satisfaction_thread ON agent_satisfaction_scores (thread_id)`,
255
+ `CREATE INDEX IF NOT EXISTS idx_satisfaction_user ON agent_satisfaction_scores (user_id, computed_at)`,
256
+ `CREATE INDEX IF NOT EXISTS idx_evals_run ON agent_evals (run_id)`,
257
+ `CREATE INDEX IF NOT EXISTS idx_evals_created ON agent_evals (created_at)`,
258
+ `CREATE INDEX IF NOT EXISTS idx_evals_user ON agent_evals (user_id, created_at)`,
259
+ `CREATE INDEX IF NOT EXISTS idx_experiment_results_exp ON agent_experiment_results (experiment_id)`,
260
+ ];
261
+ for (const sql of indexes) {
262
+ try {
263
+ await client.execute(sql);
264
+ }
265
+ catch {
266
+ // Index might already exist
267
+ }
268
+ }
269
+ }, { label: "observability" });
275
270
  // ─── Trace span CRUD ─────────────────────────────────────────────────
276
271
  export async function insertTraceSpan(span) {
277
272
  await ensureObservabilityTables();
278
273
  const client = getDbExec();
279
274
  await client.execute({
280
- sql: `INSERT INTO agent_trace_spans
281
- (id, run_id, thread_id, user_id, parent_span_id, span_type, name,
282
- input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
283
- cost_cents_x100, duration_ms, status, error_message, metadata, created_at)
275
+ sql: `INSERT INTO agent_trace_spans
276
+ (id, run_id, thread_id, user_id, parent_span_id, span_type, name,
277
+ input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
278
+ cost_cents_x100, duration_ms, status, error_message, metadata, created_at)
284
279
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
285
280
  args: [
286
281
  span.id,
@@ -310,22 +305,22 @@ export async function upsertTraceSummary(summary) {
310
305
  // owner is recorded it shouldn't change under us.
311
306
  if (isPostgres()) {
312
307
  await client.execute({
313
- sql: `INSERT INTO agent_trace_summaries
314
- (run_id, thread_id, user_id, total_spans, llm_calls, tool_calls,
315
- successful_tools, failed_tools, total_duration_ms,
316
- total_cost_cents_x100, total_input_tokens, total_output_tokens,
317
- model, created_at)
318
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
319
- ON CONFLICT (run_id) DO UPDATE SET
320
- total_spans = EXCLUDED.total_spans,
321
- llm_calls = EXCLUDED.llm_calls,
322
- tool_calls = EXCLUDED.tool_calls,
323
- successful_tools = EXCLUDED.successful_tools,
324
- failed_tools = EXCLUDED.failed_tools,
325
- total_duration_ms = EXCLUDED.total_duration_ms,
326
- total_cost_cents_x100 = EXCLUDED.total_cost_cents_x100,
327
- total_input_tokens = EXCLUDED.total_input_tokens,
328
- total_output_tokens = EXCLUDED.total_output_tokens,
308
+ sql: `INSERT INTO agent_trace_summaries
309
+ (run_id, thread_id, user_id, total_spans, llm_calls, tool_calls,
310
+ successful_tools, failed_tools, total_duration_ms,
311
+ total_cost_cents_x100, total_input_tokens, total_output_tokens,
312
+ model, created_at)
313
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
314
+ ON CONFLICT (run_id) DO UPDATE SET
315
+ total_spans = EXCLUDED.total_spans,
316
+ llm_calls = EXCLUDED.llm_calls,
317
+ tool_calls = EXCLUDED.tool_calls,
318
+ successful_tools = EXCLUDED.successful_tools,
319
+ failed_tools = EXCLUDED.failed_tools,
320
+ total_duration_ms = EXCLUDED.total_duration_ms,
321
+ total_cost_cents_x100 = EXCLUDED.total_cost_cents_x100,
322
+ total_input_tokens = EXCLUDED.total_input_tokens,
323
+ total_output_tokens = EXCLUDED.total_output_tokens,
329
324
  model = EXCLUDED.model`,
330
325
  args: [
331
326
  summary.runId,
@@ -347,22 +342,22 @@ export async function upsertTraceSummary(summary) {
347
342
  }
348
343
  else {
349
344
  await client.execute({
350
- sql: `INSERT INTO agent_trace_summaries
351
- (run_id, thread_id, user_id, total_spans, llm_calls, tool_calls,
352
- successful_tools, failed_tools, total_duration_ms,
353
- total_cost_cents_x100, total_input_tokens, total_output_tokens,
354
- model, created_at)
355
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
356
- ON CONFLICT (run_id) DO UPDATE SET
357
- total_spans = EXCLUDED.total_spans,
358
- llm_calls = EXCLUDED.llm_calls,
359
- tool_calls = EXCLUDED.tool_calls,
360
- successful_tools = EXCLUDED.successful_tools,
361
- failed_tools = EXCLUDED.failed_tools,
362
- total_duration_ms = EXCLUDED.total_duration_ms,
363
- total_cost_cents_x100 = EXCLUDED.total_cost_cents_x100,
364
- total_input_tokens = EXCLUDED.total_input_tokens,
365
- total_output_tokens = EXCLUDED.total_output_tokens,
345
+ sql: `INSERT INTO agent_trace_summaries
346
+ (run_id, thread_id, user_id, total_spans, llm_calls, tool_calls,
347
+ successful_tools, failed_tools, total_duration_ms,
348
+ total_cost_cents_x100, total_input_tokens, total_output_tokens,
349
+ model, created_at)
350
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
351
+ ON CONFLICT (run_id) DO UPDATE SET
352
+ total_spans = EXCLUDED.total_spans,
353
+ llm_calls = EXCLUDED.llm_calls,
354
+ tool_calls = EXCLUDED.tool_calls,
355
+ successful_tools = EXCLUDED.successful_tools,
356
+ failed_tools = EXCLUDED.failed_tools,
357
+ total_duration_ms = EXCLUDED.total_duration_ms,
358
+ total_cost_cents_x100 = EXCLUDED.total_cost_cents_x100,
359
+ total_input_tokens = EXCLUDED.total_input_tokens,
360
+ total_output_tokens = EXCLUDED.total_output_tokens,
366
361
  model = EXCLUDED.model`,
367
362
  args: [
368
363
  summary.runId,
@@ -435,9 +430,9 @@ export async function getTraceSummaries(opts) {
435
430
  const limit = opts.limit ?? 100;
436
431
  const { where, args } = withUserFilter("created_at >= ?", [sinceMs], opts.userId);
437
432
  const { rows } = await client.execute({
438
- sql: `SELECT * FROM agent_trace_summaries
439
- WHERE ${where}
440
- ORDER BY created_at DESC
433
+ sql: `SELECT * FROM agent_trace_summaries
434
+ WHERE ${where}
435
+ ORDER BY created_at DESC
441
436
  LIMIT ?`,
442
437
  args: [...args, limit],
443
438
  });
@@ -460,8 +455,8 @@ export async function insertFeedback(entry) {
460
455
  await ensureObservabilityTables();
461
456
  const client = getDbExec();
462
457
  await client.execute({
463
- sql: `INSERT INTO agent_feedback
464
- (id, run_id, thread_id, message_seq, feedback_type, value, user_id, created_at)
458
+ sql: `INSERT INTO agent_feedback
459
+ (id, run_id, thread_id, message_seq, feedback_type, value, user_id, created_at)
465
460
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
466
461
  args: [
467
462
  entry.id,
@@ -509,8 +504,8 @@ export async function getFeedbackStats(sinceMs, opts = {}) {
509
504
  const client = getDbExec();
510
505
  const { where, args } = withUserFilter("created_at >= ?", [sinceMs], opts.userId);
511
506
  const { rows } = await client.execute({
512
- sql: `SELECT feedback_type, value, COUNT(*) as cnt
513
- FROM agent_feedback WHERE ${where}
507
+ sql: `SELECT feedback_type, value, COUNT(*) as cnt
508
+ FROM agent_feedback WHERE ${where}
514
509
  GROUP BY feedback_type, value`,
515
510
  args,
516
511
  });
@@ -536,16 +531,16 @@ export async function upsertSatisfactionScore(score) {
536
531
  const client = getDbExec();
537
532
  if (isPostgres()) {
538
533
  await client.execute({
539
- sql: `INSERT INTO agent_satisfaction_scores
540
- (id, thread_id, user_id, frustration_score, rephrasing_score,
541
- abandonment_score, sentiment_score, length_trend_score, computed_at)
542
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
543
- ON CONFLICT (id) DO UPDATE SET
544
- frustration_score = EXCLUDED.frustration_score,
545
- rephrasing_score = EXCLUDED.rephrasing_score,
546
- abandonment_score = EXCLUDED.abandonment_score,
547
- sentiment_score = EXCLUDED.sentiment_score,
548
- length_trend_score = EXCLUDED.length_trend_score,
534
+ sql: `INSERT INTO agent_satisfaction_scores
535
+ (id, thread_id, user_id, frustration_score, rephrasing_score,
536
+ abandonment_score, sentiment_score, length_trend_score, computed_at)
537
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
538
+ ON CONFLICT (id) DO UPDATE SET
539
+ frustration_score = EXCLUDED.frustration_score,
540
+ rephrasing_score = EXCLUDED.rephrasing_score,
541
+ abandonment_score = EXCLUDED.abandonment_score,
542
+ sentiment_score = EXCLUDED.sentiment_score,
543
+ length_trend_score = EXCLUDED.length_trend_score,
549
544
  computed_at = EXCLUDED.computed_at`,
550
545
  args: [
551
546
  score.id,
@@ -562,16 +557,16 @@ export async function upsertSatisfactionScore(score) {
562
557
  }
563
558
  else {
564
559
  await client.execute({
565
- sql: `INSERT INTO agent_satisfaction_scores
566
- (id, thread_id, user_id, frustration_score, rephrasing_score,
567
- abandonment_score, sentiment_score, length_trend_score, computed_at)
568
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
569
- ON CONFLICT (id) DO UPDATE SET
570
- frustration_score = EXCLUDED.frustration_score,
571
- rephrasing_score = EXCLUDED.rephrasing_score,
572
- abandonment_score = EXCLUDED.abandonment_score,
573
- sentiment_score = EXCLUDED.sentiment_score,
574
- length_trend_score = EXCLUDED.length_trend_score,
560
+ sql: `INSERT INTO agent_satisfaction_scores
561
+ (id, thread_id, user_id, frustration_score, rephrasing_score,
562
+ abandonment_score, sentiment_score, length_trend_score, computed_at)
563
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
564
+ ON CONFLICT (id) DO UPDATE SET
565
+ frustration_score = EXCLUDED.frustration_score,
566
+ rephrasing_score = EXCLUDED.rephrasing_score,
567
+ abandonment_score = EXCLUDED.abandonment_score,
568
+ sentiment_score = EXCLUDED.sentiment_score,
569
+ length_trend_score = EXCLUDED.length_trend_score,
575
570
  computed_at = EXCLUDED.computed_at`,
576
571
  args: [
577
572
  score.id,
@@ -606,7 +601,7 @@ export async function getSatisfactionScores(opts) {
606
601
  }
607
602
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
608
603
  const { rows } = await client.execute({
609
- sql: `SELECT * FROM agent_satisfaction_scores ${where}
604
+ sql: `SELECT * FROM agent_satisfaction_scores ${where}
610
605
  ORDER BY computed_at DESC LIMIT ?`,
611
606
  args: [...args, opts.limit ?? 100],
612
607
  });
@@ -617,8 +612,8 @@ export async function insertEvalResult(result) {
617
612
  await ensureObservabilityTables();
618
613
  const client = getDbExec();
619
614
  await client.execute({
620
- sql: `INSERT INTO agent_evals
621
- (id, run_id, thread_id, user_id, eval_type, criteria, score, reasoning, metadata, created_at)
615
+ sql: `INSERT INTO agent_evals
616
+ (id, run_id, thread_id, user_id, eval_type, criteria, score, reasoning, metadata, created_at)
622
617
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
623
618
  args: [
624
619
  result.id,
@@ -649,14 +644,14 @@ export async function getEvalStats(sinceMs, opts = {}) {
649
644
  const client = getDbExec();
650
645
  const { where, args } = withUserFilter("created_at >= ?", [sinceMs], opts.userId);
651
646
  const { rows: totalRows } = await client.execute({
652
- sql: `SELECT COUNT(*) as cnt, AVG(score) as avg_score
647
+ sql: `SELECT COUNT(*) as cnt, AVG(score) as avg_score
653
648
  FROM agent_evals WHERE ${where}`,
654
649
  args,
655
650
  });
656
651
  const t = (totalRows[0] ?? {});
657
652
  const { rows: criteriaRows } = await client.execute({
658
- sql: `SELECT criteria, AVG(score) as avg_score, COUNT(*) as cnt
659
- FROM agent_evals WHERE ${where}
653
+ sql: `SELECT criteria, AVG(score) as avg_score, COUNT(*) as cnt
654
+ FROM agent_evals WHERE ${where}
660
655
  GROUP BY criteria ORDER BY cnt DESC`,
661
656
  args,
662
657
  });
@@ -675,8 +670,8 @@ export async function insertEvalDataset(dataset) {
675
670
  await ensureObservabilityTables();
676
671
  const client = getDbExec();
677
672
  await client.execute({
678
- sql: `INSERT INTO agent_eval_datasets
679
- (id, name, description, entries, created_at, updated_at)
673
+ sql: `INSERT INTO agent_eval_datasets
674
+ (id, name, description, entries, created_at, updated_at)
680
675
  VALUES (?, ?, ?, ?, ?, ?)`,
681
676
  args: [
682
677
  dataset.id,
@@ -735,9 +730,9 @@ export async function insertExperiment(exp) {
735
730
  await ensureObservabilityTables();
736
731
  const client = getDbExec();
737
732
  await client.execute({
738
- sql: `INSERT INTO agent_experiments
739
- (id, name, status, variants, metrics, assignment_level,
740
- started_at, ended_at, created_at, owner_email)
733
+ sql: `INSERT INTO agent_experiments
734
+ (id, name, status, variants, metrics, assignment_level,
735
+ started_at, ended_at, created_at, owner_email)
741
736
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
742
737
  args: [
743
738
  exp.id,
@@ -813,11 +808,11 @@ export async function upsertAssignment(assignment) {
813
808
  const client = getDbExec();
814
809
  if (isPostgres()) {
815
810
  await client.execute({
816
- sql: `INSERT INTO agent_experiment_assignments
817
- (experiment_id, user_id, variant_id, assigned_at)
818
- VALUES (?, ?, ?, ?)
819
- ON CONFLICT (experiment_id, user_id) DO UPDATE SET
820
- variant_id = EXCLUDED.variant_id,
811
+ sql: `INSERT INTO agent_experiment_assignments
812
+ (experiment_id, user_id, variant_id, assigned_at)
813
+ VALUES (?, ?, ?, ?)
814
+ ON CONFLICT (experiment_id, user_id) DO UPDATE SET
815
+ variant_id = EXCLUDED.variant_id,
821
816
  assigned_at = EXCLUDED.assigned_at`,
822
817
  args: [
823
818
  assignment.experimentId,
@@ -829,8 +824,8 @@ export async function upsertAssignment(assignment) {
829
824
  }
830
825
  else {
831
826
  await client.execute({
832
- sql: `INSERT OR REPLACE INTO agent_experiment_assignments
833
- (experiment_id, user_id, variant_id, assigned_at)
827
+ sql: `INSERT OR REPLACE INTO agent_experiment_assignments
828
+ (experiment_id, user_id, variant_id, assigned_at)
834
829
  VALUES (?, ?, ?, ?)`,
835
830
  args: [
836
831
  assignment.experimentId,
@@ -845,7 +840,7 @@ export async function getAssignment(experimentId, userId) {
845
840
  await ensureObservabilityTables();
846
841
  const client = getDbExec();
847
842
  const { rows } = await client.execute({
848
- sql: `SELECT * FROM agent_experiment_assignments
843
+ sql: `SELECT * FROM agent_experiment_assignments
849
844
  WHERE experiment_id = ? AND user_id = ?`,
850
845
  args: [experimentId, userId],
851
846
  });
@@ -864,9 +859,9 @@ export async function insertExperimentResult(result) {
864
859
  await ensureObservabilityTables();
865
860
  const client = getDbExec();
866
861
  await client.execute({
867
- sql: `INSERT INTO agent_experiment_results
868
- (id, experiment_id, variant_id, metric, value,
869
- sample_size, confidence_low, confidence_high, computed_at)
862
+ sql: `INSERT INTO agent_experiment_results
863
+ (id, experiment_id, variant_id, metric, value,
864
+ sample_size, confidence_low, confidence_high, computed_at)
870
865
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
871
866
  args: [
872
867
  result.id,
@@ -885,8 +880,8 @@ export async function getExperimentResults(experimentId) {
885
880
  await ensureObservabilityTables();
886
881
  const client = getDbExec();
887
882
  const { rows } = await client.execute({
888
- sql: `SELECT * FROM agent_experiment_results
889
- WHERE experiment_id = ?
883
+ sql: `SELECT * FROM agent_experiment_results
884
+ WHERE experiment_id = ?
890
885
  ORDER BY computed_at DESC`,
891
886
  args: [experimentId],
892
887
  });
@@ -904,29 +899,29 @@ export async function getObservabilityOverview(sinceMs, opts = {}) {
904
899
  const computed = withUserFilter("computed_at >= ?", [sinceMs], opts.userId);
905
900
  const [tracesResult, satisfactionResult, feedbackResult, evalsResult] = await Promise.all([
906
901
  client.execute({
907
- sql: `SELECT
908
- COUNT(*) as total_runs,
909
- COALESCE(SUM(total_cost_cents_x100), 0) as total_cost,
910
- COALESCE(AVG(total_duration_ms), 0) as avg_duration,
911
- COALESCE(SUM(successful_tools), 0) as success_tools,
912
- COALESCE(SUM(tool_calls), 0) as total_tools
902
+ sql: `SELECT
903
+ COUNT(*) as total_runs,
904
+ COALESCE(SUM(total_cost_cents_x100), 0) as total_cost,
905
+ COALESCE(AVG(total_duration_ms), 0) as avg_duration,
906
+ COALESCE(SUM(successful_tools), 0) as success_tools,
907
+ COALESCE(SUM(tool_calls), 0) as total_tools
913
908
  FROM agent_trace_summaries WHERE ${created.where}`,
914
909
  args: created.args,
915
910
  }),
916
911
  client.execute({
917
- sql: `SELECT COALESCE(AVG(frustration_score), 0) as avg_frustration
912
+ sql: `SELECT COALESCE(AVG(frustration_score), 0) as avg_frustration
918
913
  FROM agent_satisfaction_scores WHERE ${computed.where}`,
919
914
  args: computed.args,
920
915
  }),
921
916
  client.execute({
922
- sql: `SELECT
923
- COALESCE(SUM(CASE WHEN feedback_type = 'thumbs_up' THEN 1 ELSE 0 END), 0) as up,
924
- COALESCE(SUM(CASE WHEN feedback_type IN ('thumbs_up', 'thumbs_down') THEN 1 ELSE 0 END), 0) as total
917
+ sql: `SELECT
918
+ COALESCE(SUM(CASE WHEN feedback_type = 'thumbs_up' THEN 1 ELSE 0 END), 0) as up,
919
+ COALESCE(SUM(CASE WHEN feedback_type IN ('thumbs_up', 'thumbs_down') THEN 1 ELSE 0 END), 0) as total
925
920
  FROM agent_feedback WHERE ${created.where}`,
926
921
  args: created.args,
927
922
  }),
928
923
  client.execute({
929
- sql: `SELECT COALESCE(AVG(score), 0) as avg_score
924
+ sql: `SELECT COALESCE(AVG(score), 0) as avg_score
930
925
  FROM agent_evals WHERE ${created.where}`,
931
926
  args: created.args,
932
927
  }),