specrails-desktop 2.63.2 → 2.64.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 (72) hide show
  1. package/client/dist/assets/{ActivityFeedPage-B1bpTy7O.js → ActivityFeedPage-D23wXKgy.js} +1 -1
  2. package/client/dist/assets/{AgentBrowserCapture-BussBbN3.js → AgentBrowserCapture-DQzS4XcK.js} +1 -1
  3. package/client/dist/assets/{AgentModeAnalyticsPane-BiT1n3BU.js → AgentModeAnalyticsPane-CFTiSl8T.js} +2 -2
  4. package/client/dist/assets/{AgentModeCodePane-CUjNfeKa.js → AgentModeCodePane-DAaSzIPA.js} +2 -2
  5. package/client/dist/assets/{AgentModeJobsPane-RVEwyS59.js → AgentModeJobsPane-C29YpixV.js} +1 -1
  6. package/client/dist/assets/{AgentsPage-DKE-eMC5.js → AgentsPage-B-sSTP1I.js} +1 -1
  7. package/client/dist/assets/{AnalyticsPage-Bser2Ly3.js → AnalyticsPage-81fI80nA.js} +1 -1
  8. package/client/dist/assets/{CodePage-BK0grm6e.js → CodePage-Ct4XEPdx.js} +1 -1
  9. package/client/dist/assets/{DesktopAnalyticsPage-DEBidZbs.js → DesktopAnalyticsPage-Bt37XOLF.js} +1 -1
  10. package/client/dist/assets/{DocsDialog-Dhyo57n9.js → DocsDialog-_IEr8umI.js} +1 -1
  11. package/client/dist/assets/{DocsPage-BcEkPXd6.js → DocsPage-DX9WOqt2.js} +1 -1
  12. package/client/dist/assets/{ExportDropdown-mRmqdkjr.js → ExportDropdown-Bb1lHx3r.js} +1 -1
  13. package/client/dist/assets/{InteractiveJobComposer-BlCe3vrU.js → InteractiveJobComposer-DDI6TdsS.js} +1 -1
  14. package/client/dist/assets/{JobDetailModal-CkAejD1F.js → JobDetailModal-CkD0PBaZ.js} +1 -1
  15. package/client/dist/assets/{JobDetailPage-P-FS_lKz.js → JobDetailPage-B8Rd4U5J.js} +1 -1
  16. package/client/dist/assets/{JobsPage-CoUXgYse.js → JobsPage-ChOiSbJn.js} +1 -1
  17. package/client/dist/assets/{LoopBuilderPage-iFylNk68.js → LoopBuilderPage-6Xwt2G6G.js} +1 -1
  18. package/client/dist/assets/{LoopPreviewModal-4obS_Ptg.js → LoopPreviewModal-CyDapSvB.js} +1 -1
  19. package/client/dist/assets/{LoopWindowSurface-CLr5OPqj.js → LoopWindowSurface-gRdx4nmg.js} +1 -1
  20. package/client/dist/assets/{LoopsPage-eKpWFtCp.js → LoopsPage-BPGVFsH-.js} +1 -1
  21. package/client/dist/assets/{MinimizedChatsContext-BHIUPVmC.js → MinimizedChatsContext-DHfigjHS.js} +1 -1
  22. package/client/dist/assets/{PluginsPage-BgTC2CQx.js → PluginsPage-DQRRdCUi.js} +1 -1
  23. package/client/dist/assets/{ProjectSettingsDialog-B4chTfU8.js → ProjectSettingsDialog-Ce8DSG6H.js} +1 -1
  24. package/client/dist/assets/{RepositoryDeliveries-DAIxIYsl.js → RepositoryDeliveries-B4ybHOyB.js} +1 -1
  25. package/client/dist/assets/{RepositoryScopeSelector-DeCoUsRN.js → RepositoryScopeSelector-2eSqR9WK.js} +1 -1
  26. package/client/dist/assets/{ReviewPacketPage-Cktv0-9b.js → ReviewPacketPage-WVoio8GJ.js} +1 -1
  27. package/client/dist/assets/{TemplatePreviewModal-BZTV1TM1.js → TemplatePreviewModal-CTBZUXAN.js} +1 -1
  28. package/client/dist/assets/{TicketDetailModalContext-DiPngt2g.js → TicketDetailModalContext-Dkwo2cDs.js} +1 -1
  29. package/client/dist/assets/{Trans-ZiXIfj50.js → Trans-BbFxRFtR.js} +1 -1
  30. package/client/dist/assets/{formatDistanceToNow-CdN5lTyD.js → formatDistanceToNow-NjPHBLfB.js} +1 -1
  31. package/client/dist/assets/{getTimezoneOffsetInMilliseconds-62h40I4t.js → getTimezoneOffsetInMilliseconds-vJ7qUoqN.js} +1 -1
  32. package/client/dist/assets/{index-CECj3joE.js → index-IdGpV21X.js} +36 -36
  33. package/client/dist/assets/{jira-api-C_D_RDhl.js → jira-api-Cabj-H0J.js} +1 -1
  34. package/client/dist/assets/{project-repositories-Be0CWklo.js → project-repositories-Cnnuj9Ia.js} +1 -1
  35. package/client/dist/assets/{settings-Hw7av-B1.js → settings-B1NV8Ow9.js} +1 -1
  36. package/client/dist/assets/settings-BQ8XFZeE.js +1 -0
  37. package/client/dist/assets/settings-BRkxfBbO.js +1 -0
  38. package/client/dist/assets/settings-C_CcU1dP.js +1 -0
  39. package/client/dist/assets/settings-Cto9n0eL.js +1 -0
  40. package/client/dist/assets/{settings-nryN6wuS.js → settings-Pyw8uYtL.js} +1 -1
  41. package/client/dist/assets/settings-b3HVCNNd.js +1 -0
  42. package/client/dist/assets/settings-hdive4u6.js +1 -0
  43. package/client/dist/assets/{spending-eWiZGhC_.js → spending-CQnrWiR3.js} +1 -1
  44. package/client/dist/assets/{useDesktop-C0mRIlHT.js → useDesktop-BjLT0_EE.js} +1 -1
  45. package/client/dist/assets/{useRuntimeRuns-B1a6GChu.js → useRuntimeRuns-C97qEqMg.js} +1 -1
  46. package/client/dist/assets/{useSharedWebSocket-CcsE5986.js → useSharedWebSocket-Bj4LMp7b.js} +2 -2
  47. package/client/dist/index.html +15 -15
  48. package/docs/internals/api-reference.md +26 -0
  49. package/docs/internals/source-map.md +9 -2
  50. package/package.json +1 -1
  51. package/server/dist/index.js +14 -1
  52. package/server/dist/mobile/mobile-admin-router.js +16 -0
  53. package/server/dist/mobile/mobile-gateway.js +18 -0
  54. package/server/dist/mobile/mobile-missions.js +6 -4
  55. package/server/dist/mobile/mobile-preview-channel.js +179 -0
  56. package/server/dist/mobile/mobile-preview.js +366 -0
  57. package/server/dist/mobile/mobile-router.js +9 -1
  58. package/server/dist/mobile/mobile-webrtc-peer.js +54 -4
  59. package/server/dist/mobile/mobile-ws.js +4 -2
  60. package/server/dist/modules/browser/domain/preview-origins.js +117 -0
  61. package/server/dist/modules/browser/runtime/browser-capture-manager.js +141 -18
  62. package/server/dist/modules/browser/runtime/browser-capture-types.js +10 -1
  63. package/server/dist/modules/browser/runtime/browser-playwright.js +19 -0
  64. package/server/dist/modules/browser/runtime/preview-targets.js +80 -0
  65. package/server/dist/modules/terminals/runtime/background-process-service.js +24 -1
  66. package/server/dist/project-registry.js +10 -0
  67. package/client/dist/assets/settings-BO2N-oPf.js +0 -1
  68. package/client/dist/assets/settings-BQChAs_w.js +0 -1
  69. package/client/dist/assets/settings-BzmYlcQB.js +0 -1
  70. package/client/dist/assets/settings-CGjXb6TX.js +0 -1
  71. package/client/dist/assets/settings-CMxEWY46.js +0 -1
  72. package/client/dist/assets/settings-C_dRSgI2.js +0 -1
@@ -10,6 +10,7 @@ const ids_1 = require("../../../ids");
10
10
  const attachment_manager_1 = require("../../../attachment-manager");
11
11
  const browser_capture_types_1 = require("./browser-capture-types");
12
12
  const browser_playwright_1 = require("./browser-playwright");
13
+ const preview_origins_1 = require("../domain/preview-origins");
13
14
  const browser_viewport_1 = require("./browser-viewport");
14
15
  const WS_OPEN = 1;
15
16
  const DEFAULT_VIEWPORT = { width: 1280, height: 800 };
@@ -20,6 +21,14 @@ function isTargetClosedError(err) {
20
21
  return /target page, context or browser has been closed|target closed|has been closed/i.test(msg);
21
22
  }
22
23
  const MAX_SESSIONS_PER_PROJECT = 4;
24
+ /** Companion app-preview sessions have their own budget so a phone can never
25
+ * take Desktop's sessions: one per device (a new one replaces it), two per
26
+ * project across devices. */
27
+ const MAX_DEVICE_SESSIONS_PER_PROJECT = 2;
28
+ /** The announced-origin set is consulted on every request of a preview page
29
+ * (a dev server loads hundreds of modules); re-derive it at most this often. */
30
+ const PREVIEW_ORIGINS_TTL_MS = 1000;
31
+ const DESKTOP_OWNER = { kind: 'desktop' };
23
32
  const DEFAULT_SESSION_IDLE_TTL_MS = 10 * 60_000;
24
33
  const DEFAULT_SESSION_SWEEP_INTERVAL_MS = 60_000;
25
34
  const DOM_HTML_BYTE_CAP = 100_000;
@@ -56,13 +65,15 @@ class BrowserCaptureManager {
56
65
  contextPool;
57
66
  now;
58
67
  sessionIdleTtlMs;
68
+ previewOrigins;
69
+ previewOriginsCache = null;
59
70
  idleSweepTimer = null;
60
71
  context = null;
61
72
  contextPromise = null;
62
73
  sessions = new Map();
63
74
  /** Count of in-flight `create()` calls that have reserved a slot but not yet
64
75
  * committed their session — guards the cap against concurrent creates. */
65
- _reserved = 0;
76
+ _reserved = { desktop: 0, device: 0 };
66
77
  disposed = false;
67
78
  constructor(opts) {
68
79
  this.projectId = opts.projectId;
@@ -72,6 +83,7 @@ class BrowserCaptureManager {
72
83
  this.attachments = opts.attachments ?? attachment_manager_1.attachmentManager;
73
84
  this.contextPool = opts.contextPool ?? null;
74
85
  this.now = opts.now ?? Date.now;
86
+ this.previewOrigins = opts.previewOrigins ?? (() => new Set());
75
87
  const requestedIdleTtlMs = opts.sessionIdleTtlMs;
76
88
  this.sessionIdleTtlMs = typeof requestedIdleTtlMs === 'number' && Number.isFinite(requestedIdleTtlMs) && requestedIdleTtlMs > 0
77
89
  ? requestedIdleTtlMs
@@ -144,6 +156,7 @@ class BrowserCaptureManager {
144
156
  viewportWidth: s.viewport.width,
145
157
  viewportHeight: s.viewport.height,
146
158
  createdAt: s.createdAt,
159
+ owner: s.owner,
147
160
  };
148
161
  }
149
162
  listSessions() {
@@ -167,27 +180,46 @@ class BrowserCaptureManager {
167
180
  s.activeOperations = Math.max(0, s.activeOperations - 1);
168
181
  s.lastActivityAt = this.now();
169
182
  }
183
+ liveCount(kind) {
184
+ return [...this.sessions.values()].filter((s) => !s.closed && s.owner.kind === kind).length;
185
+ }
170
186
  async create(opts) {
187
+ const owner = opts?.owner ?? DESKTOP_OWNER;
188
+ const kind = owner.kind;
189
+ const limit = kind === 'device' ? MAX_DEVICE_SESSIONS_PER_PROJECT : MAX_SESSIONS_PER_PROJECT;
190
+ if (owner.kind === 'device') {
191
+ // A phone may only type announced origins (never an arbitrary URL), and
192
+ // is validated BEFORE replacing its previous session so a bad request
193
+ // leaves the current preview alone.
194
+ const initialUrl = opts?.initialUrl?.trim() ?? '';
195
+ if (!initialUrl || !(0, preview_origins_1.isManualPreviewUrlAllowed)(initialUrl, this.currentPreviewOrigins(true))) {
196
+ throw new browser_capture_types_1.BrowserNavigationRejectedError();
197
+ }
198
+ for (const existing of [...this.sessions.values()]) {
199
+ if (!existing.closed && existing.owner.kind === 'device' && existing.owner.deviceId === owner.deviceId) {
200
+ await this.kill(existing.id);
201
+ }
202
+ }
203
+ }
171
204
  // Reserve the slot SYNCHRONOUSLY before any await so N concurrent creates can't
172
205
  // all observe `< MAX` and race past the cap (BUG-BROWSER-02). `_reserved`
173
- // counts in-flight (not-yet-inserted) sessions; it's the only state that
174
- // mutates synchronously here.
175
- const live = [...this.sessions.values()].filter((s) => !s.closed).length;
176
- if (live + this._reserved >= MAX_SESSIONS_PER_PROJECT) {
177
- throw new browser_capture_types_1.BrowserLimitExceededError(MAX_SESSIONS_PER_PROJECT);
206
+ // counts in-flight (not-yet-inserted) sessions per budget; it's the only
207
+ // state that mutates synchronously here.
208
+ if (this.liveCount(kind) + this._reserved[kind] >= limit) {
209
+ throw new browser_capture_types_1.BrowserLimitExceededError(limit);
178
210
  }
179
- this._reserved++;
211
+ this._reserved[kind]++;
180
212
  let page;
181
213
  try {
182
214
  const ctx = await this.ensureContext();
183
215
  page = await ctx.newPage();
184
216
  }
185
217
  catch (err) {
186
- this._reserved--;
218
+ this._reserved[kind]--;
187
219
  throw err;
188
220
  }
189
221
  if (this.disposed) {
190
- this._reserved--;
222
+ this._reserved[kind]--;
191
223
  try {
192
224
  await page.close();
193
225
  }
@@ -197,20 +229,37 @@ class BrowserCaptureManager {
197
229
  // Re-check AFTER the awaits: another create that started before us may have
198
230
  // committed its session in the meantime. If the cap is now exceeded, tear the
199
231
  // extra page down and reject rather than silently exceeding the limit.
200
- const liveNow = [...this.sessions.values()].filter((s) => !s.closed).length;
201
- if (liveNow >= MAX_SESSIONS_PER_PROJECT) {
202
- this._reserved--;
232
+ if (this.liveCount(kind) >= limit) {
233
+ this._reserved[kind]--;
203
234
  try {
204
235
  await page.close();
205
236
  }
206
237
  catch { /* ignore */ }
207
- throw new browser_capture_types_1.BrowserLimitExceededError(MAX_SESSIONS_PER_PROJECT);
238
+ throw new browser_capture_types_1.BrowserLimitExceededError(limit);
239
+ }
240
+ if (kind === 'device') {
241
+ // Fail closed: a preview page without request interception could reach
242
+ // any loopback port of this machine.
243
+ try {
244
+ if (!page.setRequestGuard)
245
+ throw new Error('request guard unsupported');
246
+ await page.setRequestGuard((url) => this.allowDeviceRequest(url));
247
+ }
248
+ catch (err) {
249
+ this._reserved[kind]--;
250
+ try {
251
+ await page.close();
252
+ }
253
+ catch { /* ignore */ }
254
+ throw new browser_capture_types_1.BrowserLaunchError('failed to secure preview page', err);
255
+ }
208
256
  }
209
257
  const id = (0, ids_1.newId)();
210
258
  const activityAt = this.now();
211
259
  const session = {
212
260
  id,
213
261
  projectId: this.projectId,
262
+ owner,
214
263
  page,
215
264
  popups: [],
216
265
  popupView: false,
@@ -229,7 +278,7 @@ class BrowserCaptureManager {
229
278
  };
230
279
  this.sessions.set(id, session);
231
280
  // The reservation is now realised as a committed session — release it.
232
- this._reserved--;
281
+ this._reserved[kind]--;
233
282
  // Adopt popups the page opens (OAuth login windows etc.) as secondary pages
234
283
  // of this session, and keep the client's URL bar live on in-page navigation
235
284
  // (link clicks / redirects the REST navigate path never sees).
@@ -239,7 +288,7 @@ class BrowserCaptureManager {
239
288
  return;
240
289
  session.url = url;
241
290
  if (url && url !== 'about:blank')
242
- this.setLastUrl(url);
291
+ this.rememberUrl(session, url);
243
292
  this.broadcastControl(session, { type: 'nav', url, title: session.title });
244
293
  });
245
294
  // Start capturing the page's network requests before the first navigation so
@@ -256,7 +305,13 @@ class BrowserCaptureManager {
256
305
  // away, so the user watches the page paint progressively instead of staring
257
306
  // at "opening…". The URL is set optimistically to the target; the resolved
258
307
  // URL/title land via the `nav` control broadcast every client already handles.
259
- const target = opts?.initialUrl?.trim() || this.getLastUrl() || 'about:blank';
308
+ if (opts?.viewport) {
309
+ try {
310
+ await this.handleInput(id, { type: 'resize', ...opts.viewport });
311
+ }
312
+ catch { /* the default viewport still renders */ }
313
+ }
314
+ const target = opts?.initialUrl?.trim() || (kind === 'desktop' ? this.getLastUrl() : null) || 'about:blank';
260
315
  session.url = target;
261
316
  void this.runInitialNavigation(session, target);
262
317
  return this.toMeta(session);
@@ -276,9 +331,34 @@ class BrowserCaptureManager {
276
331
  s.url = result.url;
277
332
  s.title = result.title;
278
333
  if (result.url && result.url !== 'about:blank')
279
- this.setLastUrl(result.url);
334
+ this.rememberUrl(s, result.url);
280
335
  this.broadcastControl(s, { type: 'nav', url: result.url, title: result.title });
281
336
  }
337
+ /** Desktop's "last URL" is a Desktop convenience; a phone's preview of a
338
+ * loopback origin must never become the next Desktop session's start page. */
339
+ rememberUrl(s, url) {
340
+ if (s.owner.kind === 'desktop')
341
+ this.setLastUrl(url);
342
+ }
343
+ currentPreviewOrigins(fresh = false) {
344
+ const at = this.now();
345
+ if (!fresh && this.previewOriginsCache && at - this.previewOriginsCache.at < PREVIEW_ORIGINS_TTL_MS) {
346
+ return this.previewOriginsCache.origins;
347
+ }
348
+ let origins;
349
+ try {
350
+ origins = this.previewOrigins();
351
+ }
352
+ catch {
353
+ origins = new Set();
354
+ }
355
+ this.previewOriginsCache = { at, origins };
356
+ return origins;
357
+ }
358
+ /** The request guard of device-owned pages (documents + subresources). */
359
+ allowDeviceRequest(url) {
360
+ return (0, preview_origins_1.isPreviewRequestAllowed)(url, this.currentPreviewOrigins(), browser_playwright_1.isNavigableUrl);
361
+ }
282
362
  // ─── Popup support (OAuth login windows etc.) ───────────────────────────────
283
363
  topPopup(s) {
284
364
  return s.popups.length > 0 ? s.popups[s.popups.length - 1] : null;
@@ -299,6 +379,22 @@ class BrowserCaptureManager {
299
379
  void popup.close().catch(() => { });
300
380
  return;
301
381
  }
382
+ if (s.owner.kind === 'device') {
383
+ // A popup is a new page: install the same guard, and close one that was
384
+ // opened straight onto a forbidden URL before the guard could see it.
385
+ const opened = popup.currentUrl();
386
+ if (opened && opened !== 'about:blank' && !this.allowDeviceRequest(opened)) {
387
+ void popup.close().catch(() => { });
388
+ return;
389
+ }
390
+ if (!popup.setRequestGuard) {
391
+ void popup.close().catch(() => { });
392
+ return;
393
+ }
394
+ void popup.setRequestGuard((url) => this.allowDeviceRequest(url)).catch(() => {
395
+ void popup.close().catch(() => { });
396
+ });
397
+ }
302
398
  s.popups.push(popup);
303
399
  s.popupView = true;
304
400
  // Popups inherit the session viewport so coordinate mapping + screencast
@@ -553,7 +649,7 @@ class BrowserCaptureManager {
553
649
  s.url = result.url;
554
650
  s.title = result.title;
555
651
  if (result.url && result.url !== 'about:blank')
556
- this.setLastUrl(result.url);
652
+ this.rememberUrl(s, result.url);
557
653
  this.broadcastControl(s, { type: 'nav', url: result.url, title: result.title });
558
654
  }
559
655
  return result;
@@ -875,6 +971,33 @@ class BrowserCaptureManager {
875
971
  this.context = null;
876
972
  this.contextPromise = null;
877
973
  }
974
+ /** Owner of a live session, or null when it does not exist. */
975
+ sessionOwner(sessionId) {
976
+ return this.getSession(sessionId)?.owner ?? null;
977
+ }
978
+ /**
979
+ * Manual navigation from a device: the session must belong to that device,
980
+ * and `goto` only accepts paths on announced origins (the page itself may
981
+ * still follow public links/redirects through the request guard).
982
+ */
983
+ async navigateAsDevice(sessionId, deviceId, action, url) {
984
+ const owner = this.sessionOwner(sessionId);
985
+ if (!owner || owner.kind !== 'device' || owner.deviceId !== deviceId)
986
+ return null;
987
+ if (action === 'goto' && !(url && (0, preview_origins_1.isManualPreviewUrlAllowed)(url, this.currentPreviewOrigins(true)))) {
988
+ throw new browser_capture_types_1.BrowserNavigationRejectedError();
989
+ }
990
+ return this.navigate(sessionId, action, url);
991
+ }
992
+ /** Kill every session a device owns (device revoked / unpaired). */
993
+ async killDeviceSessions(deviceId) {
994
+ let killed = 0;
995
+ for (const s of [...this.sessions.values()]) {
996
+ if (s.owner.kind === 'device' && s.owner.deviceId === deviceId && await this.kill(s.id))
997
+ killed += 1;
998
+ }
999
+ return killed;
1000
+ }
878
1001
  sessionCount() {
879
1002
  return [...this.sessions.values()].filter((s) => !s.closed).length;
880
1003
  }
@@ -6,7 +6,7 @@
6
6
  // no real Chromium. The real Playwright/CDP implementation lives in
7
7
  // `browser-playwright.ts` and is the ONLY module that imports `playwright`.
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.BrowserLaunchError = exports.BrowserLimitExceededError = void 0;
9
+ exports.BrowserLaunchError = exports.BrowserNavigationRejectedError = exports.BrowserLimitExceededError = void 0;
10
10
  // ─── Error classes (mirror terminal-manager's typed errors) ───────────────────
11
11
  class BrowserLimitExceededError extends Error {
12
12
  limit;
@@ -17,6 +17,15 @@ class BrowserLimitExceededError extends Error {
17
17
  }
18
18
  }
19
19
  exports.BrowserLimitExceededError = BrowserLimitExceededError;
20
+ /** A device-owned preview session was asked to load a URL its origin policy
21
+ * rejects (an unannounced loopback port, or any manual non-announced URL). */
22
+ class BrowserNavigationRejectedError extends Error {
23
+ constructor() {
24
+ super('browser_navigation_rejected');
25
+ this.name = 'BrowserNavigationRejectedError';
26
+ }
27
+ }
28
+ exports.BrowserNavigationRejectedError = BrowserNavigationRejectedError;
20
29
  class BrowserLaunchError extends Error {
21
30
  cause;
22
31
  constructor(message, cause) {
@@ -447,6 +447,25 @@ class PlaywrightPageHandle {
447
447
  return null;
448
448
  }
449
449
  }
450
+ async setRequestGuard(allow) {
451
+ // Page-scoped (not context-scoped): the shared global context also serves
452
+ // Desktop sessions, which must keep their own unchanged guard and must not
453
+ // pay for request interception.
454
+ await this.page.route('**/*', async (route) => {
455
+ let url = '';
456
+ try {
457
+ url = route.request().url();
458
+ }
459
+ catch { /* treat as rejected */ }
460
+ try {
461
+ if (url && allow(url))
462
+ await route.fallback();
463
+ else
464
+ await route.abort('blockedbyclient');
465
+ }
466
+ catch { /* the page or request went away */ }
467
+ });
468
+ }
450
469
  async enableNetwork() {
451
470
  if (this.netEnabled)
452
471
  return;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createPreviewTargetSource = createPreviewTargetSource;
4
+ const transient_children_1 = require("../../../transient-children");
5
+ const preview_origins_1 = require("../domain/preview-origins");
6
+ /** Background-process statuses whose announced URLs are still served. */
7
+ const LIVE_STATUSES = new Set(['starting', 'running']);
8
+ /** Dev servers announce their URL in the first lines; a long-running process
9
+ * can still re-announce after a restart, so scan the full retained tail. */
10
+ const SCAN_LIMIT = 2000;
11
+ const defaultReader = {
12
+ list: (projectId) => (0, transient_children_1.listBackgroundProcesses)({ projectId }),
13
+ logs: (process) => (0, transient_children_1.getBackgroundProcessLogs)(process.pid, { projectId: process.projectId, processId: process.processId, limit: SCAN_LIMIT }),
14
+ };
15
+ /**
16
+ * Derives preview targets from mission background-process logs on demand.
17
+ * Targets are derived state: nothing is persisted, an exited process simply
18
+ * stops contributing, and a process recovered after a restart (`interrupted`,
19
+ * OS state unknown) never does. A per-process memo keyed on the log tail keeps
20
+ * the per-request navigation guard cheap.
21
+ */
22
+ function createPreviewTargetSource(reader = defaultReader) {
23
+ const memo = new Map();
24
+ const urlsFor = (process) => {
25
+ let snapshot = null;
26
+ try {
27
+ snapshot = reader.logs(process);
28
+ }
29
+ catch {
30
+ return [];
31
+ }
32
+ if (!snapshot)
33
+ return [];
34
+ const last = snapshot.lines[snapshot.lines.length - 1];
35
+ const key = `${snapshot.nextSequence}:${last?.sequence ?? 0}:${last?.line.length ?? 0}`;
36
+ const cached = memo.get(process.processId);
37
+ if (cached?.key === key)
38
+ return cached.urls;
39
+ const urls = (0, preview_origins_1.previewTargetsFromLines)(snapshot.lines.map((line) => line.line));
40
+ memo.set(process.processId, { projectId: process.projectId, key, urls });
41
+ return urls;
42
+ };
43
+ const live = (projectId) => {
44
+ let processes = [];
45
+ try {
46
+ processes = reader.list(projectId);
47
+ }
48
+ catch {
49
+ return [];
50
+ }
51
+ const running = processes.filter((process) => process.projectId === projectId && LIVE_STATUSES.has(process.status));
52
+ // Drop memo entries of processes that are no longer live so the cache is
53
+ // bounded by the running set of the projects being previewed.
54
+ const ids = new Set(running.map((process) => process.processId));
55
+ for (const [processId, entry] of memo) {
56
+ if (entry.projectId === projectId && !ids.has(processId))
57
+ memo.delete(processId);
58
+ }
59
+ return running;
60
+ };
61
+ const target = (process) => ({ processId: process.processId, chatId: process.chatId, urls: urlsFor(process) });
62
+ return {
63
+ list: (projectId) => live(projectId).map(target).filter((entry) => entry.urls.length > 0),
64
+ forProcess: (projectId, processId) => {
65
+ const process = live(projectId).find((candidate) => candidate.processId === processId);
66
+ return process ? target(process) : null;
67
+ },
68
+ announcedOrigins: (projectId) => {
69
+ const origins = new Set();
70
+ for (const process of live(projectId)) {
71
+ for (const url of urlsFor(process)) {
72
+ const origin = (0, preview_origins_1.previewOrigin)(url);
73
+ if (origin)
74
+ origins.add(origin);
75
+ }
76
+ }
77
+ return origins;
78
+ },
79
+ };
80
+ }
@@ -12,6 +12,7 @@ const node_path_1 = __importDefault(require("node:path"));
12
12
  const loop_runs_store_1 = require("../../loops/runtime/loop-runs-store");
13
13
  const project_repositories_1 = require("../../../project-repositories");
14
14
  const transient_children_1 = require("../../../transient-children");
15
+ const preview_origins_1 = require("../../browser/domain/preview-origins");
15
16
  /** The same scope and lifecycle policy applies to REST and the mission MCP. */
16
17
  function resolveBackgroundCwd(repositoryRoot, cwd) {
17
18
  if (cwd !== undefined && typeof cwd !== 'string')
@@ -25,7 +26,29 @@ function resolveBackgroundCwd(repositoryRoot, cwd) {
25
26
  function backgroundProcessHooks(broadcast) {
26
27
  const publish = (type) => (process) => broadcast({ type, process,
27
28
  projectId: process.projectId, timestamp: new Date().toISOString() });
28
- return { onStarted: publish('background_process.started'), onUpdated: publish('background_process.updated'), onExited: publish('background_process.exited') };
29
+ // Companion app preview: announce once per newly seen local origin so phones
30
+ // refetch the derived targets instead of tailing every log line.
31
+ const announced = new Set();
32
+ const onOutput = (event) => {
33
+ let fresh = false;
34
+ for (const url of (0, preview_origins_1.extractPreviewUrls)(event.line)) {
35
+ const origin = (0, preview_origins_1.previewOrigin)(url);
36
+ if (origin && !announced.has(origin)) {
37
+ announced.add(origin);
38
+ fresh = true;
39
+ }
40
+ }
41
+ if (fresh)
42
+ broadcast({ type: 'background_process.preview_targets', processId: event.processId, chatId: event.chatId,
43
+ projectId: event.projectId, timestamp: new Date().toISOString() });
44
+ };
45
+ const exited = publish('background_process.exited');
46
+ return {
47
+ onStarted: publish('background_process.started'),
48
+ onUpdated: publish('background_process.updated'),
49
+ onOutput,
50
+ onExited: (process) => { announced.clear(); exited(process); },
51
+ };
29
52
  }
30
53
  function backgroundStartBusyReason(c) {
31
54
  const activeJobId = typeof c.queueManager?.getActiveJobId === 'function' ? c.queueManager.getActiveJobId() : null;
@@ -30,6 +30,7 @@ const ticket_watcher_1 = require("./modules/specs/runtime/ticket-watcher");
30
30
  const terminal_manager_1 = require("./modules/terminals/runtime/terminal-manager");
31
31
  const browser_capture_manager_1 = require("./modules/browser/runtime/browser-capture-manager");
32
32
  const browser_context_pool_1 = require("./modules/browser/runtime/browser-context-pool");
33
+ const preview_targets_1 = require("./modules/browser/runtime/preview-targets");
33
34
  const explore_cwd_manager_1 = require("./modules/conversations/runtime/explore-cwd-manager");
34
35
  const hooks_1 = require("./hooks");
35
36
  const transient_children_1 = require("./transient-children");
@@ -119,6 +120,9 @@ class ProjectRegistry {
119
120
  // Chromium profile under ~/.specrails/browser-profile, so cookies/logins are
120
121
  // shared across every project. Launched lazily, disposed once at shutdown().
121
122
  _browserContextPool;
123
+ // Companion app preview: URLs announced by running mission processes,
124
+ // derived on demand from their logs (shared by every project's browser).
125
+ _previewTargets = (0, preview_targets_1.createPreviewTargetSource)();
122
126
  constructor(broadcast, desktopDbPath, desktopPort) {
123
127
  this._broadcast = broadcast;
124
128
  this._desktopDb = (0, desktop_db_1.initDesktopDb)(desktopDbPath ?? (0, desktop_db_1.getDesktopDbPath)());
@@ -128,6 +132,9 @@ class ProjectRegistry {
128
132
  this._failedProjects = new Map();
129
133
  this._browserContextPool = new browser_context_pool_1.SharedBrowserContextPool();
130
134
  }
135
+ get previewTargets() {
136
+ return this._previewTargets;
137
+ }
131
138
  get desktopDb() {
132
139
  return this._desktopDb;
133
140
  }
@@ -1136,6 +1143,9 @@ class ProjectRegistry {
1136
1143
  // Share ONE persistent Chromium profile (global cookies/logins) across all
1137
1144
  // projects instead of a per-project profile.
1138
1145
  contextPool: this._browserContextPool,
1146
+ // Only device-owned (Companion preview) sessions may load these loopback
1147
+ // origins; Desktop sessions keep the regular SSRF guard.
1148
+ previewOrigins: () => this._previewTargets.announcedOrigins(project.id),
1139
1149
  });
1140
1150
  // ── Loops engine (the Loops feature) ──────────────────────────────────────
1141
1151
  const railLoopRuns = new Map();
@@ -1 +0,0 @@
1
- var e={title:`Lingua`,description:`Lingua della UI per tutto Desktop. Si applica all'istante — nessun riavvio necessario.`,selectLabel:`Lingua dell'interfaccia`,updateFailed:`Impossibile salvare la preferenza di lingua`},t={title:`Impostazioni progetto`},n={saveFailed:`Salvataggio non riuscito: {{message}}`},r={title:`Telemetria della pipeline`,description:`Cattura l'uso dei token, le durate delle fasi e l'attività dei subagent per l'export diagnostico. Attivata per impostazione predefinita.`,toggleLabel:`Abilita la telemetria della pipeline`,toggleDescription:`Quando attiva, i dati OTEL dei job della pipeline vengono catturati localmente. Usa il pulsante <mono>Esporta diagnostica</mono> su qualsiasi card di job per scaricarli.`,enabled:`Telemetria della pipeline abilitata`,disabled:`Telemetria della pipeline disabilitata`,saveFailed:`Salvataggio dell'impostazione di telemetria non riuscito`},i={title:`Pre-prompt dei rail`,description:`Istruzioni extra specifiche del progetto aggiunte ai job rail di implement dopo il contesto del ticket e prima dell'esecuzione.`,label:`Pre-prompt`,placeholder:`Esempio: preferisci modifiche incrementali, mantieni le migrazioni retrocompatibili e aggiungi test per ogni modifica del rail.`,helper:`Usalo per indicazioni stabili di progetto che devono accompagnare ogni run di implementazione su rail.`,saveButton:`Salva pre-prompt`,cleared:`Pre-prompt rimosso`,saved:`Pre-prompt salvato`,saveFailed:`Salvataggio del pre-prompt non riuscito`},a={title:`Pre-prompt Freestyle`,description:`Istruzione inviata al provider selezionato in Freestyle. Freestyle salta la pipeline OpenSpec — consegna al provider questo pre-prompt più il testo della spec e lo lascia implementare in autonomia. Lascia vuoto per usare il default integrato.`,label:`Pre-prompt Freestyle`,placeholder:`Lascia vuoto per usare l'istruzione Freestyle predefinita.`,helper:`Il testo della spec viene aggiunto automaticamente dopo questo pre-prompt. Vuoto = default.`,saveButton:`Salva pre-prompt Freestyle`,resetToDefault:`Pre-prompt Freestyle ripristinato al default`,saved:`Pre-prompt Freestyle salvato`,saveFailed:`Salvataggio del pre-prompt Freestyle non riuscito`},o={title:`Budget`,description:`Imposta un tetto di spesa giornaliero per questo progetto. La coda si mette in pausa automaticamente al raggiungimento del limite.`,dailyLabel:`Budget giornaliero (USD)`,dailyHelper:`Lascia vuoto per disabilitare. La spesa è calcolata sulle ultime 24 ore.`,dailyPlaceholder:`es. 5.00`,perJobLabel:`Avviso di costo per job (USD)`,perJobHelper:`Avvisa quando un singolo job di questo progetto supera questo importo.`,perJobPlaceholder:`es. 0.50`,invalidNumber:`Inserisci un numero positivo o lascia vuoto per disabilitare`,dailyRemoved:`Budget giornaliero rimosso`,dailySet:"Budget giornaliero impostato a ${{amount}}",perJobAlertDisabled:`Avviso di costo per job disabilitato`,alertSet:"Avviso impostato per i job oltre ${{amount}}",saveBudgetFailed:`Salvataggio del budget non riuscito`,saveThresholdFailed:`Salvataggio della soglia non riuscito`},s={nav:{appearance:`Aspetto`,specrailsAgents:`Provider di IA`,code:`Codice`,terminal:`Terminale`,updates:`Aggiornamenti`,mobile:`Mobile`,mcp:`MCP`,projects:`Progetti`,advanced:`Avanzate`,webhooks:`Webhooks`,about:`Info`,effects:`Effetti`,subscriptionUsage:`Utilizzo abbonamenti`},title:`Impostazioni Desktop`,description:`Gestisci i progetti registrati e visualizza le informazioni di Desktop.`,registeredProjects:`Progetti registrati`,noProjects:`Nessun progetto ancora registrato`,techUrlDescription:`URL base per l'API specrails-tech (default: http://localhost:3000)`,techUrlSaved:`URL di specrails-tech salvato`,techUrlSaveFailed:`Salvataggio dell'URL non riuscito`,budgetAlertsHeading:`Budget e avvisi`,dailyBudgetLabel:`Budget giornaliero di Desktop (USD)`,dailyBudgetHelper:`Limite di spesa giornaliero globale su tutti i progetti. Le code si mettono in pausa automaticamente al superamento.`,dailyBudgetPlaceholder:`es. 10.00`,perJobHelper:`Avvisa quando un singolo job supera questo importo. Lascia vuoto per disabilitare.`,costAlertsDisabled:`Avvisi di costo disabilitati`,dailyBudgetRemoved:`Budget giornaliero di Desktop rimosso`,dailyBudgetSet:"Budget giornaliero di Desktop impostato a ${{amount}}",dailyBudgetSaveFailed:`Salvataggio del budget giornaliero di Desktop non riuscito`,projectRemoved:`Progetto rimosso`,projectRemoveFailed:`Rimozione del progetto non riuscita`,onboardingHeading:`Onboarding`,platformTour:`Tour della piattaforma`,platformTourDescription:`Rivedi il wizard di benvenuto per ripassare le funzionalità principali.`,replayTour:`Rivedi il tour`,terminalPanelHeading:`Pannello terminale`,infoHeading:`Informazioni Desktop`,infoPort:`Porta`,infoProjects:`Progetti`,infoDb:`DB di Desktop`},c={heading:`Notifiche di sistema`,description:`Mostra notifiche desktop native quando i job vengono completati o falliscono. Le notifiche appaiono solo quando la scheda non è in primo piano.`,enableLabel:`Abilita le notifiche di sistema`,notifyOn:`Notifica per:`,filterAll:`Tutti (completati e falliti)`,filterCompleted:`Solo completati`,filterFailed:`Solo falliti`,enabledToast:`Notifiche di sistema abilitate`,disabledToast:`Notifiche di sistema disabilitate`},l={heading:`Webhook in uscita`,description:`Notifica strumenti esterni (Slack, Zapier, CI/CD) sugli eventi di Desktop. Le richieste sono firmate via <code>X-Specrails-Signature</code> quando è impostato un secret.`,eventJobCompleted:`Job completato`,eventJobFailed:`Job fallito`,eventDailyBudgetExceeded:`Budget giornaliero superato`,statusOn:`on`,statusOff:`off`,disable:`Disabilita`,enable:`Abilita`,sendTestPing:`Invia ping di test`,addHeading:`Aggiungi webhook`,secretPlaceholder:`Secret di firma (facoltativo)`,addButton:`Aggiungi webhook`,urlRequired:`L'URL è obbligatorio`,selectEvent:`Seleziona almeno un evento`,added:`Webhook aggiunto`,addFailed:`Aggiunta del webhook non riuscita`,updateFailed:`Aggiornamento del webhook non riuscito`,removed:`Webhook rimosso`,removeFailed:`Rimozione del webhook non riuscita`,testPingSent:`Ping di test inviato`,testPingFailed:`Invio del ping di test non riuscito`},u={title:`Pannello terminale`,desktopDescription:`Default a livello di Desktop applicati a ogni progetto, salvo override per progetto.`,projectDescription:`Override per progetto del pannello terminale. Lascia un campo invariato per ereditare il default di Desktop.`,fontFamily:`Famiglia di font`,fontSize:`Dimensione font ({{min}}–{{max}})`,renderMode:`Modalità di rendering`,copyOnSelect:`Copia alla selezione`,shellIntegration:`Integrazione shell (marcatori OSC 133)`,notifyLongRunning:`Notifica per comandi a lunga esecuzione`,longCommandThreshold:`Soglia comandi lunghi (ms)`,imageRendering:`Rendering immagini inline (Sixel + iTerm2)`,browserShortcutUrl:`URL scorciatoia browser`,quickScript:`Script rapido (incollato nel terminale attivo — premi Invio manualmente)`,reset:`Reimposta`,unsavedChanges:`Modifiche non salvate`,clearOverride:`Rimuovi override`,clear:`Rimuovi`,inheritingDefault:`Eredita il default di Desktop`,nothingToSave:`Niente da salvare`,saved:`Impostazioni del terminale salvate`},d={heading:`Sezione Code`,summaryLanguage:`Lingua dei riassunti`,monthlyBudget:`Budget mensile (USD)`,budgetHelper:"Tetto alla spesa `surface=file-summary` per mese di calendario. Le rigenerazioni avviate dall'utente possono superarlo."},f={taglines:{dracula:`L'originale — scuro con sfumature viola e vividi accenti neon`,"aurora-light":`Chiaro premium — indaco ispirato a Linear su bianco caldo`,"obsidian-dark":`Scuro premium — quasi nero con sfumature blu e accenti elettrici`,specrails:`Tema del brand — blu navy-indaco profondo con accenti ciano saturi`,"code-rain":`Terminale al fosforo — menta tenue su quasi nero verdastro`,galaxy:`Blu spazio profondo con una scia luminosa a lama`},heading:`Aspetto`,themeGroupLabel:`Tema`,currentlyActive:`Attualmente attivo`,updateFailed:`Aggiornamento del tema non riuscito`},p={heading:`Companion mobile`,description:`Controlla Specrails dall'app SpecRails Companion sul tuo telefono, 100% sulla tua rete locale. Disattivato per impostazione predefinita.`,accessOn:`Accesso mobile attivo`,accessOff:`Accesso mobile disattivato`,listeningOnPort:`In ascolto sulla porta {{port}}`,notListening:`Non in ascolto`,turnOn:`Attiva`,turnOff:`Disattiva`,pairWebDevice:`Associa companion web`,reset:`Reimposta`,resetConfirm:`Reimpostare l'identità mobile? Ogni dispositivo associato verrà revocato e dovrà associarsi di nuovo.`,identityReset:`Identità mobile reimpostata`,enableFailed:`Impossibile abilitare l'accesso mobile: {{message}}`,disableFailed:`Impossibile disabilitare l'accesso mobile: {{message}}`,windowsFirewall:`Alla prima attivazione Windows Firewall chiederà di consentire il server SpecRails — scegli “Consenti su reti private”.`,pairedDevices:`Dispositivi associati`,noDevices:`Nessun dispositivo ancora associato.`,revokeDevice:`Revoca {{name}}`},m={title:`Associa companion web`,description:`Apri specrails.dev/companion-app sul telefono, poi scansiona questo codice con esso.`,startFailed:`Impossibile avviare l'associazione: {{error}}`,showThenScan:`Dopo che il telefono ha scansionato questo, tocca “Scansiona il codice del telefono” e punta la fotocamera verso il telefono.`,scanAnswer:`Scansiona il codice del telefono`,scanning:`Punta la fotocamera sul codice del telefono…`,cameraFailed:`Fotocamera non disponibile: {{message}}`,notAnswer:`Quel QR non è una risposta di associazione.`,connecting:`Connessione…`,paired:`✓ Associato`,pairedToast:`Companion web associato`,answerRejected:`Il desktop ha rifiutato quel codice. Generane uno nuovo e riprova.`,copyCode:`Copia codice`,codeCopied:`Codice copiato`,cancel:`Annulla`},h={heading:`Specrails Core`,installed:`Installato:`,updateAvailable:`Aggiornamento disponibile: {{version}}`,upToDate:`Aggiornato`,neverChecked:`Non verificato`,check:`Cerca aggiornamenti`,checking:`Verifica…`,update:`Aggiorna a {{version}}`,downloading:`Download…`,materializing:`Installazione…`,affectsAll:`Si applica a tutti i progetti — nessun riavvio necessario.`,unavailable:`Gli aggiornamenti del core non sono disponibili in questa build.`,toastDone:`Specrails Core aggiornato a {{version}}`,toastError:`Aggiornamento del core non riuscito: {{message}}`,toastUpToDate:`Specrails Core è aggiornato`,toastCheckFailed:`Verifica aggiornamenti non riuscita: {{message}}`,loadFailed:`Impossibile aggiornare lo stato di Core. Riprova`,runtime:`Motore: {{version}}`,bundled:`Incluso in Desktop: {{version}}`,sources:{override:`Selezione esplicita`,managed:`Gestito da Desktop`,bundled:`Incluso in Desktop`,local:`Pacchetto locale`,global:`CLI esterna`},repair:`Completa aggiornamento {{version}}`},g={heading:`Specrails Desktop`,installed:`Installata:`,check:`Cerca aggiornamenti`,checking:`Verifica…`,upToDate:`Hai l'ultima versione`,updateAvailable:`Aggiornamento disponibile: {{version}}`,autoHint:`Controllo automatico all'avvio e ogni 6 ore.`,unavailable:`Gli aggiornamenti sono disponibili solo nell'app desktop.`,toastUpToDate:`Specrails Desktop è aggiornato`,toastCheckFailed:`Ricerca aggiornamenti non riuscita: {{message}}`},_={title:`{{name}} — Impostazioni`,description:`Configurazione del progetto: prompt, branch, ambiente, budget, telemetria e terminale.`,nav:{general:`Generale`,branch:`Branch`,environment:`Ambiente`,budget:`Budget`,telemetry:`Telemetria`,terminal:`Terminale`,agentRuntime:`Comandi di verifica`,subagents:`Sotto-agenti`}},v={title:`Ramo di integrazione`,description:`Il ramo da cui i rail paralleli si diramano e a cui puntano le loro PR in bozza. Lascia vuoto per risolvere automaticamente al ramo predefinito del repository.`,placeholder:`automatico (ramo predefinito)`,resolved:`I rail si diramano da <mono>{{branch}}</mono>`,save:`Salva ramo`,saved:`Ramo di integrazione salvato`,saveFailed:`Impossibile salvare il ramo di integrazione`},y={title:`Ambiente dei worktree`,description:`Scegli i nomi delle variabili d'ambiente che questo progetto passa ai job dei rail e ai worktree isolati dei loop. Vengono salvati solo i nomi; i valori vengono letti all'avvio dell'esecuzione, recuperandoli dalla login shell se mancano nel processo Desktop.`,label:`Variabile d'ambiente`,placeholder:`NODE_AUTH_TOKEN`,helper:`Premi Invio o Aggiungi. Puoi incollare più nomi separati da virgole o spazi.`,add:`Aggiungi`,empty:`Non ci sono ancora variabili specifiche per questo progetto.`,remove:`Rimuovi {{name}}`,save:`Salva ambiente`,saved:`Ambiente dei worktree salvato`,saveFailed:`Impossibile salvare l'ambiente dei worktree`,invalidName:`{{name}} non è un nome valido per una variabile d'ambiente`,noValues:`Aggiungi solo nomi di variabili, non segreti KEY=value.`,status:{inherited:`Ereditata`,recovered:`Dalla login shell`,notDefined:`Non definita`,probeTimeout:`Timeout della shell`,probeFailed:`Lettura della shell non riuscita`,pending:`Verifica…`},hint:{defaultShell:`la tua login shell`,inherited:`Disponibile nell'ambiente con cui è stato avviato Specrails.`,recovered:`Recuperata da {{shell}}. Le esecuzioni la ricevono automaticamente; il valore non viene mai mostrato.`,notDefined:`{{shell}} non esporta {{name}}. Aggiungi un export nel profilo della shell, poi verifica di nuovo.`,notDefinedInherited:`{{name}} non è impostata nell'ambiente con cui è stato avviato Specrails. Impostala lì e riavvia Specrails.`,probeTimeout:`{{shell}} ha impiegato più di {{seconds}} s per avviarsi. Velocizza il profilo o aumenta SPECRAILS_LOGIN_SHELL_TIMEOUT_MS, poi verifica di nuovo.`,probeFailed:`Specrails non è riuscito a leggere {{shell}}. Correggi il profilo o avvia Specrails da un terminale, poi verifica di nuovo.`,pending:`Specrails sta leggendo la tua login shell.`},recheck:`Verifica di nuovo`,checking:`Verifica…`,lastChecked:`Ultima verifica alle {{time}}`,statusUnavailable:`Impossibile leggere lo stato dell'ambiente.`,unresolvedTitle:`Le esecuzioni partono senza queste variabili:`},b={heading:`Effetti`,description:`Tocchi visivi dell'app. Ognuno si può disattivare qui.`,thinkingHalo:{label:`Alone di riflessione sul composer dell'agente`,description:`Mentre l'agente pensa o scrive, l'anello orbitante del Builder brilla attorno al composer: appare in dissolvenza all'inizio del turno e svanisce quando la risposta si conclude.`},reducedMotion:`Rispetta l'impostazione di sistema per la riduzione del movimento: l'anello resta un bagliore statico.`,preview:`Anteprima`,previewThinking:`Sto pensando…`},x={title:`Sotto-agenti`,stateOn:`Gli agenti possono avviare sotto-agenti.`,stateOff:`Gli agenti lavorano da soli, senza sotto-agenti.`,explainParallel:`I sotto-agenti permettono a un agente di affidare lavoro ad aiutanti che girano in parallelo, proseguono dopo la sua risposta e riferiscono quando finiscono.`,explainCost:`Ogni sotto-agente consuma i propri token: una singola richiesta può costare molto di più.`,enabled:`Sotto-agenti consentiti`,disabled:`Sotto-agenti disattivati`,saveFailed:`Impossibile salvare l'impostazione dei sotto-agenti`,project:{description:`Consente a missioni, esplorazione e rifiniture di questo progetto di usare sotto-agenti. Disattivato per impostazione predefinita.`,toggleLabel:`Consenti sotto-agenti in questo progetto`,explainScope:`Le pipeline Implement non sono interessate. Le missioni con sotto-agenti attivi applicano la modifica quando questi finiscono.`},global:{description:`Consente alle missioni senza progetto di usare sotto-agenti. Disattivato per impostazione predefinita.`,toggleLabel:`Consenti sotto-agenti nelle missioni senza progetto`,explainScope:`Ogni progetto ha la propria impostazione nelle impostazioni del progetto. Le missioni con sotto-agenti attivi applicano la modifica quando questi finiscono.`},runtime:{label:`Esegui i sub-agenti con`,same:`Come l'agente della missione`,sameHint:`I sub-agenti usano provider, modello ed effort dell'agente della missione, avviati nativamente dal suo CLI.`,chosenHint:`Le missioni su {{provider}} avviano i propri sub-agenti con questo modello. Con qualsiasi altro provider, Specrails li avvia su {{provider}}.`,providerLabel:`Provider dei sub-agenti`,modelLabel:`Modello dei sub-agenti`,effortLabel:`Effort dei sub-agenti`,claudeEffortHint:`I sub-agenti nativi di Claude ignorano l'effort; si applica quando li avvia Specrails.`,saved:`I sub-agenti ora vengono eseguiti con {{provider}}`,reset:`I sub-agenti seguono di nuovo l'agente della missione`,confirm:{title:`Eseguire i sub-agenti con {{provider}}?`,body:`Nelle missioni con un altro provider, i sub-agenti non sono più nativi: Specrails avvia ciascuno come sessione {{provider}} propria invece del CLI della missione.`,cost:`Più lento e forse più costoso: ogni sub-agente parte a freddo, senza la cache del prompt che condividerebbe con l'agente della missione.`,control:`Più controllo e flessibilità: combina provider, scegli modello ed effort, vedi il costo reale di ogni sub-agente e lasciali lavorare dopo la risposta della missione.`,native:`Le missioni che usano già {{provider}} mantengono sub-agenti nativi, ora con questo modello ed effort.`,cancel:`Lascia come la missione`,accept:`Usa {{provider}}`}}},S={language:e,page:t,errors:n,telemetry:r,prePrompt:i,freestylePrePrompt:a,budget:o,desktop:s,notifications:c,webhooks:l,terminal:u,codeSection:d,appearance:f,mobile:p,pairWeb:m,coreUpdate:h,appUpdate:g,projectDialog:_,integrationBranch:v,worktreeEnv:y,effects:b,subagents:x};export{g as appUpdate,f as appearance,o as budget,d as codeSection,h as coreUpdate,S as default,s as desktop,b as effects,n as errors,a as freestylePrePrompt,v as integrationBranch,e as language,p as mobile,c as notifications,t as page,m as pairWeb,i as prePrompt,_ as projectDialog,x as subagents,r as telemetry,u as terminal,l as webhooks,y as worktreeEnv};
@@ -1 +0,0 @@
1
- var e={title:`言語`,description:`Desktop 全体の UI 言語。即座に適用され、再起動は不要です。`,selectLabel:`インターフェース言語`,updateFailed:`言語設定を保存できませんでした`},t={title:`プロジェクト設定`},n={saveFailed:`保存に失敗しました: {{message}}`},r={title:`パイプラインテレメトリ`,description:`診断エクスポート用にトークン使用量、フェーズ時間、サブエージェントの活動を記録します。デフォルトはオンです。`,toggleLabel:`パイプラインテレメトリを有効化`,toggleDescription:`オンにすると、パイプラインジョブの OTEL データがローカルに記録されます。ジョブカードの <mono>診断をエクスポート</mono> ボタンからダウンロードできます。`,enabled:`パイプラインテレメトリを有効にしました`,disabled:`パイプラインテレメトリを無効にしました`,saveFailed:`テレメトリ設定の保存に失敗しました`},i={title:`レールのプレプロンプト`,description:`implement のレールジョブで、チケットコンテキストの後・実行前に追加されるプロジェクト固有の指示です。`,label:`プレプロンプト`,placeholder:`例: 段階的な変更を優先し、マイグレーションの後方互換性を保ち、レールの変更ごとにテストを追加すること。`,helper:`すべてのレール実装の実行に常に添えたい、安定したプロジェクト指針に使用してください。`,saveButton:`プレプロンプトを保存`,cleared:`プレプロンプトをクリアしました`,saved:`プレプロンプトを保存しました`,saveFailed:`プレプロンプトの保存に失敗しました`},a={title:`Freestyle プレプロンプト`,description:`Freestyle で選択したプロバイダーに送られる指示です。Freestyle は OpenSpec パイプラインをスキップし、このプレプロンプトとスペックテキストをプロバイダーに渡して自律的に実装させます。空欄の場合は組み込みのデフォルトを使用します。`,label:`Freestyle プレプロンプト`,placeholder:`空欄の場合はデフォルトの Freestyle 指示を使用します。`,helper:`スペックテキストはこのプレプロンプトの後に自動で追加されます。空欄 = デフォルト。`,saveButton:`Freestyle プレプロンプトを保存`,resetToDefault:`Freestyle プレプロンプトをデフォルトにリセットしました`,saved:`Freestyle プレプロンプトを保存しました`,saveFailed:`Freestyle プレプロンプトの保存に失敗しました`},o={title:`予算`,description:`このプロジェクトの1日あたりの支出上限を設定します。上限に達するとキューは自動で一時停止します。`,dailyLabel:`日次予算(USD)`,dailyHelper:`無効にするには空欄のままにします。支出は過去24時間で計算されます。`,dailyPlaceholder:`例: 5.00`,perJobLabel:`ジョブ単位のコストアラート(USD)`,perJobHelper:`このプロジェクトの単一ジョブがこの金額を超えたときにアラートします。`,perJobPlaceholder:`例: 0.50`,invalidNumber:`正の数値を入力するか、無効にするには空欄のままにしてください`,dailyRemoved:`日次予算を削除しました`,dailySet:"日次予算を ${{amount}} に設定しました",perJobAlertDisabled:`ジョブ単位のコストアラートを無効にしました`,alertSet:"${{amount}} を超えるジョブにアラートを設定しました",saveBudgetFailed:`予算の保存に失敗しました`,saveThresholdFailed:`しきい値の保存に失敗しました`},s={nav:{appearance:`外観`,specrailsAgents:`AI プロバイダー`,code:`コード`,terminal:`ターミナル`,updates:`更新`,mobile:`モバイル`,mcp:`MCP`,projects:`プロジェクト`,advanced:`詳細`,webhooks:`Webhooks`,about:`概要`,effects:`エフェクト`,subscriptionUsage:`サブスクリプション使用量`},title:`Desktop 設定`,description:`登録済みプロジェクトの管理と Desktop 情報の表示。`,registeredProjects:`登録済みプロジェクト`,noProjects:`登録されたプロジェクトはまだありません`,techUrlDescription:`specrails-tech API のベース URL(デフォルト: http://localhost:3000)`,techUrlSaved:`specrails-tech の URL を保存しました`,techUrlSaveFailed:`URL の保存に失敗しました`,budgetAlertsHeading:`予算とアラート`,dailyBudgetLabel:`Desktop 日次予算(USD)`,dailyBudgetHelper:`全プロジェクト共通のグローバルな日次支出上限。超過するとキューは自動で一時停止します。`,dailyBudgetPlaceholder:`例: 10.00`,perJobHelper:`単一ジョブがこの金額を超えたときにアラートします。無効にするには空欄のままにします。`,costAlertsDisabled:`コストアラートを無効にしました`,dailyBudgetRemoved:`Desktop の日次予算を削除しました`,dailyBudgetSet:"Desktop の日次予算を ${{amount}} に設定しました",dailyBudgetSaveFailed:`Desktop の日次予算の保存に失敗しました`,projectRemoved:`プロジェクトを削除しました`,projectRemoveFailed:`プロジェクトの削除に失敗しました`,onboardingHeading:`オンボーディング`,platformTour:`プラットフォームツアー`,platformTourDescription:`ウェルカムウィザードを再生して主要機能を確認します。`,replayTour:`ツアーを再生`,terminalPanelHeading:`ターミナルパネル`,infoHeading:`Desktop 情報`,infoPort:`ポート`,infoProjects:`プロジェクト`,infoDb:`Desktop DB`},c={heading:`OS 通知`,description:`ジョブの完了・失敗時にネイティブのデスクトップ通知を表示します。通知はタブがフォーカスされていないときのみ表示されます。`,enableLabel:`OS 通知を有効化`,notifyOn:`通知の対象:`,filterAll:`すべて(完了と失敗)`,filterCompleted:`完了のみ`,filterFailed:`失敗のみ`,enabledToast:`OS 通知を有効にしました`,disabledToast:`OS 通知を無効にしました`},l={heading:`アウトバウンド Webhook`,description:`Desktop のイベントを外部ツール(Slack、Zapier、CI/CD)に通知します。シークレットが設定されている場合、リクエストは <code>X-Specrails-Signature</code> で署名されます。`,eventJobCompleted:`ジョブ完了`,eventJobFailed:`ジョブ失敗`,eventDailyBudgetExceeded:`日次予算超過`,statusOn:`オン`,statusOff:`オフ`,disable:`無効化`,enable:`有効化`,sendTestPing:`テスト ping を送信`,addHeading:`Webhook を追加`,secretPlaceholder:`署名シークレット(任意)`,addButton:`Webhook を追加`,urlRequired:`URL は必須です`,selectEvent:`イベントを1つ以上選択してください`,added:`Webhook を追加しました`,addFailed:`Webhook の追加に失敗しました`,updateFailed:`Webhook の更新に失敗しました`,removed:`Webhook を削除しました`,removeFailed:`Webhook の削除に失敗しました`,testPingSent:`テスト ping を送信しました`,testPingFailed:`テスト ping の送信に失敗しました`},u={title:`ターミナルパネル`,desktopDescription:`プロジェクト別のオーバーライドがない限り、すべてのプロジェクトに適用される Desktop 全体のデフォルトです。`,projectDescription:`ターミナルパネルのプロジェクト別オーバーライド。Desktop のデフォルトを継承するには、フィールドを変更しないでください。`,fontFamily:`フォントファミリー`,fontSize:`フォントサイズ({{min}}–{{max}})`,renderMode:`レンダリングモード`,copyOnSelect:`選択時にコピー`,shellIntegration:`シェル統合(OSC 133 マーク)`,notifyLongRunning:`長時間実行コマンドの通知`,longCommandThreshold:`長時間コマンドのしきい値(ms)`,imageRendering:`インライン画像レンダリング(Sixel + iTerm2)`,browserShortcutUrl:`ブラウザショートカット URL`,quickScript:`クイックスクリプト(アクティブなターミナルに貼り付け — Enter は手動)`,reset:`リセット`,unsavedChanges:`未保存の変更`,clearOverride:`オーバーライドをクリア`,clear:`クリア`,inheritingDefault:`Desktop のデフォルトを継承中`,nothingToSave:`保存する変更はありません`,saved:`ターミナル設定を保存しました`},d={heading:`Code セクション`,summaryLanguage:`要約の言語`,monthlyBudget:`月次予算(USD)`,budgetHelper:"暦月ごとの `surface=file-summary` 支出の上限。ユーザーが開始した再生成は上限を超えられます。"},f={taglines:{dracula:`オリジナル — 紫がかったダークにネオンのアクセント`,"aurora-light":`プレミアムライト — Linear風のインディゴと温かみのあるオフホワイト`,"obsidian-dark":`プレミアムダーク — 青みがかった漆黒にエレクトリックなアクセント`,specrails:`ブランドテーマ — 深いネイビーインディゴに鮮やかなシアン`,"code-rain":`蛍光ターミナル — 緑がかった漆黒にソフトミント`,galaxy:`深宇宙のブルーと輝くブレードの軌跡`},heading:`外観`,themeGroupLabel:`テーマ`,currentlyActive:`現在アクティブ`,updateFailed:`テーマの更新に失敗しました`},p={heading:`モバイルコンパニオン`,description:`スマートフォンの SpecRails Companion アプリから Specrails を操作します。通信は 100% ローカルネットワーク経由です。デフォルトはオフです。`,accessOn:`モバイルアクセス オン`,accessOff:`モバイルアクセス オフ`,listeningOnPort:`ポート {{port}} で待機中`,notListening:`待機していません`,turnOn:`オンにする`,turnOff:`オフにする`,pairWebDevice:`Web コンパニオンをペアリング`,reset:`リセット`,resetConfirm:`モバイル ID をリセットしますか?ペアリング済みのすべてのデバイスが無効化され、再ペアリングが必要になります。`,identityReset:`モバイル ID をリセットしました`,enableFailed:`モバイルアクセスを有効にできませんでした: {{message}}`,disableFailed:`モバイルアクセスを無効にできませんでした: {{message}}`,windowsFirewall:`初回有効化時に Windows ファイアウォールが SpecRails サーバーの許可を求めます — 「プライベートネットワークで許可」を選択してください。`,pairedDevices:`ペアリング済みデバイス`,noDevices:`ペアリングされたデバイスはまだありません。`,revokeDevice:`{{name}} を無効化`},m={title:`Web コンパニオンをペアリング`,description:`スマートフォンで specrails.dev/companion-app を開き、それでこのコードをスキャンしてください。`,startFailed:`ペアリングを開始できませんでした: {{error}}`,showThenScan:`スマートフォンでこれをスキャンしたら、「スマートフォンのコードをスキャン」をタップし、カメラをスマートフォンに向けてください。`,scanAnswer:`スマートフォンのコードをスキャン`,scanning:`カメラをスマートフォンのコードに向けてください…`,cameraFailed:`カメラを利用できません: {{message}}`,notAnswer:`その QR はペアリング応答ではありません。`,connecting:`接続中…`,paired:`✓ ペアリング済み`,pairedToast:`Web コンパニオンをペアリングしました`,answerRejected:`デスクトップがそのコードを拒否しました。新しいコードを生成して再試行してください。`,copyCode:`コードをコピー`,codeCopied:`コードをコピーしました`,cancel:`キャンセル`},h={heading:`Specrails Core`,installed:`インストール済み:`,updateAvailable:`アップデートあり: {{version}}`,upToDate:`最新です`,neverChecked:`未確認`,check:`アップデートを確認`,checking:`確認中…`,update:`{{version}} に更新`,downloading:`ダウンロード中…`,materializing:`インストール中…`,affectsAll:`すべてのプロジェクトに適用 — 再起動不要。`,unavailable:`このビルドではコアの更新は利用できません。`,toastDone:`Specrails Core を {{version}} に更新しました`,toastError:`コアの更新に失敗しました: {{message}}`,toastUpToDate:`Specrails Core は最新です`,toastCheckFailed:`更新の確認に失敗しました: {{message}}`,loadFailed:`Core の状態を更新できませんでした。再試行`,runtime:`ランタイム: {{version}}`,bundled:`Desktop 同梱: {{version}}`,sources:{override:`明示的な指定`,managed:`Desktop が管理`,bundled:`Desktop 同梱`,local:`ローカルパッケージ`,global:`外部 CLI`},repair:`{{version}} への更新を完了`},g={heading:`Specrails Desktop`,installed:`インストール済み:`,check:`アップデートを確認`,checking:`確認中…`,upToDate:`最新バージョンです`,updateAvailable:`アップデートあり: {{version}}`,autoHint:`起動時と6時間ごとに自動確認します。`,unavailable:`アップデートはデスクトップアプリでのみ利用できます。`,toastUpToDate:`Specrails Desktop は最新です`,toastCheckFailed:`アップデートの確認に失敗しました: {{message}}`},_={title:`{{name}} — 設定`,description:`プロジェクト設定:プロンプト、ブランチ、環境、予算、テレメトリ、ターミナル。`,nav:{general:`一般`,branch:`ブランチ`,environment:`環境`,budget:`予算`,telemetry:`テレメトリ`,terminal:`ターミナル`,agentRuntime:`検証コマンド`,subagents:`サブエージェント`}},v={title:`統合ブランチ`,description:`並列レールが分岐し、ドラフト PR の対象となるブランチです。空欄にするとリポジトリのデフォルトブランチに自動解決されます。`,placeholder:`自動(リポジトリのデフォルト)`,resolved:`レールは <mono>{{branch}}</mono> から分岐します`,save:`ブランチを保存`,saved:`統合ブランチを保存しました`,saveFailed:`統合ブランチを保存できませんでした`},y={title:`Worktree 環境`,description:`このプロジェクトが rail ジョブと分離された loop worktree に渡す環境変数名を選びます。保存されるのは名前だけで、値は実行開始時に読み取られ、Desktop プロセスにない場合は login shell から復元を試みます。`,label:`環境変数`,placeholder:`NODE_AUTH_TOKEN`,helper:`Enter または追加を押します。カンマやスペース区切りで複数の名前を貼り付けられます。`,add:`追加`,empty:`このプロジェクト固有の変数はまだ設定されていません。`,remove:`{{name}} を削除`,save:`環境を保存`,saved:`Worktree 環境を保存しました`,saveFailed:`Worktree 環境を保存できませんでした`,invalidName:`{{name}} は有効な環境変数名ではありません`,noValues:`ここには変数名だけを追加してください。KEY=value のシークレットは入力しないでください。`,status:{inherited:`継承`,recovered:`ログインシェルから取得`,notDefined:`未定義`,probeTimeout:`シェルがタイムアウト`,probeFailed:`シェルの読み取りに失敗`,pending:`確認中…`},hint:{defaultShell:`ログインシェル`,inherited:`Specrails の起動時の環境で利用できます。`,recovered:`{{shell}} から取得しました。実行には自動的に渡され、値が表示されることはありません。`,notDefined:`{{shell}} は {{name}} を export していません。シェルのプロファイルに export を追加してから、再確認してください。`,notDefinedInherited:`{{name}} は Specrails の起動時の環境に設定されていません。そこで設定してから Specrails を再起動してください。`,probeTimeout:`{{shell}} の起動に {{seconds}} 秒以上かかりました。プロファイルを軽くするか SPECRAILS_LOGIN_SHELL_TIMEOUT_MS を増やしてから、再確認してください。`,probeFailed:`Specrails は {{shell}} を読み取れませんでした。プロファイルを修正するか、ターミナルから Specrails を起動してから、再確認してください。`,pending:`Specrails がログインシェルを読み取っています。`},recheck:`再確認`,checking:`確認中…`,lastChecked:`最終確認: {{time}}`,statusUnavailable:`環境のステータスを読み取れませんでした。`,unresolvedTitle:`次の変数なしで実行が開始されます:`},b={heading:`エフェクト`,description:`アプリ内の視覚的な演出。それぞれここでオフにできます。`,thinkingHalo:{label:`エージェントコンポーザーの思考ハロー`,description:`エージェントが考えたり書いたりしている間、ビルダーの回転リングがコンポーザーの周りで光ります。ターン開始時にフェードイン、返答完了時にフェードアウトします。`},reducedMotion:`システムの「視差効果を減らす」設定に従い、リングは静的な光のままになります。`,preview:`プレビュー`,previewThinking:`考え中…`},x={title:`サブエージェント`,stateOn:`エージェントはサブエージェントを起動できます。`,stateOff:`エージェントはサブエージェントなしで単独で作業します。`,explainParallel:`サブエージェントを使うと、エージェントは並行して動くヘルパーに作業を任せられます。ヘルパーは返信後も作業を続け、終わると報告します。`,explainCost:`サブエージェントはそれぞれ独自にトークンを消費するため、1 回の依頼でもコストが大きく増えることがあります。`,enabled:`サブエージェントを許可しました`,disabled:`サブエージェントを無効にしました`,saveFailed:`サブエージェントの設定を保存できませんでした`,project:{description:`このプロジェクトのミッション、探索、リファインのエージェントにサブエージェントの使用を許可します。既定ではオフです。`,toggleLabel:`このプロジェクトでサブエージェントを許可`,explainScope:`Implement パイプラインには影響しません。サブエージェントが動作中のミッションは、それらの完了後に変更を適用します。`},global:{description:`プロジェクトに属さないミッションにサブエージェントの使用を許可します。既定ではオフです。`,toggleLabel:`プロジェクトのないミッションでサブエージェントを許可`,explainScope:`各プロジェクトにはプロジェクト設定に独自の設定があります。サブエージェントが動作中のミッションは、それらの完了後に変更を適用します。`},runtime:{label:`サブエージェントの実行環境`,same:`ミッションのエージェントと同じ`,sameHint:`サブエージェントはミッションのエージェントと同じプロバイダー・モデル・エフォートを使い、その CLI がネイティブに起動します。`,chosenHint:`{{provider}} のミッションは、このモデルで自身のサブエージェントを起動します。ほかのプロバイダーでは、Specrails が {{provider}} で起動します。`,providerLabel:`サブエージェントのプロバイダー`,modelLabel:`サブエージェントのモデル`,effortLabel:`サブエージェントのエフォート`,claudeEffortHint:`Claude 自身のサブエージェントはエフォートを無視します。Specrails が起動する場合に適用されます。`,saved:`サブエージェントは {{provider}} で実行されます`,reset:`サブエージェントは再びミッションのエージェントに従います`,confirm:{title:`サブエージェントを {{provider}} で実行しますか?`,body:`別のプロバイダーのミッションでは、サブエージェントはネイティブではなくなります。Specrails がミッションの CLI の代わりに、それぞれを独立した {{provider}} セッションとして起動します。`,cost:`遅くなり、コストが増える場合があります。各サブエージェントはミッションのエージェントと共有できるプロンプトキャッシュなしでコールドスタートします。`,control:`より高い制御と柔軟性:プロバイダーの組み合わせ、モデルとエフォートの選択、サブエージェントごとの実コストの確認、ミッションの返信後も作業を継続できます。`,native:`すでに {{provider}} を使うミッションは、このモデルとエフォートでネイティブなサブエージェントを維持します。`,cancel:`ミッションと同じままにする`,accept:`{{provider}} を使う`}}},S={language:e,page:t,errors:n,telemetry:r,prePrompt:i,freestylePrePrompt:a,budget:o,desktop:s,notifications:c,webhooks:l,terminal:u,codeSection:d,appearance:f,mobile:p,pairWeb:m,coreUpdate:h,appUpdate:g,projectDialog:_,integrationBranch:v,worktreeEnv:y,effects:b,subagents:x};export{g as appUpdate,f as appearance,o as budget,d as codeSection,h as coreUpdate,S as default,s as desktop,b as effects,n as errors,a as freestylePrePrompt,v as integrationBranch,e as language,p as mobile,c as notifications,t as page,m as pairWeb,i as prePrompt,_ as projectDialog,x as subagents,r as telemetry,u as terminal,l as webhooks,y as worktreeEnv};
@@ -1 +0,0 @@
1
- var e={title:`Langue`,description:`Langue de l'interface pour tout Desktop. S'applique instantanément — aucun redémarrage requis.`,selectLabel:`Langue de l'interface`,updateFailed:`Impossible d'enregistrer la préférence de langue`},t={title:`Paramètres du projet`},n={saveFailed:`Échec de l'enregistrement : {{message}}`},r={title:`Télémétrie du pipeline`,description:`Capture l'usage des tokens, la durée des phases et l'activité des sous-agents pour l'export de diagnostic. Activé par défaut.`,toggleLabel:`Activer la télémétrie du pipeline`,toggleDescription:`Quand elle est activée, les données OTEL des jobs du pipeline sont capturées localement. Utilisez le bouton <mono>Exporter le diagnostic</mono> sur n'importe quelle carte de job pour les télécharger.`,enabled:`Télémétrie du pipeline activée`,disabled:`Télémétrie du pipeline désactivée`,saveFailed:`Échec de l'enregistrement du réglage de télémétrie`},i={title:`Pré-prompt de rail`,description:`Instructions supplémentaires propres au projet, ajoutées aux jobs de rail implement après le contexte du ticket et avant l'exécution.`,label:`Pré-prompt`,placeholder:`Exemple : privilégier les changements incrémentaux, garder les migrations rétrocompatibles et ajouter des tests pour chaque changement de rail.`,helper:`À utiliser pour des consignes projet stables qui doivent accompagner chaque exécution d'implémentation sur rail.`,saveButton:`Enregistrer le pré-prompt`,cleared:`Pré-prompt effacé`,saved:`Pré-prompt enregistré`,saveFailed:`Échec de l'enregistrement du pré-prompt`},a={title:`Pré-prompt Freestyle`,description:`Instruction envoyée au fournisseur sélectionné en mode Freestyle. Freestyle saute le pipeline OpenSpec — il transmet au fournisseur ce pré-prompt plus le texte de la spec et le laisse implémenter en autonomie. Laissez vide pour utiliser la valeur par défaut intégrée.`,label:`Pré-prompt Freestyle`,placeholder:`Laissez vide pour utiliser l'instruction Freestyle par défaut.`,helper:`Le texte de la spec est ajouté automatiquement après ce pré-prompt. Vide = défaut.`,saveButton:`Enregistrer le pré-prompt Freestyle`,resetToDefault:`Pré-prompt Freestyle réinitialisé à la valeur par défaut`,saved:`Pré-prompt Freestyle enregistré`,saveFailed:`Échec de l'enregistrement du pré-prompt Freestyle`},o={title:`Budget`,description:`Définissez un plafond de dépense quotidien pour ce projet. La file se met automatiquement en pause quand la limite est atteinte.`,dailyLabel:`Budget quotidien (USD)`,dailyHelper:`Laissez vide pour désactiver. La dépense est calculée sur les dernières 24 heures.`,dailyPlaceholder:`ex. 5.00`,perJobLabel:`Alerte de coût par job (USD)`,perJobHelper:`Alerte quand un seul job de ce projet dépasse ce montant.`,perJobPlaceholder:`ex. 0.50`,invalidNumber:`Saisissez un nombre positif ou laissez vide pour désactiver`,dailyRemoved:`Budget quotidien supprimé`,dailySet:`Budget quotidien défini à {{amount}} $`,perJobAlertDisabled:`Alerte de coût par job désactivée`,alertSet:`Alerte définie pour les jobs dépassant {{amount}} $`,saveBudgetFailed:`Échec de l'enregistrement du budget`,saveThresholdFailed:`Échec de l'enregistrement du seuil`},s={nav:{appearance:`Apparence`,specrailsAgents:`Fournisseurs d'IA`,code:`Code`,terminal:`Terminal`,updates:`Mises à jour`,mobile:`Mobile`,mcp:`MCP`,projects:`Projets`,advanced:`Avancé`,webhooks:`Webhooks`,about:`À propos`,effects:`Effets`,subscriptionUsage:`Utilisation des abonnements`},title:`Paramètres de Desktop`,description:`Gérez les projets enregistrés et consultez les informations de Desktop.`,registeredProjects:`Projets enregistrés`,noProjects:`Aucun projet enregistré pour l'instant`,techUrlDescription:`URL de base de l'API specrails-tech (par défaut : http://localhost:3000)`,techUrlSaved:`URL specrails-tech enregistrée`,techUrlSaveFailed:`Échec de l'enregistrement de l'URL`,budgetAlertsHeading:`Budget & alertes`,dailyBudgetLabel:`Budget quotidien de Desktop (USD)`,dailyBudgetHelper:`Limite de dépense quotidienne globale pour tous les projets. Les files se mettent automatiquement en pause en cas de dépassement.`,dailyBudgetPlaceholder:`ex. 10.00`,perJobHelper:`Alerte quand un seul job dépasse ce montant. Laissez vide pour désactiver.`,costAlertsDisabled:`Alertes de coût désactivées`,dailyBudgetRemoved:`Budget quotidien de Desktop supprimé`,dailyBudgetSet:`Budget quotidien de Desktop défini à {{amount}} $`,dailyBudgetSaveFailed:`Échec de l'enregistrement du budget quotidien de Desktop`,projectRemoved:`Projet supprimé`,projectRemoveFailed:`Échec de la suppression du projet`,onboardingHeading:`Onboarding`,platformTour:`Visite de la plateforme`,platformTourDescription:`Rejouez l'assistant de bienvenue pour revoir les fonctionnalités clés.`,replayTour:`Rejouer la visite`,terminalPanelHeading:`Panneau de terminal`,infoHeading:`Informations de Desktop`,infoPort:`Port`,infoProjects:`Projets`,infoDb:`BD de Desktop`},c={heading:`Notifications système`,description:`Affiche des notifications de bureau natives quand des jobs se terminent ou échouent. Les notifications n'apparaissent que lorsque l'onglet n'a pas le focus.`,enableLabel:`Activer les notifications système`,notifyOn:`Notifier pour :`,filterAll:`Tout (terminés & échoués)`,filterCompleted:`Terminés uniquement`,filterFailed:`Échoués uniquement`,enabledToast:`Notifications système activées`,disabledToast:`Notifications système désactivées`},l={heading:`Webhooks sortants`,description:`Notifiez des outils externes (Slack, Zapier, CI/CD) lors des événements de Desktop. Les requêtes sont signées via <code>X-Specrails-Signature</code> quand un secret est défini.`,eventJobCompleted:`Job terminé`,eventJobFailed:`Job en échec`,eventDailyBudgetExceeded:`Budget quotidien dépassé`,statusOn:`actif`,statusOff:`inactif`,disable:`Désactiver`,enable:`Activer`,sendTestPing:`Envoyer un ping de test`,addHeading:`Ajouter un webhook`,secretPlaceholder:`Secret de signature (facultatif)`,addButton:`Ajouter le webhook`,urlRequired:`L'URL est requise`,selectEvent:`Sélectionnez au moins un événement`,added:`Webhook ajouté`,addFailed:`Échec de l'ajout du webhook`,updateFailed:`Échec de la mise à jour du webhook`,removed:`Webhook supprimé`,removeFailed:`Échec de la suppression du webhook`,testPingSent:`Ping de test envoyé`,testPingFailed:`Échec de l'envoi du ping de test`},u={title:`Panneau de terminal`,desktopDescription:`Valeurs par défaut de Desktop, appliquées à chaque projet sauf surcharge par projet.`,projectDescription:`Surcharges par projet pour le panneau de terminal. Laissez un champ inchangé pour hériter de la valeur par défaut de Desktop.`,fontFamily:`Police`,fontSize:`Taille de police ({{min}}–{{max}})`,renderMode:`Mode de rendu`,copyOnSelect:`Copier à la sélection`,shellIntegration:`Intégration shell (marques OSC 133)`,notifyLongRunning:`Notifier les commandes de longue durée`,longCommandThreshold:`Seuil de commande longue (ms)`,imageRendering:`Rendu d'images inline (Sixel + iTerm2)`,browserShortcutUrl:`URL du raccourci navigateur`,quickScript:`Script rapide (collé dans le terminal actif — Entrée manuelle)`,reset:`Réinitialiser`,unsavedChanges:`Modifications non enregistrées`,clearOverride:`Effacer la surcharge`,clear:`Effacer`,inheritingDefault:`Hérite de la valeur par défaut de Desktop`,nothingToSave:`Rien à enregistrer`,saved:`Paramètres du terminal enregistrés`},d={heading:`Section Code`,summaryLanguage:`Langue des résumés`,monthlyBudget:`Budget mensuel (USD)`,budgetHelper:"Plafond de dépense `surface=file-summary` par mois calendaire. Les régénérations lancées par l'utilisateur peuvent le dépasser."},f={taglines:{dracula:`L'original — sombre teinté de violet aux accents néon éclatants`,"aurora-light":`Clair premium — indigo inspiré de Linear sur blanc cassé chaleureux`,"obsidian-dark":`Sombre premium — presque noir teinté de bleu aux accents électriques`,specrails:`Thème de la marque — bleu marine-indigo profond aux accents cyan saturés`,"code-rain":`Terminal phosphore — menthe douce sur fond presque noir verdâtre`,galaxy:`Bleu spatial profond avec une traînée de lame lumineuse`},heading:`Apparence`,themeGroupLabel:`Thème`,currentlyActive:`Actuellement actif`,updateFailed:`Échec de la mise à jour du thème`},p={heading:`Compagnon mobile`,description:`Contrôlez Specrails depuis l'application SpecRails Companion sur votre téléphone, 100 % via votre réseau local. Désactivé par défaut.`,accessOn:`Accès mobile activé`,accessOff:`Accès mobile désactivé`,listeningOnPort:`À l'écoute sur le port {{port}}`,notListening:`Pas à l'écoute`,turnOn:`Activer`,turnOff:`Désactiver`,pairWebDevice:`Appairer le compagnon web`,reset:`Réinitialiser`,resetConfirm:`Réinitialiser l'identité mobile ? Tous les appareils appairés seront révoqués et devront s'appairer à nouveau.`,identityReset:`Identité mobile réinitialisée`,enableFailed:`Impossible d'activer l'accès mobile : {{message}}`,disableFailed:`Impossible de désactiver l'accès mobile : {{message}}`,windowsFirewall:`Le Pare-feu Windows demandera d'autoriser le serveur SpecRails à la première activation — choisissez « Autoriser sur les réseaux privés ».`,pairedDevices:`Appareils appairés`,noDevices:`Aucun appareil appairé pour l'instant.`,revokeDevice:`Révoquer {{name}}`},m={title:`Appairer le compagnon web`,description:`Ouvrez specrails.dev/companion-app sur votre téléphone, puis scannez ce code avec.`,startFailed:`Impossible de démarrer l'appairage : {{error}}`,showThenScan:`Une fois que votre téléphone l'a scanné, appuyez sur « Scanner le code du téléphone » et pointez la caméra vers votre téléphone.`,scanAnswer:`Scanner le code du téléphone`,scanning:`Pointez la caméra vers le code sur votre téléphone…`,cameraFailed:`Caméra indisponible : {{message}}`,notAnswer:`Ce QR code n'est pas une réponse d'appairage.`,connecting:`Connexion…`,paired:`✓ Appairé`,pairedToast:`Compagnon web appairé`,answerRejected:`Le bureau a rejeté ce code. Générez-en un nouveau et réessayez.`,copyCode:`Copier le code`,codeCopied:`Code copié`,cancel:`Annuler`},h={heading:`Specrails Core`,installed:`Installé :`,updateAvailable:`Mise à jour disponible : {{version}}`,upToDate:`À jour`,neverChecked:`Non vérifié`,check:`Rechercher des mises à jour`,checking:`Vérification…`,update:`Mettre à jour vers {{version}}`,downloading:`Téléchargement…`,materializing:`Installation…`,affectsAll:`S'applique à tous les projets — aucun redémarrage requis.`,unavailable:`Les mises à jour du core ne sont pas disponibles dans cette version.`,toastDone:`Specrails Core mis à jour vers {{version}}`,toastError:`Échec de la mise à jour du core : {{message}}`,toastUpToDate:`Specrails Core est à jour`,toastCheckFailed:`Échec de la vérification : {{message}}`,loadFailed:`Impossible de charger l’état de Core. Réessayer`,runtime:`Moteur : {{version}}`,bundled:`Inclus dans Desktop : {{version}}`,sources:{override:`Sélection explicite`,managed:`Géré par Desktop`,bundled:`Inclus dans Desktop`,local:`Paquet local`,global:`CLI externe`},repair:`Terminer la mise à jour {{version}}`},g={heading:`Specrails Desktop`,installed:`Installée :`,check:`Rechercher des mises à jour`,checking:`Vérification…`,upToDate:`Vous avez la dernière version`,updateAvailable:`Mise à jour disponible : {{version}}`,autoHint:`Vérification automatique au lancement et toutes les 6 heures.`,unavailable:`Les mises à jour ne sont disponibles que dans l'app de bureau.`,toastUpToDate:`Specrails Desktop est à jour`,toastCheckFailed:`Échec de la recherche de mises à jour : {{message}}`},_={title:`{{name}} — Paramètres`,description:`Configuration du projet : prompts, branche, environnement, budget, télémétrie et terminal.`,nav:{general:`Général`,branch:`Branche`,environment:`Environnement`,budget:`Budget`,telemetry:`Télémétrie`,terminal:`Terminal`,agentRuntime:`Commandes de vérification`,subagents:`Sous-agents`}},v={title:`Branche d'intégration`,description:`La branche depuis laquelle les rails parallèles se ramifient et que ciblent leurs PR en brouillon. Laissez vide pour résoudre automatiquement vers la branche par défaut du dépôt.`,placeholder:`automatique (branche par défaut)`,resolved:`Les rails partent de <mono>{{branch}}</mono>`,save:`Enregistrer la branche`,saved:`Branche d'intégration enregistrée`,saveFailed:`Impossible d'enregistrer la branche d'intégration`},y={title:`Environnement des worktrees`,description:`Choisissez les noms de variables d'environnement que ce projet transmet aux jobs de rail et aux worktrees isolés des loops. Seuls les noms sont enregistrés ; les valeurs sont lues au démarrage, avec récupération depuis le login shell si le processus Desktop ne les a pas.`,label:`Variable d'environnement`,placeholder:`NODE_AUTH_TOKEN`,helper:`Appuyez sur Entrée ou Ajouter. Vous pouvez coller plusieurs noms séparés par des virgules ou des espaces.`,add:`Ajouter`,empty:`Aucune variable propre à ce projet n'est encore configurée.`,remove:`Supprimer {{name}}`,save:`Enregistrer l'environnement`,saved:`Environnement des worktrees enregistré`,saveFailed:`Impossible d'enregistrer l'environnement des worktrees`,invalidName:`{{name}} n'est pas un nom de variable d'environnement valide`,noValues:`Ajoutez uniquement des noms de variables, pas de secrets KEY=value.`,status:{inherited:`Héritée`,recovered:`Du shell de connexion`,notDefined:`Non définie`,probeTimeout:`Délai du shell dépassé`,probeFailed:`Échec de lecture du shell`,pending:`Vérification…`},hint:{defaultShell:`votre shell de connexion`,inherited:`Disponible dans l'environnement avec lequel Specrails a démarré.`,recovered:`Récupérée depuis {{shell}}. Les exécutions la reçoivent automatiquement ; la valeur n'est jamais affichée.`,notDefined:`{{shell}} n'exporte pas {{name}}. Ajoutez un export dans le profil du shell, puis vérifiez de nouveau.`,notDefinedInherited:`{{name}} n'est pas définie dans l'environnement avec lequel Specrails a démarré. Définissez-la puis redémarrez Specrails.`,probeTimeout:`{{shell}} a mis plus de {{seconds}} s à démarrer. Allégez le profil ou augmentez SPECRAILS_LOGIN_SHELL_TIMEOUT_MS, puis vérifiez de nouveau.`,probeFailed:`Specrails n'a pas pu lire {{shell}}. Corrigez le profil ou lancez Specrails depuis un terminal, puis vérifiez de nouveau.`,pending:`Specrails lit votre shell de connexion.`},recheck:`Vérifier à nouveau`,checking:`Vérification…`,lastChecked:`Dernière vérification à {{time}}`,statusUnavailable:`Impossible de lire l'état de l'environnement.`,unresolvedTitle:`Les exécutions démarrent sans ces variables :`},b={heading:`Effets`,description:`Touches visuelles de l'app. Chacune peut être désactivée ici.`,thinkingHalo:{label:`Halo de réflexion sur le composeur de l'agent`,description:`Pendant que l'agent réfléchit ou écrit, l'anneau orbital du Constructeur brille autour du composeur : il apparaît en fondu au début du tour et s'estompe quand la réponse se termine.`},reducedMotion:`Respecte le réglage système de réduction des animations : l'anneau reste une lueur statique.`,preview:`Aperçu`,previewThinking:`Réflexion…`},x={title:`Sous-agents`,stateOn:`Les agents peuvent lancer des sous-agents.`,stateOff:`Les agents travaillent seuls, sans sous-agents.`,explainParallel:`Les sous-agents permettent à un agent de confier du travail à des assistants qui tournent en parallèle, continuent après sa réponse et rendent compte une fois terminés.`,explainCost:`Chaque sous-agent consomme ses propres jetons : une seule demande peut coûter nettement plus.`,enabled:`Sous-agents autorisés`,disabled:`Sous-agents désactivés`,saveFailed:`Impossible d'enregistrer le réglage des sous-agents`,project:{description:`Autorise les missions, l'exploration et les affinages de ce projet à utiliser des sous-agents. Désactivé par défaut.`,toggleLabel:`Autoriser les sous-agents dans ce projet`,explainScope:`Les pipelines Implement ne sont pas concernés. Les missions dont des sous-agents tournent appliquent le changement quand ils ont fini.`},global:{description:`Autorise les missions sans projet à utiliser des sous-agents. Désactivé par défaut.`,toggleLabel:`Autoriser les sous-agents dans les missions sans projet`,explainScope:`Chaque projet a son propre réglage dans ses paramètres. Les missions dont des sous-agents tournent appliquent le changement quand ils ont fini.`},runtime:{label:`Exécuter les sous-agents avec`,same:`Comme l'agent de la mission`,sameHint:`Les sous-agents utilisent le fournisseur, le modèle et l'effort de l'agent de la mission, lancés nativement par son propre CLI.`,chosenHint:`Les missions sur {{provider}} lancent leurs propres sous-agents avec ce modèle. Avec tout autre fournisseur, Specrails les lance sur {{provider}}.`,providerLabel:`Fournisseur des sous-agents`,modelLabel:`Modèle des sous-agents`,effortLabel:`Effort des sous-agents`,claudeEffortHint:`Les sous-agents propres à Claude ignorent l'effort ; il s'applique quand Specrails les lance.`,saved:`Les sous-agents s'exécutent désormais avec {{provider}}`,reset:`Les sous-agents suivent à nouveau l'agent de la mission`,confirm:{title:`Exécuter les sous-agents avec {{provider}} ?`,body:`Dans les missions sur un autre fournisseur, les sous-agents ne sont plus natifs : Specrails lance chacun comme sa propre session {{provider}} au lieu du CLI de la mission.`,cost:`Plus lent et parfois plus cher : chaque sous-agent démarre à froid, sans le cache de prompt qu'il partagerait avec l'agent de la mission.`,control:`Plus de contrôle et de flexibilité : mélangez les fournisseurs, choisissez modèle et effort, voyez le coût réel de chaque sous-agent et laissez-les travailler après la réponse de la mission.`,native:`Les missions déjà sur {{provider}} gardent des sous-agents natifs, désormais avec ce modèle et cet effort.`,cancel:`Garder comme la mission`,accept:`Utiliser {{provider}}`}}},S={language:e,page:t,errors:n,telemetry:r,prePrompt:i,freestylePrePrompt:a,budget:o,desktop:s,notifications:c,webhooks:l,terminal:u,codeSection:d,appearance:f,mobile:p,pairWeb:m,coreUpdate:h,appUpdate:g,projectDialog:_,integrationBranch:v,worktreeEnv:y,effects:b,subagents:x};export{g as appUpdate,f as appearance,o as budget,d as codeSection,h as coreUpdate,S as default,s as desktop,b as effects,n as errors,a as freestylePrePrompt,v as integrationBranch,e as language,p as mobile,c as notifications,t as page,m as pairWeb,i as prePrompt,_ as projectDialog,x as subagents,r as telemetry,u as terminal,l as webhooks,y as worktreeEnv};