@refrainai/cli 0.4.1 → 0.4.2

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 (83) hide show
  1. package/dist/{ai-model-FM6GWCID.js → ai-model-DP5PKGM6.js} +2 -2
  2. package/dist/{chunk-2BVDAJZT.js → chunk-65CTEK2K.js} +9 -6
  3. package/dist/chunk-65CTEK2K.js.map +1 -0
  4. package/dist/chunk-BGC75OVR.js +30 -0
  5. package/dist/chunk-BGC75OVR.js.map +1 -0
  6. package/dist/{chunk-H47NWH7N.js → chunk-CJM3IU5Q.js} +611 -73
  7. package/dist/chunk-CJM3IU5Q.js.map +1 -0
  8. package/dist/{chunk-CLYJHKPY.js → chunk-CMWLFQXD.js} +43 -42
  9. package/dist/chunk-CMWLFQXD.js.map +1 -0
  10. package/dist/{chunk-IGFCYKHC.js → chunk-CNJ5KCDN.js} +252 -295
  11. package/dist/chunk-CNJ5KCDN.js.map +1 -0
  12. package/dist/{chunk-DJVUITRB.js → chunk-ELQ23L4Z.js} +898 -1135
  13. package/dist/chunk-ELQ23L4Z.js.map +1 -0
  14. package/dist/chunk-EMAYENG4.js +1146 -0
  15. package/dist/chunk-EMAYENG4.js.map +1 -0
  16. package/dist/{chunk-7UCVPKD4.js → chunk-F7WTOQIQ.js} +12 -72
  17. package/dist/chunk-F7WTOQIQ.js.map +1 -0
  18. package/dist/{chunk-WEYR56ZN.js → chunk-HHRHHFSK.js} +4 -4
  19. package/dist/{chunk-UGPXCQY3.js → chunk-KFNW4XR2.js} +13 -4
  20. package/dist/chunk-KFNW4XR2.js.map +1 -0
  21. package/dist/{chunk-RT664YIO.js → chunk-LZDZGI4M.js} +3 -1
  22. package/dist/chunk-LZDZGI4M.js.map +1 -0
  23. package/dist/chunk-RBZK7T76.js +349 -0
  24. package/dist/chunk-RBZK7T76.js.map +1 -0
  25. package/dist/{chunk-HQDXLWAY.js → chunk-SDV3X5UN.js} +2 -2
  26. package/dist/{chunk-Z33FCOTZ.js → chunk-VVXNFUPL.js} +4 -2
  27. package/dist/chunk-VVXNFUPL.js.map +1 -0
  28. package/dist/chunk-XIVS7N3V.js +74 -0
  29. package/dist/chunk-XIVS7N3V.js.map +1 -0
  30. package/dist/chunk-YTVEYQGA.js +64 -0
  31. package/dist/chunk-YTVEYQGA.js.map +1 -0
  32. package/dist/{chunk-RYIJPYM3.js → chunk-YW46VP57.js} +25 -8
  33. package/dist/chunk-YW46VP57.js.map +1 -0
  34. package/dist/cli.js +5 -5
  35. package/dist/{compose-MTSIJY5D.js → compose-B2IAO7YW.js} +9 -7
  36. package/dist/{compose-MTSIJY5D.js.map → compose-B2IAO7YW.js.map} +1 -1
  37. package/dist/extraction-prompt-VDCKIFLB.js +17 -0
  38. package/dist/extraction-prompt-VDCKIFLB.js.map +1 -0
  39. package/dist/{fix-runbook-ZSBOTLC2.js → fix-runbook-JJN4HVIP.js} +12 -10
  40. package/dist/{fix-runbook-ZSBOTLC2.js.map → fix-runbook-JJN4HVIP.js.map} +1 -1
  41. package/dist/prompts-XMJXIITW.js +13 -0
  42. package/dist/runbook-builder-2ZLE2AEO.js +11 -0
  43. package/dist/{runbook-data-helpers-KRR2SH76.js → runbook-data-helpers-5UAO65TZ.js} +3 -3
  44. package/dist/{runbook-executor-K7T6RJWJ.js → runbook-executor-TVV5EG6Q.js} +41 -444
  45. package/dist/runbook-executor-TVV5EG6Q.js.map +1 -0
  46. package/dist/{runbook-generator-MPXJBQ5N.js → runbook-generator-4XKNV2B7.js} +61 -136
  47. package/dist/runbook-generator-4XKNV2B7.js.map +1 -0
  48. package/dist/{runbook-schema-3T6TP3JJ.js → runbook-schema-X7DW725O.js} +2 -2
  49. package/dist/runbook-store-S24PXIHD.js +11 -0
  50. package/dist/{schema-5G6UQSPT.js → schema-XFSD5EWN.js} +2 -2
  51. package/dist/{server-AG3LXQBI.js → server-5XARL5N7.js} +1176 -128
  52. package/dist/server-5XARL5N7.js.map +1 -0
  53. package/dist/{tenant-ai-config-QPFEJUVJ.js → tenant-ai-config-4NHKRW7O.js} +4 -4
  54. package/dist/tenant-ai-config-4NHKRW7O.js.map +1 -0
  55. package/dist/yaml-patcher-32QBPXT2.js +18 -0
  56. package/dist/yaml-patcher-32QBPXT2.js.map +1 -0
  57. package/package.json +3 -2
  58. package/dist/chunk-2BVDAJZT.js.map +0 -1
  59. package/dist/chunk-7UCVPKD4.js.map +0 -1
  60. package/dist/chunk-CLYJHKPY.js.map +0 -1
  61. package/dist/chunk-DJVUITRB.js.map +0 -1
  62. package/dist/chunk-H47NWH7N.js.map +0 -1
  63. package/dist/chunk-IGFCYKHC.js.map +0 -1
  64. package/dist/chunk-RT664YIO.js.map +0 -1
  65. package/dist/chunk-RYIJPYM3.js.map +0 -1
  66. package/dist/chunk-UGPXCQY3.js.map +0 -1
  67. package/dist/chunk-VPK2MQAZ.js +0 -589
  68. package/dist/chunk-VPK2MQAZ.js.map +0 -1
  69. package/dist/chunk-Z33FCOTZ.js.map +0 -1
  70. package/dist/runbook-executor-K7T6RJWJ.js.map +0 -1
  71. package/dist/runbook-generator-MPXJBQ5N.js.map +0 -1
  72. package/dist/runbook-store-G5GUOWRR.js +0 -11
  73. package/dist/server-AG3LXQBI.js.map +0 -1
  74. package/dist/yaml-patcher-VGUS2JGH.js +0 -15
  75. /package/dist/{ai-model-FM6GWCID.js.map → ai-model-DP5PKGM6.js.map} +0 -0
  76. /package/dist/{chunk-WEYR56ZN.js.map → chunk-HHRHHFSK.js.map} +0 -0
  77. /package/dist/{chunk-HQDXLWAY.js.map → chunk-SDV3X5UN.js.map} +0 -0
  78. /package/dist/{runbook-data-helpers-KRR2SH76.js.map → prompts-XMJXIITW.js.map} +0 -0
  79. /package/dist/{runbook-schema-3T6TP3JJ.js.map → runbook-builder-2ZLE2AEO.js.map} +0 -0
  80. /package/dist/{runbook-store-G5GUOWRR.js.map → runbook-data-helpers-5UAO65TZ.js.map} +0 -0
  81. /package/dist/{schema-5G6UQSPT.js.map → runbook-schema-X7DW725O.js.map} +0 -0
  82. /package/dist/{tenant-ai-config-QPFEJUVJ.js.map → runbook-store-S24PXIHD.js.map} +0 -0
  83. /package/dist/{yaml-patcher-VGUS2JGH.js.map → schema-XFSD5EWN.js.map} +0 -0
@@ -1,4 +1,56 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ RunbookStore
4
+ } from "./chunk-HHRHHFSK.js";
5
+ import {
6
+ isEditable,
7
+ isVerifiable
8
+ } from "./chunk-VVXNFUPL.js";
9
+ import {
10
+ explore,
11
+ generateGenerationReportFile
12
+ } from "./chunk-CNJ5KCDN.js";
13
+ import {
14
+ COMMUNITY_PLAN,
15
+ DEFAULT_APPROVAL_TIMEOUT_MS,
16
+ DEFAULT_CALLBACK_PORT,
17
+ PLAN_PRICING,
18
+ RuntimeStore,
19
+ SELF_HEAL_DEFAULTS,
20
+ SharedChatBot,
21
+ TIER_TO_PLAN,
22
+ analyzeDebugResult,
23
+ applyExecutorDefaults,
24
+ buildApprovalCard,
25
+ buildApprovalResultCard,
26
+ buildDebugNotificationCard,
27
+ buildGenerateNotificationCard,
28
+ buildNotificationCard,
29
+ buildSelectorHint,
30
+ detectKeyKind,
31
+ enforceFeatureGates,
32
+ execute,
33
+ generateDebugToken,
34
+ generateExecutionReportFile,
35
+ getDebugPublicKey,
36
+ getDebugSigningKey,
37
+ mergeVariablesAndSecrets,
38
+ normalizeUrlPattern,
39
+ resolvePlanFromTier,
40
+ resolveVariablesCore
41
+ } from "./chunk-CJM3IU5Q.js";
42
+ import {
43
+ AgentBrowser,
44
+ DownloadManager,
45
+ InMemoryDataStore,
46
+ deserializeSecrets,
47
+ mergeVariablesIntoSecrets,
48
+ serializeSecrets
49
+ } from "./chunk-ELQ23L4Z.js";
50
+ import {
51
+ scanStepsForTemplates
52
+ } from "./chunk-D5SI2PHK.js";
53
+ import "./chunk-XIVS7N3V.js";
2
54
  import {
3
55
  loadServerConfig
4
56
  } from "./chunk-TDSM3UXI.js";
@@ -8,14 +60,7 @@ import {
8
60
  isMaskedValue,
9
61
  maskSensitiveValue,
10
62
  resolveTenantAIConfig
11
- } from "./chunk-HQDXLWAY.js";
12
- import {
13
- RunbookStore
14
- } from "./chunk-WEYR56ZN.js";
15
- import {
16
- isEditable,
17
- isVerifiable
18
- } from "./chunk-Z33FCOTZ.js";
63
+ } from "./chunk-SDV3X5UN.js";
19
64
  import {
20
65
  apiKeys,
21
66
  invitations,
@@ -37,6 +82,7 @@ import {
37
82
  runbookSettings,
38
83
  runbookSteps,
39
84
  runbookVariables,
85
+ runbookVersionReviews,
40
86
  runbookVersions,
41
87
  runbooks,
42
88
  schedules,
@@ -53,60 +99,7 @@ import {
53
99
  tenantUsage,
54
100
  tenants,
55
101
  user
56
- } from "./chunk-CLYJHKPY.js";
57
- import {
58
- buildRunbookYaml,
59
- createReviewPrompt,
60
- explore,
61
- reviewResponseSchema
62
- } from "./chunk-IGFCYKHC.js";
63
- import {
64
- COMMUNITY_PLAN,
65
- DEFAULT_APPROVAL_TIMEOUT_MS,
66
- DEFAULT_CALLBACK_PORT,
67
- PLAN_PRICING,
68
- RuntimeStore,
69
- SELF_HEAL_DEFAULTS,
70
- SharedChatBot,
71
- TIER_TO_PLAN,
72
- analyzeDebugResult,
73
- applyExecutorDefaults,
74
- buildApprovalCard,
75
- buildApprovalResultCard,
76
- buildDebugNotificationCard,
77
- buildGenerateNotificationCard,
78
- buildNotificationCard,
79
- buildSelectorHint,
80
- detectKeyKind,
81
- enforceFeatureGates,
82
- execute,
83
- generateDebugToken,
84
- getDebugPublicKey,
85
- getDebugSigningKey,
86
- mergeVariablesAndSecrets,
87
- normalizeUrlPattern,
88
- resolvePlanFromTier,
89
- resolveVariablesCore
90
- } from "./chunk-H47NWH7N.js";
91
- import {
92
- AgentBrowser,
93
- DownloadManager,
94
- InMemoryDataStore,
95
- deserializeSecrets,
96
- mergeVariablesIntoSecrets,
97
- serializeSecrets
98
- } from "./chunk-DJVUITRB.js";
99
- import "./chunk-XMFCXPYU.js";
100
- import "./chunk-AG3CFMYU.js";
101
- import {
102
- scanStepsForTemplates
103
- } from "./chunk-D5SI2PHK.js";
104
- import {
105
- resolveAcceptLanguage,
106
- t,
107
- tWithLocale,
108
- tfWithLocale
109
- } from "./chunk-7UCVPKD4.js";
102
+ } from "./chunk-CMWLFQXD.js";
110
103
  import {
111
104
  AIMetricsCollector,
112
105
  DEFAULT_MODEL_ID,
@@ -115,10 +108,27 @@ import {
115
108
  getUserFriendlyAIErrorFromMessage,
116
109
  runWithMetricsCollector,
117
110
  trackedGenerateObject
118
- } from "./chunk-UGPXCQY3.js";
111
+ } from "./chunk-KFNW4XR2.js";
112
+ import "./chunk-XMFCXPYU.js";
113
+ import "./chunk-AG3CFMYU.js";
114
+ import {
115
+ buildRunbookYaml
116
+ } from "./chunk-RBZK7T76.js";
117
+ import "./chunk-YTVEYQGA.js";
119
118
  import {
120
119
  ParsedRunbookSchema
121
- } from "./chunk-RT664YIO.js";
120
+ } from "./chunk-LZDZGI4M.js";
121
+ import {
122
+ createReviewPrompt,
123
+ reviewResponseSchema
124
+ } from "./chunk-BGC75OVR.js";
125
+ import "./chunk-EMAYENG4.js";
126
+ import {
127
+ resolveAcceptLanguage,
128
+ t,
129
+ tWithLocale,
130
+ tfWithLocale
131
+ } from "./chunk-F7WTOQIQ.js";
122
132
  import "./chunk-2H7UOFLK.js";
123
133
 
124
134
  // src/server/index.ts
@@ -126,7 +136,7 @@ import { execSync } from "child_process";
126
136
  import { serve } from "@hono/node-server";
127
137
 
128
138
  // src/server/app.ts
129
- import { Hono as Hono15 } from "hono";
139
+ import { Hono as Hono16 } from "hono";
130
140
  import { cors } from "hono/cors";
131
141
  import { logger } from "hono/logger";
132
142
  import { serve as serveInngest } from "inngest/hono";
@@ -484,7 +494,7 @@ async function closeDb() {
484
494
  }
485
495
 
486
496
  // src/server/store/job-store.ts
487
- import { eq as eq2, and, desc, count, sql } from "drizzle-orm";
497
+ import { eq as eq2, and, desc, count, sql, inArray } from "drizzle-orm";
488
498
  var JobStore = class {
489
499
  constructor(db) {
490
500
  this.db = db;
@@ -634,10 +644,14 @@ var JobStore = class {
634
644
  };
635
645
  }
636
646
  async list(tenantId, opts = {}) {
637
- const { runbookId, status, mode, limit = 50, offset = 0 } = opts;
647
+ const { runbookId, status, statuses, mode, limit = 50, offset = 0 } = opts;
638
648
  const conditions = [eq2(jobs.tenantId, tenantId)];
639
649
  if (runbookId) conditions.push(eq2(jobs.runbookId, runbookId));
640
- if (status) conditions.push(eq2(jobs.status, status));
650
+ if (statuses && statuses.length > 0) {
651
+ conditions.push(inArray(jobs.status, statuses));
652
+ } else if (status) {
653
+ conditions.push(eq2(jobs.status, status));
654
+ }
641
655
  if (mode) conditions.push(eq2(jobs.mode, mode));
642
656
  const where = and(...conditions);
643
657
  const rows = await this.db.select().from(jobs).where(where).orderBy(desc(jobs.createdAt)).limit(limit).offset(offset);
@@ -889,7 +903,7 @@ var JobStore = class {
889
903
  };
890
904
 
891
905
  // src/server/store/artifact-store.ts
892
- import { eq as eq3, and as and2, asc, inArray } from "drizzle-orm";
906
+ import { eq as eq3, and as and2, asc, inArray as inArray2 } from "drizzle-orm";
893
907
  var ArtifactDBStore = class {
894
908
  constructor(db) {
895
909
  this.db = db;
@@ -943,7 +957,7 @@ var ArtifactDBStore = class {
943
957
  const rows = await this.db.selectDistinct({ jobId: jobArtifacts.jobId }).from(jobArtifacts).where(
944
958
  and2(
945
959
  eq3(jobArtifacts.tenantId, tenantId),
946
- inArray(jobArtifacts.jobId, jobIds)
960
+ inArray2(jobArtifacts.jobId, jobIds)
947
961
  )
948
962
  );
949
963
  return new Set(rows.map((r) => r.jobId));
@@ -968,7 +982,7 @@ var ArtifactDBStore = class {
968
982
  const toDelete = all.slice(0, excess);
969
983
  const ids = toDelete.map((r) => r.id);
970
984
  const paths = toDelete.map((r) => r.storagePath);
971
- await this.db.delete(jobArtifacts).where(inArray(jobArtifacts.id, ids));
985
+ await this.db.delete(jobArtifacts).where(inArray2(jobArtifacts.id, ids));
972
986
  return { purgedCount: ids.length, purgedPaths: paths };
973
987
  }
974
988
  };
@@ -1030,6 +1044,7 @@ async function loadRunbookForExecution(db, tenantId, runbookId, encryptionKey, v
1030
1044
  if (dbStep.value) action.value = dbStep.value;
1031
1045
  if (dbStep.optionText) action.optionText = dbStep.optionText;
1032
1046
  if (dbStep.script) action.script = dbStep.script;
1047
+ if (dbStep.extractPrompt) action.extractPrompt = dbStep.extractPrompt;
1033
1048
  if (dbStep.keys) action.keys = dbStep.keys;
1034
1049
  if (dbStep.downloadPath) action.downloadPath = dbStep.downloadPath;
1035
1050
  if (dbStep.exportCollection) action.exportCollection = dbStep.exportCollection;
@@ -2402,6 +2417,49 @@ var TelemetryStore = class {
2402
2417
  suggestionOutcomes
2403
2418
  };
2404
2419
  }
2420
+ /**
2421
+ * ジョブレポート生成に必要な全データを一括取得する。
2422
+ * モードに応じて必要なテーブルを並列取得。
2423
+ */
2424
+ async getJobReportData(jobId, mode) {
2425
+ const aiMetricsPromise = this.db.select().from(jobAiMetrics).where(eq7(jobAiMetrics.jobId, jobId)).limit(1);
2426
+ if (mode === "execute" || mode === "self_heal") {
2427
+ const [
2428
+ execDetails,
2429
+ [executionConfig],
2430
+ [telemetrySummary],
2431
+ resolutionOutcomes,
2432
+ failureEvents,
2433
+ [aiMetricsRow2],
2434
+ ...selfHealData
2435
+ ] = await Promise.all([
2436
+ this.getJobExecutionDetails(jobId),
2437
+ this.db.select().from(jobExecutionConfigs).where(eq7(jobExecutionConfigs.jobId, jobId)).limit(1),
2438
+ this.db.select().from(telemetryJobSummaries).where(eq7(telemetryJobSummaries.jobId, jobId)).limit(1),
2439
+ this.db.select().from(telemetryResolutionOutcomes).where(eq7(telemetryResolutionOutcomes.jobId, jobId)).orderBy(telemetryResolutionOutcomes.stepOrdinal),
2440
+ this.db.select().from(telemetryFailureEvents).where(eq7(telemetryFailureEvents.jobId, jobId)).orderBy(telemetryFailureEvents.stepOrdinal),
2441
+ aiMetricsPromise,
2442
+ ...mode === "self_heal" ? [
2443
+ this.db.select().from(jobDebugSuggestions).where(eq7(jobDebugSuggestions.jobId, jobId)),
2444
+ this.db.select().from(jobDebugWarnings).where(eq7(jobDebugWarnings.jobId, jobId))
2445
+ ] : []
2446
+ ]);
2447
+ return {
2448
+ aiMetrics: aiMetricsRow2 ?? null,
2449
+ stepResults: execDetails.stepResults,
2450
+ executionConfig: executionConfig ?? null,
2451
+ telemetrySummary: telemetrySummary ?? null,
2452
+ resolutionOutcomes,
2453
+ failureEvents,
2454
+ debugSuggestions: mode === "self_heal" ? selfHealData[0] ?? [] : void 0,
2455
+ debugWarnings: mode === "self_heal" ? selfHealData[1] ?? [] : void 0
2456
+ };
2457
+ }
2458
+ const [aiMetricsRow] = await aiMetricsPromise;
2459
+ return {
2460
+ aiMetrics: aiMetricsRow ?? null
2461
+ };
2462
+ }
2405
2463
  /** ジョブ実行詳細(jobResults + diagnostics + aiMetrics)を取得 */
2406
2464
  async getJobExecutionDetails(jobId) {
2407
2465
  const results = await this.db.select().from(jobResults).where(eq7(jobResults.jobId, jobId)).orderBy(jobResults.ordinal);
@@ -3399,6 +3457,7 @@ var generateJob = inngest.createFunction(
3399
3457
  stealth: gc?.stealth,
3400
3458
  proxy: gc?.proxy,
3401
3459
  skills: gc?.skills ?? resolvedConfig.skills,
3460
+ forceReport: false,
3402
3461
  aiModelConfig: resolvedConfig.aiConfig
3403
3462
  };
3404
3463
  const browser = new AgentBrowser();
@@ -3431,7 +3490,12 @@ var generateJob = inngest.createFunction(
3431
3490
  onExplorerStep: async (stepNumber, description, recordedStep) => {
3432
3491
  await emitter.emit({
3433
3492
  type: "explorer_step",
3434
- data: { stepNumber, description, actionType: recordedStep.action.action }
3493
+ data: {
3494
+ stepNumber,
3495
+ ordinal: recordedStep.ordinal,
3496
+ description,
3497
+ actionType: recordedStep.action.action
3498
+ }
3435
3499
  });
3436
3500
  await jobStore.updateStatus(jobId, "exploring", { currentStep: stepNumber });
3437
3501
  await jobStore.saveRecordedSteps(jobId, [{
@@ -3551,9 +3615,17 @@ var generateJob = inngest.createFunction(
3551
3615
  });
3552
3616
  let yamlGenerated = false;
3553
3617
  await step.run("save-output", async () => {
3618
+ let existingTitle;
3619
+ if (jobInfo.runbookId) {
3620
+ const { RunbookStore: RS } = await import("./runbook-store-S24PXIHD.js");
3621
+ const rs = new RS(db);
3622
+ const existingRb = await rs.findById(tenantId, jobInfo.runbookId);
3623
+ existingTitle = existingRb?.title;
3624
+ }
3554
3625
  let yamlContent;
3555
3626
  try {
3556
3627
  yamlContent = buildRunbookYaml({
3628
+ title: existingTitle,
3557
3629
  goal: jobInfo.goal,
3558
3630
  startUrl: jobInfo.startUrl,
3559
3631
  recordedSteps: exploreResult.recordedSteps,
@@ -3582,8 +3654,8 @@ var generateJob = inngest.createFunction(
3582
3654
  });
3583
3655
  if (jobInfo.runbookId && yamlContent) {
3584
3656
  const { parse: yamlParse } = await import("yaml");
3585
- const { ParsedRunbookSchema: ParsedRunbookSchema2 } = await import("./runbook-schema-3T6TP3JJ.js");
3586
- const { RunbookStore: RunbookStore2 } = await import("./runbook-store-G5GUOWRR.js");
3657
+ const { ParsedRunbookSchema: ParsedRunbookSchema2 } = await import("./runbook-schema-X7DW725O.js");
3658
+ const { RunbookStore: RunbookStore2 } = await import("./runbook-store-S24PXIHD.js");
3587
3659
  const parsed = ParsedRunbookSchema2.safeParse(yamlParse(yamlContent));
3588
3660
  if (parsed.success) {
3589
3661
  const runbookStore = new RunbookStore2(db);
@@ -3821,9 +3893,9 @@ var schedulePoller = inngest.createFunction(
3821
3893
  continue;
3822
3894
  }
3823
3895
  const activeVersionId = rb.activeVersionId;
3824
- const { runbookSteps: runbookSteps2 } = await import("./schema-5G6UQSPT.js");
3825
- const { count: count5, isNull: isNull4 } = await import("drizzle-orm");
3826
- const [stepCountResult] = await db.select({ cnt: count5() }).from(runbookSteps2).where(
3896
+ const { runbookSteps: runbookSteps2 } = await import("./schema-XFSD5EWN.js");
3897
+ const { count: count7, isNull: isNull4 } = await import("drizzle-orm");
3898
+ const [stepCountResult] = await db.select({ cnt: count7() }).from(runbookSteps2).where(
3827
3899
  and6(
3828
3900
  eq11(runbookSteps2.versionId, activeVersionId),
3829
3901
  isNull4(runbookSteps2.parentStepId)
@@ -4375,6 +4447,23 @@ function createRunbookHandlers(store, jobStore, inngest2, encryptionKey, schedul
4375
4447
  return c.json({ error: "Not found" }, 404);
4376
4448
  }
4377
4449
  const totalSteps = rb.steps.filter((s) => !s.parentStepId).length;
4450
+ const body = await c.req.json().catch(() => ({}));
4451
+ const storedVars = rb.variables.filter((v) => v.value != null).map((v) => ({
4452
+ key: v.name,
4453
+ value: v.value,
4454
+ sensitive: v.sensitive
4455
+ }));
4456
+ if (body.variables) {
4457
+ for (const [key, value] of Object.entries(body.variables)) {
4458
+ const existing = storedVars.find((v) => v.key === key);
4459
+ const varDef = rb.variables.find((v) => v.name === key);
4460
+ if (existing) {
4461
+ existing.value = value;
4462
+ } else {
4463
+ storedVars.push({ key, value, sensitive: varDef?.sensitive ?? false });
4464
+ }
4465
+ }
4466
+ }
4378
4467
  if (totalSteps === 0) {
4379
4468
  if (!rb.startUrl || !rb.goal) {
4380
4469
  return c.json({ error: "startUrl and goal are required for generation" }, 400);
@@ -4388,7 +4477,8 @@ function createRunbookHandlers(store, jobStore, inngest2, encryptionKey, schedul
4388
4477
  createdBy: userId,
4389
4478
  startUrl: rb.startUrl,
4390
4479
  goal: rb.goal,
4391
- context: rb.context ?? ""
4480
+ context: rb.context ?? "",
4481
+ variables: storedVars.length > 0 ? storedVars : void 0
4392
4482
  });
4393
4483
  await store.setVerificationJob(tenantId, id, job2.id);
4394
4484
  await inngest2.send({
@@ -4404,6 +4494,7 @@ function createRunbookHandlers(store, jobStore, inngest2, encryptionKey, schedul
4404
4494
  totalSteps,
4405
4495
  purpose: "verification",
4406
4496
  createdBy: userId,
4497
+ variables: storedVars.length > 0 ? storedVars : void 0,
4407
4498
  executionConfig: buildExecutionConfig(rb.settings)
4408
4499
  });
4409
4500
  await store.setVerificationJob(tenantId, id, job.id);
@@ -4454,6 +4545,11 @@ function createRunbookHandlers(store, jobStore, inngest2, encryptionKey, schedul
4454
4545
  if (db && rb.currentVersionId) {
4455
4546
  await db.delete(runbookSteps).where(eq13(runbookSteps.versionId, rb.currentVersionId));
4456
4547
  }
4548
+ const regenStoredVars = rb.variables.filter((v) => v.value != null).map((v) => ({
4549
+ key: v.name,
4550
+ value: v.value,
4551
+ sensitive: v.sensitive
4552
+ }));
4457
4553
  const job = await jobStore.insert({
4458
4554
  tenantId,
4459
4555
  mode: "generate",
@@ -4463,7 +4559,8 @@ function createRunbookHandlers(store, jobStore, inngest2, encryptionKey, schedul
4463
4559
  createdBy: userId,
4464
4560
  startUrl: rb.startUrl,
4465
4561
  goal: rb.goal,
4466
- context: rb.context ?? void 0
4562
+ context: rb.context ?? void 0,
4563
+ variables: regenStoredVars.length > 0 ? regenStoredVars : void 0
4467
4564
  });
4468
4565
  await store.setVerificationJob(tenantId, id, job.id);
4469
4566
  await inngest2.send({
@@ -4773,6 +4870,23 @@ function createRunbookHandlers(store, jobStore, inngest2, encryptionKey, schedul
4773
4870
  return c.json({ error: `Cannot verify: version is ${version.status}` }, 400);
4774
4871
  }
4775
4872
  const totalSteps = version.steps.filter((s) => !s.parentStepId).length;
4873
+ const vBody = await c.req.json().catch(() => ({}));
4874
+ const vStoredVars = version.variables.filter((v) => v.value != null).map((v) => ({
4875
+ key: v.name,
4876
+ value: v.value,
4877
+ sensitive: v.sensitive
4878
+ }));
4879
+ if (vBody.variables) {
4880
+ for (const [key, value] of Object.entries(vBody.variables)) {
4881
+ const existing = vStoredVars.find((v) => v.key === key);
4882
+ const varDef = version.variables.find((v) => v.name === key);
4883
+ if (existing) {
4884
+ existing.value = value;
4885
+ } else {
4886
+ vStoredVars.push({ key, value, sensitive: varDef?.sensitive ?? false });
4887
+ }
4888
+ }
4889
+ }
4776
4890
  if (totalSteps === 0) {
4777
4891
  if (!version.startUrl || !version.goal) {
4778
4892
  return c.json({ error: "startUrl and goal are required for generation" }, 400);
@@ -4786,7 +4900,8 @@ function createRunbookHandlers(store, jobStore, inngest2, encryptionKey, schedul
4786
4900
  createdBy: userId,
4787
4901
  startUrl: version.startUrl,
4788
4902
  goal: version.goal,
4789
- context: version.context ?? ""
4903
+ context: version.context ?? "",
4904
+ variables: vStoredVars.length > 0 ? vStoredVars : void 0
4790
4905
  });
4791
4906
  await store.versions.setVerificationJob(vid, job2.id);
4792
4907
  await inngest2.send({
@@ -4802,6 +4917,7 @@ function createRunbookHandlers(store, jobStore, inngest2, encryptionKey, schedul
4802
4917
  totalSteps,
4803
4918
  purpose: "verification",
4804
4919
  createdBy: userId,
4920
+ variables: vStoredVars.length > 0 ? vStoredVars : void 0,
4805
4921
  executionConfig: buildExecutionConfig(version.settings)
4806
4922
  });
4807
4923
  await store.versions.setVerificationJob(vid, job.id);
@@ -4839,6 +4955,11 @@ function createRunbookHandlers(store, jobStore, inngest2, encryptionKey, schedul
4839
4955
  if (db) {
4840
4956
  await db.delete(runbookSteps).where(eq13(runbookSteps.versionId, vid));
4841
4957
  }
4958
+ const vRegenStoredVars = version.variables.filter((v) => v.value != null).map((v) => ({
4959
+ key: v.name,
4960
+ value: v.value,
4961
+ sensitive: v.sensitive
4962
+ }));
4842
4963
  const job = await jobStore.insert({
4843
4964
  tenantId,
4844
4965
  mode: "generate",
@@ -4848,7 +4969,8 @@ function createRunbookHandlers(store, jobStore, inngest2, encryptionKey, schedul
4848
4969
  createdBy: userId,
4849
4970
  startUrl: version.startUrl,
4850
4971
  goal: version.goal,
4851
- context: version.context ?? void 0
4972
+ context: version.context ?? void 0,
4973
+ variables: vRegenStoredVars.length > 0 ? vRegenStoredVars : void 0
4852
4974
  });
4853
4975
  await store.versions.setVerificationJob(vid, job.id);
4854
4976
  await inngest2.send({
@@ -4894,7 +5016,7 @@ function createRunbookHandlers(store, jobStore, inngest2, encryptionKey, schedul
4894
5016
  return c.json({ error: "No pending review found" }, 404);
4895
5017
  }
4896
5018
  if (body.status === "approved" && userId === pendingReview.requestedBy && db) {
4897
- const { tenantDevelopmentSettings } = await import("./schema-5G6UQSPT.js");
5019
+ const { tenantDevelopmentSettings } = await import("./schema-XFSD5EWN.js");
4898
5020
  const devSettings = await db.query.tenantDevelopmentSettings.findFirst({
4899
5021
  where: eq13(tenantDevelopmentSettings.tenantId, tenantId)
4900
5022
  });
@@ -4915,7 +5037,7 @@ function createRunbookHandlers(store, jobStore, inngest2, encryptionKey, schedul
4915
5037
  const id = c.req.param("id");
4916
5038
  const vid = c.req.param("vid");
4917
5039
  if (plan.features.runbookApprovalWorkflow && db) {
4918
- const { tenantDevelopmentSettings } = await import("./schema-5G6UQSPT.js");
5040
+ const { tenantDevelopmentSettings } = await import("./schema-XFSD5EWN.js");
4919
5041
  const devSettings = await db.query.tenantDevelopmentSettings.findFirst({
4920
5042
  where: eq13(tenantDevelopmentSettings.tenantId, tenantId)
4921
5043
  });
@@ -5174,6 +5296,7 @@ var TERMINAL_EVENT_TYPES = /* @__PURE__ */ new Set([
5174
5296
  var KEEPALIVE_INTERVAL_MS = 3e4;
5175
5297
  function handleJobStream(c, jobStore, tenantId, jobId, redisUrl, db) {
5176
5298
  const channel = `job:${jobId}:events`;
5299
+ let cleanupRef = null;
5177
5300
  const stream = new ReadableStream({
5178
5301
  async start(controller) {
5179
5302
  const encoder = new TextEncoder();
@@ -5255,6 +5378,7 @@ function handleJobStream(c, jobStore, tenantId, jobId, redisUrl, db) {
5255
5378
  } catch {
5256
5379
  }
5257
5380
  };
5381
+ cleanupRef = cleanup;
5258
5382
  try {
5259
5383
  subscriber = createRedisSubscriber(redisUrl);
5260
5384
  await subscriber.subscribe(channel);
@@ -5283,6 +5407,9 @@ function handleJobStream(c, jobStore, tenantId, jobId, redisUrl, db) {
5283
5407
  } catch {
5284
5408
  cleanup();
5285
5409
  }
5410
+ },
5411
+ cancel() {
5412
+ cleanupRef?.();
5286
5413
  }
5287
5414
  });
5288
5415
  return new Response(stream, {
@@ -5431,7 +5558,7 @@ function createArtifactHandlers(artifactDb, artifactStore) {
5431
5558
  }
5432
5559
 
5433
5560
  // src/server/handlers/jobs.ts
5434
- import { and as and10, eq as eq16, inArray as inArray2 } from "drizzle-orm";
5561
+ import { and as and10, eq as eq16, inArray as inArray3 } from "drizzle-orm";
5435
5562
  var validStatuses = /* @__PURE__ */ new Set([
5436
5563
  "scheduled",
5437
5564
  "queued",
@@ -5493,6 +5620,7 @@ function createJobHandlers(store, runbookStore, inngest2, redisUrl, db, config)
5493
5620
  const tenantId = c.get("tenantId");
5494
5621
  const runbookId = c.req.query("runbookId");
5495
5622
  const statusParam = c.req.query("status");
5623
+ const statusGroupParam = c.req.query("statusGroup");
5496
5624
  const modeParam = c.req.query("mode");
5497
5625
  const limit = Number(c.req.query("limit") ?? "50");
5498
5626
  const offset = Number(c.req.query("offset") ?? "0");
@@ -5503,6 +5631,15 @@ function createJobHandlers(store, runbookStore, inngest2, redisUrl, db, config)
5503
5631
  }
5504
5632
  status = statusParam;
5505
5633
  }
5634
+ const statusGroupMap = {
5635
+ running: ["scheduled", "running", "waiting_approval", "exploring", "reviewing", "queued"],
5636
+ completed: ["completed"],
5637
+ failed: ["failed"]
5638
+ };
5639
+ const statuses = statusGroupParam ? statusGroupMap[statusGroupParam] : void 0;
5640
+ if (statusGroupParam && !statuses) {
5641
+ return c.json({ error: `Invalid statusGroup: ${statusGroupParam}` }, 400);
5642
+ }
5506
5643
  let mode;
5507
5644
  if (modeParam) {
5508
5645
  if (!validModes.has(modeParam)) {
@@ -5513,6 +5650,7 @@ function createJobHandlers(store, runbookStore, inngest2, redisUrl, db, config)
5513
5650
  const result = await store.list(tenantId, {
5514
5651
  runbookId,
5515
5652
  status,
5653
+ statuses,
5516
5654
  mode,
5517
5655
  limit,
5518
5656
  offset
@@ -5520,13 +5658,13 @@ function createJobHandlers(store, runbookStore, inngest2, redisUrl, db, config)
5520
5658
  const runbookIds = [...new Set(result.items.filter((r) => r.runbookId).map((r) => r.runbookId))];
5521
5659
  let titleMap = /* @__PURE__ */ new Map();
5522
5660
  if (runbookIds.length > 0 && db) {
5523
- const rbRows = await db.select({ id: runbooks.id, title: runbooks.title }).from(runbooks).where(inArray2(runbooks.id, runbookIds));
5661
+ const rbRows = await db.select({ id: runbooks.id, title: runbooks.title }).from(runbooks).where(inArray3(runbooks.id, runbookIds));
5524
5662
  titleMap = new Map(rbRows.map((r) => [r.id, r.title]));
5525
5663
  }
5526
5664
  const versionIds = [...new Set(result.items.filter((r) => r.runbookVersionId).map((r) => r.runbookVersionId))];
5527
5665
  let versionMap = /* @__PURE__ */ new Map();
5528
5666
  if (versionIds.length > 0 && db) {
5529
- const vRows = await db.select({ id: runbookVersions.id, versionNumber: runbookVersions.versionNumber }).from(runbookVersions).where(inArray2(runbookVersions.id, versionIds));
5667
+ const vRows = await db.select({ id: runbookVersions.id, versionNumber: runbookVersions.versionNumber }).from(runbookVersions).where(inArray3(runbookVersions.id, versionIds));
5530
5668
  versionMap = new Map(vRows.map((r) => [r.id, r.versionNumber]));
5531
5669
  }
5532
5670
  const jobIds = result.items.map((r) => r.id);
@@ -5739,14 +5877,17 @@ function createJobHandlers(store, runbookStore, inngest2, redisUrl, db, config)
5739
5877
  return c.json({ error: "Job must be completed or failed to apply fix" }, 400);
5740
5878
  }
5741
5879
  const body = await c.req.json();
5742
- if (typeof body.suggestionIndex !== "number") {
5743
- return c.json({ error: "suggestionIndex (number) is required" }, 400);
5880
+ const indices = body.suggestionIndices ?? (typeof body.suggestionIndex === "number" ? [body.suggestionIndex] : null);
5881
+ if (!indices || indices.length === 0) {
5882
+ return c.json({ error: "suggestionIndex or suggestionIndices is required" }, 400);
5744
5883
  }
5745
5884
  const suggestions = row.debugSuggestions ?? [];
5746
- if (body.suggestionIndex < 0 || body.suggestionIndex >= suggestions.length) {
5747
- return c.json({ error: "Invalid suggestionIndex" }, 400);
5885
+ for (const idx of indices) {
5886
+ if (idx < 0 || idx >= suggestions.length) {
5887
+ return c.json({ error: `Invalid suggestionIndex: ${idx}` }, 400);
5888
+ }
5748
5889
  }
5749
- const suggestion = suggestions[body.suggestionIndex];
5890
+ const suggestion = suggestions[indices[0]];
5750
5891
  if (!row.runbookId) {
5751
5892
  return c.json({ error: "Job has no associated runbook" }, 400);
5752
5893
  }
@@ -5756,31 +5897,40 @@ function createJobHandlers(store, runbookStore, inngest2, redisUrl, db, config)
5756
5897
  }
5757
5898
  try {
5758
5899
  const { stringify: yamlStringify2 } = await import("yaml");
5759
- const { generatePatchCore } = await import("./yaml-patcher-VGUS2JGH.js");
5760
- const { runWithModelContext } = await import("./ai-model-FM6GWCID.js");
5761
- const { resolveTenantAIConfig: resolveTenantAIConfig2, buildModelFactory: buildModelFactory2 } = await import("./tenant-ai-config-QPFEJUVJ.js");
5900
+ const { generatePatchCore } = await import("./yaml-patcher-32QBPXT2.js");
5901
+ const { runWithModelContext } = await import("./ai-model-DP5PKGM6.js");
5902
+ const { resolveTenantAIConfig: resolveTenantAIConfig2, buildModelFactory: buildModelFactory2 } = await import("./tenant-ai-config-4NHKRW7O.js");
5762
5903
  const { loadServerConfig: loadServerConfig2 } = await import("./config-ZSUNCFXR.js");
5763
5904
  const serverConfig = loadServerConfig2();
5764
5905
  const parsedRb = rebuildParsedRunbook(rb);
5765
5906
  const originalYaml = yamlStringify2(parsedRb);
5766
5907
  const aiConfig = await resolveTenantAIConfig2(db, tenantId, serverConfig.encryptionKey);
5767
5908
  const modelFactory = await buildModelFactory2(aiConfig);
5768
- const patchResult = await runWithModelContext(
5909
+ const { generatePatchPipeline } = await import("./yaml-patcher-32QBPXT2.js");
5910
+ const extractedSuggestions = indices.map((idx) => {
5911
+ const s = suggestions[idx];
5912
+ return {
5913
+ stepOrdinal: s.stepOrdinal ?? 0,
5914
+ description: s.stepDescription ?? "",
5915
+ error: s.error ?? "",
5916
+ yamlFix: s.runbookFix ?? void 0,
5917
+ category: s.failureCategory ?? void 0
5918
+ };
5919
+ });
5920
+ const patchResult = indices.length === 1 ? await runWithModelContext(
5769
5921
  aiConfig,
5770
5922
  modelFactory,
5771
- () => generatePatchCore(originalYaml, {
5772
- stepOrdinal: suggestion.stepOrdinal ?? 0,
5773
- description: suggestion.stepDescription ?? "",
5774
- error: suggestion.error ?? "",
5775
- yamlFix: suggestion.runbookFix ?? void 0,
5776
- category: suggestion.failureCategory ?? void 0
5777
- })
5923
+ () => generatePatchCore(originalYaml, extractedSuggestions[0], void 0, body.additionalPrompt)
5924
+ ) : await runWithModelContext(
5925
+ aiConfig,
5926
+ modelFactory,
5927
+ () => generatePatchPipeline(originalYaml, extractedSuggestions, void 0, body.additionalPrompt)
5778
5928
  );
5779
5929
  if (!patchResult.success) {
5780
5930
  return c.json({ error: `Patch generation failed: ${patchResult.error}` }, 500);
5781
5931
  }
5782
5932
  const { parse: yamlParse } = await import("yaml");
5783
- const { ParsedRunbookSchema: ParsedRunbookSchema2 } = await import("./runbook-schema-3T6TP3JJ.js");
5933
+ const { ParsedRunbookSchema: ParsedRunbookSchema2 } = await import("./runbook-schema-X7DW725O.js");
5784
5934
  const patchedDoc = yamlParse(patchResult.patchedYaml);
5785
5935
  const validated = ParsedRunbookSchema2.parse(patchedDoc);
5786
5936
  const existingVariables = Object.fromEntries(
@@ -5792,6 +5942,7 @@ function createJobHandlers(store, runbookStore, inngest2, redisUrl, db, config)
5792
5942
  value: v.value ?? void 0
5793
5943
  }])
5794
5944
  );
5945
+ validated.title = rb.title;
5795
5946
  await runbookStore.update(tenantId, row.runbookId, {
5796
5947
  runbook: validated,
5797
5948
  variables: existingVariables
@@ -5803,6 +5954,451 @@ function createJobHandlers(store, runbookStore, inngest2, redisUrl, db, config)
5803
5954
  }, 500);
5804
5955
  }
5805
5956
  });
5957
+ app.get("/:id/review-detail", async (c) => {
5958
+ const tenantId = c.get("tenantId");
5959
+ const id = c.req.param("id");
5960
+ const row = await store.findById(tenantId, id);
5961
+ if (!row) {
5962
+ return c.json({ error: "Not found" }, 404);
5963
+ }
5964
+ if (row.mode !== "generate") {
5965
+ return c.json({ error: "review-detail is only available for generate jobs" }, 400);
5966
+ }
5967
+ const reviewResultRow = row.reviewResult;
5968
+ if (!reviewResultRow) {
5969
+ return c.json({ error: "No review result found" }, 404);
5970
+ }
5971
+ const { jobReviewedSteps: jobReviewedSteps2 } = await import("./schema-XFSD5EWN.js");
5972
+ const { eq: eq25 } = await import("drizzle-orm");
5973
+ const reviewedSteps = db ? await db.select().from(jobReviewedSteps2).where(eq25(jobReviewedSteps2.reviewResultId, reviewResultRow.id)) : [];
5974
+ const recorded = row.recordedSteps ?? [];
5975
+ const recordedMap = new Map(recorded.map((s) => [s.ordinal, s]));
5976
+ const items = reviewedSteps.map((rs) => {
5977
+ const original = recordedMap.get(rs.originalOrdinal);
5978
+ return {
5979
+ originalOrdinal: rs.originalOrdinal,
5980
+ keep: rs.keep,
5981
+ removalReason: rs.removalReason,
5982
+ riskLevel: rs.riskLevel ?? "low",
5983
+ requiresConfirmation: rs.requiresConfirmation ?? false,
5984
+ confirmationReason: rs.confirmationReason,
5985
+ originalStep: {
5986
+ description: original?.description ?? "",
5987
+ actionType: original?.actionType ?? "click",
5988
+ url: original?.url ?? void 0
5989
+ }
5990
+ };
5991
+ });
5992
+ return c.json({ items });
5993
+ });
5994
+ app.post("/:id/apply-review", requireRole("developer"), async (c) => {
5995
+ const tenantId = c.get("tenantId");
5996
+ const id = c.req.param("id");
5997
+ const row = await store.findById(tenantId, id);
5998
+ if (!row) {
5999
+ return c.json({ error: "Not found" }, 404);
6000
+ }
6001
+ if (row.mode !== "generate") {
6002
+ return c.json({ error: "apply-review is only available for generate jobs" }, 400);
6003
+ }
6004
+ if (!row.runbookId) {
6005
+ return c.json({ error: "Job has no associated runbook" }, 400);
6006
+ }
6007
+ const body = await c.req.json();
6008
+ if (!body.decisions || body.decisions.length === 0) {
6009
+ return c.json({ error: "decisions array is required" }, 400);
6010
+ }
6011
+ try {
6012
+ const { jobReviewedSteps: jobReviewedSteps2, jobReviewResults: jobReviewResults2 } = await import("./schema-XFSD5EWN.js");
6013
+ const { eq: eq25 } = await import("drizzle-orm");
6014
+ const reviewResultRow = row.reviewResult;
6015
+ if (!reviewResultRow || !db) {
6016
+ return c.json({ error: "No review result found" }, 404);
6017
+ }
6018
+ await db.delete(jobReviewedSteps2).where(eq25(jobReviewedSteps2.reviewResultId, reviewResultRow.id));
6019
+ await db.insert(jobReviewedSteps2).values(
6020
+ body.decisions.map((d) => ({
6021
+ reviewResultId: reviewResultRow.id,
6022
+ originalOrdinal: d.originalOrdinal,
6023
+ keep: d.keep,
6024
+ riskLevel: d.riskLevel,
6025
+ requiresConfirmation: d.requiresConfirmation
6026
+ }))
6027
+ );
6028
+ const rb = await runbookStore.findById(tenantId, row.runbookId);
6029
+ if (!rb) {
6030
+ return c.json({ error: "Associated runbook not found" }, 404);
6031
+ }
6032
+ const { buildRunbookYaml: buildRunbookYaml2 } = await import("./runbook-builder-2ZLE2AEO.js");
6033
+ const dbRows = row.recordedSteps ?? [];
6034
+ const recorded = dbRows.map((r) => ({
6035
+ ordinal: r.ordinal,
6036
+ action: {
6037
+ action: r.actionType,
6038
+ selector: r.selector ?? void 0,
6039
+ value: r.value ?? void 0,
6040
+ description: r.description ?? "",
6041
+ inputCategory: r.inputCategory,
6042
+ variableName: r.variableName ?? void 0,
6043
+ suggestedCaptures: r.suggestedCaptures,
6044
+ script: r.script ?? void 0,
6045
+ extractPrompt: r.extractPrompt ?? void 0,
6046
+ memoryCollection: r.memoryCollection ?? void 0,
6047
+ aggregation: r.aggregation,
6048
+ downloadPath: r.downloadPath ?? void 0,
6049
+ exportCollection: r.exportCollection ?? void 0,
6050
+ exportFormat: r.exportFormat,
6051
+ exportPath: r.exportPath ?? void 0,
6052
+ keys: r.keys ?? void 0
6053
+ },
6054
+ snapshotBefore: r.snapshotBefore ?? "",
6055
+ url: r.url ?? "",
6056
+ success: r.success ?? false,
6057
+ error: r.error ?? void 0,
6058
+ screenshotPath: r.screenshotPath ?? void 0,
6059
+ failureCategory: r.failureCategory,
6060
+ durationMs: r.durationMs ?? void 0
6061
+ }));
6062
+ const reviewResult = {
6063
+ reviewedSteps: body.decisions.map((d) => ({
6064
+ originalOrdinal: d.originalOrdinal,
6065
+ keep: d.keep,
6066
+ removalReason: void 0,
6067
+ riskLevel: d.riskLevel,
6068
+ requiresConfirmation: d.requiresConfirmation
6069
+ })),
6070
+ summary: reviewResultRow.summary ?? ""
6071
+ };
6072
+ const yamlContent = buildRunbookYaml2({
6073
+ title: rb.title,
6074
+ goal: row.goal ?? rb.goal,
6075
+ startUrl: row.startUrl ?? rb.startUrl ?? "",
6076
+ recordedSteps: recorded,
6077
+ goalAchieved: true,
6078
+ stepDelay: rb.settings?.pauseBetweenSteps ?? 500,
6079
+ reviewResult,
6080
+ contextMarkdown: row.context ?? rb.context ?? ""
6081
+ });
6082
+ const { parse: yamlParse } = await import("yaml");
6083
+ const { ParsedRunbookSchema: ParsedRunbookSchema2 } = await import("./runbook-schema-X7DW725O.js");
6084
+ const patchedDoc = yamlParse(yamlContent);
6085
+ const validated = ParsedRunbookSchema2.parse(patchedDoc);
6086
+ await runbookStore.versions.updateVersion(tenantId, row.runbookId, row.runbookVersionId, {
6087
+ goal: validated.metadata.goal,
6088
+ startUrl: validated.metadata.startUrl,
6089
+ title: validated.title,
6090
+ steps: validated.steps,
6091
+ skipVerificationJobClear: true
6092
+ });
6093
+ return c.json({ success: true });
6094
+ } catch (error) {
6095
+ return c.json({
6096
+ error: `Failed to apply review: ${error instanceof Error ? error.message : String(error)}`
6097
+ }, 500);
6098
+ }
6099
+ });
6100
+ app.post("/:id/regenerate-review", requireRole("developer"), async (c) => {
6101
+ const tenantId = c.get("tenantId");
6102
+ const id = c.req.param("id");
6103
+ const row = await store.findById(tenantId, id);
6104
+ if (!row) {
6105
+ return c.json({ error: "Not found" }, 404);
6106
+ }
6107
+ if (row.mode !== "generate") {
6108
+ return c.json({ error: "regenerate-review is only available for generate jobs" }, 400);
6109
+ }
6110
+ const body = await c.req.json();
6111
+ if (!body.additionalPrompt) {
6112
+ return c.json({ error: "additionalPrompt is required" }, 400);
6113
+ }
6114
+ try {
6115
+ const { createReviewPrompt: createReviewPrompt2, reviewResponseSchema: reviewResponseSchema2 } = await import("./prompts-XMJXIITW.js");
6116
+ const { runWithModelContext, trackedGenerateObject: trackedGenerateObject2, getModel: getModel2 } = await import("./ai-model-DP5PKGM6.js");
6117
+ const { resolveTenantAIConfig: resolveTenantAIConfig2, buildModelFactory: buildModelFactory2 } = await import("./tenant-ai-config-4NHKRW7O.js");
6118
+ const { loadServerConfig: loadServerConfig2 } = await import("./config-ZSUNCFXR.js");
6119
+ const serverConfig = loadServerConfig2();
6120
+ const aiConfig = await resolveTenantAIConfig2(db, tenantId, serverConfig.encryptionKey);
6121
+ const modelFactory = await buildModelFactory2(aiConfig);
6122
+ const dbRows2 = row.recordedSteps ?? [];
6123
+ const recorded = dbRows2.map((r) => ({
6124
+ ordinal: r.ordinal,
6125
+ action: {
6126
+ action: r.actionType,
6127
+ selector: r.selector ?? void 0,
6128
+ value: r.value ?? void 0,
6129
+ description: r.description ?? "",
6130
+ inputCategory: r.inputCategory,
6131
+ variableName: r.variableName ?? void 0,
6132
+ suggestedCaptures: r.suggestedCaptures,
6133
+ script: r.script ?? void 0,
6134
+ extractPrompt: r.extractPrompt ?? void 0,
6135
+ memoryCollection: r.memoryCollection ?? void 0,
6136
+ aggregation: r.aggregation,
6137
+ downloadPath: r.downloadPath ?? void 0,
6138
+ exportCollection: r.exportCollection ?? void 0,
6139
+ exportFormat: r.exportFormat,
6140
+ exportPath: r.exportPath ?? void 0,
6141
+ keys: r.keys ?? void 0
6142
+ },
6143
+ snapshotBefore: r.snapshotBefore ?? "",
6144
+ url: r.url ?? "",
6145
+ success: r.success ?? false,
6146
+ error: r.error ?? void 0,
6147
+ screenshotPath: r.screenshotPath ?? void 0,
6148
+ failureCategory: r.failureCategory,
6149
+ durationMs: r.durationMs ?? void 0
6150
+ }));
6151
+ const { system, userPrompt } = createReviewPrompt2(
6152
+ row.goal ?? "",
6153
+ recorded,
6154
+ true
6155
+ );
6156
+ const enrichedPrompt = `${userPrompt}
6157
+
6158
+ ## Additional Instructions from User
6159
+ ${body.additionalPrompt}`;
6160
+ const result = await runWithModelContext(aiConfig, modelFactory, async () => {
6161
+ const { object } = await trackedGenerateObject2("review", {
6162
+ model: getModel2("review"),
6163
+ schema: reviewResponseSchema2,
6164
+ messages: [
6165
+ {
6166
+ role: "system",
6167
+ content: system,
6168
+ providerOptions: {
6169
+ anthropic: { cacheControl: { type: "ephemeral" } }
6170
+ }
6171
+ },
6172
+ { role: "user", content: enrichedPrompt }
6173
+ ],
6174
+ temperature: 0
6175
+ });
6176
+ return object;
6177
+ });
6178
+ const parsedResult = reviewResponseSchema2.parse(result);
6179
+ const reviewResultRow = row.reviewResult;
6180
+ if (reviewResultRow && db) {
6181
+ const { jobReviewedSteps: jobReviewedSteps2 } = await import("./schema-XFSD5EWN.js");
6182
+ const { eq: eq25 } = await import("drizzle-orm");
6183
+ await db.delete(jobReviewedSteps2).where(eq25(jobReviewedSteps2.reviewResultId, reviewResultRow.id));
6184
+ await db.insert(jobReviewedSteps2).values(
6185
+ parsedResult.reviewedSteps.map((s) => ({
6186
+ reviewResultId: reviewResultRow.id,
6187
+ originalOrdinal: s.originalOrdinal,
6188
+ keep: s.keep,
6189
+ removalReason: s.removalReason ?? null,
6190
+ riskLevel: s.riskLevel,
6191
+ requiresConfirmation: s.requiresConfirmation,
6192
+ confirmationReason: s.confirmationReason ?? null
6193
+ }))
6194
+ );
6195
+ }
6196
+ const typedRecorded = recorded;
6197
+ const recordedMap = new Map(typedRecorded.map((s) => [s.ordinal, s]));
6198
+ const items = parsedResult.reviewedSteps.map((rs) => {
6199
+ const original = recordedMap.get(rs.originalOrdinal);
6200
+ return {
6201
+ ...rs,
6202
+ originalStep: {
6203
+ description: original?.description ?? "",
6204
+ actionType: original?.actionType ?? "click",
6205
+ url: original?.url ?? void 0
6206
+ }
6207
+ };
6208
+ });
6209
+ return c.json({ items });
6210
+ } catch (error) {
6211
+ return c.json({
6212
+ error: `Failed to regenerate review: ${error instanceof Error ? error.message : String(error)}`
6213
+ }, 500);
6214
+ }
6215
+ });
6216
+ app.post("/:id/preview-fix", requireRole("developer"), async (c) => {
6217
+ const tenantId = c.get("tenantId");
6218
+ const id = c.req.param("id");
6219
+ const row = await store.findById(tenantId, id);
6220
+ if (!row) {
6221
+ return c.json({ error: "Not found" }, 404);
6222
+ }
6223
+ if (row.mode !== "self_heal") {
6224
+ return c.json({ error: "preview-fix is only available for self_heal jobs" }, 400);
6225
+ }
6226
+ if (!row.runbookId) {
6227
+ return c.json({ error: "Job has no associated runbook" }, 400);
6228
+ }
6229
+ const body = await c.req.json();
6230
+ const suggestions = row.debugSuggestions ?? [];
6231
+ for (const idx of body.suggestionIndices) {
6232
+ if (idx < 0 || idx >= suggestions.length) {
6233
+ return c.json({ error: `Invalid suggestionIndex: ${idx}` }, 400);
6234
+ }
6235
+ }
6236
+ try {
6237
+ const rb = await runbookStore.findById(tenantId, row.runbookId);
6238
+ if (!rb) {
6239
+ return c.json({ error: "Associated runbook not found" }, 404);
6240
+ }
6241
+ const { stringify: yamlStringify2, parse: yamlParse } = await import("yaml");
6242
+ const { generatePatchCore, generatePatchPipeline } = await import("./yaml-patcher-32QBPXT2.js");
6243
+ const { runWithModelContext } = await import("./ai-model-DP5PKGM6.js");
6244
+ const { resolveTenantAIConfig: resolveTenantAIConfig2, buildModelFactory: buildModelFactory2 } = await import("./tenant-ai-config-4NHKRW7O.js");
6245
+ const { loadServerConfig: loadServerConfig2 } = await import("./config-ZSUNCFXR.js");
6246
+ const serverConfig = loadServerConfig2();
6247
+ const parsedRb = rebuildParsedRunbook(rb);
6248
+ const originalYaml = yamlStringify2(parsedRb);
6249
+ const aiConfig = await resolveTenantAIConfig2(db, tenantId, serverConfig.encryptionKey);
6250
+ const modelFactory = await buildModelFactory2(aiConfig);
6251
+ const extractedSuggestions = body.suggestionIndices.map((idx) => {
6252
+ const s = suggestions[idx];
6253
+ return {
6254
+ stepOrdinal: s.stepOrdinal ?? 0,
6255
+ description: s.stepDescription ?? "",
6256
+ error: s.error ?? "",
6257
+ yamlFix: s.runbookFix ?? void 0,
6258
+ category: s.failureCategory ?? void 0
6259
+ };
6260
+ });
6261
+ const patchResult = extractedSuggestions.length === 1 ? await runWithModelContext(
6262
+ aiConfig,
6263
+ modelFactory,
6264
+ () => generatePatchCore(originalYaml, extractedSuggestions[0], void 0, body.additionalPrompt)
6265
+ ) : await runWithModelContext(
6266
+ aiConfig,
6267
+ modelFactory,
6268
+ () => generatePatchPipeline(originalYaml, extractedSuggestions, void 0, body.additionalPrompt)
6269
+ );
6270
+ if (!patchResult.success) {
6271
+ return c.json({ error: `Preview generation failed: ${patchResult.error}` }, 500);
6272
+ }
6273
+ const originalDoc = yamlParse(originalYaml);
6274
+ const patchedDoc = yamlParse(patchResult.patchedYaml);
6275
+ const originalSteps = originalDoc.steps ?? [];
6276
+ const patchedSteps = patchedDoc.steps ?? [];
6277
+ const affectedSteps = [];
6278
+ const patchedMap = new Map(patchedSteps.map((s) => [s.ordinal, s]));
6279
+ const originalMap = new Map(originalSteps.map((s) => [s.ordinal, s]));
6280
+ for (const s of originalSteps) {
6281
+ if (!patchedMap.has(s.ordinal)) {
6282
+ affectedSteps.push({ ordinal: s.ordinal, description: s.description, changeType: "removed" });
6283
+ } else {
6284
+ const patched = patchedMap.get(s.ordinal);
6285
+ if (JSON.stringify(s) !== JSON.stringify(patched)) {
6286
+ affectedSteps.push({ ordinal: s.ordinal, description: patched.description, changeType: "modified" });
6287
+ }
6288
+ }
6289
+ }
6290
+ for (const s of patchedSteps) {
6291
+ if (!originalMap.has(s.ordinal)) {
6292
+ affectedSteps.push({ ordinal: s.ordinal, description: s.description, changeType: "added" });
6293
+ }
6294
+ }
6295
+ return c.json({
6296
+ originalYaml,
6297
+ patchedYaml: patchResult.patchedYaml,
6298
+ affectedSteps
6299
+ });
6300
+ } catch (error) {
6301
+ return c.json({
6302
+ error: `Preview failed: ${error instanceof Error ? error.message : String(error)}`
6303
+ }, 500);
6304
+ }
6305
+ });
6306
+ app.post("/:id/regenerate-suggestions", requireRole("developer"), async (c) => {
6307
+ const tenantId = c.get("tenantId");
6308
+ const id = c.req.param("id");
6309
+ const row = await store.findById(tenantId, id);
6310
+ if (!row) {
6311
+ return c.json({ error: "Not found" }, 404);
6312
+ }
6313
+ if (row.mode !== "self_heal") {
6314
+ return c.json({ error: "regenerate-suggestions is only available for self_heal jobs" }, 400);
6315
+ }
6316
+ const body = await c.req.json();
6317
+ if (!body.additionalPrompt) {
6318
+ return c.json({ error: "additionalPrompt is required" }, 400);
6319
+ }
6320
+ try {
6321
+ if (!row.runbookId) {
6322
+ return c.json({ error: "Job has no associated runbook" }, 400);
6323
+ }
6324
+ const rb = await runbookStore.findById(tenantId, row.runbookId);
6325
+ if (!rb) {
6326
+ return c.json({ error: "Associated runbook not found" }, 404);
6327
+ }
6328
+ const { stringify: yamlStringify2 } = await import("yaml");
6329
+ const { runWithModelContext, trackedGenerateText, getModel: getModel2 } = await import("./ai-model-DP5PKGM6.js");
6330
+ const { resolveTenantAIConfig: resolveTenantAIConfig2, buildModelFactory: buildModelFactory2 } = await import("./tenant-ai-config-4NHKRW7O.js");
6331
+ const { loadServerConfig: loadServerConfig2 } = await import("./config-ZSUNCFXR.js");
6332
+ const serverConfig = loadServerConfig2();
6333
+ const aiConfig = await resolveTenantAIConfig2(db, tenantId, serverConfig.encryptionKey);
6334
+ const modelFactory = await buildModelFactory2(aiConfig);
6335
+ const parsedRb = rebuildParsedRunbook(rb);
6336
+ const yamlContent = yamlStringify2(parsedRb);
6337
+ const results = row.results ?? [];
6338
+ const failedSteps = results.filter(
6339
+ (r) => r.status === "failed" && (!body.stepOrdinals || body.stepOrdinals.includes(r.ordinal))
6340
+ );
6341
+ const stepsInfo = failedSteps.map(
6342
+ (r) => `Step #${r.ordinal}: ${r.description ?? ""} \u2014 Error: ${r.error ?? "unknown"}`
6343
+ ).join("\n");
6344
+ const prompt = `You are a browser automation diagnostic AI.
6345
+ Analyze the following failed steps and suggest fixes for the runbook YAML.
6346
+
6347
+ ## Runbook YAML
6348
+ \`\`\`yaml
6349
+ ${yamlContent}
6350
+ \`\`\`
6351
+
6352
+ ## Failed Steps
6353
+ ${stepsInfo}
6354
+
6355
+ ## Additional Instructions
6356
+ ${body.additionalPrompt}
6357
+
6358
+ Respond with a JSON array of suggestions, each with:
6359
+ - stepOrdinal (number)
6360
+ - stepDescription (string)
6361
+ - error (string)
6362
+ - runbookFix (string or null) \u2014 suggested YAML fix description
6363
+ - contextFix (string or null) \u2014 suggested context fix
6364
+ - severity ("error" | "warning")
6365
+ - failureCategory (string or null)`;
6366
+ const result = await runWithModelContext(aiConfig, modelFactory, async () => {
6367
+ const { text } = await trackedGenerateText("review", {
6368
+ model: getModel2("review"),
6369
+ messages: [{ role: "user", content: prompt }],
6370
+ temperature: 0
6371
+ });
6372
+ return text;
6373
+ });
6374
+ const jsonMatch = result.match(/\[[\s\S]*\]/);
6375
+ const newSuggestions = jsonMatch ? JSON.parse(jsonMatch[0]) : [];
6376
+ if (db) {
6377
+ const { jobDebugSuggestions: jobDebugSuggestions2 } = await import("./schema-XFSD5EWN.js");
6378
+ const { eq: eq25 } = await import("drizzle-orm");
6379
+ await db.delete(jobDebugSuggestions2).where(eq25(jobDebugSuggestions2.jobId, id));
6380
+ if (newSuggestions.length > 0) {
6381
+ await db.insert(jobDebugSuggestions2).values(
6382
+ newSuggestions.map((s) => ({
6383
+ jobId: id,
6384
+ stepOrdinal: s.stepOrdinal ?? null,
6385
+ stepDescription: s.stepDescription ?? null,
6386
+ error: s.error ?? null,
6387
+ runbookFix: s.runbookFix ?? null,
6388
+ contextFix: s.contextFix ?? null,
6389
+ severity: s.severity ?? null,
6390
+ failureCategory: s.failureCategory ?? null
6391
+ }))
6392
+ );
6393
+ }
6394
+ }
6395
+ return c.json({ suggestions: newSuggestions });
6396
+ } catch (error) {
6397
+ return c.json({
6398
+ error: `Failed to regenerate suggestions: ${error instanceof Error ? error.message : String(error)}`
6399
+ }, 500);
6400
+ }
6401
+ });
5806
6402
  app.post("/:id/adopt", requireRole("developer"), async (c) => {
5807
6403
  const tenantId = c.get("tenantId");
5808
6404
  const userId = c.get("user")?.id;
@@ -6043,6 +6639,17 @@ function createScheduleHandlers(store, jobStore, db) {
6043
6639
  if (!isValidCron(body.cron)) {
6044
6640
  return c.json({ error: "Invalid cron expression" }, 400);
6045
6641
  }
6642
+ if ((body.purpose ?? "execute") !== "self_heal") {
6643
+ const [rbRow] = await db.select({ activeVersionId: runbooks.activeVersionId }).from(runbooks).where(eq17(runbooks.id, body.runbookId)).limit(1);
6644
+ if (rbRow?.activeVersionId) {
6645
+ const rbVarSources = await db.select({ source: runbookVariables.source }).from(runbookVariables).where(eq17(runbookVariables.versionId, rbRow.activeVersionId));
6646
+ if (rbVarSources.some((v) => v.source === "prompt")) {
6647
+ return c.json({
6648
+ error: tfWithLocale("serverErrors.recurringBlockedByPromptVars", reqLocale(c), {})
6649
+ }, 400);
6650
+ }
6651
+ }
6652
+ }
6046
6653
  }
6047
6654
  if (type === "once") {
6048
6655
  if (!body.scheduledAt) {
@@ -6085,6 +6692,17 @@ function createScheduleHandlers(store, jobStore, db) {
6085
6692
  ]);
6086
6693
  const totalSteps = stepCountResult[0]?.cnt ?? 0;
6087
6694
  const storedVars = rbVars.filter((v) => v.value != null).map((v) => ({ key: v.name, value: v.value, sensitive: v.sensitive }));
6695
+ if (body.variables) {
6696
+ for (const [key, value] of Object.entries(body.variables)) {
6697
+ const existing = storedVars.find((v) => v.key === key);
6698
+ const varDef = rbVars.find((v) => v.name === key);
6699
+ if (existing) {
6700
+ existing.value = value;
6701
+ } else {
6702
+ storedVars.push({ key, value, sensitive: varDef?.sensitive ?? false });
6703
+ }
6704
+ }
6705
+ }
6088
6706
  const job = await jobStore.insert({
6089
6707
  tenantId,
6090
6708
  mode,
@@ -6466,15 +7084,18 @@ function tenantsRoutes(db, config) {
6466
7084
  }
6467
7085
  if (body.slug?.trim()) {
6468
7086
  const slug = body.slug.trim();
6469
- const validation = validateSlug(slug, locale);
6470
- if (!validation.valid) {
6471
- return c.json({ error: validation.reason }, 400);
6472
- }
6473
- const [existing] = await db.select({ id: tenants.id }).from(tenants).where(and13(eq20(tenants.slug, slug), ne(tenants.id, tenantId))).limit(1);
6474
- if (existing) {
6475
- return c.json({ error: tWithLocale("tenant.slugTaken", locale) }, 409);
7087
+ const [current] = await db.select({ slug: tenants.slug }).from(tenants).where(eq20(tenants.id, tenantId)).limit(1);
7088
+ if (current?.slug !== slug) {
7089
+ const validation = validateSlug(slug, locale);
7090
+ if (!validation.valid) {
7091
+ return c.json({ error: validation.reason }, 400);
7092
+ }
7093
+ const [existing] = await db.select({ id: tenants.id }).from(tenants).where(and13(eq20(tenants.slug, slug), ne(tenants.id, tenantId))).limit(1);
7094
+ if (existing) {
7095
+ return c.json({ error: tWithLocale("tenant.slugTaken", locale) }, 409);
7096
+ }
7097
+ updates.slug = slug;
6476
7098
  }
6477
- updates.slug = slug;
6478
7099
  }
6479
7100
  if (Object.keys(updates).length === 0) {
6480
7101
  return c.json({ error: tWithLocale("serverErrors.noFieldsToUpdate", locale) }, 400);
@@ -6925,7 +7546,7 @@ function usersRoutes(db) {
6925
7546
 
6926
7547
  // src/server/routes/settings.ts
6927
7548
  import { Hono as Hono9 } from "hono";
6928
- import { eq as eq22, and as and14 } from "drizzle-orm";
7549
+ import { eq as eq22, and as and14, desc as desc4, count as count5, inArray as inArray4 } from "drizzle-orm";
6929
7550
 
6930
7551
  // src/server/telemetry/debug-report-generator.ts
6931
7552
  function generateDebugTelemetryReport(data) {
@@ -7320,6 +7941,240 @@ function truncate(s, max) {
7320
7941
  return s.length > max ? `${s.slice(0, max)}...` : s;
7321
7942
  }
7322
7943
 
7944
+ // src/server/telemetry/report-adapter.ts
7945
+ function convertDiagnostics(diag) {
7946
+ if (!diag) return void 0;
7947
+ return {
7948
+ lastSnapshotPreview: diag.lastSnapshotPreview ?? void 0,
7949
+ failureHistory: Array.isArray(diag.failureHistory) ? diag.failureHistory : void 0,
7950
+ lastAiResponseText: diag.lastAiResponseText ?? void 0,
7951
+ recoveryHint: diag.recoveryHint ?? void 0,
7952
+ deterministicResolveResult: diag.deterministicResolveResult ?? void 0,
7953
+ visionFallbackResult: diag.visionFallbackResult,
7954
+ agentFallbackResult: diag.agentFallbackResult,
7955
+ validationWarnings: Array.isArray(diag.validationWarnings) ? diag.validationWarnings : void 0,
7956
+ validationErrors: Array.isArray(diag.validationErrors) ? diag.validationErrors : void 0,
7957
+ stepUrl: diag.stepUrl ?? void 0
7958
+ };
7959
+ }
7960
+ function convertStepResult(row) {
7961
+ return {
7962
+ ordinal: row.ordinal,
7963
+ description: row.description,
7964
+ status: row.status,
7965
+ durationMs: row.durationMs,
7966
+ error: row.error ?? void 0,
7967
+ actionType: row.actionType ?? void 0,
7968
+ retryCount: row.retryCount ?? void 0,
7969
+ failureCategory: row.failureCategory,
7970
+ capturedValues: row.capturedValues,
7971
+ conditionSkipped: row.conditionSkipped ?? void 0,
7972
+ loopIterations: row.loopIterations ?? void 0,
7973
+ branchMatch: row.branchMatch ?? void 0,
7974
+ forEachItemCount: row.forEachItemCount ?? void 0,
7975
+ extractedData: row.extractedData ?? void 0,
7976
+ downloadedFile: row.downloadedFile ?? void 0,
7977
+ exportedFile: row.exportedFile ?? void 0,
7978
+ diagnostics: convertDiagnostics(row.diagnostics)
7979
+ };
7980
+ }
7981
+ function convertAiMetrics(row) {
7982
+ return {
7983
+ totalCalls: row.totalCalls,
7984
+ totalInputTokens: row.totalInputTokens,
7985
+ totalOutputTokens: row.totalOutputTokens,
7986
+ totalCachedInputTokens: row.totalCachedInputTokens,
7987
+ totalCacheCreationTokens: row.totalCacheCreationTokens,
7988
+ cacheHitRate: row.cacheHitRate,
7989
+ estimatedCostUsd: row.estimatedCostUsd,
7990
+ totalDurationMs: row.totalDurationMs,
7991
+ // DB stores only aggregate totals, not per-purpose/model breakdowns
7992
+ byPurpose: {},
7993
+ byModel: {}
7994
+ };
7995
+ }
7996
+ function buildExecutionReportFromDb(job, details, enrichedData) {
7997
+ const steps = details.stepResults.map(convertStepResult);
7998
+ if (enrichedData?.resolutionOutcomes && enrichedData.resolutionOutcomes.length > 0) {
7999
+ const outcomeMap = /* @__PURE__ */ new Map();
8000
+ for (const o of enrichedData.resolutionOutcomes) {
8001
+ outcomeMap.set(o.stepOrdinal, o);
8002
+ }
8003
+ for (const step of steps) {
8004
+ const outcome = outcomeMap.get(step.ordinal);
8005
+ if (outcome) {
8006
+ step.resolveMethod = outcome.resolveMethod ?? void 0;
8007
+ step.deterministicMatchType = outcome.deterministicMatchType ?? void 0;
8008
+ step.deterministicConfidence = outcome.deterministicConfidence ?? void 0;
8009
+ step.contextChunksUsed = outcome.contextChunksUsed ?? void 0;
8010
+ step.failureHintsProvided = outcome.failureHintsProvided ?? void 0;
8011
+ step.workingMemoryTokens = outcome.workingMemoryTokens ?? void 0;
8012
+ step.snapshotElementCount = outcome.snapshotElementCount ?? void 0;
8013
+ }
8014
+ }
8015
+ }
8016
+ if (enrichedData?.failureEvents && enrichedData.failureEvents.length > 0) {
8017
+ const failureMap = /* @__PURE__ */ new Map();
8018
+ for (const f of enrichedData.failureEvents) {
8019
+ failureMap.set(f.stepOrdinal, f);
8020
+ }
8021
+ for (const step of steps) {
8022
+ const fe = failureMap.get(step.ordinal);
8023
+ if (!fe) continue;
8024
+ if (!step.retryDetails && fe.retryDetails && Array.isArray(fe.retryDetails)) {
8025
+ step.retryDetails = fe.retryDetails;
8026
+ }
8027
+ if (!step.diagnostics) {
8028
+ step.diagnostics = {};
8029
+ }
8030
+ if (!step.diagnostics.stepAction && fe.stepAction && typeof fe.stepAction === "object") {
8031
+ step.diagnostics.stepAction = fe.stepAction;
8032
+ }
8033
+ if (!step.diagnostics.executionStrategy && fe.executionStrategy && typeof fe.executionStrategy === "object") {
8034
+ step.diagnostics.executionStrategy = fe.executionStrategy;
8035
+ }
8036
+ }
8037
+ }
8038
+ const succeeded = steps.filter((s) => s.status === "success").length;
8039
+ const failed = steps.filter((s) => s.status === "failed").length;
8040
+ const skipped = steps.filter((s) => s.status === "skipped").length;
8041
+ const totalDurationMs = steps.reduce((sum2, s) => sum2 + s.durationMs, 0);
8042
+ const report = {
8043
+ runbookTitle: job.title,
8044
+ startUrl: job.startUrl,
8045
+ totalSteps: steps.length,
8046
+ executed: succeeded + failed,
8047
+ succeeded,
8048
+ failed,
8049
+ skipped,
8050
+ aborted: false,
8051
+ steps,
8052
+ totalDurationMs,
8053
+ aiMetrics: details.aiMetrics ? convertAiMetrics(details.aiMetrics) : void 0
8054
+ };
8055
+ const generationContext = job.mode === "generate" ? {
8056
+ goal: job.goal,
8057
+ startUrl: job.startUrl,
8058
+ goalAchieved: failed === 0,
8059
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8060
+ totalSteps: steps.length
8061
+ } : void 0;
8062
+ let cliOptions;
8063
+ if (enrichedData?.executionConfig) {
8064
+ const ec = enrichedData.executionConfig;
8065
+ cliOptions = [
8066
+ { name: "--default-timeout", value: String(ec.defaultTimeout) },
8067
+ { name: "--pause-between-steps", value: String(ec.pauseBetweenSteps) },
8068
+ { name: "--stop-on-error", value: String(ec.stopOnError) },
8069
+ ...ec.stepDelay != null ? [{ name: "--step-delay", value: String(ec.stepDelay) }] : [],
8070
+ { name: "--enable-selector-cache", value: String(ec.enableSelectorCache) },
8071
+ { name: "--enable-agent-fallback", value: String(ec.enableAgentFallback) },
8072
+ { name: "--enable-vision-fallback", value: String(ec.enableVisionFallback) },
8073
+ ...ec.modelId ? [{ name: "--model", value: ec.modelId }] : [],
8074
+ ...ec.modelProvider ? [{ name: "--model-provider", value: ec.modelProvider }] : []
8075
+ ];
8076
+ }
8077
+ let telemetryStats;
8078
+ if (enrichedData?.telemetrySummary) {
8079
+ const ts = enrichedData.telemetrySummary;
8080
+ telemetryStats = {
8081
+ resolveMethodCounts: ts.resolveMethodCounts && typeof ts.resolveMethodCounts === "object" ? ts.resolveMethodCounts : {},
8082
+ visionFallbackAttempts: ts.visionFallbackAttempts ?? 0,
8083
+ visionFallbackSuccesses: ts.visionFallbackSuccesses ?? 0,
8084
+ agentFallbackAttempts: ts.agentFallbackAttempts ?? 0,
8085
+ agentFallbackSuccesses: ts.agentFallbackSuccesses ?? 0,
8086
+ stepsWithRetries: ts.stepsWithRetries ?? 0,
8087
+ totalRetryCount: ts.totalRetryCount ?? 0
8088
+ };
8089
+ }
8090
+ let selfHealSuggestions;
8091
+ if (enrichedData?.debugSuggestions && enrichedData.debugSuggestions.length > 0) {
8092
+ selfHealSuggestions = enrichedData.debugSuggestions.filter((s) => s.stepOrdinal != null).map((s) => ({
8093
+ stepOrdinal: s.stepOrdinal,
8094
+ stepDescription: s.stepDescription ?? "",
8095
+ error: s.error ?? "",
8096
+ runbookFix: s.runbookFix ?? "",
8097
+ contextFix: s.contextFix ?? "",
8098
+ severity: s.severity === "warning" ? "warning" : "error",
8099
+ failureCategory: s.failureCategory,
8100
+ suggestedStrategy: s.suggestedStrategy ?? void 0
8101
+ }));
8102
+ }
8103
+ return {
8104
+ runbookPath: "(from database)",
8105
+ report,
8106
+ errorMessage: job.errorMessage ?? void 0,
8107
+ generationContext,
8108
+ cliOptions,
8109
+ telemetryStats,
8110
+ selfHealSuggestions
8111
+ };
8112
+ }
8113
+ function convertDbRecordedStep(row) {
8114
+ const action = {
8115
+ action: row.actionType,
8116
+ selector: row.selector ?? void 0,
8117
+ value: row.value ?? void 0,
8118
+ description: row.description ?? "",
8119
+ inputCategory: row.inputCategory,
8120
+ variableName: row.variableName ?? void 0,
8121
+ suggestedCaptures: row.suggestedCaptures,
8122
+ script: row.script ?? void 0,
8123
+ extractPrompt: row.extractPrompt ?? void 0,
8124
+ memoryCollection: row.memoryCollection ?? void 0,
8125
+ aggregation: row.aggregation,
8126
+ downloadPath: row.downloadPath ?? void 0,
8127
+ exportCollection: row.exportCollection ?? void 0,
8128
+ exportFormat: row.exportFormat,
8129
+ exportPath: row.exportPath ?? void 0,
8130
+ keys: row.keys ?? void 0
8131
+ };
8132
+ return {
8133
+ ordinal: row.ordinal,
8134
+ action,
8135
+ snapshotBefore: row.snapshotBefore ?? "",
8136
+ url: row.url ?? "",
8137
+ success: row.success ?? true,
8138
+ error: row.error ?? void 0,
8139
+ failureCategory: row.failureCategory,
8140
+ durationMs: row.durationMs ?? void 0
8141
+ };
8142
+ }
8143
+ function convertDbReviewedStep(row) {
8144
+ return {
8145
+ originalOrdinal: row.originalOrdinal,
8146
+ keep: row.keep,
8147
+ removalReason: row.removalReason ?? void 0,
8148
+ riskLevel: row.riskLevel ?? "low",
8149
+ requiresConfirmation: row.requiresConfirmation ?? false,
8150
+ confirmationReason: row.confirmationReason ?? void 0
8151
+ };
8152
+ }
8153
+ function buildGenerationReportInputFromDb(job, data) {
8154
+ return {
8155
+ goal: job.goal,
8156
+ startUrl: job.startUrl,
8157
+ goalAchieved: data.reviewResult?.goalAchieved ?? false,
8158
+ recordedSteps: data.recordedSteps.map(convertDbRecordedStep),
8159
+ reviewResult: data.reviewResult ? {
8160
+ summary: data.reviewResult.summary ?? void 0,
8161
+ yamlContent: data.reviewResult.yamlContent ?? void 0,
8162
+ goalAchieved: data.reviewResult.goalAchieved ?? void 0,
8163
+ reviewedSteps: data.reviewedSteps.map(convertDbReviewedStep)
8164
+ } : void 0,
8165
+ generateConfig: data.generateConfig ? {
8166
+ maxIterations: data.generateConfig.maxIterations,
8167
+ modelId: data.generateConfig.modelId ?? void 0,
8168
+ modelProvider: data.generateConfig.modelProvider ?? void 0,
8169
+ headless: data.generateConfig.headless,
8170
+ stealth: data.generateConfig.stealth,
8171
+ snapshotFilter: data.generateConfig.snapshotFilter
8172
+ } : void 0,
8173
+ aiMetrics: data.aiMetrics ? convertAiMetrics(data.aiMetrics) : void 0,
8174
+ errorMessage: job.errorMessage ?? void 0
8175
+ };
8176
+ }
8177
+
7323
8178
  // src/server/routes/settings.ts
7324
8179
  import { z as z2 } from "zod";
7325
8180
 
@@ -7881,7 +8736,7 @@ function createSettingsRoutes(db, config) {
7881
8736
  app.get("/development", async (c) => {
7882
8737
  const tenantId = c.get("tenantId");
7883
8738
  if (!tenantId) return c.json({ error: tWithLocale("serverErrors.tenantRequired", reqLocale(c)) }, 403);
7884
- const { tenantDevelopmentSettings } = await import("./schema-5G6UQSPT.js");
8739
+ const { tenantDevelopmentSettings } = await import("./schema-XFSD5EWN.js");
7885
8740
  const row = await db.query.tenantDevelopmentSettings.findFirst({
7886
8741
  where: eq22(tenantDevelopmentSettings.tenantId, tenantId)
7887
8742
  });
@@ -7903,7 +8758,7 @@ function createSettingsRoutes(db, config) {
7903
8758
  if (!parsed.success) {
7904
8759
  return c.json({ error: parsed.error.flatten() }, 400);
7905
8760
  }
7906
- const { tenantDevelopmentSettings } = await import("./schema-5G6UQSPT.js");
8761
+ const { tenantDevelopmentSettings } = await import("./schema-XFSD5EWN.js");
7907
8762
  await db.insert(tenantDevelopmentSettings).values({
7908
8763
  tenantId,
7909
8764
  requireApprovalBeforePublish: parsed.data.requireApprovalBeforePublish,
@@ -8058,12 +8913,136 @@ function createSettingsRoutes(db, config) {
8058
8913
  const token = generateDebugToken(tenantId, signingKey, getDebugPublicKey());
8059
8914
  return c.json({ ok: true, token });
8060
8915
  });
8061
- const telemetryStore = new TelemetryStore(db);
8062
8916
  function safeInt2(value) {
8063
8917
  if (!value) return void 0;
8064
8918
  const n = Number(value);
8065
8919
  return Number.isFinite(n) && n > 0 ? Math.floor(n) : void 0;
8066
8920
  }
8921
+ app.get("/debug/jobs/recent", requireRole("owner", { rejectApiKey: true }), async (c) => {
8922
+ if (config.nodeEnv !== "development") {
8923
+ return c.json({ error: "Debug endpoints are only available in development" }, 403);
8924
+ }
8925
+ const limit = safeInt2(c.req.query("limit")) ?? 20;
8926
+ const offset = safeInt2(c.req.query("offset")) ?? 0;
8927
+ const runbookIdParam = c.req.query("runbookId");
8928
+ const modeParam = c.req.query("mode");
8929
+ const statusGroupParam = c.req.query("statusGroup");
8930
+ const statusGroupMap = {
8931
+ running: ["scheduled", "running", "waiting_approval", "exploring", "reviewing", "queued"],
8932
+ completed: ["completed"],
8933
+ failed: ["failed", "cancelled"]
8934
+ };
8935
+ if (statusGroupParam && !statusGroupMap[statusGroupParam]) {
8936
+ return c.json({ error: `Invalid statusGroup: ${statusGroupParam}` }, 400);
8937
+ }
8938
+ const conditions = [];
8939
+ if (runbookIdParam) conditions.push(eq22(jobs.runbookId, runbookIdParam));
8940
+ if (modeParam) conditions.push(eq22(jobs.mode, modeParam));
8941
+ if (statusGroupParam) conditions.push(inArray4(jobs.status, statusGroupMap[statusGroupParam]));
8942
+ const where = conditions.length > 0 ? and14(...conditions) : void 0;
8943
+ const [rows, [totalRow]] = await Promise.all([
8944
+ db.select({
8945
+ id: jobs.id,
8946
+ runbookId: jobs.runbookId,
8947
+ runbookTitle: runbooks.title,
8948
+ mode: jobs.mode,
8949
+ status: jobs.status,
8950
+ purpose: jobs.purpose,
8951
+ totalSteps: jobs.totalSteps,
8952
+ startUrl: jobs.startUrl,
8953
+ goal: jobs.goal,
8954
+ completedAt: jobs.completedAt,
8955
+ createdAt: jobs.createdAt
8956
+ }).from(jobs).leftJoin(runbooks, eq22(jobs.runbookId, runbooks.id)).where(where).orderBy(desc4(jobs.createdAt)).limit(limit).offset(offset),
8957
+ db.select({ total: count5() }).from(jobs).where(where)
8958
+ ]);
8959
+ return c.json({ items: rows, total: totalRow.total });
8960
+ });
8961
+ app.get("/debug/jobs/:jobId/execution-report", requireRole("owner", { rejectApiKey: true }), async (c) => {
8962
+ if (config.nodeEnv !== "development") {
8963
+ return c.json({ error: "Debug endpoints are only available in development" }, 403);
8964
+ }
8965
+ const jobId = c.req.param("jobId");
8966
+ const job = await db.query.jobs.findFirst({
8967
+ where: eq22(jobs.id, jobId)
8968
+ });
8969
+ if (!job) {
8970
+ return c.json({ error: "Job not found" }, 404);
8971
+ }
8972
+ let title = "Unknown";
8973
+ if (job.runbookId) {
8974
+ const rb = await db.query.runbooks.findFirst({
8975
+ where: eq22(runbooks.id, job.runbookId),
8976
+ columns: { title: true }
8977
+ });
8978
+ if (rb) title = rb.title;
8979
+ }
8980
+ let md;
8981
+ if (job.mode === "generate") {
8982
+ const [recordedSteps, reviewResult, generateConfig, aiMetricsRow] = await Promise.all([
8983
+ db.query.jobRecordedSteps.findMany({
8984
+ where: eq22(jobRecordedSteps.jobId, jobId),
8985
+ orderBy: [jobRecordedSteps.ordinal]
8986
+ }),
8987
+ db.query.jobReviewResults.findFirst({
8988
+ where: eq22(jobReviewResults.jobId, jobId)
8989
+ }),
8990
+ db.query.jobGenerateConfigs.findFirst({
8991
+ where: eq22(jobGenerateConfigs.jobId, jobId)
8992
+ }),
8993
+ db.query.jobAiMetrics.findFirst({
8994
+ where: eq22(jobAiMetrics.jobId, jobId)
8995
+ })
8996
+ ]);
8997
+ let reviewedSteps = [];
8998
+ if (reviewResult) {
8999
+ reviewedSteps = await db.query.jobReviewedSteps.findMany({
9000
+ where: eq22(jobReviewedSteps.reviewResultId, reviewResult.id)
9001
+ });
9002
+ }
9003
+ const reportInput = buildGenerationReportInputFromDb(
9004
+ {
9005
+ title,
9006
+ startUrl: job.startUrl ?? "",
9007
+ goal: job.goal ?? "",
9008
+ mode: job.mode,
9009
+ errorMessage: job.errorMessage
9010
+ },
9011
+ { recordedSteps, reviewResult: reviewResult ?? null, reviewedSteps, generateConfig: generateConfig ?? null, aiMetrics: aiMetricsRow ?? null }
9012
+ );
9013
+ md = await generateGenerationReportFile(reportInput);
9014
+ } else {
9015
+ const telemetry = new TelemetryStore(db);
9016
+ const mode = job.mode === "self_heal" ? "self_heal" : "execute";
9017
+ const reportData = await telemetry.getJobReportData(jobId, mode);
9018
+ const data = reportData;
9019
+ const reportInput = buildExecutionReportFromDb(
9020
+ {
9021
+ title,
9022
+ startUrl: job.startUrl ?? "",
9023
+ goal: job.goal ?? "",
9024
+ mode: job.mode,
9025
+ errorMessage: job.errorMessage
9026
+ },
9027
+ {
9028
+ stepResults: data.stepResults ?? [],
9029
+ aiMetrics: reportData.aiMetrics
9030
+ },
9031
+ {
9032
+ executionConfig: data.executionConfig ?? null,
9033
+ telemetrySummary: data.telemetrySummary ?? null,
9034
+ resolutionOutcomes: data.resolutionOutcomes ?? [],
9035
+ failureEvents: data.failureEvents ?? [],
9036
+ debugSuggestions: data.debugSuggestions
9037
+ }
9038
+ );
9039
+ md = await generateExecutionReportFile(reportInput);
9040
+ }
9041
+ return new Response(md, {
9042
+ headers: { "Content-Type": "text/markdown; charset=utf-8" }
9043
+ });
9044
+ });
9045
+ const telemetryStore = new TelemetryStore(db);
8067
9046
  app.get("/debug/telemetry/summaries", requireRole("owner", { rejectApiKey: true }), async (c) => {
8068
9047
  if (config.nodeEnv !== "development") {
8069
9048
  return c.json({ error: "Debug endpoints are only available in development" }, 403);
@@ -8349,8 +9328,68 @@ function createTelemetryHandlers(db) {
8349
9328
  return app;
8350
9329
  }
8351
9330
 
8352
- // src/server/webhooks/slack.ts
9331
+ // src/server/handlers/dashboard.ts
8353
9332
  import { Hono as Hono12 } from "hono";
9333
+ import { eq as eq23, and as and15, count as count6 } from "drizzle-orm";
9334
+ function createDashboardHandlers(db) {
9335
+ const app = new Hono12();
9336
+ app.get("/inbox", async (c) => {
9337
+ const tenantId = c.get("tenantId");
9338
+ if (!tenantId) {
9339
+ return c.json({ error: "Tenant required" }, 400);
9340
+ }
9341
+ const rows = await db.select({
9342
+ reviewId: runbookVersionReviews.id,
9343
+ versionId: runbookVersionReviews.versionId,
9344
+ runbookId: runbookVersions.runbookId,
9345
+ runbookTitle: runbooks.title,
9346
+ versionNumber: runbookVersions.versionNumber,
9347
+ goal: runbookVersions.goal,
9348
+ requestedBy: runbookVersionReviews.requestedBy,
9349
+ requestedByName: user.name,
9350
+ requestedAt: runbookVersionReviews.requestedAt
9351
+ }).from(runbookVersionReviews).innerJoin(
9352
+ runbookVersions,
9353
+ eq23(runbookVersionReviews.versionId, runbookVersions.id)
9354
+ ).innerJoin(runbooks, eq23(runbookVersions.runbookId, runbooks.id)).innerJoin(user, eq23(runbookVersionReviews.requestedBy, user.id)).where(
9355
+ and15(
9356
+ eq23(runbookVersionReviews.status, "pending"),
9357
+ eq23(runbooks.tenantId, tenantId)
9358
+ )
9359
+ );
9360
+ const versionIds = [...new Set(rows.map((r) => r.versionId))];
9361
+ let stepCounts = {};
9362
+ if (versionIds.length > 0) {
9363
+ const counts = await Promise.all(
9364
+ versionIds.map(async (vid) => {
9365
+ const [row] = await db.select({ count: count6() }).from(runbookSteps).where(eq23(runbookSteps.versionId, vid));
9366
+ return { versionId: vid, count: row?.count ?? 0 };
9367
+ })
9368
+ );
9369
+ stepCounts = Object.fromEntries(
9370
+ counts.map((c2) => [c2.versionId, c2.count])
9371
+ );
9372
+ }
9373
+ return c.json({
9374
+ pendingReviews: rows.map((r) => ({
9375
+ reviewId: r.reviewId,
9376
+ versionId: r.versionId,
9377
+ runbookId: r.runbookId,
9378
+ runbookTitle: r.runbookTitle,
9379
+ versionNumber: r.versionNumber,
9380
+ goal: r.goal,
9381
+ stepsCount: stepCounts[r.versionId] ?? 0,
9382
+ requestedBy: r.requestedBy,
9383
+ requestedByName: r.requestedByName,
9384
+ requestedAt: r.requestedAt.toISOString()
9385
+ }))
9386
+ });
9387
+ });
9388
+ return app;
9389
+ }
9390
+
9391
+ // src/server/webhooks/slack.ts
9392
+ import { Hono as Hono13 } from "hono";
8354
9393
  import { createHmac, timingSafeEqual } from "crypto";
8355
9394
 
8356
9395
  // src/server/webhooks/approval-parser.ts
@@ -8375,15 +9414,15 @@ function parseApprovalButtonId(rawId) {
8375
9414
  }
8376
9415
 
8377
9416
  // src/server/webhooks/credential-resolver.ts
8378
- import { eq as eq23 } from "drizzle-orm";
9417
+ import { eq as eq24 } from "drizzle-orm";
8379
9418
  async function resolveWebhookCredential(deps, jobId, platform) {
8380
9419
  const job = await deps.db.query.jobs.findFirst({
8381
- where: eq23(jobs.id, jobId),
9420
+ where: eq24(jobs.id, jobId),
8382
9421
  columns: { tenantId: true }
8383
9422
  });
8384
9423
  if (!job) return null;
8385
9424
  const settings = await deps.db.query.tenantExecutionSettings.findFirst({
8386
- where: eq23(tenantExecutionSettings.tenantId, job.tenantId),
9425
+ where: eq24(tenantExecutionSettings.tenantId, job.tenantId),
8387
9426
  columns: { notificationCredentials: true }
8388
9427
  });
8389
9428
  const allCreds = settings?.notificationCredentials;
@@ -8422,7 +9461,7 @@ async function resolveAllDiscordPublicKeys(deps) {
8422
9461
 
8423
9462
  // src/server/webhooks/slack.ts
8424
9463
  function createSlackWebhookRoute(deps) {
8425
- const app = new Hono12();
9464
+ const app = new Hono13();
8426
9465
  app.post("/", async (c) => {
8427
9466
  const timestamp = c.req.header("x-slack-request-timestamp");
8428
9467
  const signature = c.req.header("x-slack-signature");
@@ -8478,7 +9517,7 @@ function createSlackWebhookRoute(deps) {
8478
9517
  }
8479
9518
 
8480
9519
  // src/server/webhooks/teams.ts
8481
- import { Hono as Hono13 } from "hono";
9520
+ import { Hono as Hono14 } from "hono";
8482
9521
  import { createRemoteJWKSet, jwtVerify } from "jose";
8483
9522
  var BOT_FRAMEWORK_OPENID_METADATA = "https://login.botframework.com/v1/.well-known/openidconfiguration";
8484
9523
  var BOT_FRAMEWORK_ISSUERS = [
@@ -8504,7 +9543,7 @@ async function verifyBotFrameworkToken(token, teamsAppId) {
8504
9543
  });
8505
9544
  }
8506
9545
  function createTeamsWebhookRoute(deps) {
8507
- const app = new Hono13();
9546
+ const app = new Hono14();
8508
9547
  app.post("/", async (c) => {
8509
9548
  const authHeader = c.req.header("Authorization");
8510
9549
  if (!authHeader?.startsWith("Bearer ")) {
@@ -8544,7 +9583,7 @@ function createTeamsWebhookRoute(deps) {
8544
9583
  }
8545
9584
 
8546
9585
  // src/server/webhooks/discord.ts
8547
- import { Hono as Hono14 } from "hono";
9586
+ import { Hono as Hono15 } from "hono";
8548
9587
  import { verify } from "crypto";
8549
9588
  var InteractionType = {
8550
9589
  PING: 1,
@@ -8576,7 +9615,7 @@ function verifyDiscordSignature(publicKeyHex, signature, timestamp, body) {
8576
9615
  }
8577
9616
  }
8578
9617
  function createDiscordWebhookRoute(deps) {
8579
- const app = new Hono14();
9618
+ const app = new Hono15();
8580
9619
  app.post("/", async (c) => {
8581
9620
  const signature = c.req.header("X-Signature-Ed25519");
8582
9621
  const timestamp = c.req.header("X-Signature-Timestamp");
@@ -8636,7 +9675,7 @@ function createDiscordWebhookRoute(deps) {
8636
9675
  // src/server/app.ts
8637
9676
  function createApp(config, pool, db) {
8638
9677
  const auth = createAuth(config, pool, db);
8639
- const app = new Hono15();
9678
+ const app = new Hono16();
8640
9679
  if (config.nodeEnv !== "test") {
8641
9680
  app.use("*", logger());
8642
9681
  }
@@ -8648,7 +9687,7 @@ function createApp(config, pool, db) {
8648
9687
  })
8649
9688
  );
8650
9689
  app.use("*", createAuthMiddleware(auth, db));
8651
- app.on(["POST", "GET"], "/api/auth/**", (c) => {
9690
+ app.on(["POST", "GET"], "/api/auth/*", (c) => {
8652
9691
  return auth.handler(c.req.raw);
8653
9692
  });
8654
9693
  app.get(
@@ -8685,13 +9724,14 @@ function createApp(config, pool, db) {
8685
9724
  const runbookStore = new RunbookStore(db);
8686
9725
  const jobStore = new JobStore(db);
8687
9726
  const scheduleStore = new ScheduleStore(db);
8688
- const v1 = new Hono15();
9727
+ const v1 = new Hono16();
8689
9728
  v1.route("/runbooks", createRunbookHandlers(runbookStore, jobStore, inngest, config.encryptionKey, scheduleStore, db, config));
8690
9729
  v1.route("/jobs", createJobHandlers(jobStore, runbookStore, inngest, config.redisUrl, db, config));
8691
9730
  v1.route("/schedules", createScheduleHandlers(scheduleStore, jobStore, db));
8692
9731
  v1.route("/settings", createSettingsRoutes(db, config));
8693
9732
  v1.route("/usage", createUsageHandlers(db));
8694
9733
  v1.route("/telemetry", createTelemetryHandlers(db));
9734
+ v1.route("/dashboard", createDashboardHandlers(db));
8695
9735
  app.route("/api/v1", v1);
8696
9736
  app.route("/api/v1/api-keys", apiKeysRoutes(db));
8697
9737
  app.route("/api/v1/tenants", tenantsRoutes(db, config));
@@ -8714,6 +9754,14 @@ function createApp(config, pool, db) {
8714
9754
 
8715
9755
  // src/server/index.ts
8716
9756
  async function main() {
9757
+ process.on("uncaughtException", (err) => {
9758
+ if (err.code === "EPIPE" || err.code === "ECONNRESET") {
9759
+ console.warn(`[server] Client disconnected (${err.code})`);
9760
+ return;
9761
+ }
9762
+ console.error("[server] Uncaught exception:", err);
9763
+ process.exit(1);
9764
+ });
8717
9765
  const config = loadServerConfig();
8718
9766
  let db;
8719
9767
  try {
@@ -8775,4 +9823,4 @@ async function main() {
8775
9823
  process.on("SIGINT", shutdown);
8776
9824
  }
8777
9825
  main();
8778
- //# sourceMappingURL=server-AG3LXQBI.js.map
9826
+ //# sourceMappingURL=server-5XARL5N7.js.map