specrails-desktop 2.62.0 → 2.63.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 (161) hide show
  1. package/cli/dist/specrails-desktop.js +39 -5
  2. package/client/dist/assets/{ActivityFeedPage-C_pqc281.js → ActivityFeedPage-Bn2MpG5M.js} +1 -1
  3. package/client/dist/assets/{AgentBrowserCapture-vz6rbDtu.js → AgentBrowserCapture-CHlXWdXq.js} +1 -1
  4. package/client/dist/assets/AgentModeAnalyticsPane-yj-YCgrK.js +2 -0
  5. package/client/dist/assets/AgentModeCodePane-Xczcyghb.js +2 -0
  6. package/client/dist/assets/{AgentModeJobsPane-99fBBLri.js → AgentModeJobsPane-DtOW01ln.js} +1 -1
  7. package/client/dist/assets/AgentsPage-BMfJ4mzU.js +118 -0
  8. package/client/dist/assets/AnalyticsPage-CGn5cgtc.js +1 -0
  9. package/client/dist/assets/BarChart-BDMqVFJy.js +55 -0
  10. package/client/dist/assets/CodePage-C8NIginr.js +3 -0
  11. package/client/dist/assets/DesktopAnalyticsPage-C87r4y26.js +1 -0
  12. package/client/dist/assets/{DocsDialog-XUCYKoq0.js → DocsDialog-Z50a_KPv.js} +1 -1
  13. package/client/dist/assets/{DocsPage-dOLC8bXA.js → DocsPage-DIArbEPl.js} +1 -1
  14. package/client/dist/assets/{ExportDropdown-BvKQgwM9.js → ExportDropdown-CJdCqYd7.js} +1 -1
  15. package/client/dist/assets/IntegrationsPage-EAipiDWm.js +1 -0
  16. package/client/dist/assets/InteractiveJobComposer-BxLh4me-.js +19 -0
  17. package/client/dist/assets/{JobDetailModal-CkiubrkZ.js → JobDetailModal-C7dWoRu6.js} +1 -1
  18. package/client/dist/assets/{JobDetailPage-CNe4Wn7l.js → JobDetailPage-Ilu294WS.js} +1 -1
  19. package/client/dist/assets/JobsPage-fPq1hefb.js +1 -0
  20. package/client/dist/assets/LoopBuilderPage-5M7IypEP.js +1 -0
  21. package/client/dist/assets/{LoopPreviewModal-Dv3dxHr_.js → LoopPreviewModal-ChI3NQC7.js} +1 -1
  22. package/client/dist/assets/{LoopWindowSurface-DN2lbSnL.js → LoopWindowSurface-D1WB9i-9.js} +1 -1
  23. package/client/dist/assets/LoopsPage-CCwc0Biu.js +2 -0
  24. package/client/dist/assets/MinimizedChatsContext-eH_KaAZ1.js +1 -0
  25. package/client/dist/assets/PluginsPage-DfDfEfbW.js +2 -0
  26. package/client/dist/assets/ProjectSettingsDialog-9NHnG8JQ.js +1 -0
  27. package/client/dist/assets/RepositoryDeliveries-DgzNnJmn.js +5 -0
  28. package/client/dist/assets/{RepositoryScopeSelector-DrtPqvc_.js → RepositoryScopeSelector-BSgUqk_u.js} +1 -1
  29. package/client/dist/assets/ReviewPacketPage-D34bGpSA.js +4 -0
  30. package/client/dist/assets/{TemplatePreviewModal-DcFlhUxy.js → TemplatePreviewModal-CcfSxWQ7.js} +1 -1
  31. package/client/dist/assets/TicketDetailModalContext-BoafEcm4.js +16 -0
  32. package/client/dist/assets/Trans-CVSryAL1.js +1 -0
  33. package/client/dist/assets/ban-wqfdzoXt.js +1 -0
  34. package/client/dist/assets/{button-D1xExSTF.js → button-4FS1ZDTp.js} +1 -1
  35. package/client/dist/assets/chevron-up-6EFqEHBj.js +1 -0
  36. package/client/dist/assets/chunk-OB3PAWPO-B1EMk45A.js +3 -0
  37. package/client/dist/assets/circle-x-D2ttKotB.js +1 -0
  38. package/client/dist/assets/createLucideIcon-DtODIU64.js +1 -0
  39. package/client/dist/assets/{dialog-CMgps2MY.js → dialog-DtdR05v1.js} +1 -1
  40. package/client/dist/assets/download-C9wP_42h.js +1 -0
  41. package/client/dist/assets/external-link-CZ6Q-v1-.js +1 -0
  42. package/client/dist/assets/file-text-C0blZHLh.js +1 -0
  43. package/client/dist/assets/{formatDistanceToNow-DlQY1Lm_.js → formatDistanceToNow-NVM6rK3i.js} +1 -1
  44. package/client/dist/assets/{getTimezoneOffsetInMilliseconds-COUeq65G.js → getTimezoneOffsetInMilliseconds-ZHg7WpdH.js} +1 -1
  45. package/client/dist/assets/git-branch-yf6LE1Bw.js +1 -0
  46. package/client/dist/assets/{index-CONHvEJw.js → index-Culan6y7.js} +40 -40
  47. package/client/dist/assets/{index-BacrO_ub.css → index-D1v33giW.css} +1 -1
  48. package/client/dist/assets/jira-api-Bl8xEIpk.js +1 -0
  49. package/client/dist/assets/jsx-runtime-BbYvbtY8.js +1 -0
  50. package/client/dist/assets/layers-BCLTgHXO.js +1 -0
  51. package/client/dist/assets/{lib-C7Mhm-gT.js → lib-CQJT8S6Q.js} +1 -1
  52. package/client/dist/assets/{lib-CGdyxIWg.js → lib-DWJfei6y.js} +1 -1
  53. package/client/dist/assets/loader-circle-BqV9xYGf.js +1 -0
  54. package/client/dist/assets/{loop-layout-BF8xMiNv.js → loop-layout-CyXj9223.js} +1 -1
  55. package/client/dist/assets/pencil-CxrRbDwb.js +1 -0
  56. package/client/dist/assets/plus-fHDdjBk1.js +1 -0
  57. package/client/dist/assets/{project-repositories-Cc3oNv8X.js → project-repositories-CvJf1a6H.js} +1 -1
  58. package/client/dist/assets/react-dom-DDD8FYaU.js +1 -0
  59. package/client/dist/assets/rotate-ccw-DalJAQQX.js +1 -0
  60. package/client/dist/assets/save-LmPSfsvZ.js +1 -0
  61. package/client/dist/assets/search-BDVp3fkH.js +1 -0
  62. package/client/dist/assets/{settings-BKJZ2KEM.js → settings-BO2N-oPf.js} +1 -1
  63. package/client/dist/assets/{settings-LfFnl1UH.js → settings-BQChAs_w.js} +1 -1
  64. package/client/dist/assets/{settings-BV3jocIx.js → settings-BzmYlcQB.js} +1 -1
  65. package/client/dist/assets/{settings-BjM2Dw8_.js → settings-CGjXb6TX.js} +1 -1
  66. package/client/dist/assets/{settings-DA77M48m.js → settings-CMxEWY46.js} +1 -1
  67. package/client/dist/assets/{settings-P1IQvhnL.js → settings-C_dRSgI2.js} +1 -1
  68. package/client/dist/assets/{settings-eIMJjyK_.js → settings-Hw7av-B1.js} +1 -1
  69. package/client/dist/assets/{settings-CKu0LTib.js → settings-nryN6wuS.js} +1 -1
  70. package/client/dist/assets/sparkles-CCfUdFGY.js +1 -0
  71. package/client/dist/assets/spending-BfjSJjBd.js +1 -0
  72. package/client/dist/assets/square-D0JTbRSR.js +1 -0
  73. package/client/dist/assets/trash-D8zYUmTg.js +1 -0
  74. package/client/dist/assets/triangle-alert-Cxpn8bqr.js +1 -0
  75. package/client/dist/assets/upload-CCHvUkRs.js +1 -0
  76. package/client/dist/assets/{useDesktop-9Lwoel_8.js → useDesktop-C5JVBeur.js} +1 -1
  77. package/client/dist/assets/{useRuntimeRuns-DL0xYoZ2.js → useRuntimeRuns-DohBmeg8.js} +1 -1
  78. package/client/dist/assets/useSharedWebSocket-CNOvmgTr.js +2 -0
  79. package/client/dist/assets/{value-s-gzBeyF.js → value-DtOOK0wQ.js} +1 -1
  80. package/client/dist/assets/x-Bu7X_eR2.js +1 -0
  81. package/client/dist/assets/zap-bugHyWzQ.js +1 -0
  82. package/client/dist/index.html +43 -44
  83. package/docs/ci-cd.md +8 -1
  84. package/docs/customizing.md +1 -0
  85. package/docs/getting-started.md +1 -1
  86. package/docs/guide/de/settings/5-project-environment.md +27 -0
  87. package/docs/guide/en/settings/5-project-environment.md +27 -0
  88. package/docs/guide/es/settings/5-project-environment.md +27 -0
  89. package/docs/guide/fr/settings/5-project-environment.md +27 -0
  90. package/docs/guide/it/settings/5-project-environment.md +27 -0
  91. package/docs/guide/ja/settings/5-project-environment.md +27 -0
  92. package/docs/guide/pt/settings/5-project-environment.md +27 -0
  93. package/docs/guide/zh/settings/5-project-environment.md +27 -0
  94. package/docs/internals/api-reference.md +3 -1
  95. package/docs/internals/configuration.md +46 -5
  96. package/docs/internals/safe-pr-review-flow.md +15 -0
  97. package/docs/internals/source-map.md +3 -1
  98. package/docs/mcp.md +1 -1
  99. package/docs/platforms/macos.md +2 -0
  100. package/docs/platforms/windows-parity.md +4 -4
  101. package/docs/platforms/windows.md +9 -9
  102. package/local-runner/dist/specrails-local-runner.js +1 -1
  103. package/package.json +8 -9
  104. package/server/dist/mcp/tools/env.js +38 -3
  105. package/server/dist/modules/execution/runtime/queue-manager.js +8 -0
  106. package/server/dist/modules/loops/runtime/loop-executors.js +1 -0
  107. package/server/dist/modules/loops/runtime/loop-run-manager.js +7 -0
  108. package/server/dist/modules/project-settings/adapters/http.js +24 -0
  109. package/server/dist/path-resolver.js +135 -20
  110. package/server/dist/project-env.js +265 -17
  111. package/server/dist/project-registry.js +14 -0
  112. package/server/dist/project-router-settings.js +17 -0
  113. package/server/dist/worktree-node-modules.js +95 -1
  114. package/client/dist/assets/AgentModeAnalyticsPane-BHCcUahS.js +0 -2
  115. package/client/dist/assets/AgentModeCodePane-DhmxgG3W.js +0 -2
  116. package/client/dist/assets/AgentsPage-CTwm_6T8.js +0 -118
  117. package/client/dist/assets/AnalyticsPage-YD8MLoPa.js +0 -1
  118. package/client/dist/assets/BarChart-nBOMNfiy.js +0 -33
  119. package/client/dist/assets/CodePage-CLJn2F2e.js +0 -3
  120. package/client/dist/assets/DesktopAnalyticsPage-MWGQVbwp.js +0 -1
  121. package/client/dist/assets/IntegrationsPage-Bhy58lnQ.js +0 -1
  122. package/client/dist/assets/InteractiveJobComposer-DxOvwmBF.js +0 -19
  123. package/client/dist/assets/JobsPage-DSts_L_d.js +0 -1
  124. package/client/dist/assets/LoopBuilderPage-C9D9eer5.js +0 -1
  125. package/client/dist/assets/LoopsPage-DHgIreEW.js +0 -2
  126. package/client/dist/assets/MinimizedChatsContext-Ds2-R6qJ.js +0 -1
  127. package/client/dist/assets/PluginsPage-D1EJ9oja.js +0 -2
  128. package/client/dist/assets/ProjectSettingsDialog-DKpa1wKk.js +0 -1
  129. package/client/dist/assets/RepositoryDeliveries-CW4eKG3x.js +0 -5
  130. package/client/dist/assets/ReviewPacketPage-BtjPI0Cc.js +0 -4
  131. package/client/dist/assets/TicketDetailModalContext-Dea22CI-.js +0 -16
  132. package/client/dist/assets/Trans-CAY2Rd2f.js +0 -1
  133. package/client/dist/assets/ban-E4EaZe3a.js +0 -1
  134. package/client/dist/assets/chevron-up-CwGUHd6D.js +0 -1
  135. package/client/dist/assets/chunk-KS7C4IRE-DEKu087k.js +0 -3
  136. package/client/dist/assets/circle-x-CMYsN4ZH.js +0 -1
  137. package/client/dist/assets/createLucideIcon-Bjbbp_KS.js +0 -1
  138. package/client/dist/assets/dist-Bq10nS2m.js +0 -1
  139. package/client/dist/assets/download-CFHyyHcw.js +0 -1
  140. package/client/dist/assets/external-link-D6XxC_Cu.js +0 -1
  141. package/client/dist/assets/file-text-BcLCfcWZ.js +0 -1
  142. package/client/dist/assets/git-branch-BRM0F5GN.js +0 -1
  143. package/client/dist/assets/jira-api-C94VNHNY.js +0 -1
  144. package/client/dist/assets/jsx-runtime-zu2_FqZY.js +0 -1
  145. package/client/dist/assets/layers-eDMjG41I.js +0 -1
  146. package/client/dist/assets/loader-circle-DcygOQpC.js +0 -1
  147. package/client/dist/assets/pencil-C9RkrIPZ.js +0 -1
  148. package/client/dist/assets/plus-CsnjfkmX.js +0 -1
  149. package/client/dist/assets/react-dom-DiWzBDP9.js +0 -1
  150. package/client/dist/assets/rotate-ccw-Wh4id13q.js +0 -1
  151. package/client/dist/assets/save-B6wGWwBR.js +0 -1
  152. package/client/dist/assets/search-Cq_gE3Ik.js +0 -1
  153. package/client/dist/assets/sparkles-DrY8hb0X.js +0 -1
  154. package/client/dist/assets/spending-DemeFfrH.js +0 -1
  155. package/client/dist/assets/square-Ci-PFcFe.js +0 -1
  156. package/client/dist/assets/trash-2-BOU3WdZZ.js +0 -1
  157. package/client/dist/assets/triangle-alert-H9T4jAmh.js +0 -1
  158. package/client/dist/assets/upload-DYMjAQ6M.js +0 -1
  159. package/client/dist/assets/useSharedWebSocket-BEHPeU1Y.js +0 -2
  160. package/client/dist/assets/x-CBMI9ejn.js +0 -1
  161. package/client/dist/assets/zap-CHo3aCD1.js +0 -1
@@ -1,37 +1,237 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PROJECT_ENV_FAILURE_TTL_MS = exports.PROJECT_ENV_SUCCESS_TTL_MS = void 0;
4
+ exports.refreshProjectEnv = refreshProjectEnv;
5
+ exports.ensureProjectEnvFresh = ensureProjectEnvFresh;
6
+ exports.startProjectEnvWarmup = startProjectEnvWarmup;
7
+ exports.stopProjectEnvWarmup = stopProjectEnvWarmup;
3
8
  exports.resolveWorktreeEnvPassthrough = resolveWorktreeEnvPassthrough;
4
9
  exports.applyWorktreeEnvPassthrough = applyWorktreeEnvPassthrough;
10
+ exports.getProjectEnvStatus = getProjectEnvStatus;
11
+ exports.worktreeEnvPassthroughWarning = worktreeEnvPassthroughWarning;
5
12
  const db_1 = require("./db");
6
13
  const path_resolver_1 = require("./path-resolver");
7
- // Values stay in the owning project's memory, never process.env or SQLite.
8
- // Both successful and missing lookups expire so profiles can recover/rotate.
9
- const LOGIN_SHELL_CACHE_MS = 30_000;
10
- const shellValues = new WeakMap();
14
+ /** Successful recoveries stay valid for 10 minutes; the background refresh
15
+ * rotates them well inside that window. */
16
+ exports.PROJECT_ENV_SUCCESS_TTL_MS = 10 * 60_000;
17
+ /** Failed or empty lookups are retried after 30 seconds. */
18
+ exports.PROJECT_ENV_FAILURE_TTL_MS = 30_000;
19
+ /** The background refresh runs this long before a success expires. */
20
+ const PROJECT_ENV_REFRESH_LEAD_MS = 60_000;
21
+ const states = new WeakMap();
22
+ function shellIdentity() {
23
+ return JSON.stringify([process.env.SHELL, process.env.HOME, process.env.ZDOTDIR]);
24
+ }
25
+ function stateFor(db) {
26
+ const identity = shellIdentity();
27
+ let state = states.get(db);
28
+ if (!state) {
29
+ state = { identity, records: new Map(), values: new Map(), inflight: null, queued: null, timer: null, active: false };
30
+ states.set(db, state);
31
+ }
32
+ else if (state.identity !== identity) {
33
+ state.identity = identity;
34
+ state.records.clear();
35
+ state.values.clear();
36
+ }
37
+ return state;
38
+ }
39
+ function configuredNames(db) {
40
+ try {
41
+ return (0, db_1.getProjectSettings)(db).worktreeEnvPassthrough;
42
+ }
43
+ catch {
44
+ // A closed/unloaded project connection has no configured names.
45
+ return [];
46
+ }
47
+ }
48
+ function missingNames(names) {
49
+ return names.filter(name => !process.env[name]);
50
+ }
51
+ /** Drop records for names no longer configured or now inherited. */
52
+ function prune(state, names) {
53
+ for (const name of [...state.records.keys()]) {
54
+ if (!names.includes(name) || process.env[name]) {
55
+ state.records.delete(name);
56
+ state.values.delete(name);
57
+ }
58
+ }
59
+ }
60
+ function isValid(record, now) {
61
+ return Boolean(record && now < record.expiresAt);
62
+ }
63
+ function statusFromProbe(probe, value) {
64
+ if (value)
65
+ return 'recovered';
66
+ if (probe.status === 'timeout')
67
+ return 'probe-timeout';
68
+ if (probe.status === 'failed')
69
+ return 'probe-failed';
70
+ // `ok` without a value, or `skipped` (Windows / no login-shell recovery).
71
+ return 'not-defined';
72
+ }
73
+ function recordProbe(state, probe, names, now) {
74
+ for (const name of names) {
75
+ const value = probe.values[name];
76
+ const status = statusFromProbe(probe, value);
77
+ const previous = state.records.get(name);
78
+ // A transient probe failure never discards a still-valid recovered value.
79
+ if (status !== 'recovered' && previous?.status === 'recovered' && isValid(previous, now) && probe.status !== 'ok')
80
+ continue;
81
+ state.records.set(name, {
82
+ status,
83
+ shell: probe.shell,
84
+ checkedAt: now,
85
+ expiresAt: now + (status === 'recovered' ? exports.PROJECT_ENV_SUCCESS_TTL_MS : exports.PROJECT_ENV_FAILURE_TTL_MS),
86
+ exitCode: probe.exitCode,
87
+ });
88
+ if (value)
89
+ state.values.set(name, value);
90
+ else
91
+ state.values.delete(name);
92
+ }
93
+ }
94
+ function scheduleRefresh(db, state) {
95
+ if (state.timer) {
96
+ clearTimeout(state.timer);
97
+ state.timer = null;
98
+ }
99
+ if (!state.active || states.get(db) !== state)
100
+ return;
101
+ let earliest = Infinity;
102
+ for (const record of state.records.values()) {
103
+ if (record.status === 'recovered')
104
+ earliest = Math.min(earliest, record.expiresAt);
105
+ }
106
+ if (earliest === Infinity)
107
+ return;
108
+ const now = Date.now();
109
+ const at = Math.max(now + exports.PROJECT_ENV_FAILURE_TTL_MS, earliest - PROJECT_ENV_REFRESH_LEAD_MS);
110
+ state.timer = setTimeout(() => {
111
+ state.timer = null;
112
+ if (state.active && states.get(db) === state)
113
+ void refreshProjectEnv(db);
114
+ }, at - now);
115
+ state.timer.unref?.();
116
+ }
117
+ /** Probe the login shell asynchronously for every configured name missing from
118
+ * the process environment and update this project's records. De-duplicated
119
+ * while a probe is in flight: a call during a probe resolves after one more
120
+ * probe that starts when the current one ends, so a settings change or an
121
+ * explicit recheck is never answered with stale results. Never rejects. */
122
+ function refreshProjectEnv(db) {
123
+ const state = stateFor(db);
124
+ if (state.inflight) {
125
+ // A request during a probe (settings change, recheck) gets a fresh probe
126
+ // after the current one; concurrent requests share that single follow-up.
127
+ if (!state.queued) {
128
+ state.queued = state.inflight.then(() => {
129
+ state.queued = null;
130
+ return refreshProjectEnv(db);
131
+ });
132
+ }
133
+ return state.queued;
134
+ }
135
+ const names = configuredNames(db);
136
+ prune(state, names);
137
+ const missing = missingNames(names);
138
+ if (missing.length === 0) {
139
+ scheduleRefresh(db, state);
140
+ return Promise.resolve();
141
+ }
142
+ const run = (0, path_resolver_1.readEnvFromLoginShell)(missing, { timeoutMs: (0, path_resolver_1.resolveLoginShellProbeTimeoutMs)() })
143
+ .then(probe => {
144
+ if (states.get(db) !== state)
145
+ return;
146
+ const current = configuredNames(db);
147
+ prune(state, current);
148
+ // Only names still configured and still missing are recorded.
149
+ const stillMissing = missingNames(current);
150
+ recordProbe(state, probe, missing.filter(name => stillMissing.includes(name)), Date.now());
151
+ })
152
+ .catch(() => { })
153
+ .finally(() => {
154
+ state.inflight = null;
155
+ scheduleRefresh(db, state);
156
+ });
157
+ state.inflight = run;
158
+ return run;
159
+ }
160
+ /** Probe only when a configured missing name has no valid record yet. */
161
+ function ensureProjectEnvFresh(db) {
162
+ const state = stateFor(db);
163
+ if (state.inflight)
164
+ return state.inflight;
165
+ const names = configuredNames(db);
166
+ prune(state, names);
167
+ const now = Date.now();
168
+ const stale = missingNames(names).some(name => !isValid(state.records.get(name), now));
169
+ return stale ? refreshProjectEnv(db) : Promise.resolve();
170
+ }
171
+ /** Start background warm-up for a loaded project: probe now, then refresh
172
+ * before successful results expire, until `stopProjectEnvWarmup`. */
173
+ function startProjectEnvWarmup(db) {
174
+ const state = stateFor(db);
175
+ state.active = true;
176
+ void refreshProjectEnv(db);
177
+ }
178
+ /** Stop the background refresh and forget this project's recovered values. */
179
+ function stopProjectEnvWarmup(db) {
180
+ const state = states.get(db);
181
+ if (!state)
182
+ return;
183
+ state.active = false;
184
+ if (state.timer) {
185
+ clearTimeout(state.timer);
186
+ state.timer = null;
187
+ }
188
+ states.delete(db);
189
+ }
11
190
  function recoverProjectEnv(db, names) {
12
- const missing = names.filter(name => !process.env[name]);
13
- if (!missing.length) {
14
- shellValues.delete(db);
191
+ const state = stateFor(db);
192
+ prune(state, names);
193
+ const missing = missingNames(names);
194
+ if (!missing.length)
15
195
  return {};
196
+ const now = Date.now();
197
+ // Cold fallback: a name with no record (or an expired success) gets the
198
+ // short synchronous probe. Expired failures are retried asynchronously.
199
+ const cold = missing.filter(name => {
200
+ const record = state.records.get(name);
201
+ return !record || (record.status === 'recovered' && !isValid(record, now));
202
+ });
203
+ let retry = missing.some(name => {
204
+ const record = state.records.get(name);
205
+ return record !== undefined && record.status !== 'recovered' && !isValid(record, now);
206
+ });
207
+ if (cold.length) {
208
+ const probe = (0, path_resolver_1.readEnvFromLoginShellSyncDetailed)(cold);
209
+ recordProbe(state, probe, cold, now);
210
+ // The async probe has a larger budget than the cold sync one.
211
+ if (probe.status === 'timeout' || probe.status === 'failed')
212
+ retry = true;
213
+ }
214
+ if (retry)
215
+ void refreshProjectEnv(db);
216
+ const out = {};
217
+ for (const name of missing) {
218
+ const value = state.values.get(name);
219
+ if (value && isValid(state.records.get(name), now))
220
+ out[name] = value;
16
221
  }
17
- const identity = JSON.stringify([names, missing, process.env.SHELL, process.env.HOME, process.env.ZDOTDIR]);
18
- const cached = shellValues.get(db);
19
- if (cached?.identity === identity && Date.now() < cached.expiresAt)
20
- return cached.env;
21
- const env = (0, path_resolver_1.readEnvFromLoginShellSync)(missing);
22
- shellValues.set(db, { identity, expiresAt: Date.now() + LOGIN_SHELL_CACHE_MS, env });
23
- return env;
222
+ return out;
24
223
  }
25
224
  /** Resolve the per-project env passthrough overlay for a spawn.
26
225
  *
27
226
  * The project setting stores names only. Values are read from `sourceEnv` at
28
227
  * spawn time so credentials can rotate without touching SQLite. When the
29
- * source is `process.env`, missing configured names get bounded login-shell
30
- * recovery with a short, project-owned cache. Unresolved names remain absent.
228
+ * source is `process.env`, missing configured names come from the project's
229
+ * warm login-shell cache; the short synchronous probe runs only when cold.
230
+ * Unresolved names remain absent.
31
231
  */
32
232
  function resolveWorktreeEnvPassthrough(db, sourceEnv = process.env) {
33
233
  const out = {};
34
- const names = (0, db_1.getProjectSettings)(db).worktreeEnvPassthrough;
234
+ const names = configuredNames(db);
35
235
  const recovered = sourceEnv === process.env ? recoverProjectEnv(db, names) : {};
36
236
  for (const name of names) {
37
237
  const value = sourceEnv[name] || recovered[name] || sourceEnv[name];
@@ -44,3 +244,51 @@ function applyWorktreeEnvPassthrough(db, env, sourceEnv = process.env) {
44
244
  const passthrough = resolveWorktreeEnvPassthrough(db, sourceEnv);
45
245
  return Object.keys(passthrough).length === 0 ? env : { ...env, ...passthrough };
46
246
  }
247
+ /** Value-free status of every configured name for this project. */
248
+ function getProjectEnvStatus(db) {
249
+ const state = stateFor(db);
250
+ const names = configuredNames(db);
251
+ prune(state, names);
252
+ const nowIso = new Date(Date.now()).toISOString();
253
+ return {
254
+ loginShellRecovery: process.platform !== 'win32',
255
+ timeoutMs: (0, path_resolver_1.resolveLoginShellProbeTimeoutMs)(),
256
+ checking: state.inflight !== null,
257
+ names: names.map((name) => {
258
+ if (process.env[name])
259
+ return { name, status: 'inherited', shell: null, checkedAt: nowIso, exitCode: null };
260
+ const record = state.records.get(name);
261
+ if (!record)
262
+ return { name, status: 'pending', shell: null, checkedAt: null, exitCode: null };
263
+ return {
264
+ name,
265
+ status: record.status,
266
+ shell: record.shell,
267
+ checkedAt: new Date(record.checkedAt).toISOString(),
268
+ exitCode: record.exitCode,
269
+ };
270
+ }),
271
+ };
272
+ }
273
+ /** One aggregated, value-free run-log line naming every configured name that
274
+ * is absent from `env`, or null when all configured names resolved. */
275
+ function worktreeEnvPassthroughWarning(db, env) {
276
+ const names = configuredNames(db);
277
+ const unresolved = names.filter(name => !env[name]);
278
+ if (!unresolved.length)
279
+ return null;
280
+ const state = states.get(db);
281
+ const now = Date.now();
282
+ const parts = unresolved.map(name => {
283
+ const record = state?.records.get(name);
284
+ // An expired success no longer counts as recovered; failures keep their cause.
285
+ const status = !record ? 'not-defined' : record.status === 'recovered' && now >= record.expiresAt ? 'not-defined' : record.status;
286
+ return { name, status };
287
+ });
288
+ const hint = process.platform === 'win32'
289
+ ? 'set it in the environment Specrails starts with'
290
+ : 'configure it in the login shell or launch from a terminal';
291
+ if (parts.length === 1)
292
+ return `[environment] ${parts[0].name} not available (${parts[0].status}); ${hint}`;
293
+ return `[environment] ${parts.map(p => `${p.name} (${p.status})`).join(', ')} not available; ${hint.replace(' it ', ' them ')}`;
294
+ }
@@ -390,6 +390,11 @@ class ProjectRegistry {
390
390
  (0, telemetry_receiver_1.dropBlobStatesForProject)(id);
391
391
  }
392
392
  catch { /* ignore */ }
393
+ // Stop the login-shell env refresh and forget recovered values.
394
+ try {
395
+ (0, project_env_1.stopProjectEnvWarmup)(ctx.db);
396
+ }
397
+ catch { /* ignore */ }
393
398
  // Close the DB connection BEFORE removing the project's data dir below.
394
399
  try {
395
400
  ctx.db.close();
@@ -639,6 +644,10 @@ class ProjectRegistry {
639
644
  ctx.fileSummaryManager.dispose();
640
645
  }
641
646
  catch { /* ignore */ }
647
+ try {
648
+ (0, project_env_1.stopProjectEnvWarmup)(ctx.db);
649
+ }
650
+ catch { /* ignore */ }
642
651
  ctx.ticketWatcher.close().catch(() => { });
643
652
  }
644
653
  // Close the shared browser context once, after every per-project manager has
@@ -1152,6 +1161,8 @@ class ProjectRegistry {
1152
1161
  sourcePath: () => (0, project_repositories_1.resolveProjectRepository)(project).path,
1153
1162
  workspacePaths: () => { const primary = (0, project_repositories_1.resolveProjectRepository)(project); return primary.workspacePaths ?? (primary.workspacePath ? [primary.workspacePath] : undefined); },
1154
1163
  env: () => (0, workspace_resolution_1.resolveLoopBaseEnv)({ slug: project.slug, path: project.path }, undefined, (0, project_env_1.applyWorktreeEnvPassthrough)(db, process.env)),
1164
+ // One value-free run-log line when configured names are unresolved.
1165
+ environmentNotice: () => (0, project_env_1.worktreeEnvPassthroughWarning)(db, (0, project_env_1.applyWorktreeEnvPassthrough)(db, process.env)),
1155
1166
  // Named rail profiles and global per-agent defaults ride the same
1156
1167
  // SPECRAILS_PROFILE_PATH snapshot in both AI transports. An explicit null
1157
1168
  // rail selection opts out; omitted selections keep Core's default rules.
@@ -1486,6 +1497,9 @@ class ProjectRegistry {
1486
1497
  const ctx = { project, db, queueManager, chatManager, setupManager, proposalManager, agentRefineManager, fileSummaryManager, specLauncherManager, ticketWatcher, browserCaptureManager, jiraSyncManager, stuckRunDetector, broadcast: boundBroadcast, railJobs, loopRunManager, railLoopRuns, onLoopRunFinished, getTicketSpec, desktopDb: this._desktopDb, milestoneProgress };
1487
1498
  ctx.milestoneChains = milestoneChains;
1488
1499
  this._contexts.set(project.id, ctx);
1500
+ // Warm the project's login-shell env cache off the event loop so the first
1501
+ // rail/loop spawn reads recovered credentials instead of a cold 1.5 s probe.
1502
+ (0, project_env_1.startProjectEnvWarmup)(db);
1489
1503
  const recoverAfterProbe = (definitionStates) => {
1490
1504
  if (this._contexts.get(project.id) !== ctx)
1491
1505
  return;
@@ -9,6 +9,7 @@ exports.registerSettingsRoutes = registerSettingsRoutes;
9
9
  const project_settings_1 = require("./modules/project-settings");
10
10
  const sqlite_1 = require("./modules/project-settings/adapters/sqlite");
11
11
  const http_1 = require("./modules/project-settings/adapters/http");
12
+ const project_env_1 = require("./project-env");
12
13
  const fs_1 = __importDefault(require("fs"));
13
14
  const path_1 = __importDefault(require("path"));
14
15
  const db_1 = require("./db");
@@ -35,6 +36,22 @@ function registerSettingsRoutes(deps) {
35
36
  const runtimeChanged = JSON.stringify(settings.subagentRuntime) !== JSON.stringify(previous.subagentRuntime);
36
37
  if (settings.allowSubagents !== previous.allowSubagents || runtimeChanged)
37
38
  registry.notifySettingsChanged({ projectId: String(req.params.projectId) });
39
+ // Configured env names changed: re-warm this project's login-shell cache.
40
+ if (JSON.stringify(settings.worktreeEnvPassthrough) !== JSON.stringify(previous.worktreeEnvPassthrough))
41
+ void (0, project_env_1.refreshProjectEnv)(ctx(req).db);
42
+ });
43
+ // Value-free resolution status of the configured env names (+ recheck).
44
+ (0, http_1.registerEnvPassthroughStatusHttp)(router, {
45
+ read: async (req) => {
46
+ const { db } = ctx(req);
47
+ await (0, project_env_1.ensureProjectEnvFresh)(db);
48
+ return (0, project_env_1.getProjectEnvStatus)(db);
49
+ },
50
+ recheck: async (req) => {
51
+ const { db } = ctx(req);
52
+ await (0, project_env_1.refreshProjectEnv)(db);
53
+ return (0, project_env_1.getProjectEnvStatus)(db);
54
+ },
38
55
  });
39
56
  // ─── Per-project Quick mode Contract Refine last-used value ─────────────────
40
57
  router.get('/:projectId/add-spec-quick-contract-refine-last', (req, res) => {
@@ -33,9 +33,11 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.WORKTREE_DEPENDENCY_CACHES = void 0;
36
+ exports.DEPENDENCY_LOCKFILES = exports.WORKTREE_DEPENDENCY_CACHES = void 0;
37
37
  exports.isWorktreeNodeModulesEnabled = isWorktreeNodeModulesEnabled;
38
38
  exports.discoverPackageDirs = discoverPackageDirs;
39
+ exports.dependencyInputs = dependencyInputs;
40
+ exports.differingDependencyInput = differingDependencyInput;
39
41
  exports.authenticateWarmNodeModulesLinks = authenticateWarmNodeModulesLinks;
40
42
  exports.linkNodeModulesIntoWorktree = linkNodeModulesIntoWorktree;
41
43
  /**
@@ -48,7 +50,14 @@ exports.linkNodeModulesIntoWorktree = linkNodeModulesIntoWorktree;
48
50
  * Cleanup authority covers individual source-anchored links, never the entire
49
51
  * writable directory. Replaced packages and newly created files therefore keep
50
52
  * the existing ignored-artifact / recoverable-work settlement guarantees.
53
+ *
54
+ * Freshness guard: a package directory is linked only when its dependency inputs
55
+ * (nearest lockfile up to the Git top level + the manifest's dependency fields)
56
+ * match between the base checkout and the worktree. Otherwise the directory is
57
+ * left without node_modules so the runtime installs it cold from the worktree's
58
+ * own lockfile. The base checkout is only ever read.
51
59
  */
60
+ const crypto_1 = require("crypto");
52
61
  const fs = __importStar(require("fs"));
53
62
  const path = __importStar(require("path"));
54
63
  const worktree_overlay_1 = require("./worktree-overlay");
@@ -112,6 +121,81 @@ function discoverPackageDirs(baseRepo) {
112
121
  walk('', 0);
113
122
  return found;
114
123
  }
124
+ /** Lockfiles in lookup priority order; the first one found in a directory wins. */
125
+ exports.DEPENDENCY_LOCKFILES = ['pnpm-lock.yaml', 'yarn.lock', 'package-lock.json', 'npm-shrinkwrap.json', 'bun.lock'];
126
+ const DEPENDENCY_FIELDS = ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'];
127
+ function sha256(data) {
128
+ return (0, crypto_1.createHash)('sha256').update(data).digest('hex');
129
+ }
130
+ function canonicalJson(value) {
131
+ if (Array.isArray(value))
132
+ return `[${value.map(canonicalJson).join(',')}]`;
133
+ if (value && typeof value === 'object') {
134
+ const record = value;
135
+ return `{${Object.keys(record).sort().map(key => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(',')}}`;
136
+ }
137
+ return JSON.stringify(value) ?? 'null';
138
+ }
139
+ /** Git top level of a checkout: nearest ancestor holding a `.git` entry (dir
140
+ * for a clone, file for a linked worktree). Outside Git, the checkout itself
141
+ * bounds the lockfile search so it never escapes into unrelated parents. */
142
+ function gitTopLevel(checkoutRoot) {
143
+ const start = path.resolve(checkoutRoot);
144
+ for (let current = start;; current = path.dirname(current)) {
145
+ if (exists(path.join(current, '.git')))
146
+ return current;
147
+ if (path.dirname(current) === current)
148
+ return start;
149
+ }
150
+ }
151
+ /**
152
+ * Fingerprint the inputs that decide what a package directory installs. Never
153
+ * throws: unreadable inputs yield null digests, which compare as different.
154
+ */
155
+ function dependencyInputs(checkoutRoot, pkgRel) {
156
+ const top = gitTopLevel(checkoutRoot);
157
+ const pkgDir = pkgRel === '' ? path.resolve(checkoutRoot) : path.resolve(checkoutRoot, ...pkgRel.split('/'));
158
+ let lockfile = null;
159
+ for (let current = pkgDir; lockfile === null; current = path.dirname(current)) {
160
+ for (const name of exports.DEPENDENCY_LOCKFILES) {
161
+ const file = path.join(current, name);
162
+ if (!exists(file))
163
+ continue;
164
+ let digest = null;
165
+ try {
166
+ digest = sha256(fs.readFileSync(file));
167
+ }
168
+ catch { /* unreadable: never equal */ }
169
+ lockfile = { name, location: path.relative(pkgDir, current).split(path.sep).join('/'), digest };
170
+ break;
171
+ }
172
+ if (current === top || path.dirname(current) === current)
173
+ break;
174
+ }
175
+ let manifestDigest = null;
176
+ try {
177
+ const manifest = JSON.parse(fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8'));
178
+ const fields = {};
179
+ for (const field of DEPENDENCY_FIELDS)
180
+ if (manifest?.[field] !== undefined)
181
+ fields[field] = manifest[field];
182
+ manifestDigest = sha256(canonicalJson(fields));
183
+ }
184
+ catch { /* missing or unparsable manifest: never equal */ }
185
+ return { lockfile, manifestDigest };
186
+ }
187
+ /** Name of the first dependency input that differs, or null when warm reuse is safe. */
188
+ function differingDependencyInput(base, worktree) {
189
+ const a = base.lockfile, b = worktree.lockfile;
190
+ if (a || b) {
191
+ if (!a || !b || a.name !== b.name || a.location !== b.location || a.digest === null || a.digest !== b.digest) {
192
+ return (a ?? b).name;
193
+ }
194
+ }
195
+ if (base.manifestDigest === null || base.manifestDigest !== worktree.manifestDigest)
196
+ return 'package.json';
197
+ return null;
198
+ }
115
199
  /** Resolve a path through symlinks when possible; the literal path otherwise
116
200
  * (a nonexistent target must still compare, and must still compare EQUAL to an
117
201
  * equally-nonexistent expectation rather than silently authorizing). */
@@ -305,6 +389,16 @@ function linkNodeModulesIntoWorktree(baseRepo, worktreePath) {
305
389
  }
306
390
  const dest = path.join(worktreePath, ...rel.split('/'));
307
391
  const legacy = authenticateWarmLink(baseRepo, worktreePath, rel, sourceRel);
392
+ if (!exists(dest)) {
393
+ // Freshness guard, only when a NEW link would be prepared: an existing
394
+ // (resumed or legacy) link keeps its authentication below.
395
+ const differing = differingDependencyInput(dependencyInputs(baseRepo, pkgRel), dependencyInputs(prefix ? path.join(worktreePath, ...prefix.split('/')) : worktreePath, pkgRel));
396
+ if (differing) {
397
+ const pkgDir = [prefix, pkgRel].filter(Boolean).join('/') || '.';
398
+ result.warnings.push(`${pkgDir}: ${differing} differs from the base checkout; dependencies will be installed in the worktree`);
399
+ continue;
400
+ }
401
+ }
308
402
  if (!exists(dest) || legacy) {
309
403
  let staging;
310
404
  let removedLegacy = false;
@@ -1,2 +0,0 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/AnalyticsPage-YD8MLoPa.js","assets/rolldown-runtime-CNC7AqOf.js","assets/jsx-runtime-zu2_FqZY.js","assets/chunk-KS7C4IRE-DEKu087k.js","assets/preload-helper-Czpn1I53.js","assets/useSharedWebSocket-BEHPeU1Y.js","assets/activity-DiyjsgQV.js","assets/addspec-BOvgmcgk.js","assets/agent-Dip4h2YE.js","assets/agentRuntime-C_yLPKDy.js","assets/agents-Doe1sHY5.js","assets/agentstudio-BzugqB_N.js","assets/aiedit-iIjz2_Qk.js","assets/analytics-BWynXPEm.js","assets/attachments-B53PG733.js","assets/browser-CG0PE1j_.js","assets/builder-CpS9liE9.js","assets/chat-BGD7MXAy.js","assets/code-DaZ-ZwCT.js","assets/commands-BAWSYHjQ.js","assets/common-Bdi0qN4S.js","assets/dashboard-XGdQ5mjw.js","assets/explore-DgBACn39.js","assets/integrations-Dwl9KQp0.js","assets/jira-FXr7u2lh.js","assets/jobs-De3EqaO0.js","assets/loops-WyWDkhok.js","assets/mcp-CUAN6ORK.js","assets/narration-DbHnz0qV.js","assets/nav--u7iPYlS.js","assets/packet-B0H15elv.js","assets/settings-CKu0LTib.js","assets/setup-CKGUxKr-.js","assets/specs-DfIdoU5L.js","assets/subscriptionUsage-CdlE5Mg1.js","assets/terminal-BAzpHtpY.js","assets/tickets-D41ZMQpf.js","assets/createLucideIcon-Bjbbp_KS.js","assets/Trans-CAY2Rd2f.js","assets/useDesktop-9Lwoel_8.js","assets/react-dom-DiWzBDP9.js","assets/provider-capabilities-lePfeT_Y.js","assets/spending-DemeFfrH.js","assets/clsx-CjueKrWZ.js","assets/formatDistanceToNow-DlQY1Lm_.js","assets/getTimezoneOffsetInMilliseconds-COUeq65G.js","assets/index-CONHvEJw.js","assets/tauri-shell-DE6LfGPw.js","assets/TicketDetailModalContext-Dea22CI-.js","assets/save-B6wGWwBR.js","assets/MinimizedChatsContext-Ds2-R6qJ.js","assets/chevron-up-CwGUHd6D.js","assets/sparkles-DrY8hb0X.js","assets/x-CBMI9ejn.js","assets/ban-E4EaZe3a.js","assets/file-text-BcLCfcWZ.js","assets/circle-x-CMYsN4ZH.js","assets/download-CFHyyHcw.js","assets/external-link-D6XxC_Cu.js","assets/git-branch-BRM0F5GN.js","assets/layers-eDMjG41I.js","assets/loader-circle-DcygOQpC.js","assets/pencil-C9RkrIPZ.js","assets/square-Ci-PFcFe.js","assets/plus-CsnjfkmX.js","assets/rotate-ccw-Wh4id13q.js","assets/search-Cq_gE3Ik.js","assets/trash-2-BOU3WdZZ.js","assets/triangle-alert-H9T4jAmh.js","assets/jira-api-C94VNHNY.js","assets/zap-CHo3aCD1.js","assets/dialog-CMgps2MY.js","assets/utils-BC4DHlz2.js","assets/lib-CGdyxIWg.js","assets/button-D1xExSTF.js","assets/RepositoryScopeSelector-DrtPqvc_.js","assets/project-repositories-Cc3oNv8X.js","assets/core-B3mKBIh5.js","assets/RepositoryDeliveries-CW4eKG3x.js","assets/dist-Bq10nS2m.js","assets/useRuntimeRuns-DL0xYoZ2.js","assets/event-Bt7f3wXe.js","assets/index-BacrO_ub.css","assets/ExportDropdown-BvKQgwM9.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{$ as c,Mt as l,Nt as u}from"./index-CONHvEJw.js";var d=e(t(),1),f=n(),p=(0,d.lazy)(()=>r(()=>import(`./AnalyticsPage-YD8MLoPa.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,79,80,81,82,83,84,85]))),m=420,h=560;function g(){let{t:e}=i(`agent`),{closeAnalyticsPane:t}=c(),[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)(l,{className:`h-4 w-4`}):(0,f.jsx)(u,{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-CLJn2F2e.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-DEKu087k.js","assets/useSharedWebSocket-BEHPeU1Y.js","assets/activity-DiyjsgQV.js","assets/addspec-BOvgmcgk.js","assets/agent-Dip4h2YE.js","assets/agentRuntime-C_yLPKDy.js","assets/agents-Doe1sHY5.js","assets/agentstudio-BzugqB_N.js","assets/aiedit-iIjz2_Qk.js","assets/analytics-BWynXPEm.js","assets/attachments-B53PG733.js","assets/browser-CG0PE1j_.js","assets/builder-CpS9liE9.js","assets/chat-BGD7MXAy.js","assets/code-DaZ-ZwCT.js","assets/commands-BAWSYHjQ.js","assets/common-Bdi0qN4S.js","assets/dashboard-XGdQ5mjw.js","assets/explore-DgBACn39.js","assets/integrations-Dwl9KQp0.js","assets/jira-FXr7u2lh.js","assets/jobs-De3EqaO0.js","assets/loops-WyWDkhok.js","assets/mcp-CUAN6ORK.js","assets/narration-DbHnz0qV.js","assets/nav--u7iPYlS.js","assets/packet-B0H15elv.js","assets/settings-CKu0LTib.js","assets/setup-CKGUxKr-.js","assets/specs-DfIdoU5L.js","assets/subscriptionUsage-CdlE5Mg1.js","assets/terminal-BAzpHtpY.js","assets/tickets-D41ZMQpf.js","assets/createLucideIcon-Bjbbp_KS.js","assets/useDesktop-9Lwoel_8.js","assets/TicketDetailModalContext-Dea22CI-.js","assets/tauri-shell-DE6LfGPw.js","assets/Trans-CAY2Rd2f.js","assets/provider-capabilities-lePfeT_Y.js","assets/save-B6wGWwBR.js","assets/spending-DemeFfrH.js","assets/MinimizedChatsContext-Ds2-R6qJ.js","assets/chevron-up-CwGUHd6D.js","assets/sparkles-DrY8hb0X.js","assets/x-CBMI9ejn.js","assets/ban-E4EaZe3a.js","assets/file-text-BcLCfcWZ.js","assets/circle-x-CMYsN4ZH.js","assets/download-CFHyyHcw.js","assets/external-link-D6XxC_Cu.js","assets/git-branch-BRM0F5GN.js","assets/layers-eDMjG41I.js","assets/loader-circle-DcygOQpC.js","assets/pencil-C9RkrIPZ.js","assets/square-Ci-PFcFe.js","assets/plus-CsnjfkmX.js","assets/rotate-ccw-Wh4id13q.js","assets/search-Cq_gE3Ik.js","assets/trash-2-BOU3WdZZ.js","assets/triangle-alert-H9T4jAmh.js","assets/jira-api-C94VNHNY.js","assets/zap-CHo3aCD1.js","assets/dialog-CMgps2MY.js","assets/utils-BC4DHlz2.js","assets/clsx-CjueKrWZ.js","assets/lib-CGdyxIWg.js","assets/formatDistanceToNow-DlQY1Lm_.js","assets/getTimezoneOffsetInMilliseconds-COUeq65G.js","assets/button-D1xExSTF.js","assets/RepositoryScopeSelector-DrtPqvc_.js","assets/project-repositories-Cc3oNv8X.js","assets/index-CONHvEJw.js","assets/core-B3mKBIh5.js","assets/RepositoryDeliveries-CW4eKG3x.js","assets/dist-Bq10nS2m.js","assets/useRuntimeRuns-DL0xYoZ2.js","assets/event-Bt7f3wXe.js","assets/index-BacrO_ub.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{$ as o,Mt as s,Nt as c,dt as l,ft as u}from"./index-CONHvEJw.js";var d=e(t(),1),f=n(),p=(0,d.lazy)(()=>r(()=>import(`./CodePage-CLJn2F2e.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,79,80,81,82]))),m=420,h=560;function g({projectId:e,conversationId:t}){let{t:n}=i(`agent`),{closeCodePane:r}=o(),[g,_]=(0,d.useState)(h),[v,y]=(0,d.useState)(()=>window.innerWidth),[b,x]=(0,d.useState)(!1),S=(0,d.useRef)(null),C=(0,d.useRef)(null),w=Math.max(0,v-Math.min(m,v/2)),T=Math.min(m,w),E=Math.min(w,Math.max(T,g)),D=(0,d.useCallback)(e=>Math.min(w,Math.max(T,e)),[w,T]),O=`${e}:${t}`,k=(0,d.useCallback)(n=>{u(e,t,{path:n})},[e,t]);(0,d.useLayoutEffect)(()=>{let e=S.current,t=e?.parentElement;if(!e||!t)return;let n=()=>{let n=t.getBoundingClientRect().width||t.clientWidth||window.innerWidth,r=0;for(let n of t.children){if(n===e||!(n instanceof HTMLElement))continue;let t=getComputedStyle(n);t.display===`none`||t.position===`absolute`||t.position===`fixed`||Number(t.flexGrow)>0||(r+=n.getBoundingClientRect().width)}y(Math.max(0,n-r))},r=new ResizeObserver(n),i=()=>{r.disconnect(),r.observe(t);for(let n of t.children)n!==e&&r.observe(n);n()},a=new MutationObserver(i);return a.observe(t,{childList:!0}),i(),window.addEventListener(`resize`,n),()=>{r.disconnect(),a.disconnect(),window.removeEventListener(`resize`,n)}},[O]),(0,d.useEffect)(()=>()=>{C.current?.()},[O,b,w]);let A=(0,d.useCallback)(e=>{if(e.button!==0)return;e.preventDefault(),C.current?.();let t=e.clientX,n=E,r=e.pointerId,i=e.currentTarget;try{i.setPointerCapture(r)}catch{}let a=e=>{e.pointerId===r&&_(D(n+t-e.clientX))},o=()=>{window.removeEventListener(`pointermove`,a),window.removeEventListener(`pointerup`,s),window.removeEventListener(`pointercancel`,s),window.removeEventListener(`blur`,o),i.removeEventListener(`lostpointercapture`,o);try{i.hasPointerCapture(r)&&i.releasePointerCapture(r)}catch{}C.current=null},s=e=>{e.pointerId===r&&o()};C.current=o,window.addEventListener(`pointermove`,a),window.addEventListener(`pointerup`,s),window.addEventListener(`pointercancel`,s),window.addEventListener(`blur`,o),i.addEventListener(`lostpointercapture`,o)},[E,D]);return(0,f.jsxs)(`div`,{ref:S,className:`flex h-full min-w-0 flex-col overflow-hidden border-l border-border bg-background${b?` absolute inset-0 z-30 border-l-0`:` relative`}`,style:b?void 0:{width:E,maxWidth:`100%`,flexShrink:0},"data-testid":`agent-code-pane`,children:[!b&&(0,f.jsx)(`div`,{role:`separator`,"aria-orientation":`vertical`,onPointerDown:A,tabIndex:0,"aria-label":n(`workspace.resizeCode`),"aria-valuemin":Math.round(T),"aria-valuemax":Math.round(w),"aria-valuenow":Math.round(E),onKeyDown:e=>{let t=e.key===`ArrowLeft`?E+32:e.key===`ArrowRight`?E-32:e.key===`Home`?T:e.key===`End`?w:null;t!==null&&(e.preventDefault(),_(D(t)))},className:`absolute inset-y-0 left-0 z-20 w-1.5 cursor-col-resize select-none touch-none hover:bg-accent-primary/20 focus-visible:bg-accent-primary/30 focus-visible:outline-none`,title:n(`workspace.resizeCode`)}),(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:n(`workspace.files`)}),(0,f.jsxs)(`div`,{className:`ml-auto flex items-center gap-1`,children:[(0,f.jsx)(`button`,{type:`button`,onClick:()=>{C.current?.(),x(e=>!e)},"aria-label":n(b?`restore`:`maximize`),title:n(b?`restore`:`maximize`),className:`rounded-md p-1 text-foreground/60 hover:bg-surface hover:text-foreground`,children:b?(0,f.jsx)(s,{className:`h-4 w-4`}):(0,f.jsx)(c,{className:`h-4 w-4`})}),(0,f.jsx)(`button`,{type:`button`,onClick:()=>{C.current?.(),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,f.jsx)(a,{className:`h-4 w-4`})})]})]}),(0,f.jsx)(`div`,{className:`min-h-0 flex-1 overflow-hidden`,children:(0,f.jsx)(d.Suspense,{fallback:(0,f.jsx)(`div`,{className:`flex h-full items-center justify-center text-sm text-muted-foreground`,children:`…`}),children:(0,f.jsx)(p,{embedded:!0,initialRepositoryId:l(e,t).repositoryId,onRepositoryChange:n=>u(e,t,{repositoryId:n}),initialPath:l(e,t).path,onSelectedPathChange:k},O)})})]},O)}export{g as AgentModeCodePane};