specrails-desktop 2.34.0 → 2.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/client/dist/assets/ActivityFeedPage-LCBJ5WiD.js +1 -0
  2. package/client/dist/assets/AgentBrowserCapture-B-xQpyZr.js +1 -0
  3. package/client/dist/assets/AgentModeAnalyticsPane-BI1Vcf3T.js +2 -0
  4. package/client/dist/assets/AgentModeCodePane-BTRwCl9h.js +2 -0
  5. package/client/dist/assets/AgentModeJobsPane-VzeBJLqP.js +1 -0
  6. package/client/dist/assets/AgentsPage-CJj1K24O.js +87 -0
  7. package/client/dist/assets/AnalyticsPage-CQpV5_fX.js +1 -0
  8. package/client/dist/assets/CodePage-9-tFh0jB.js +2 -0
  9. package/client/dist/assets/DesktopAnalyticsPage-BcZ0_O_C.js +1 -0
  10. package/client/dist/assets/DocsDialog-BPAzDICW.js +11 -0
  11. package/client/dist/assets/DocsPage-Dqo4Hn6G.js +11 -0
  12. package/client/dist/assets/IntegrationsPage-Bhy58lnQ.js +1 -0
  13. package/client/dist/assets/InteractiveJobComposer-D36fZWai.js +19 -0
  14. package/client/dist/assets/JobDetailModal-BxsktbkQ.js +1 -0
  15. package/client/dist/assets/JobDetailPage-WWyCy6Sp.js +1 -0
  16. package/client/dist/assets/JobsPage-CEbM7uxU.js +1 -0
  17. package/client/dist/assets/{LoopBuilderPage-WeiofeL6.js → LoopBuilderPage-CYsSN8io.js} +2 -2
  18. package/client/dist/assets/LoopPreviewModal-BJhgX5wY.js +1 -0
  19. package/client/dist/assets/LoopsPage-DB4OqMot.js +1 -0
  20. package/client/dist/assets/MinimizedChatsContext-CcjBHMHd.js +1 -0
  21. package/client/dist/assets/PluginsPage-CR8K4nM_.js +2 -0
  22. package/client/dist/assets/{ProjectSettingsDialog-BkCXBQWk.js → ProjectSettingsDialog-C45-qhF4.js} +1 -1
  23. package/client/dist/assets/ReviewPacketPage-Cz_QQeC3.js +1 -0
  24. package/client/dist/assets/TemplatePreviewModal-wFBd2vQP.js +1 -0
  25. package/client/dist/assets/TicketDetailModal-BvZoyDAL.js +14 -0
  26. package/client/dist/assets/{Trans-CTqw6rr9.js → Trans-DXZ-BLXb.js} +1 -1
  27. package/client/dist/assets/agent-BQgKv_2F.js +1 -0
  28. package/client/dist/assets/agent-C1NRDJqL.js +1 -0
  29. package/client/dist/assets/agent-CLNleiy6.js +1 -0
  30. package/client/dist/assets/agent-CmceArkQ.js +1 -0
  31. package/client/dist/assets/agent-D3UxQ3SR.js +1 -0
  32. package/client/dist/assets/agent-DB1JeE64.js +1 -0
  33. package/client/dist/assets/agent-DXkHcQ-D.js +1 -0
  34. package/client/dist/assets/agent-DjJJJ2M8.js +1 -0
  35. package/client/dist/assets/ban-E4EaZe3a.js +1 -0
  36. package/client/dist/assets/bot-DzuuuC5R.js +1 -0
  37. package/client/dist/assets/{browser-DxP-alBK.js → browser-BgqHDMXP.js} +1 -1
  38. package/client/dist/assets/{browser-CyR1Kp3m.js → browser-CsKJtrpv.js} +1 -1
  39. package/client/dist/assets/browser-Cu58EpZU.js +1 -0
  40. package/client/dist/assets/{browser-rRCwi7Y7.js → browser-CvXOJm-j.js} +1 -1
  41. package/client/dist/assets/{browser-CvgDySd9.js → browser-DOrFh1c7.js} +1 -1
  42. package/client/dist/assets/{browser-DwZljcRh.js → browser-DQv_Jo7x.js} +1 -1
  43. package/client/dist/assets/{browser-C25DkQiO.js → browser-LPgxvJu2.js} +1 -1
  44. package/client/dist/assets/{browser-CiJ7-L32.js → browser-eL-3MIEI.js} +1 -1
  45. package/client/dist/assets/button-CJqFnUOL.js +1 -0
  46. package/client/dist/assets/{chunk-KS7C4IRE-Cx5sqopX.js → chunk-KS7C4IRE-DEKu087k.js} +1 -1
  47. package/client/dist/assets/{commands-DyUZQ1FI.js → commands-6tYQih5m.js} +1 -1
  48. package/client/dist/assets/{commands-B9RZO4qS.js → commands-BK2QinXN.js} +1 -1
  49. package/client/dist/assets/{commands-Bbxu8kLU.js → commands-C2Nca76h.js} +1 -1
  50. package/client/dist/assets/{commands-CDU3qv68.js → commands-CKdLihNX.js} +1 -1
  51. package/client/dist/assets/{commands-DTQBF_q5.js → commands-CYIBTAlc.js} +1 -1
  52. package/client/dist/assets/{commands-hOtKuOXc.js → commands-CjUtYJIM.js} +1 -1
  53. package/client/dist/assets/{commands-BEwbAjtD.js → commands-Cq_u_8y9.js} +1 -1
  54. package/client/dist/assets/{commands-CzezgtQj.js → commands-D_ZHYQug.js} +1 -1
  55. package/client/dist/assets/core-B3mKBIh5.js +1 -0
  56. package/client/dist/assets/dashboard-B9GhC5ku.js +1 -0
  57. package/client/dist/assets/dashboard-C8WOEiPu.js +1 -0
  58. package/client/dist/assets/dashboard-CBnv6zOI.js +1 -0
  59. package/client/dist/assets/dashboard-CaP0fU87.js +1 -0
  60. package/client/dist/assets/dashboard-D4ZZ4x84.js +1 -0
  61. package/client/dist/assets/dashboard-DmuTQBX8.js +1 -0
  62. package/client/dist/assets/dashboard-DynLYj3d.js +1 -0
  63. package/client/dist/assets/dashboard-zj4gGFJ1.js +1 -0
  64. package/client/dist/assets/dialog-CdWCb02j.js +45 -0
  65. package/client/dist/assets/dist-js-CWeUdf5Z.js +1 -0
  66. package/client/dist/assets/dist-js-Rlh4FLH5.js +1 -0
  67. package/client/dist/assets/event-Bt7f3wXe.js +1 -0
  68. package/client/dist/assets/file-text-BcLCfcWZ.js +1 -0
  69. package/client/dist/assets/{formatDistanceToNow-BOuXgebD.js → formatDistanceToNow-C0ImhVKp.js} +1 -1
  70. package/client/dist/assets/{getTimezoneOffsetInMilliseconds-EjGOpn4b.js → getTimezoneOffsetInMilliseconds-owhDdTXs.js} +1 -1
  71. package/client/dist/assets/git-refresh-wTW0rO0Z.js +1 -0
  72. package/client/dist/assets/i18n-DwgvwTnx.js +2 -0
  73. package/client/dist/assets/index-DhjjFyU5.js +66 -0
  74. package/client/dist/assets/index-xBFsOEiJ.css +2 -0
  75. package/client/dist/assets/jira-api-CcZMcEN5.js +1 -0
  76. package/client/dist/assets/loops-B7YpGizO.js +1 -0
  77. package/client/dist/assets/{loops-CMMQOw2P.js → loops-BomD2J-h.js} +1 -1
  78. package/client/dist/assets/{loops-B2_97D26.js → loops-BrQHSQw3.js} +1 -1
  79. package/client/dist/assets/{loops-BwxCndZk.js → loops-BvBwvTPA.js} +1 -1
  80. package/client/dist/assets/{loops-BISOkuCu.js → loops-CODk6Slz.js} +1 -1
  81. package/client/dist/assets/{loops-CD19kEzq.js → loops-DC6k-Ym-.js} +1 -1
  82. package/client/dist/assets/{loops-aEb4RxN2.js → loops-DSEg8-uY.js} +1 -1
  83. package/client/dist/assets/{loops-Bo6w_peZ.js → loops-DhCoXD_v.js} +1 -1
  84. package/client/dist/assets/narration-4Z2VUhZw.js +1 -0
  85. package/client/dist/assets/narration-BGuiEDj3.js +1 -0
  86. package/client/dist/assets/narration-BuSoqRmv.js +1 -0
  87. package/client/dist/assets/narration-CB9t29fy.js +1 -0
  88. package/client/dist/assets/narration-CU3x8FHD.js +1 -0
  89. package/client/dist/assets/narration-CWeXhfP5.js +1 -0
  90. package/client/dist/assets/narration-DRaDnhT5.js +1 -0
  91. package/client/dist/assets/narration-DtEbQgCw.js +1 -0
  92. package/client/dist/assets/packet-B1IxaL5f.js +1 -0
  93. package/client/dist/assets/packet-Bh5kkbiB.js +1 -0
  94. package/client/dist/assets/packet-By8fqYxW.js +1 -0
  95. package/client/dist/assets/packet-CZdvA44E.js +1 -0
  96. package/client/dist/assets/packet-CrjHRQCP.js +1 -0
  97. package/client/dist/assets/packet-Dwi2pTtx.js +1 -0
  98. package/client/dist/assets/packet-_-ZIa0IR.js +1 -0
  99. package/client/dist/assets/packet-n0W6E1Hn.js +1 -0
  100. package/client/dist/assets/save-B6wGWwBR.js +1 -0
  101. package/client/dist/assets/settings-Bd0x4TBa.js +1 -0
  102. package/client/dist/assets/settings-C1-7cXzC.js +1 -0
  103. package/client/dist/assets/settings-CAFaT38b.js +1 -0
  104. package/client/dist/assets/settings-Ct3v1nW2.js +1 -0
  105. package/client/dist/assets/settings-DHh7_IF5.js +1 -0
  106. package/client/dist/assets/settings-DQXAmdcv.js +1 -0
  107. package/client/dist/assets/settings-guzlhpup.js +1 -0
  108. package/client/dist/assets/settings-sZfTPMEH.js +1 -0
  109. package/client/dist/assets/{spending-BpYPsevQ.js → spending-BQX3P7Cq.js} +1 -1
  110. package/client/dist/assets/useDesktop-BzX769yH.js +1 -0
  111. package/client/dist/index.html +29 -31
  112. package/docs/internals/embedded-browser-native-webview-evaluation.md +161 -0
  113. package/docs/internals/review-packet.md +212 -0
  114. package/package.json +1 -1
  115. package/server/dist/accept-ladder.js +61 -0
  116. package/server/dist/agent-chat-manager.js +22 -1
  117. package/server/dist/agent-defaults.js +359 -0
  118. package/server/dist/agent-operator-prompt.js +13 -5
  119. package/server/dist/browser-playwright.js +39 -2
  120. package/server/dist/db.js +38 -0
  121. package/server/dist/delivery-evidence.js +242 -0
  122. package/server/dist/desktop-router.js +23 -0
  123. package/server/dist/feature-flags.js +23 -0
  124. package/server/dist/loop-command-catalog.js +33 -0
  125. package/server/dist/loop-executors.js +12 -2
  126. package/server/dist/loop-factory.js +16 -1
  127. package/server/dist/loops-router.js +2 -0
  128. package/server/dist/mcp/tools/rails.js +16 -0
  129. package/server/dist/project-registry.js +33 -1
  130. package/server/dist/project-router-loop-runs.js +27 -2
  131. package/server/dist/providers/claude-adapter.js +60 -3
  132. package/server/dist/queue-manager.js +47 -4
  133. package/server/dist/rail-isolated-launch.js +119 -5
  134. package/server/dist/rail-pr-store.js +40 -3
  135. package/server/dist/rail-worktree-release.js +17 -1
  136. package/server/dist/rails-router.js +124 -3
  137. package/server/dist/review-packet.js +327 -0
  138. package/server/dist/revision-seed.js +73 -0
  139. package/server/dist/run-duration-stats.js +71 -0
  140. package/server/dist/stuck-run-detector.js +153 -0
  141. package/server/dist/telemetry-receiver.js +22 -0
  142. package/server/dist/worktree-node-modules.js +138 -16
  143. package/client/dist/assets/ActivityFeedPage-DIDxNsDj.js +0 -1
  144. package/client/dist/assets/AgentBrowserCapture-7pJ2G5VY.js +0 -1
  145. package/client/dist/assets/AgentModeAnalyticsPane-BSSyTnTM.js +0 -2
  146. package/client/dist/assets/AgentModeCodePane-DzW6gvDw.js +0 -2
  147. package/client/dist/assets/AgentModeJobsPane-9wJff15f.js +0 -1
  148. package/client/dist/assets/AgentsPage-CIA67psb.js +0 -87
  149. package/client/dist/assets/AnalyticsPage-C0LwYELz.js +0 -1
  150. package/client/dist/assets/CodePage-RtKvHmAO.js +0 -2
  151. package/client/dist/assets/DesktopAnalyticsPage-BU8Y_Ts1.js +0 -1
  152. package/client/dist/assets/DocsDialog-DIpoaGlQ.js +0 -11
  153. package/client/dist/assets/DocsPage-ME47KZpK.js +0 -11
  154. package/client/dist/assets/IntegrationsPage-Bs00_tl4.js +0 -1
  155. package/client/dist/assets/InteractiveJobComposer-BGjIe9R6.js +0 -19
  156. package/client/dist/assets/JobDetailModal-1UZkqH4I.js +0 -1
  157. package/client/dist/assets/JobDetailPage-CN65sAf1.js +0 -1
  158. package/client/dist/assets/JobsPage-f-4dqys7.js +0 -1
  159. package/client/dist/assets/LoopPreviewModal-DDTnTyH0.js +0 -1
  160. package/client/dist/assets/LoopsPage-BVNchIex.js +0 -1
  161. package/client/dist/assets/MinimizedChatsContext-CHsu3NCX.js +0 -1
  162. package/client/dist/assets/PluginsPage-CdFzGEfQ.js +0 -2
  163. package/client/dist/assets/TemplatePreviewModal-RDi0khFG.js +0 -1
  164. package/client/dist/assets/TicketDetailModal-MhJzrqbi.js +0 -14
  165. package/client/dist/assets/agent-Bd-q3Wqp.js +0 -1
  166. package/client/dist/assets/agent-BlvdW4Hv.js +0 -1
  167. package/client/dist/assets/agent-C-KQhxeM.js +0 -1
  168. package/client/dist/assets/agent-Ca15w5eu.js +0 -1
  169. package/client/dist/assets/agent-D6fKaoMd.js +0 -1
  170. package/client/dist/assets/agent-HFMYMTbB.js +0 -1
  171. package/client/dist/assets/agent-QIEbkBjg.js +0 -1
  172. package/client/dist/assets/agent-mTCsAbCl.js +0 -1
  173. package/client/dist/assets/api-B2M7CtDw.js +0 -1
  174. package/client/dist/assets/arrow-left-CpIR4j7G.js +0 -1
  175. package/client/dist/assets/arrow-up-right-9DoMn7fD.js +0 -1
  176. package/client/dist/assets/browser-CXUcnTMI.js +0 -1
  177. package/client/dist/assets/button-BUIIBQPT.js +0 -1
  178. package/client/dist/assets/chevron-right-CViMxucY.js +0 -1
  179. package/client/dist/assets/dashboard-BHRqsGCV.js +0 -1
  180. package/client/dist/assets/dashboard-Bfr90vOl.js +0 -1
  181. package/client/dist/assets/dashboard-CTsM8x3m.js +0 -1
  182. package/client/dist/assets/dashboard-CqC6wX0h.js +0 -1
  183. package/client/dist/assets/dashboard-CuZtAszc.js +0 -1
  184. package/client/dist/assets/dashboard-DAcqIc-n.js +0 -1
  185. package/client/dist/assets/dashboard-DekiV-86.js +0 -1
  186. package/client/dist/assets/dashboard-DoDczc5S.js +0 -1
  187. package/client/dist/assets/dialog-CzIekCw0.js +0 -45
  188. package/client/dist/assets/dist-Cez5CRkX.js +0 -1
  189. package/client/dist/assets/dist-js-BmsFWRly.js +0 -1
  190. package/client/dist/assets/dist-js-J5W_N5ng.js +0 -1
  191. package/client/dist/assets/external-link-BcU5EWZp.js +0 -1
  192. package/client/dist/assets/file-text-CFT-xXKN.js +0 -1
  193. package/client/dist/assets/i18n-nJQvD5zJ.js +0 -2
  194. package/client/dist/assets/index-C_sdsSZt.js +0 -63
  195. package/client/dist/assets/index-DCN5QbDm.css +0 -2
  196. package/client/dist/assets/jira-api-Bhixwn-d.js +0 -1
  197. package/client/dist/assets/loops-CnxqL8IL.js +0 -1
  198. package/client/dist/assets/save-28sIlCdr.js +0 -1
  199. package/client/dist/assets/settings-BveOJoaI.js +0 -1
  200. package/client/dist/assets/settings-Cj1C6F5k.js +0 -1
  201. package/client/dist/assets/settings-CuVwOpe0.js +0 -1
  202. package/client/dist/assets/settings-D5X4DfRc.js +0 -1
  203. package/client/dist/assets/settings-DXwxe-BW.js +0 -1
  204. package/client/dist/assets/settings-Di81Ba_V.js +0 -1
  205. package/client/dist/assets/settings-jbLjCYC_.js +0 -1
  206. package/client/dist/assets/settings-wIZNPFnn.js +0 -1
  207. package/client/dist/assets/useBrowserCaptureSession-BBXBN0Na.js +0 -1
  208. package/client/dist/assets/useDesktop-B6Du2N83.js +0 -1
  209. package/client/dist/assets/useSharedWebSocket-DK96ctb7.js +0 -1
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MIN_DURATION_SAMPLES = void 0;
4
+ exports.percentileMs = percentileMs;
5
+ exports.rangeFromSamples = rangeFromSamples;
6
+ exports.getLoopDurationRange = getLoopDurationRange;
7
+ exports.jobCommandShape = jobCommandShape;
8
+ exports.getJobCommandDurationRange = getJobCommandDurationRange;
9
+ /** Fewer settled runs than this and no band is returned. */
10
+ exports.MIN_DURATION_SAMPLES = 5;
11
+ /**
12
+ * Nearest-rank percentile over an ASCENDING sample. Deliberately not
13
+ * interpolated: an interpolated value is a number no run actually took, and
14
+ * every figure this module emits must be traceable to a real measurement.
15
+ */
16
+ function percentileMs(sortedAsc, fraction) {
17
+ if (sortedAsc.length === 0)
18
+ return 0;
19
+ const rank = Math.ceil(fraction * sortedAsc.length);
20
+ const index = Math.min(sortedAsc.length - 1, Math.max(0, rank - 1));
21
+ return sortedAsc[index];
22
+ }
23
+ function rangeFromSamples(samplesMs) {
24
+ const usable = samplesMs.filter((ms) => Number.isFinite(ms) && ms > 0).sort((a, b) => a - b);
25
+ if (usable.length < exports.MIN_DURATION_SAMPLES)
26
+ return null;
27
+ return {
28
+ p25Ms: percentileMs(usable, 0.25),
29
+ medianMs: percentileMs(usable, 0.5),
30
+ p75Ms: percentileMs(usable, 0.75),
31
+ sampleCount: usable.length,
32
+ };
33
+ }
34
+ /** Cap the history window so a long-lived project's ancient runs (different
35
+ * codebase, different model) cannot skew the band a user is shown today. */
36
+ const HISTORY_LIMIT = 50;
37
+ /** Measured band for a loop id, or null below the sample floor. */
38
+ function getLoopDurationRange(db, projectId, loopId) {
39
+ const rows = db.prepare(`
40
+ SELECT total_duration_ms AS ms
41
+ FROM loop_runs
42
+ WHERE project_id = ? AND loop_id = ? AND finished_at IS NOT NULL AND total_duration_ms > 0
43
+ ORDER BY started_at DESC
44
+ LIMIT ?
45
+ `).all(projectId, loopId, HISTORY_LIMIT);
46
+ return rangeFromSamples(rows.map((row) => row.ms));
47
+ }
48
+ /**
49
+ * Commands carry per-run arguments (`loop:<uuid>`, `/specrails:implement #12`),
50
+ * so history is grouped by a stable SHAPE rather than the literal string:
51
+ * the command's leading token with ticket refs and ids stripped.
52
+ */
53
+ function jobCommandShape(command) {
54
+ const head = command.trim().split(/\s+/)[0] ?? '';
55
+ if (head.startsWith('loop:'))
56
+ return 'loop:';
57
+ return head;
58
+ }
59
+ /** Measured band for a job command shape, or null below the sample floor. */
60
+ function getJobCommandDurationRange(db, commandShape) {
61
+ const rows = db.prepare(`
62
+ SELECT duration_ms AS ms, command
63
+ FROM jobs
64
+ WHERE status = 'completed' AND duration_ms > 0
65
+ ORDER BY started_at DESC
66
+ LIMIT 500
67
+ `).all();
68
+ return rangeFromSamples(rows.filter((row) => jobCommandShape(row.command ?? '') === commandShape)
69
+ .slice(0, HISTORY_LIMIT)
70
+ .map((row) => row.ms));
71
+ }
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StuckRunDetector = exports.STUCK_SWEEP_INTERVAL_MS = exports.STUCK_FLOOR_MS = void 0;
4
+ exports.resolveStuckThresholdMs = resolveStuckThresholdMs;
5
+ exports.findStuckCandidates = findStuckCandidates;
6
+ const loop_runs_store_1 = require("./loop-runs-store");
7
+ /** Never notify sooner than this, however the threshold is configured. */
8
+ exports.STUCK_FLOOR_MS = 10 * 60 * 1000;
9
+ /** How often the sweep runs while the server is up. */
10
+ exports.STUCK_SWEEP_INTERVAL_MS = 60 * 1000;
11
+ /**
12
+ * `SPECRAILS_STUCK_THRESHOLD_MS` raises (never lowers) the floor; `0`/`false`/
13
+ * `off` disables detection entirely.
14
+ */
15
+ function resolveStuckThresholdMs(env = process.env) {
16
+ const raw = env.SPECRAILS_STUCK_THRESHOLD_MS;
17
+ if (raw === undefined || raw === '')
18
+ return exports.STUCK_FLOOR_MS;
19
+ const normalized = raw.trim().toLowerCase();
20
+ if (normalized === '0' || normalized === 'false' || normalized === 'off')
21
+ return null;
22
+ const parsed = Number(normalized);
23
+ if (!Number.isFinite(parsed) || parsed <= 0)
24
+ return exports.STUCK_FLOOR_MS;
25
+ return Math.max(exports.STUCK_FLOOR_MS, Math.trunc(parsed));
26
+ }
27
+ function lastActivityMs(payload) {
28
+ const candidates = [payload.lastActivityAtMs, payload.activeTurnStartedAtMs]
29
+ .filter((value) => Number.isFinite(value) && value > 0);
30
+ return candidates.length > 0 ? Math.max(...candidates) : null;
31
+ }
32
+ function defaultIsRunActive(db, runId) {
33
+ const row = db.prepare(`SELECT status FROM loop_runs WHERE id = ?`).get(runId);
34
+ return row?.status === 'running';
35
+ }
36
+ /**
37
+ * One pass over the persisted step checkpoints. Pure with respect to the
38
+ * database (reads only) so the caller owns broadcasting and episode memory.
39
+ */
40
+ function findStuckCandidates(io) {
41
+ const threshold = io.thresholdMs === undefined ? resolveStuckThresholdMs() : io.thresholdMs;
42
+ if (threshold === null)
43
+ return [];
44
+ const now = io.now?.() ?? Date.now();
45
+ const isRunActive = io.isRunActive ?? ((runId) => defaultIsRunActive(io.db, runId));
46
+ const out = [];
47
+ let rows;
48
+ try {
49
+ rows = (0, loop_runs_store_1.listLoopStepRecoveries)(io.db);
50
+ }
51
+ catch {
52
+ return [];
53
+ }
54
+ for (const row of rows) {
55
+ let payload;
56
+ try {
57
+ payload = JSON.parse(row.payload);
58
+ }
59
+ catch {
60
+ continue;
61
+ }
62
+ const last = lastActivityMs(payload);
63
+ if (last === null)
64
+ continue;
65
+ const staleMs = now - last;
66
+ if (staleMs < threshold)
67
+ continue;
68
+ try {
69
+ if (!isRunActive(row.run_id))
70
+ continue;
71
+ }
72
+ catch {
73
+ continue;
74
+ }
75
+ out.push({
76
+ runId: row.run_id,
77
+ stepKey: row.step_key,
78
+ projectId: payload.projectId,
79
+ staleMs,
80
+ episode: `${row.run_id}:${row.step_key}:${last}`,
81
+ });
82
+ }
83
+ return out;
84
+ }
85
+ /**
86
+ * Owns episode memory across sweeps and emits at most one message per stall.
87
+ * Constructed per project context so the broadcast is already project-scoped.
88
+ */
89
+ class StuckRunDetector {
90
+ projectId;
91
+ io;
92
+ broadcast;
93
+ notified = new Set();
94
+ timer = null;
95
+ constructor(projectId, io, broadcast) {
96
+ this.projectId = projectId;
97
+ this.io = io;
98
+ this.broadcast = broadcast;
99
+ }
100
+ /** One sweep; returns the messages actually emitted (for tests/diagnostics). */
101
+ sweep() {
102
+ const candidates = findStuckCandidates(this.io);
103
+ const live = new Set(candidates.map((candidate) => candidate.episode));
104
+ // Forget episodes that ended so a later stall of the same step re-notifies.
105
+ for (const episode of [...this.notified]) {
106
+ if (!live.has(episode))
107
+ this.notified.delete(episode);
108
+ }
109
+ const emitted = [];
110
+ for (const candidate of candidates) {
111
+ if (this.notified.has(candidate.episode))
112
+ continue;
113
+ this.notified.add(candidate.episode);
114
+ const message = {
115
+ type: 'job.stuck',
116
+ projectId: this.projectId,
117
+ jobId: candidate.runId,
118
+ stepKey: candidate.stepKey,
119
+ staleMs: candidate.staleMs,
120
+ timestamp: new Date(this.io.now?.() ?? Date.now()).toISOString(),
121
+ };
122
+ try {
123
+ this.broadcast(message);
124
+ emitted.push(message);
125
+ }
126
+ catch {
127
+ // A failed broadcast must not wedge the sweep; the next pass retries
128
+ // because the episode is only remembered after a successful send.
129
+ this.notified.delete(candidate.episode);
130
+ }
131
+ }
132
+ return emitted;
133
+ }
134
+ start(intervalMs = exports.STUCK_SWEEP_INTERVAL_MS) {
135
+ if (this.timer)
136
+ return;
137
+ if ((this.io.thresholdMs === undefined ? resolveStuckThresholdMs() : this.io.thresholdMs) === null)
138
+ return;
139
+ this.timer = setInterval(() => {
140
+ try {
141
+ this.sweep();
142
+ }
143
+ catch { /* never let a sweep crash the server */ }
144
+ }, intervalMs);
145
+ this.timer.unref?.();
146
+ }
147
+ stop() {
148
+ if (this.timer)
149
+ clearInterval(this.timer);
150
+ this.timer = null;
151
+ }
152
+ }
153
+ exports.StuckRunDetector = StuckRunDetector;
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.awaitTelemetryAppendQuiescence = awaitTelemetryAppendQuiescence;
6
7
  exports.dropBlobState = dropBlobState;
7
8
  exports.dropBlobStatesForProject = dropBlobStatesForProject;
8
9
  exports.createTelemetryRouter = createTelemetryRouter;
@@ -35,6 +36,27 @@ function getBlobState(key) {
35
36
  function blobKey(projectId, jobId) {
36
37
  return `${projectId}:${jobId}`;
37
38
  }
39
+ /**
40
+ * Await full append-queue quiescence (every blob: idle writer + empty queue).
41
+ * Test seam: the ingest handler ACKs 200 while queued gzip appends are still
42
+ * in flight; a suite that removes the telemetry dir in afterEach races them —
43
+ * the orphaned append then console.errors DURING vitest environment teardown
44
+ * ("Closing rpc while onUserConsoleLog was pending", flaked in CI 2026-07-27).
45
+ * Bounded so a wedged writer can never hang a suite.
46
+ */
47
+ async function awaitTelemetryAppendQuiescence(maxWaitMs = 2000) {
48
+ const deadline = Date.now() + maxWaitMs;
49
+ const busy = () => {
50
+ for (const state of _blobState.values()) {
51
+ if (state.writing || state.queue.length > 0)
52
+ return true;
53
+ }
54
+ return false;
55
+ };
56
+ while (busy() && Date.now() < deadline) {
57
+ await new Promise((resolve) => setTimeout(resolve, 5));
58
+ }
59
+ }
38
60
  /** Evict the in-memory BlobState for one job (bounds the per-(project,job) leak —
39
61
  * the map otherwise grew for the whole process lifetime). */
40
62
  function dropBlobState(projectId, jobId) {
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.isWorktreeNodeModulesEnabled = isWorktreeNodeModulesEnabled;
37
+ exports.authenticateWarmNodeModulesLinks = authenticateWarmNodeModulesLinks;
37
38
  exports.linkNodeModulesIntoWorktree = linkNodeModulesIntoWorktree;
38
39
  /**
39
40
  * Warm-dependency reuse for isolated rail worktrees.
@@ -57,10 +58,26 @@ exports.linkNodeModulesIntoWorktree = linkNodeModulesIntoWorktree;
57
58
  * prior link, anything) is left untouched, so resume passes are idempotent
58
59
  * and an agent-made install is never clobbered.
59
60
  *
60
- * The returned `linked` paths are appended to the launch's overlay-exclusion
61
- * list, so a linked `node_modules` inherits the same commit-time guarantees as
62
- * overlay scaffolding (excluded pathspecs + index audit) even if the repo's
63
- * `.gitignore` were ever missing the entry.
61
+ * The returned `authenticated` paths are appended to the launch's overlay-
62
+ * exclusion list, so a linked `node_modules` inherits the same commit-time
63
+ * guarantees as overlay scaffolding (excluded pathspecs + index audit) even if
64
+ * the repo's `.gitignore` were ever missing the entry.
65
+ *
66
+ * Release safety (why `authenticated` exists next to `linked`): a repo whose
67
+ * `.gitignore` carries the ordinary `node_modules/` DIRECTORY pattern does not
68
+ * ignore a SYMLINK named `node_modules`, so the link surfaces as an untracked
69
+ * entry in `git status`. Without evidence authorizing it, worktree release read
70
+ * that as "the worktree contains changes made after settlement", parked the row
71
+ * at `needs-review` forever and permanently blocked checkout of the branch. The
72
+ * links therefore also carry `OverlayCleanupEvidence`, and a link created by an
73
+ * EARLIER pass (resume) is re-authenticated instead of being ignored — the old
74
+ * `linked`-only contract silently dropped both guarantees on resume.
75
+ *
76
+ * Authentication is deliberately narrow and source-anchored, never name-based:
77
+ * the worktree entry must be a symlink whose resolved target is exactly the base
78
+ * checkout's identically-named dependency directory. A real directory, a copy,
79
+ * or a link pointing anywhere else stays unauthorized and preserves the
80
+ * worktree, as `Recoverable work is never removed automatically` requires.
64
81
  *
65
82
  * Honest caveat (documented, accepted): the link SHARES the dependency tree
66
83
  * with the base checkout. Reads (tests, builds) are safe under concurrency; an
@@ -72,8 +89,11 @@ exports.linkNodeModulesIntoWorktree = linkNodeModulesIntoWorktree;
72
89
  */
73
90
  const fs = __importStar(require("fs"));
74
91
  const path = __importStar(require("path"));
92
+ const worktree_overlay_1 = require("./worktree-overlay");
75
93
  /** Package-dir discovery depth: 0 = repo root, 1 = `client/`-style children. */
76
94
  const MAX_DEPTH = 2;
95
+ /** Name of the dependency directory this module links. */
96
+ const DEPS_DIR = 'node_modules';
77
97
  function isWorktreeNodeModulesEnabled() {
78
98
  return (process.env.SPECRAILS_WORKTREE_NODE_MODULES ?? '').toLowerCase() !== 'false';
79
99
  }
@@ -127,31 +147,133 @@ function discoverPackageDirs(baseRepo) {
127
147
  walk('', 0);
128
148
  return found;
129
149
  }
150
+ /** Resolve a path through symlinks when possible; the literal path otherwise
151
+ * (a nonexistent target must still compare, and must still compare EQUAL to an
152
+ * equally-nonexistent expectation rather than silently authorizing). */
153
+ function resolveRealPath(target) {
154
+ try {
155
+ return fs.realpathSync(target);
156
+ }
157
+ catch {
158
+ return path.resolve(target);
159
+ }
160
+ }
161
+ /**
162
+ * Prove one worktree-relative path is an app-created warm-dependency link and
163
+ * fingerprint it. Returns null for anything that is not EXACTLY a symlink whose
164
+ * target resolves to the base checkout's identically-named directory — a real
165
+ * directory, a dereferencing copy, a dangling link, or a link into some other
166
+ * tree are all unauthorized and must keep preserving the worktree.
167
+ */
168
+ function authenticateWarmLink(baseRepo, worktreePath, rel) {
169
+ const dest = path.join(worktreePath, ...rel.split('/'));
170
+ try {
171
+ if (!fs.lstatSync(dest).isSymbolicLink())
172
+ return null;
173
+ const target = path.resolve(path.dirname(dest), fs.readlinkSync(dest));
174
+ const expected = path.join(baseRepo, ...rel.split('/'));
175
+ if (resolveRealPath(target) !== resolveRealPath(expected))
176
+ return null;
177
+ // A link to something that is not a live directory proves nothing about the
178
+ // base checkout's dependency tree.
179
+ if (!isDir(target))
180
+ return null;
181
+ }
182
+ catch {
183
+ return null;
184
+ }
185
+ const fingerprint = (0, worktree_overlay_1.fingerprintOverlayCleanupPath)(dest);
186
+ return fingerprint ? { path: rel, ...fingerprint } : null;
187
+ }
188
+ /** Worktree-relative POSIX paths of every `node_modules` entry the worktree
189
+ * holds at depth ≤ MAX_DEPTH. Mirrors the linker's own discovery depth, so it
190
+ * can only ever see paths this module could have created. Recursion uses lstat
191
+ * so a symlinked directory is never walked into. */
192
+ function discoverWorktreeDependencyPaths(worktreePath) {
193
+ const found = [];
194
+ const walk = (rel, depth) => {
195
+ const abs = rel === '' ? worktreePath : path.join(worktreePath, rel);
196
+ let names;
197
+ try {
198
+ names = fs.readdirSync(abs);
199
+ }
200
+ catch {
201
+ return;
202
+ }
203
+ for (const name of names) {
204
+ const childRel = rel === '' ? name : `${rel}/${name}`;
205
+ if (name === DEPS_DIR) {
206
+ found.push(childRel);
207
+ continue;
208
+ }
209
+ if (depth >= MAX_DEPTH || name.startsWith('.'))
210
+ continue;
211
+ try {
212
+ if (!fs.lstatSync(path.join(abs, name)).isDirectory())
213
+ continue;
214
+ }
215
+ catch {
216
+ continue;
217
+ }
218
+ walk(childRel, depth + 1);
219
+ }
220
+ };
221
+ walk('', 0);
222
+ return found;
223
+ }
224
+ /**
225
+ * Live authentication of the warm-dependency links a worktree currently holds.
226
+ *
227
+ * Deriving this from the filesystem (instead of only from persisted evidence)
228
+ * is what heals worktrees that settled BEFORE the links carried evidence: they
229
+ * are stuck at `needs-review` with the link as their only "dirt", and a
230
+ * persisted-only fix would never reach them. The proof is identical either way
231
+ * — the link target anchored to a directory the app controls.
232
+ */
233
+ function authenticateWarmNodeModulesLinks(baseRepo, worktreePath) {
234
+ const evidence = [];
235
+ for (const rel of discoverWorktreeDependencyPaths(worktreePath)) {
236
+ const entry = authenticateWarmLink(baseRepo, worktreePath, rel);
237
+ if (entry)
238
+ evidence.push(entry);
239
+ }
240
+ return evidence;
241
+ }
130
242
  /**
131
243
  * Link the base checkout's installed `node_modules` trees into a fresh
132
244
  * worktree. Best-effort and side-effect-transparent: never throws, never
133
- * overwrites, returns what it created so the caller can exclude those paths
134
- * from commits.
245
+ * overwrites, and returns both what it created and what it can PROVE is a warm
246
+ * link (including links a previous pass created), so the caller can exclude
247
+ * those paths from commits and authorize them for release.
135
248
  */
136
249
  function linkNodeModulesIntoWorktree(baseRepo, worktreePath) {
137
- const result = { linked: [], warnings: [] };
250
+ const result = { linked: [], authenticated: [], evidence: [], warnings: [] };
138
251
  if (!isWorktreeNodeModulesEnabled())
139
252
  return result;
140
253
  for (const pkgRel of discoverPackageDirs(baseRepo)) {
141
- const rel = pkgRel === '' ? 'node_modules' : `${pkgRel}/node_modules`;
254
+ const rel = pkgRel === '' ? DEPS_DIR : `${pkgRel}/${DEPS_DIR}`;
142
255
  const src = path.join(baseRepo, ...rel.split('/'));
143
256
  if (!isDir(src))
144
257
  continue;
145
258
  const dest = path.join(worktreePath, ...rel.split('/'));
146
- if (exists(dest))
147
- continue;
148
- try {
149
- fs.mkdirSync(path.dirname(dest), { recursive: true });
150
- fs.symlinkSync(src, dest, process.platform === 'win32' ? 'junction' : undefined);
151
- result.linked.push(rel);
259
+ if (!exists(dest)) {
260
+ try {
261
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
262
+ fs.symlinkSync(src, dest, process.platform === 'win32' ? 'junction' : undefined);
263
+ result.linked.push(rel);
264
+ }
265
+ catch (err) {
266
+ result.warnings.push(`failed to link ${rel}: ${errMsg(err)}`);
267
+ continue;
268
+ }
152
269
  }
153
- catch (err) {
154
- result.warnings.push(`failed to link ${rel}: ${errMsg(err)}`);
270
+ // Authenticate what is actually on disk NOW — a link we just made, or one a
271
+ // previous pass made. An entry that cannot prove itself (a real dir, an
272
+ // agent-made install) is deliberately left unauthorized.
273
+ const entry = authenticateWarmLink(baseRepo, worktreePath, rel);
274
+ if (entry) {
275
+ result.authenticated.push(rel);
276
+ result.evidence.push(entry);
155
277
  }
156
278
  }
157
279
  return result;
@@ -1 +0,0 @@
1
- import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{n as t,t as n}from"./jsx-runtime-zu2_FqZY.js";import{o as r}from"./i18n-nJQvD5zJ.js";import{n as i,t as a}from"./createLucideIcon-Bjbbp_KS.js";import{t as o}from"./circle-x-CMYsN4ZH.js";import{t as s}from"./loader-circle-DcygOQpC.js";import{t as c}from"./zap-CHo3aCD1.js";import{r as l}from"./useSharedWebSocket-DK96ctb7.js";import{t as u}from"./api-B2M7CtDw.js";import{r as d}from"./useDesktop-B6Du2N83.js";import{Dt as f,Pt as p}from"./index-C_sdsSZt.js";var m=a(`ban`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`M4.929 4.929 19.07 19.071`,key:`196cmz`}]]),h=e(t(),1);function g(e){let t=new Set;return e.filter(e=>{let n=`${e.type}:${e.jobId}`;return t.has(n)?!1:(t.add(n),!0)})}function _({activeProjectId:e,limit:t=50}){let[n,i]=(0,h.useState)([]),[a,o]=(0,h.useState)(!1),[s,c]=(0,h.useState)(!0),d=(0,h.useRef)(e);(0,h.useEffect)(()=>{d.current=e},[e]);let{registerHandler:f,unregisterHandler:p}=l();(0,h.useEffect)(()=>{if(!e){i([]),c(!0);return}let n=!1;o(!0),i([]),c(!0);async function r(){let e=u();try{let r=await fetch(`${e}/activity?limit=${t}`);if(!r.ok||n)return;let a=await r.json();if(n)return;i(a),c(a.length===t)}catch{}finally{n||o(!1)}}return r(),()=>{n=!0}},[e,t]);let m=(0,h.useCallback)(async()=>{if(a||!s)return;o(!0);let e=u(),n=d.current;try{i(r=>{let a=r[r.length-1];if(!a)return r;let s=encodeURIComponent(a.timestamp);return fetch(`${e}/activity?limit=${t}&before=${s}`).then(e=>e.json()).then(e=>{if(d.current!==n){o(!1);return}i(t=>g([...t,...e])),c(e.length===t),o(!1)}).catch(()=>o(!1)),r})}catch{o(!1)}},[a,s,t]),_=(0,h.useCallback)(e=>{let t=e,n=d.current;if(!(!t||typeof t.type!=`string`)&&!(t.projectId&&t.projectId!==n)){if(t.type===`queue`&&Array.isArray(t.jobs)){let e=[];for(let n of t.jobs){let t=n.status===`completed`?`job_completed`:n.status===`failed`?`job_failed`:n.status===`canceled`?`job_canceled`:`job_started`;e.push({id:`${t}:${n.id}`,type:t,jobId:n.id,jobCommand:n.command??``,timestamp:n.startedAt??new Date().toISOString(),summary:`${t.replace(`_`,` `)}: ${n.command??``}`,costUsd:null})}e.length>0&&i(t=>g([...e,...t]))}if(t.type===`phase`&&t.phase&&t.state&&t.timestamp){let e={id:`phase:${t.phase}:${t.state}:${t.timestamp}`,type:`job_started`,jobId:`phase-${t.phase}`,jobCommand:r.t(`activity:feed.phaseCommand`,{phase:t.phase,state:t.state}),timestamp:t.timestamp,summary:`Phase ${t.phase} is ${t.state}`,costUsd:null};i(t=>g([e,...t]))}}},[]);return(0,h.useLayoutEffect)(()=>(f(`activity`,_),()=>p(`activity`)),[_,f,p]),{items:n,loading:a,hasMore:s,loadMore:m}}var v=n();function y(e,t){let n=Date.now()-new Date(e).getTime(),r=Math.floor(n/1e3);if(r<60)return t(`feed.relativeTime.seconds`,{value:r});let i=Math.floor(r/60);if(i<60)return t(`feed.relativeTime.minutes`,{value:i});let a=Math.floor(i/60);return a<24?t(`feed.relativeTime.hours`,{value:a}):t(`feed.relativeTime.days`,{value:Math.floor(a/24)})}function b({type:e}){switch(e){case`job_completed`:return(0,v.jsx)(f,{className:`w-4 h-4 text-green-500 aurora-light:text-accent-success flex-shrink-0`});case`job_failed`:return(0,v.jsx)(o,{className:`w-4 h-4 text-red-500 aurora-light:text-destructive flex-shrink-0`});case`job_canceled`:return(0,v.jsx)(m,{className:`w-4 h-4 text-muted-foreground flex-shrink-0`});default:return(0,v.jsx)(c,{className:`w-4 h-4 text-blue-500 aurora-light:text-accent-info flex-shrink-0`})}}function x(e,t){switch(e){case`job_completed`:return t(`common:status.completed`);case`job_failed`:return t(`common:status.failed`);case`job_canceled`:return t(`common:status.canceled`);default:return t(`feed.typeStarted`)}}function S(e){switch(e){case`job_completed`:return`text-green-500 aurora-light:text-accent-success`;case`job_failed`:return`text-red-500 aurora-light:text-destructive`;case`job_canceled`:return`text-muted-foreground`;default:return`text-blue-500 aurora-light:text-accent-info`}}function C(){let{t:e}=i(`activity`),{activeProjectId:t}=d(),{items:n,loading:r,hasMore:a,loadMore:o}=_({activeProjectId:t}),c=(0,h.useRef)(null);return(0,h.useEffect)(()=>{let e=c.current;if(!e)return;let t=new IntersectionObserver(e=>{e[0].isIntersecting&&a&&!r&&o()},{threshold:.1});return t.observe(e),()=>t.disconnect()},[a,r,o]),(0,v.jsxs)(`div`,{className:`flex flex-col h-full overflow-hidden`,children:[(0,v.jsxs)(`div`,{className:`flex items-center gap-2 px-4 py-3 border-b border-border bg-background/50`,children:[(0,v.jsx)(p,{className:`w-4 h-4 text-muted-foreground`}),(0,v.jsx)(`h1`,{className:`text-sm font-medium`,children:e(`feed.title`)})]}),(0,v.jsxs)(`div`,{className:`flex-1 overflow-y-auto`,children:[r&&n.length===0?(0,v.jsx)(`div`,{className:`flex items-center justify-center h-32`,children:(0,v.jsx)(s,{className:`w-4 h-4 animate-spin text-muted-foreground`})}):n.length===0?(0,v.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-48 gap-2 text-muted-foreground`,children:[(0,v.jsx)(p,{className:`w-8 h-8 opacity-40`}),(0,v.jsx)(`p`,{className:`text-sm`,children:e(`feed.emptyTitle`)}),(0,v.jsx)(`p`,{className:`text-xs opacity-70`,children:e(`feed.emptyHint`)})]}):(0,v.jsx)(`ul`,{className:`divide-y divide-border/50`,children:n.map(t=>(0,v.jsxs)(`li`,{className:`flex items-center gap-3 px-4 py-2.5 hover:bg-accent/30 transition-colors`,children:[(0,v.jsx)(b,{type:t.type}),(0,v.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,v.jsx)(`p`,{className:`text-xs text-foreground truncate`,title:t.jobCommand,children:t.jobCommand}),(0,v.jsxs)(`div`,{className:`flex items-center gap-2 mt-0.5`,children:[(0,v.jsx)(`span`,{className:`text-xs font-medium ${S(t.type)}`,children:x(t.type,e)}),t.costUsd!=null&&(0,v.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[`$`,t.costUsd.toFixed(4)]})]})]}),(0,v.jsx)(`span`,{className:`text-xs text-muted-foreground flex-shrink-0 tabular-nums`,children:y(t.timestamp,e)})]},`${t.type}:${t.jobId}`))}),(0,v.jsx)(`div`,{ref:c,className:`h-1`}),r&&n.length>0&&(0,v.jsx)(`div`,{className:`flex justify-center py-3`,children:(0,v.jsx)(s,{className:`w-4 h-4 animate-spin text-muted-foreground`})}),!a&&n.length>0&&(0,v.jsx)(`p`,{className:`text-center text-xs text-muted-foreground py-3`,children:e(`feed.allLoaded`)})]})]})}export{C as default};
@@ -1 +0,0 @@
1
- import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{n as t,t as n}from"./jsx-runtime-zu2_FqZY.js";import{n as r}from"./createLucideIcon-Bjbbp_KS.js";import{n as i}from"./dist-bVqfYmby.js";import{D as a,i as o,l as s}from"./index-C_sdsSZt.js";var c=e(t(),1);function l(e,t){let n=e.indexOf(`,`);if(!e.startsWith(`data:`)||n===-1)throw Error(`dataUrlToFile: not a data URL`);let r=e.slice(0,n),i=/^data:([^;,]+)/.exec(r)?.[1]||`application/octet-stream`,a=e.slice(n+1);if(/;base64$/i.test(r)){let e=atob(a),n=new Uint8Array(e.length);for(let t=0;t<e.length;t++)n[t]=e.charCodeAt(t);return new File([n],t,{type:i})}return new File([decodeURIComponent(a)],t,{type:i})}var u=n();function d({projectId:e,conversationId:t}){let{t:n}=r(`agent`),{closeBrowser:d,queueCapture:f}=o(),p=(0,c.useMemo)(()=>`agent-${t??`home`}-${Math.round(performance.now())}`,[t]),m=async e=>{if(!t){i.error(n(`workspace.requiresConversation`));return}try{let r=await s(t,l(e.screenshotDataUrl,`capture-${Date.now()}.png`));f(r),i.success(n(`workspace.browserCaptured`)),d()}catch(e){i.error(n(`workspace.uploadFailed`),{description:e instanceof Error?e.message:void 0})}};return(0,u.jsx)(a,{open:!0,onClose:d,projectId:e,pendingSpecId:p,confirmLabel:n(`workspace.browserConfirm`),onCaptured:e=>{m(e)}})}export{d as AgentBrowserCapture};
@@ -1,2 +0,0 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/AnalyticsPage-C0LwYELz.js","assets/rolldown-runtime-CNC7AqOf.js","assets/jsx-runtime-zu2_FqZY.js","assets/chunk-KS7C4IRE-Cx5sqopX.js","assets/preload-helper-Czpn1I53.js","assets/i18n-nJQvD5zJ.js","assets/createLucideIcon-Bjbbp_KS.js","assets/activity-DiyjsgQV.js","assets/addspec-D2YIQ0GU.js","assets/agent-mTCsAbCl.js","assets/agents-ms7TwG16.js","assets/agentstudio-CN2pokQu.js","assets/aiedit-iIjz2_Qk.js","assets/analytics-BMzCvrCU.js","assets/attachments-B53PG733.js","assets/browser-CXUcnTMI.js","assets/builder-CHuahItu.js","assets/chat-BGD7MXAy.js","assets/code-CHPkOkDS.js","assets/commands-DyUZQ1FI.js","assets/common-DHsv9N5d.js","assets/dashboard-CTsM8x3m.js","assets/explore-DgBACn39.js","assets/integrations-mB-7zoyT.js","assets/jira-CKJg3HZd.js","assets/jobs-B_TeSQxb.js","assets/loops-CnxqL8IL.js","assets/mcp-uDkXYu4L.js","assets/nav-DjkmQJjd.js","assets/settings-CuVwOpe0.js","assets/setup-3jWaoVUO.js","assets/specs-DII0C8gw.js","assets/terminal-BFRZnMPP.js","assets/tickets-W6pBmbrX.js","assets/Trans-CTqw6rr9.js","assets/arrow-up-right-9DoMn7fD.js","assets/useSharedWebSocket-DK96ctb7.js","assets/auth-D_5bQv5W.js","assets/api-B2M7CtDw.js","assets/useDesktop-B6Du2N83.js","assets/dist-bVqfYmby.js","assets/react-dom-DiWzBDP9.js","assets/clsx-CjueKrWZ.js","assets/provider-capabilities-Doej9lfa.js","assets/formatDistanceToNow-BOuXgebD.js","assets/getTimezoneOffsetInMilliseconds-EjGOpn4b.js","assets/spending-BpYPsevQ.js","assets/index-C_sdsSZt.js","assets/arrow-left-CpIR4j7G.js","assets/useBrowserCaptureSession-BBXBN0Na.js","assets/MinimizedChatsContext-CHsu3NCX.js","assets/sparkles-DrY8hb0X.js","assets/x-CBMI9ejn.js","assets/external-link-BcU5EWZp.js","assets/chevron-right-CViMxucY.js","assets/TicketDetailModal-MhJzrqbi.js","assets/circle-x-CMYsN4ZH.js","assets/download-CFHyyHcw.js","assets/file-text-CFT-xXKN.js","assets/git-branch-BRM0F5GN.js","assets/loader-circle-DcygOQpC.js","assets/pencil-C9RkrIPZ.js","assets/play-DNlrawbS.js","assets/plus-CsnjfkmX.js","assets/save-28sIlCdr.js","assets/search-Cq_gE3Ik.js","assets/trash-2-BOU3WdZZ.js","assets/triangle-alert-H9T4jAmh.js","assets/zap-CHo3aCD1.js","assets/button-BUIIBQPT.js","assets/dist-Cez5CRkX.js","assets/utils-BC4DHlz2.js","assets/dialog-CzIekCw0.js","assets/jira-api-Bhixwn-d.js","assets/lib-CGdyxIWg.js","assets/index-DCN5QbDm.css","assets/ExportDropdown-BrtfWzEG.js","assets/BarChart-nBOMNfiy.js","assets/value-s-gzBeyF.js"])))=>i.map(i=>d[i]);
2
- import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{n as t,t as n}from"./jsx-runtime-zu2_FqZY.js";import{t as r}from"./preload-helper-Czpn1I53.js";import{n as i}from"./createLucideIcon-Bjbbp_KS.js";import{t as a}from"./loader-circle-DcygOQpC.js";import{t as o}from"./x-CBMI9ejn.js";import{t as s}from"./utils-BC4DHlz2.js";import{dt as c,ft as l,i as u}from"./index-C_sdsSZt.js";var d=e(t(),1),f=n(),p=(0,d.lazy)(()=>r(()=>import(`./AnalyticsPage-C0LwYELz.js`),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78]))),m=420,h=560;function g(){let{t:e}=i(`agent`),{closeAnalyticsPane:t}=u(),[n,r]=(0,d.useState)(h),[g,_]=(0,d.useState)(!1),v=(0,d.useCallback)(e=>{let t=e.clientX,i=n,a=window.innerWidth-m;try{e.currentTarget.setPointerCapture(e.pointerId)}catch{}let o=e=>{let n=Math.min(Math.max(i+(t-e.clientX),m),a);r(n)},s=()=>{window.removeEventListener(`pointermove`,o),window.removeEventListener(`pointerup`,s),window.removeEventListener(`pointercancel`,s)};window.addEventListener(`pointermove`,o),window.addEventListener(`pointerup`,s),window.addEventListener(`pointercancel`,s)},[n]);return(0,f.jsxs)(`div`,{className:s(`flex h-full flex-col overflow-hidden border-l border-border bg-background`,g&&`absolute inset-0 z-30 border-l-0`),style:g?void 0:{width:n,flexShrink:0},"data-testid":`agent-analytics-pane`,children:[(0,f.jsx)(`div`,{className:`relative flex items-center`,children:!g&&(0,f.jsx)(`div`,{role:`separator`,"aria-orientation":`vertical`,onPointerDown:v,className:`absolute left-0 top-0 h-full w-1.5 -translate-x-full cursor-col-resize select-none touch-none hover:bg-accent-primary/20`,title:e(`workspace.resizeAnalytics`)})}),(0,f.jsxs)(`div`,{className:`flex items-center gap-2 border-b border-border/60 bg-surface/40 px-3 py-1.5`,children:[(0,f.jsx)(`span`,{className:`text-xs font-medium text-foreground/70`,children:e(`workspace.analytics`)}),(0,f.jsxs)(`div`,{className:`ml-auto flex items-center gap-1`,children:[(0,f.jsx)(`button`,{type:`button`,onClick:()=>_(e=>!e),"aria-label":e(g?`restore`:`maximize`),title:e(g?`restore`:`maximize`),className:`rounded-md p-1 text-foreground/60 hover:bg-surface hover:text-foreground`,children:g?(0,f.jsx)(c,{className:`h-4 w-4`}):(0,f.jsx)(l,{className:`h-4 w-4`})}),(0,f.jsx)(`button`,{type:`button`,onClick:t,"aria-label":e(`workspace.closeAnalytics`),title:e(`workspace.closeAnalytics`),className:`rounded-md p-1 text-foreground/60 hover:bg-surface hover:text-foreground`,children:(0,f.jsx)(o,{className:`h-4 w-4`})})]})]}),(0,f.jsx)(`div`,{className:`min-h-0 flex-1 overflow-y-auto`,children:(0,f.jsx)(d.Suspense,{fallback:(0,f.jsx)(`div`,{className:`flex h-24 items-center justify-center text-muted-foreground`,children:(0,f.jsx)(a,{className:`h-4 w-4 animate-spin`})}),children:(0,f.jsx)(p,{})})})]})}export{g as AgentModeAnalyticsPane};
@@ -1,2 +0,0 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/CodePage-RtKvHmAO.js","assets/rolldown-runtime-CNC7AqOf.js","assets/jsx-runtime-zu2_FqZY.js","assets/react-dom-DiWzBDP9.js","assets/preload-helper-Czpn1I53.js","assets/chunk-KS7C4IRE-Cx5sqopX.js","assets/createLucideIcon-Bjbbp_KS.js","assets/dist-bVqfYmby.js","assets/useBrowserCaptureSession-BBXBN0Na.js","assets/i18n-nJQvD5zJ.js","assets/activity-DiyjsgQV.js","assets/addspec-D2YIQ0GU.js","assets/agent-mTCsAbCl.js","assets/agents-ms7TwG16.js","assets/agentstudio-CN2pokQu.js","assets/aiedit-iIjz2_Qk.js","assets/analytics-BMzCvrCU.js","assets/attachments-B53PG733.js","assets/browser-CXUcnTMI.js","assets/builder-CHuahItu.js","assets/chat-BGD7MXAy.js","assets/code-CHPkOkDS.js","assets/commands-DyUZQ1FI.js","assets/common-DHsv9N5d.js","assets/dashboard-CTsM8x3m.js","assets/explore-DgBACn39.js","assets/integrations-mB-7zoyT.js","assets/jira-CKJg3HZd.js","assets/jobs-B_TeSQxb.js","assets/loops-CnxqL8IL.js","assets/mcp-uDkXYu4L.js","assets/nav-DjkmQJjd.js","assets/settings-CuVwOpe0.js","assets/setup-3jWaoVUO.js","assets/specs-DII0C8gw.js","assets/terminal-BFRZnMPP.js","assets/tickets-W6pBmbrX.js","assets/auth-D_5bQv5W.js","assets/api-B2M7CtDw.js","assets/MinimizedChatsContext-CHsu3NCX.js","assets/sparkles-DrY8hb0X.js","assets/x-CBMI9ejn.js","assets/useSharedWebSocket-DK96ctb7.js","assets/useDesktop-B6Du2N83.js","assets/external-link-BcU5EWZp.js","assets/chevron-right-CViMxucY.js","assets/TicketDetailModal-MhJzrqbi.js","assets/Trans-CTqw6rr9.js","assets/arrow-left-CpIR4j7G.js","assets/circle-x-CMYsN4ZH.js","assets/download-CFHyyHcw.js","assets/file-text-CFT-xXKN.js","assets/git-branch-BRM0F5GN.js","assets/loader-circle-DcygOQpC.js","assets/pencil-C9RkrIPZ.js","assets/play-DNlrawbS.js","assets/plus-CsnjfkmX.js","assets/save-28sIlCdr.js","assets/search-Cq_gE3Ik.js","assets/trash-2-BOU3WdZZ.js","assets/triangle-alert-H9T4jAmh.js","assets/zap-CHo3aCD1.js","assets/button-BUIIBQPT.js","assets/dist-Cez5CRkX.js","assets/clsx-CjueKrWZ.js","assets/utils-BC4DHlz2.js","assets/dialog-CzIekCw0.js","assets/jira-api-Bhixwn-d.js","assets/provider-capabilities-Doej9lfa.js","assets/lib-CGdyxIWg.js","assets/formatDistanceToNow-BOuXgebD.js","assets/getTimezoneOffsetInMilliseconds-EjGOpn4b.js","assets/spending-BpYPsevQ.js","assets/index-C_sdsSZt.js","assets/arrow-up-right-9DoMn7fD.js","assets/index-DCN5QbDm.css"])))=>i.map(i=>d[i]);
2
- import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{n as t,t as n}from"./jsx-runtime-zu2_FqZY.js";import{t as r}from"./preload-helper-Czpn1I53.js";import{n as i}from"./createLucideIcon-Bjbbp_KS.js";import{t as a}from"./x-CBMI9ejn.js";import{dt as o,ft as s,i as c}from"./index-C_sdsSZt.js";var l=e(t(),1),u=n(),d=(0,l.lazy)(()=>r(()=>import(`./CodePage-RtKvHmAO.js`),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75]))),f=new Map,p=420,m=560;function h({projectId:e,conversationId:t}){let{t:n}=i(`agent`),{closeCodePane:r}=c(),[h,g]=(0,l.useState)(m),[_,v]=(0,l.useState)(!1),y=(0,l.useRef)(null),b=`${e}:${t}`,x=(0,l.useCallback)(e=>{f.set(b,e)},[b]),S=(0,l.useCallback)(e=>{let t=e.clientX,n=h,r=window.innerWidth-p;try{e.currentTarget.setPointerCapture(e.pointerId)}catch{}let i=e=>{let i=Math.min(Math.max(n+(t-e.clientX),p),r);g(i)},a=()=>{window.removeEventListener(`pointermove`,i),window.removeEventListener(`pointerup`,a),window.removeEventListener(`pointercancel`,a)};window.addEventListener(`pointermove`,i),window.addEventListener(`pointerup`,a),window.addEventListener(`pointercancel`,a)},[h]);return(0,u.jsxs)(`div`,{ref:y,className:`flex h-full flex-col overflow-hidden border-l border-border bg-background${_?` absolute inset-0 z-30 border-l-0`:``}`,style:_?void 0:{width:h,flexShrink:0},children:[(0,u.jsx)(`div`,{className:`relative flex items-center`,children:!_&&(0,u.jsx)(`div`,{role:`separator`,"aria-orientation":`vertical`,onPointerDown:S,className:`absolute left-0 top-0 h-full w-1.5 -translate-x-full cursor-col-resize select-none touch-none hover:bg-accent-primary/20`,title:n(`workspace.resizeCode`)})}),(0,u.jsxs)(`div`,{className:`flex items-center gap-2 border-b border-border/60 bg-surface/40 px-3 py-1.5`,children:[(0,u.jsx)(`span`,{className:`text-xs font-medium text-foreground/70`,children:n(`workspace.files`)}),(0,u.jsxs)(`div`,{className:`ml-auto flex items-center gap-1`,children:[(0,u.jsx)(`button`,{type:`button`,onClick:()=>v(e=>!e),"aria-label":n(_?`restore`:`maximize`),title:n(_?`restore`:`maximize`),className:`rounded-md p-1 text-foreground/60 hover:bg-surface hover:text-foreground`,children:_?(0,u.jsx)(o,{className:`h-4 w-4`}):(0,u.jsx)(s,{className:`h-4 w-4`})}),(0,u.jsx)(`button`,{type:`button`,onClick:r,"aria-label":n(`workspace.closeFiles`),title:n(`workspace.closeFiles`),className:`rounded-md p-1 text-foreground/60 hover:bg-surface hover:text-foreground`,children:(0,u.jsx)(a,{className:`h-4 w-4`})})]})]}),(0,u.jsx)(`div`,{className:`min-h-0 flex-1 overflow-hidden`,children:(0,u.jsx)(l.Suspense,{fallback:(0,u.jsx)(`div`,{className:`flex h-full items-center justify-center text-sm text-muted-foreground`,children:`…`}),children:(0,u.jsx)(d,{embedded:!0,initialPath:f.get(b)??null,onSelectedPathChange:x})})})]},b)}export{h as AgentModeCodePane};
@@ -1 +0,0 @@
1
- import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{n as t,t as n}from"./jsx-runtime-zu2_FqZY.js";import{a as r}from"./i18n-nJQvD5zJ.js";import{n as i}from"./createLucideIcon-Bjbbp_KS.js";import{t as a}from"./loader-circle-DcygOQpC.js";import{t as o}from"./x-CBMI9ejn.js";import{r as s}from"./useSharedWebSocket-DK96ctb7.js";import{t as c}from"./api-B2M7CtDw.js";import{i as l,r as u}from"./useDesktop-B6Du2N83.js";import{t as d}from"./utils-BC4DHlz2.js";import{t as f}from"./formatDistanceToNow-BOuXgebD.js";import{At as p,I as m,dt as h,ft as g,i as _}from"./index-C_sdsSZt.js";import{n as v,t as y}from"./job-time-BCL7wee9.js";import{JobDetailModal as b}from"./JobDetailModal-1UZkqH4I.js";import{t as x}from"./format-command-C0D9IPmV.js";var S=e(t(),1),C=n(),w=360,T=480,E={running:`running`,completed:`success`,failed:`failed`,canceled:`canceled`,queued:`queued`};function D({projectId:e}){let{t}=i(`agent`),{t:n}=i(`jobs`),{closeJobsPane:D}=_(),{projects:O}=u(),k=O.find(t=>t.id===e)?.provider,[A,j]=(0,S.useState)(T),[M,N]=(0,S.useState)(!1),[P,F]=(0,S.useState)(null),{data:I,isFirstLoad:L,refresh:R}=l({namespace:`agent-jobs`,projectId:e,initialValue:[],fetcher:async()=>{let e=await fetch(`${c()}/jobs?limit=50`);return e.ok?(await e.json()).jobs:[]},pollInterval:5e3}),{registerHandler:z,unregisterHandler:B}=s(),V=(0,S.useRef)(e);V.current=e;let H=(0,S.useRef)(R);H.current=R,(0,S.useEffect)(()=>(z(`agent-jobs-pane`,e=>{let t=e;t.type===`queue`&&(t.projectId&&t.projectId!==V.current||H.current())}),()=>B(`agent-jobs-pane`)),[z,B]);let U=(0,S.useCallback)(e=>{let t=e.clientX,n=A,r=window.innerWidth-w;try{e.currentTarget.setPointerCapture(e.pointerId)}catch{}let i=e=>{let i=Math.min(Math.max(n+(t-e.clientX),w),r);j(i)},a=()=>{window.removeEventListener(`pointermove`,i),window.removeEventListener(`pointerup`,a),window.removeEventListener(`pointercancel`,a)};window.addEventListener(`pointermove`,i),window.addEventListener(`pointerup`,a),window.addEventListener(`pointercancel`,a)},[A]);return(0,C.jsxs)(`div`,{className:d(`flex h-full flex-col overflow-hidden border-l border-border bg-background`,M&&`absolute inset-0 z-30 border-l-0`),style:M?void 0:{width:A,flexShrink:0},children:[(0,C.jsx)(`div`,{className:`relative flex items-center`,children:!M&&(0,C.jsx)(`div`,{role:`separator`,"aria-orientation":`vertical`,onPointerDown:U,className:`absolute left-0 top-0 h-full w-1.5 -translate-x-full cursor-col-resize select-none touch-none hover:bg-accent-primary/20`,title:t(`workspace.resizeJobs`)})}),(0,C.jsxs)(`div`,{className:`flex items-center gap-2 border-b border-border/60 bg-surface/40 px-3 py-1.5`,children:[(0,C.jsx)(`span`,{className:`text-xs font-medium text-foreground/70`,children:t(`workspace.jobs`)}),(0,C.jsxs)(`div`,{className:`ml-auto flex items-center gap-1`,children:[(0,C.jsx)(`button`,{type:`button`,onClick:()=>N(e=>!e),"aria-label":t(M?`restore`:`maximize`),title:t(M?`restore`:`maximize`),className:`rounded-md p-1 text-foreground/60 hover:bg-surface hover:text-foreground`,children:M?(0,C.jsx)(h,{className:`h-4 w-4`}):(0,C.jsx)(g,{className:`h-4 w-4`})}),(0,C.jsx)(`button`,{type:`button`,onClick:D,"aria-label":t(`workspace.closeJobs`),title:t(`workspace.closeJobs`),className:`rounded-md p-1 text-foreground/60 hover:bg-surface hover:text-foreground`,children:(0,C.jsx)(o,{className:`h-4 w-4`})})]})]}),(0,C.jsx)(`div`,{className:`min-h-0 flex-1 overflow-y-auto p-2`,children:L?(0,C.jsx)(`div`,{className:`flex h-24 items-center justify-center text-muted-foreground`,children:(0,C.jsx)(a,{className:`h-4 w-4 animate-spin`})}):I.length===0?(0,C.jsxs)(`div`,{className:`flex h-full flex-col items-center justify-center gap-2 text-center text-muted-foreground`,children:[(0,C.jsx)(p,{className:`h-6 w-6 opacity-40`}),(0,C.jsx)(`p`,{className:`text-xs`,children:t(`workspace.noJobs`)})]}):(0,C.jsx)(`ul`,{className:`space-y-1`,children:I.map(e=>(0,C.jsx)(`li`,{children:(0,C.jsxs)(`button`,{type:`button`,onClick:()=>F(e.id),className:d(`flex w-full flex-col gap-1 rounded-lg border border-border/50 bg-surface/30 px-2.5 py-2 text-left transition-colors`,`hover:border-accent-primary/40 hover:bg-surface/60`),children:[(0,C.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,C.jsx)(m,{variant:E[e.status]??`queued`,children:n(`statusLabel.${e.status in E?e.status:`queued`}`)}),e.status===`running`&&(0,C.jsx)(a,{className:`h-3 w-3 animate-spin text-accent-primary`}),(0,C.jsx)(`span`,{className:`ml-auto text-[10px] text-muted-foreground`,children:(()=>{let t=v(y(e));return t?f(t,{addSuffix:!0,locale:r()}):`—`})()})]}),(0,C.jsx)(`code`,{className:`truncate font-mono text-xs text-foreground/85`,children:x(e.command,k)})]})},e.id))})}),P&&(0,C.jsx)(b,{jobId:P,onClose:()=>F(null)})]})}export{D as AgentModeJobsPane};