specrails-desktop 2.59.1 → 2.59.3

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 (88) hide show
  1. package/client/dist/assets/{ActivityFeedPage-vAY9C71W.js → ActivityFeedPage-BGEayjTY.js} +1 -1
  2. package/client/dist/assets/{AgentBrowserCapture-CpwjGXQm.js → AgentBrowserCapture-Qvg4y5Tr.js} +1 -1
  3. package/client/dist/assets/{AgentModeAnalyticsPane-BHeoJVxQ.js → AgentModeAnalyticsPane-CR7aFeLJ.js} +2 -2
  4. package/client/dist/assets/{AgentModeCodePane-B0Nf3YkN.js → AgentModeCodePane-gAd88Gng.js} +2 -2
  5. package/client/dist/assets/{AgentModeJobsPane-CpbIItY_.js → AgentModeJobsPane-EjCDuTow.js} +1 -1
  6. package/client/dist/assets/{AgentsPage-BVJAFXn3.js → AgentsPage-CRHHcp1V.js} +1 -1
  7. package/client/dist/assets/{AnalyticsPage-DDWees-K.js → AnalyticsPage-a0b1fNEl.js} +1 -1
  8. package/client/dist/assets/{CodePage-CzV5tkvS.js → CodePage-BmV1m7l3.js} +1 -1
  9. package/client/dist/assets/{DesktopAnalyticsPage-DfRHNK7i.js → DesktopAnalyticsPage-B87WmXOQ.js} +1 -1
  10. package/client/dist/assets/{DocsDialog-BxduBYbh.js → DocsDialog-Co0O672q.js} +1 -1
  11. package/client/dist/assets/{DocsPage-DOVeRJn_.js → DocsPage-DxGI6A2T.js} +1 -1
  12. package/client/dist/assets/{ExportDropdown-BnKVV8UK.js → ExportDropdown-1Ce031vB.js} +1 -1
  13. package/client/dist/assets/{InteractiveJobComposer-Da9dvM-0.js → InteractiveJobComposer-DkqR77pO.js} +2 -2
  14. package/client/dist/assets/JobDetailModal-DVLDVgzz.js +1 -0
  15. package/client/dist/assets/JobDetailPage-RcP9N-D-.js +1 -0
  16. package/client/dist/assets/{JobsPage-0vdlbNik.js → JobsPage-R9M4xQnp.js} +1 -1
  17. package/client/dist/assets/{LoopBuilderPage-DCUHozXh.js → LoopBuilderPage-C4S-rkWp.js} +1 -1
  18. package/client/dist/assets/{LoopPreviewModal-Df-S8NGf.js → LoopPreviewModal-DA2JeKWX.js} +1 -1
  19. package/client/dist/assets/{LoopWindowSurface-sdb7QbSo.js → LoopWindowSurface-BkkeD0hx.js} +1 -1
  20. package/client/dist/assets/{LoopsPage-CDfCC9Px.js → LoopsPage-BHv--iI7.js} +1 -1
  21. package/client/dist/assets/{MinimizedChatsContext-DKlPf54Y.js → MinimizedChatsContext-Bc9xHByW.js} +1 -1
  22. package/client/dist/assets/PluginsPage-BEy62gxK.js +2 -0
  23. package/client/dist/assets/{ProjectSettingsDialog-DOkjT7xv.js → ProjectSettingsDialog-DDQ_f94T.js} +1 -1
  24. package/client/dist/assets/{RepositoryDeliveries-CJVpOUpZ.js → RepositoryDeliveries-DIWDFLRM.js} +1 -1
  25. package/client/dist/assets/{RepositoryScopeSelector-BeM-2gIZ.js → RepositoryScopeSelector-D3PlHIM-.js} +1 -1
  26. package/client/dist/assets/{ReviewPacketPage-EBCim2w_.js → ReviewPacketPage-BXdPwmdE.js} +1 -1
  27. package/client/dist/assets/{TemplatePreviewModal-1-92UGwN.js → TemplatePreviewModal-C7-G6lvI.js} +1 -1
  28. package/client/dist/assets/{TicketDetailModalContext-CkVXuHxU.js → TicketDetailModalContext-gGUv7gsQ.js} +1 -1
  29. package/client/dist/assets/{Trans-CzhPjnjB.js → Trans-Br_OIUKP.js} +1 -1
  30. package/client/dist/assets/{formatDistanceToNow-DlQaXc7c.js → formatDistanceToNow-Q7QPRFV4.js} +1 -1
  31. package/client/dist/assets/{getTimezoneOffsetInMilliseconds-DxUFGcVq.js → getTimezoneOffsetInMilliseconds-Bq0CXBaJ.js} +1 -1
  32. package/client/dist/assets/{index-Czov72g7.js → index-DVkWHOR3.js} +7 -7
  33. package/client/dist/assets/jira-B6OYw4CJ.js +1 -0
  34. package/client/dist/assets/jira-BTF6RaOe.js +1 -0
  35. package/client/dist/assets/jira-CEetIB2q.js +1 -0
  36. package/client/dist/assets/jira-CIzsZLw5.js +1 -0
  37. package/client/dist/assets/{jira-BOoaUXbb.js → jira-Cq6yvN4q.js} +1 -1
  38. package/client/dist/assets/jira-Daw9tmpI.js +1 -0
  39. package/client/dist/assets/jira-FXr7u2lh.js +1 -0
  40. package/client/dist/assets/jira-LaNQn7yl.js +1 -0
  41. package/client/dist/assets/{jira-api-CbJvbRYx.js → jira-api-C2Tzc362.js} +1 -1
  42. package/client/dist/assets/{jobs-DZAbh1m2.js → jobs-ARwD85B9.js} +1 -1
  43. package/client/dist/assets/{jobs-BD47Ov11.js → jobs-BMF1w4WT.js} +1 -1
  44. package/client/dist/assets/{jobs-tWjcDF_6.js → jobs-CBfzGLwm.js} +1 -1
  45. package/client/dist/assets/{jobs-CSrRx5lD.js → jobs-CPjiluBY.js} +1 -1
  46. package/client/dist/assets/{jobs-epP7gt8W.js → jobs-CQQd58pY.js} +1 -1
  47. package/client/dist/assets/{jobs-1G3wodhl.js → jobs-De3EqaO0.js} +1 -1
  48. package/client/dist/assets/{jobs-BLOoVRRl.js → jobs-Du9-Ei9a.js} +1 -1
  49. package/client/dist/assets/{jobs-B4pu8TdF.js → jobs-qMHbnxwC.js} +1 -1
  50. package/client/dist/assets/{project-repositories-BkBD3rxx.js → project-repositories-DZKmieH3.js} +1 -1
  51. package/client/dist/assets/{spending-CYotH-9N.js → spending-EXaboRU0.js} +1 -1
  52. package/client/dist/assets/{useDesktop-DpxVGfUC.js → useDesktop-ChSmmJfc.js} +1 -1
  53. package/client/dist/assets/{useRuntimeRuns-CfGRbhr9.js → useRuntimeRuns-w9-5OzZl.js} +1 -1
  54. package/client/dist/assets/{useSharedWebSocket-C17TGqT8.js → useSharedWebSocket-g0nTHfTT.js} +2 -2
  55. package/client/dist/index.html +16 -16
  56. package/docs/internals/configuration.md +45 -0
  57. package/docs/internals/loop-step-log-explorer.md +27 -0
  58. package/docs/internals/mission-rail-cards.md +7 -0
  59. package/docs/internals/safe-pr-review-flow.md +9 -0
  60. package/docs/internals/source-map.md +5 -1
  61. package/docs/jira-integration-plan.md +7 -4
  62. package/package.json +1 -1
  63. package/server/dist/core-execution.js +5 -18
  64. package/server/dist/jira/jira-db.js +33 -6
  65. package/server/dist/jira/jira-materializer.js +11 -9
  66. package/server/dist/jira/jira-status-resolver.js +64 -22
  67. package/server/dist/jira/jira-sync-manager.js +40 -1
  68. package/server/dist/jira-router.js +8 -41
  69. package/server/dist/modules/agent-runtime/runtime/agent-runtime-bridge.js +18 -68
  70. package/server/dist/modules/agent-runtime/runtime/agent-runtime-controls.js +7 -4
  71. package/server/dist/modules/agent-runtime/runtime/agent-runtime-verification-log.js +252 -0
  72. package/server/dist/modules/delivery/runtime/rail-relaunch.js +10 -2
  73. package/server/dist/modules/loops/runtime/loop-implement-recipe.js +11 -7
  74. package/server/dist/modules/loops/runtime/loop-run-manager.js +5 -3
  75. package/server/dist/path-resolver.js +41 -20
  76. package/server/dist/project-env.js +22 -6
  77. package/server/dist/util/checkout-path.js +22 -0
  78. package/server/dist/worktree-node-modules.js +70 -34
  79. package/client/dist/assets/JobDetailModal-DhwKbZGe.js +0 -1
  80. package/client/dist/assets/JobDetailPage-MtPE-E_1.js +0 -1
  81. package/client/dist/assets/PluginsPage-iL0G3rQQ.js +0 -2
  82. package/client/dist/assets/jira-BNZu8f9O.js +0 -1
  83. package/client/dist/assets/jira-BYVN4L2u.js +0 -1
  84. package/client/dist/assets/jira-CKJg3HZd.js +0 -1
  85. package/client/dist/assets/jira-DDDsZ4ub.js +0 -1
  86. package/client/dist/assets/jira-DXwYjVE4.js +0 -1
  87. package/client/dist/assets/jira-Dn6U2nRW.js +0 -1
  88. package/client/dist/assets/jira-j7hVolR5.js +0 -1
@@ -7,6 +7,7 @@ const string = { type: 'string' };
7
7
  const strings = { type: 'array', items: string, maxItems: 100 };
8
8
  const score = { type: 'number', minimum: 0, maximum: 100 };
9
9
  const aspects = { type_correctness: 60, pattern_adherence: 60, test_coverage: 60, security: 75, architectural_alignment: 60 };
10
+ const reviewPolicy = 'approved == true; issues must be empty; score >= 70; ' + Object.entries(aspects).map(([aspect, threshold]) => `${aspect} >= ${threshold}`).join('; ');
10
11
  /** The recipe, roles, schemas and decisions are Desktop data, never Core phases. */
11
12
  function configurableImplementGraph() {
12
13
  const agents = (0, loop_agents_1.defaultLoopAgents)();
@@ -31,23 +32,26 @@ function configurableImplementGraph() {
31
32
  node('clarify', 'Clarify plan', 'question', { text: '{{outputs.architect.structured.question}}' }, { next: 'reset-confidence' });
32
33
  node('reset-confidence', 'Reset confidence after answer', 'assign', { set: { planInvestigated: false } }, { next: 'architect', failed: 'failed' });
33
34
  node('validate', 'Validate OpenSpec', 'openspec-validate', { change }, { pass: 'freeze', fail: 'architect', failed: 'failed' });
34
- node('freeze', 'Freeze approved artifacts', 'artifact-contract', { change, contractId: 'plan', action: 'freeze' }, { pass: 'developer', fail: 'architect', failed: 'failed' });
35
+ node('freeze', 'Freeze approved artifacts', 'artifact-contract', { change, contractId: 'plan', action: 'freeze' }, { pass: 'initialize-corrections', fail: 'architect', failed: 'failed' });
36
+ node('initialize-corrections', 'Initialize automatic correction budget', 'assign', { set: { correctionAttempts: 0, correctionLimit: 3 } }, { next: 'developer', failed: 'failed' });
35
37
  const implementation = objectSchema({ summary: string, incomplete: strings });
36
38
  turn('developer', 'Developer', 'build', `Implement every approved task for change ${change} using the bound official apply skill. Preserve proposal, design and specs; only tick tasks you completed. Report summary and incomplete task descriptions. Prior execution evidence and human answers: {{history.text}}`, implementation, 'tasks');
37
39
  node('tasks', 'Check artifacts and completed tasks', 'artifact-contract', { change, contractId: 'plan', action: 'check', requireCompletedTasks: true }, { pass: 'verify', fail: 'developer', failed: 'failed' });
38
40
  node('verify', 'Verify candidate', 'verify', { commands: 'configured', additionalCommandsFrom: 'architect' }, { pass: 'reviewer', fail: 'correction-context', failed: 'failed' });
39
- turn('reviewer', 'Reviewer', 'assess', `Review change ${change} read-only against all approved artifacts and frozen ticket acceptance criteria. Inspect real code and the host verification evidence: {{outputs.verify}}. Return approved, summary, issues, score (0-100), and aspects. Prior execution: {{history.text}}`, objectSchema({ approved: { type: 'boolean' }, summary: string, issues: strings, score, aspects: objectSchema(Object.fromEntries(Object.keys(aspects).map(aspect => [aspect, score]))) }), 'review-policy');
41
+ turn('reviewer', 'Reviewer', 'assess', `Review change ${change} read-only against all approved artifacts and frozen ticket acceptance criteria. Inspect real code and the host verification evidence: {{outputs.verify}}. Return approved, summary, issues, score (0-100), and aspects. Acceptance policy: ${reviewPolicy}. For any rejection or below-threshold score, explain the concrete unmet requirement and actionable repair in summary and issues; passing commands do not prove every acceptance obligation. Prior execution: {{history.text}}`, objectSchema({ approved: { type: 'boolean' }, summary: string, issues: strings, score, aspects: objectSchema(Object.fromEntries(Object.keys(aspects).map(aspect => [aspect, score]))) }), 'review-policy');
40
42
  node('review-policy', 'Review acceptance policy', 'condition', { expr: ['$outputs.reviewer.structured.approved == true', '$outputs.reviewer.structured.issues.length == 0', '$outputs.reviewer.structured.score >= 70', ...Object.entries(aspects).map(([aspect, threshold]) => `$outputs.reviewer.structured.aspects.${aspect} >= ${threshold}`)].join(' && ') }, { true: 'approve', false: 'correction-context' });
41
43
  node('correction-context', 'Select current review findings', 'condition', { expr: '$outputs.verify.valid == true && exists($outputs.reviewer.candidateHash) && $outputs.reviewer.candidateHash == $outputs.verify.candidateHash' }, { true: 'capture-review', false: 'clear-review' });
42
- node('capture-review', 'Capture current review findings', 'assign', { set: { latestReview: '{{outputs.reviewer}}' } }, { next: 'fixer', failed: 'failed' });
43
- node('clear-review', 'Clear outdated review findings', 'assign', { set: { latestReview: 'No reviewer findings for the current verified candidate. Diagnose the current host verification failure first.' } }, { next: 'fixer', failed: 'failed' });
44
- turn('fixer', 'Fixer', 'correct', `Correct the reported implementation, test or review failure for change ${change} using the bound apply skill and preserving approved artifacts. Read failureSummary, command, cwd, exitCode and evidenceId first; use read_verification_evidence for truncated evidence. Reproduce the exact focused failure, inspect the assertion and tested code, then apply the smallest repair that preserves the required behavior. An unchanged filename does not prove an unrelated failure. A mandatory test inside the admitted workspace may receive a proven formatting/compatibility repair while retaining every required guard. Report diagnosis, focused commands with original exit codes, repair and unresolved failures in summary and incomplete. If the cause cannot be repaired within scope, explain the evidence and required action; an unchanged candidate with failed verification stops this run. Host verification: {{outputs.verify}}. Latest review findings for this candidate: {{run.latestReview}}. Prior execution: {{history.text}}`, implementation, 'correction-progress');
45
- node('correction-progress', 'Check correction progress', 'condition', { expr: '$outputs.verify.valid == true || (exists($outputs.fixer.candidateHash) && $outputs.fixer.candidateHash != null && $outputs.fixer.candidateHash != $outputs.verify.candidateHash)' }, { true: 'tasks', false: 'correction-stalled' });
44
+ node('capture-review', 'Capture current review findings', 'assign', { set: { latestReview: '{{outputs.reviewer.structured}}' } }, { next: 'correction-budget', failed: 'failed' });
45
+ node('clear-review', 'Clear outdated review findings', 'assign', { set: { latestReview: 'No reviewer findings for the current verified candidate. Diagnose the current host verification failure first.' } }, { next: 'correction-budget', failed: 'failed' });
46
+ node('correction-budget', 'Check automatic correction budget', 'condition', { expr: '$vars.correctionAttempts < $vars.correctionLimit' }, { true: 'begin-correction', false: 'correction-exhausted' });
47
+ node('begin-correction', 'Count automatic correction attempt', 'assign', { increment: { correctionAttempts: 1 } }, { next: 'fixer', failed: 'failed' });
48
+ turn('fixer', 'Fixer', 'correct', `Correct the reported implementation, test or review failure for change ${change} using the bound apply skill and preserving approved artifacts. Read failureSummary, command, cwd, exitCode and evidenceId first; use read_verification_evidence for truncated evidence. If Host verification is valid, repair the concrete rejected review obligation or below-threshold aspect instead of only rerunning green commands. Acceptance policy: ${reviewPolicy}. Reproduce the exact focused failure, inspect the assertion and tested code, then apply the smallest repair that preserves the required behavior. An unchanged filename does not prove an unrelated failure. A mandatory test inside the admitted workspace may receive a proven formatting/compatibility repair while retaining every required guard. Report diagnosis, focused commands with original exit codes, repair and unresolved failures in summary and incomplete. If the cause cannot be repaired within scope, explain the evidence and required action; any unchanged correction stops this run even when host checks pass. Do not make unrelated edits merely to change the candidate hash. Host verification: {{outputs.verify}}. Latest review findings for this candidate: {{run.latestReview}}. Prior execution: {{history.text}}`, implementation, 'correction-progress');
49
+ node('correction-progress', 'Check correction progress', 'condition', { expr: 'exists($outputs.fixer.candidateHash) && $outputs.fixer.candidateHash != null && $outputs.fixer.candidateHash != $outputs.verify.candidateHash' }, { true: 'tasks', false: 'correction-stalled' });
46
50
  node('approve', 'Approve archive', 'approval', { reason: `Approve archiving the reviewed and verified candidate for change ${change}.`, bindCandidate: true, enabled: agents.approvalBeforeArchive === true }, { next: 'approval-policy' });
47
51
  node('approval-policy', 'Approval decision', 'condition', { expr: '$outputs.approve.response.approved == true' }, { true: 'archive', false: 'failed' });
48
52
  node('archive', 'Archive OpenSpec', 'openspec-archive', { change, allowArchived: true, requiresVerified: true, reviewedCandidate: '{{outputs.reviewer.candidateHash}}', approvedCandidate: '{{outputs.approve.candidateHash}}' }, { next: 'verify-archive', failed: 'verify' });
49
53
  node('verify-archive', 'Verify archived candidate', 'verify', { commands: 'configured', additionalCommandsFrom: 'architect' }, { pass: 'done', fail: 'failed', failed: 'failed' });
50
- nodes.push({ id: 'done', type: 'end', position: { x: 0, y: nodes.length * 140 }, data: { outcome: 'success', requiresVerified: true } }, { id: 'failed', type: 'end', position: { x: 360, y: nodes.length * 140 }, data: { outcome: 'failure' } }, { id: 'correction-stalled', type: 'end', position: { x: 720, y: nodes.length * 140 }, data: { outcome: 'failure', reason: 'Verification remains failed and the correction made no candidate changes. {{outputs.fixer.structured.summary}}' } });
54
+ nodes.push({ id: 'done', type: 'end', position: { x: 0, y: nodes.length * 140 }, data: { outcome: 'success', requiresVerified: true } }, { id: 'failed', type: 'end', position: { x: 360, y: nodes.length * 140 }, data: { outcome: 'failure' } }, { id: 'correction-stalled', type: 'end', position: { x: 720, y: nodes.length * 140 }, data: { outcome: 'failure', reason: `Automatic correction made no candidate changes. Host verification is valid: {{outputs.verify.valid}}. Acceptance policy: ${reviewPolicy}. Latest review findings: {{run.latestReview}}. Fixer diagnosis: {{outputs.fixer.structured.summary}}` } }, { id: 'correction-exhausted', type: 'end', position: { x: 1080, y: nodes.length * 140 }, data: { outcome: 'failure', reason: `Automatic correction limit reached ({{run.correctionAttempts}}/{{run.correctionLimit}}). Acceptance policy: ${reviewPolicy}. Latest review findings: {{run.latestReview}}. Fixer diagnosis: {{outputs.fixer.structured.summary}}. Host verification: {{outputs.verify}}` } });
51
55
  edges.unshift({ id: 'e-start', source: 'start', target: 'architect' });
52
56
  return { nodes, edges, config: { maxIterations: 12, maxTransitions: 200, timeoutMinutes: 0, aiStepTimeoutMinutes: 0, change: 'new', journal: 'ledger-only', ticketScope: 'all', agents, reviewerStepId: 'reviewer' } };
53
57
  }
@@ -950,13 +950,15 @@ class LoopRunManager {
950
950
  openStep = null;
951
951
  emitRunEvent('loop_step_end', payload);
952
952
  };
953
- const logLine = (line, source = 'stdout') => {
953
+ const logLine = (line, source = 'stdout', metadata) => {
954
+ const attemptId = metadata?.attemptId;
955
+ const attribution = typeof attemptId === 'string' && attemptId.trim() && attemptId.length <= 256 ? { attemptId } : {};
954
956
  const s = takeSeq();
955
957
  try {
956
- (0, db_1.appendEvent)(this.db, runId, s, { event_type: 'log', source, payload: JSON.stringify({ line }) });
958
+ (0, db_1.appendEvent)(this.db, runId, s, { event_type: 'log', source, payload: JSON.stringify({ line, ...attribution }) });
957
959
  }
958
960
  catch { /* events table best-effort */ }
959
- this._emit({ type: 'log', source, line, timestamp: new Date(this.now()).toISOString(), processId: runId });
961
+ this._emit({ type: 'log', source, line, timestamp: new Date(this.now()).toISOString(), processId: runId, ...attribution });
960
962
  };
961
963
  // Forward a RAW provider stdout line (claude/codex JSONL) as an `event` —
962
964
  // identical to QueueManager — so JobStatusPanel parses real activity
@@ -12,6 +12,7 @@ exports.parseLoginShellOutput = parseLoginShellOutput;
12
12
  exports.augmentPathFromLoginShell = augmentPathFromLoginShell;
13
13
  exports.parseLoginShellEnv = parseLoginShellEnv;
14
14
  exports.augmentEnvFromLoginShell = augmentEnvFromLoginShell;
15
+ exports.readEnvFromLoginShellSync = readEnvFromLoginShellSync;
15
16
  exports.augmentEnvFromLoginShellSync = augmentEnvFromLoginShellSync;
16
17
  exports.augmentAuthEnvFromLoginShell = augmentAuthEnvFromLoginShell;
17
18
  exports.getPathDiagnostic = getPathDiagnostic;
@@ -372,6 +373,17 @@ function parseLoginShellOutput(stdout) {
372
373
  return null;
373
374
  return stdout.slice(start, end);
374
375
  }
376
+ /** GUI launches may omit SHELL even when the account uses zsh/bash. */
377
+ function resolveLoginShell(env = process.env) {
378
+ if (env.SHELL)
379
+ return env.SHELL;
380
+ try {
381
+ return os_1.default.userInfo().shell || '/bin/sh';
382
+ }
383
+ catch {
384
+ return '/bin/sh';
385
+ }
386
+ }
375
387
  /**
376
388
  * Spawn the user's login shell once and merge any additional PATH segments
377
389
  * it exposes (Volta/nvm/fnm/asdf shims) into `process.env.PATH`. Async,
@@ -398,7 +410,7 @@ async function augmentPathFromLoginShell(opts = {}) {
398
410
  }
399
411
  const spawnFn = opts.spawnFn ?? child_process_1.spawn;
400
412
  const timeoutMs = opts.timeoutMs ?? LOGIN_SHELL_TIMEOUT_MS;
401
- const shell = process.env.SHELL || '/bin/sh';
413
+ const shell = resolveLoginShell();
402
414
  const command = `printf "${PATH_BEGIN}%s${PATH_END}" "$PATH"`;
403
415
  const status = await new Promise((resolve) => {
404
416
  let child;
@@ -474,7 +486,6 @@ exports.AUTH_ENV_VARS = [
474
486
  'GOOGLE_APPLICATION_CREDENTIALS',
475
487
  ];
476
488
  const LOGIN_SHELL_ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
477
- const loginShellEnvProbeCache = new Set();
478
489
  function normalizeLoginShellEnvNames(names) {
479
490
  const out = [];
480
491
  for (const raw of names) {
@@ -528,7 +539,7 @@ async function augmentEnvFromLoginShell(names, opts = {}) {
528
539
  return;
529
540
  const spawnFn = opts.spawnFn ?? child_process_1.spawn;
530
541
  const timeoutMs = opts.timeoutMs ?? LOGIN_SHELL_TIMEOUT_MS;
531
- const shell = process.env.SHELL || '/bin/sh';
542
+ const shell = resolveLoginShell();
532
543
  const command = buildLoginShellEnvCommand(wanted);
533
544
  await new Promise((resolve) => {
534
545
  let child;
@@ -569,42 +580,53 @@ async function augmentEnvFromLoginShell(names, opts = {}) {
569
580
  });
570
581
  });
571
582
  }
572
- /** Synchronous, cached variant for spawn-time env assembly. It probes each
573
- * missing name at most once per process to keep loops from paying the login
574
- * shell timeout on every step. */
575
- function augmentEnvFromLoginShellSync(names, opts = {}) {
583
+ /** Read missing configured names without mutating the caller's or global env.
584
+ * Callers own any cache so project credentials cannot leak across scopes. */
585
+ function readEnvFromLoginShellSync(names, opts = {}) {
576
586
  if (process.platform === 'win32')
577
- return;
587
+ return {};
578
588
  if (process.env.NODE_ENV === 'test' || process.env.VITEST === 'true')
579
- return;
580
- const wanted = normalizeLoginShellEnvNames(names).filter((name) => !process.env[name] && !loginShellEnvProbeCache.has(name));
589
+ return {};
590
+ const env = opts.env ?? process.env;
591
+ const wanted = normalizeLoginShellEnvNames(names).filter((name) => !env[name]);
581
592
  if (wanted.length === 0)
582
- return;
583
- for (const name of wanted)
584
- loginShellEnvProbeCache.add(name);
593
+ return {};
585
594
  const spawnSyncFn = opts.spawnSyncFn ?? child_process_1.spawnSync;
586
595
  const timeoutMs = opts.timeoutMs ?? LOGIN_SHELL_TIMEOUT_MS;
587
- const shell = process.env.SHELL || '/bin/sh';
596
+ const shell = resolveLoginShell(env);
588
597
  const command = buildLoginShellEnvCommand(wanted);
589
598
  let res;
590
599
  try {
591
600
  res = spawnSyncFn(shell, ['-l', '-i', '-c', command], {
601
+ env,
592
602
  encoding: 'utf8',
593
603
  stdio: ['ignore', 'pipe', 'pipe'],
594
604
  timeout: timeoutMs,
605
+ maxBuffer: 1024 * 1024,
595
606
  });
596
607
  }
597
608
  catch {
598
- return;
609
+ return {};
599
610
  }
600
- if (res.error)
601
- return;
611
+ if (res.error || res.status !== 0)
612
+ return {};
602
613
  const stdout = typeof res.stdout === 'string' ? res.stdout : res.stdout?.toString('utf8') ?? '';
603
614
  const recovered = parseLoginShellEnv(stdout);
615
+ const out = {};
604
616
  for (const key of wanted) {
605
617
  const val = recovered[key];
606
- if (val && !process.env[key])
607
- process.env[key] = val;
618
+ if (val)
619
+ out[key] = val;
620
+ }
621
+ return out;
622
+ }
623
+ /** Compatibility backfill for callers that intentionally augment process.env.
624
+ * Failed probes remain retryable; successful names are already present. */
625
+ function augmentEnvFromLoginShellSync(names, opts = {}) {
626
+ const recovered = readEnvFromLoginShellSync(names, opts);
627
+ for (const [key, value] of Object.entries(recovered)) {
628
+ if (value && !process.env[key])
629
+ process.env[key] = value;
608
630
  }
609
631
  }
610
632
  /** Startup backfill for the built-in provider auth variables. */
@@ -647,5 +669,4 @@ function __resetPathResolverForTest() {
647
669
  diagnostic = { pathSegments: [], pathSources: [], loginShellStatus: 'skipped' };
648
670
  warnedLoginShell = false;
649
671
  bundledRuntimesActive = false;
650
- loginShellEnvProbeCache.clear();
651
672
  }
@@ -4,21 +4,37 @@ exports.resolveWorktreeEnvPassthrough = resolveWorktreeEnvPassthrough;
4
4
  exports.applyWorktreeEnvPassthrough = applyWorktreeEnvPassthrough;
5
5
  const db_1 = require("./db");
6
6
  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();
11
+ function recoverProjectEnv(db, names) {
12
+ const missing = names.filter(name => !process.env[name]);
13
+ if (!missing.length) {
14
+ shellValues.delete(db);
15
+ return {};
16
+ }
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;
24
+ }
7
25
  /** Resolve the per-project env passthrough overlay for a spawn.
8
26
  *
9
27
  * The project setting stores names only. Values are read from `sourceEnv` at
10
28
  * spawn time so credentials can rotate without touching SQLite. When the
11
- * source is `process.env`, missing configured names get one bounded login-shell
12
- * recovery attempt first; unresolved names are still ignored instead of
13
- * blocking the run.
29
+ * source is `process.env`, missing configured names get bounded login-shell
30
+ * recovery with a short, project-owned cache. Unresolved names remain absent.
14
31
  */
15
32
  function resolveWorktreeEnvPassthrough(db, sourceEnv = process.env) {
16
33
  const out = {};
17
34
  const names = (0, db_1.getProjectSettings)(db).worktreeEnvPassthrough;
18
- if (sourceEnv === process.env)
19
- (0, path_resolver_1.augmentEnvFromLoginShellSync)(names);
35
+ const recovered = sourceEnv === process.env ? recoverProjectEnv(db, names) : {};
20
36
  for (const name of names) {
21
- const value = sourceEnv[name];
37
+ const value = sourceEnv[name] || recovered[name] || sourceEnv[name];
22
38
  if (value !== undefined)
23
39
  out[name] = value;
24
40
  }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkoutSubdirectory = checkoutSubdirectory;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ /** Where a directory sits inside its Git checkout. Handles worktree .git files
7
+ * as well as directories; root and non-Git directories both return ''. */
8
+ function checkoutSubdirectory(directory) {
9
+ let start;
10
+ try {
11
+ start = (0, node_fs_1.realpathSync)(directory);
12
+ }
13
+ catch {
14
+ return '';
15
+ }
16
+ for (let current = start;; current = (0, node_path_1.dirname)(current)) {
17
+ if ((0, node_fs_1.existsSync)((0, node_path_1.join)(current, '.git')))
18
+ return (0, node_path_1.relative)(current, start).split(node_path_1.sep).join('/');
19
+ if ((0, node_path_1.dirname)(current) === current)
20
+ return '';
21
+ }
22
+ }
@@ -52,6 +52,7 @@ exports.linkNodeModulesIntoWorktree = linkNodeModulesIntoWorktree;
52
52
  const fs = __importStar(require("fs"));
53
53
  const path = __importStar(require("path"));
54
54
  const worktree_overlay_1 = require("./worktree-overlay");
55
+ const checkout_path_1 = require("./util/checkout-path");
55
56
  /** Package-dir discovery depth: 0 = repo root, 1 = `client/`-style children. */
56
57
  const MAX_DEPTH = 2;
57
58
  /** Name of the dependency directory this module links. */
@@ -122,24 +123,43 @@ function resolveRealPath(target) {
122
123
  return path.resolve(target);
123
124
  }
124
125
  }
126
+ /** Destination ancestors must be local directories, never links into another
127
+ * checkout. Missing directories can be created during preparation. */
128
+ function hasLocalParents(worktreePath, rel) {
129
+ let parent = worktreePath;
130
+ for (const segment of rel.split('/').slice(0, -1)) {
131
+ parent = path.join(parent, segment);
132
+ try {
133
+ if (!fs.lstatSync(parent).isDirectory())
134
+ return false;
135
+ }
136
+ catch (error) {
137
+ if (error.code !== 'ENOENT')
138
+ return false;
139
+ }
140
+ }
141
+ return true;
142
+ }
125
143
  /**
126
144
  * Prove one worktree-relative path is an app-created warm-dependency link and
127
145
  * fingerprint it. Returns null for anything that is not EXACTLY a symlink whose
128
- * target resolves to the base checkout's identically-named directory — a real
146
+ * target resolves to the registered source's corresponding entry — a real
129
147
  * directory, a dereferencing copy, a dangling link, or a link into some other
130
148
  * tree are all unauthorized and must keep preserving the worktree.
131
149
  */
132
- function authenticateWarmLink(baseRepo, worktreePath, rel) {
150
+ function authenticateWarmLink(baseRepo, worktreePath, rel, sourceRel = rel) {
151
+ if (!hasLocalParents(worktreePath, rel))
152
+ return null;
133
153
  const dest = path.join(worktreePath, ...rel.split('/'));
134
154
  try {
135
155
  if (!fs.lstatSync(dest).isSymbolicLink())
136
156
  return null;
137
157
  const target = path.resolve(path.dirname(dest), fs.readlinkSync(dest));
138
- const expected = path.join(baseRepo, ...rel.split('/'));
158
+ const expected = path.join(baseRepo, ...sourceRel.split('/'));
139
159
  if (resolveRealPath(target) !== resolveRealPath(expected))
140
160
  return null;
141
- // A link to something that is not a live directory proves nothing about the
142
- // base checkout's dependency tree.
161
+ // A dangling link proves nothing about the source dependency tree (entries
162
+ // include both package directories and files such as Yarn's install state).
143
163
  if (!exists(target))
144
164
  return null;
145
165
  }
@@ -195,39 +215,49 @@ function discoverWorktreeDependencyPaths(worktreePath) {
195
215
  * — the link target anchored to a directory the app controls.
196
216
  */
197
217
  function authenticateWarmNodeModulesLinks(baseRepo, worktreePath) {
198
- const evidence = [];
199
- for (const rel of discoverWorktreeDependencyPaths(worktreePath)) {
200
- const legacy = authenticateWarmLink(baseRepo, worktreePath, rel);
201
- if (legacy) {
202
- evidence.push(legacy);
218
+ const evidence = new Map();
219
+ const prefix = (0, checkout_path_1.checkoutSubdirectory)(baseRepo);
220
+ // Retained mounts may still contain the former misplaced layout. Keep its
221
+ // exact source proof, without moving it or broadening cleanup to directories.
222
+ for (const destinationPrefix of prefix ? [prefix, ''] : ['']) {
223
+ const project = (rel) => destinationPrefix ? `${destinationPrefix}/${rel}` : rel;
224
+ if (!hasLocalParents(worktreePath, project(DEPS_DIR)))
203
225
  continue;
204
- }
205
- const dest = path.join(worktreePath, rel);
206
- try {
207
- if (!fs.lstatSync(dest).isDirectory())
226
+ const destinationRoot = path.join(worktreePath, destinationPrefix);
227
+ const authenticate = (sourceRel) => authenticateWarmLink(baseRepo, worktreePath, project(sourceRel), sourceRel);
228
+ for (const rel of discoverWorktreeDependencyPaths(destinationRoot)) {
229
+ const legacy = authenticate(rel);
230
+ if (legacy) {
231
+ evidence.set(legacy.path, legacy);
208
232
  continue;
209
- for (const name of fs.readdirSync(dest)) {
210
- if (exports.WORKTREE_DEPENDENCY_CACHES.includes(name))
211
- continue;
212
- const entryRel = `${rel}/${name}`;
213
- const entry = authenticateWarmLink(baseRepo, worktreePath, entryRel);
214
- if (entry) {
215
- evidence.push(entry);
233
+ }
234
+ const dest = path.join(destinationRoot, rel);
235
+ try {
236
+ if (!fs.lstatSync(dest).isDirectory())
216
237
  continue;
217
- }
218
- // Scoped package parents are local too; never follow foreign symlinks.
219
- if (name.startsWith('@') && fs.lstatSync(path.join(dest, name)).isDirectory()) {
220
- for (const scoped of fs.readdirSync(path.join(dest, name))) {
221
- const child = authenticateWarmLink(baseRepo, worktreePath, `${entryRel}/${scoped}`);
222
- if (child)
223
- evidence.push(child);
238
+ for (const name of fs.readdirSync(dest)) {
239
+ if (exports.WORKTREE_DEPENDENCY_CACHES.includes(name))
240
+ continue;
241
+ const entryRel = `${rel}/${name}`;
242
+ const entry = authenticate(entryRel);
243
+ if (entry) {
244
+ evidence.set(entry.path, entry);
245
+ continue;
246
+ }
247
+ // Scoped package parents are local too; never follow foreign symlinks.
248
+ if (name.startsWith('@') && fs.lstatSync(path.join(dest, name)).isDirectory()) {
249
+ for (const scoped of fs.readdirSync(path.join(dest, name))) {
250
+ const child = authenticate(`${entryRel}/${scoped}`);
251
+ if (child)
252
+ evidence.set(child.path, child);
253
+ }
224
254
  }
225
255
  }
226
256
  }
257
+ catch { /* Unreadable or concurrently removed entries confer no authority. */ }
227
258
  }
228
- catch { /* Unreadable or concurrently removed entries confer no authority. */ }
229
259
  }
230
- return evidence;
260
+ return [...evidence.values()];
231
261
  }
232
262
  function linkDependencyEntry(source, destination) {
233
263
  fs.symlinkSync(source, destination, process.platform === 'win32'
@@ -262,13 +292,19 @@ function linkNodeModulesIntoWorktree(baseRepo, worktreePath) {
262
292
  const result = { linked: [], authenticated: [], evidence: [], warnings: [] };
263
293
  if (!isWorktreeNodeModulesEnabled())
264
294
  return result;
295
+ const prefix = (0, checkout_path_1.checkoutSubdirectory)(baseRepo);
265
296
  for (const pkgRel of discoverPackageDirs(baseRepo)) {
266
- const rel = pkgRel === '' ? DEPS_DIR : `${pkgRel}/${DEPS_DIR}`;
267
- const src = path.join(baseRepo, ...rel.split('/'));
297
+ const sourceRel = pkgRel === '' ? DEPS_DIR : `${pkgRel}/${DEPS_DIR}`;
298
+ const rel = prefix ? `${prefix}/${sourceRel}` : sourceRel;
299
+ const src = path.join(baseRepo, ...sourceRel.split('/'));
268
300
  if (!isDir(src))
269
301
  continue;
302
+ if (!hasLocalParents(worktreePath, rel)) {
303
+ result.warnings.push(`failed to prepare ${rel}: destination parent is not a local directory`);
304
+ continue;
305
+ }
270
306
  const dest = path.join(worktreePath, ...rel.split('/'));
271
- const legacy = authenticateWarmLink(baseRepo, worktreePath, rel);
307
+ const legacy = authenticateWarmLink(baseRepo, worktreePath, rel, sourceRel);
272
308
  if (!exists(dest) || legacy) {
273
309
  let staging;
274
310
  let removedLegacy = false;
@@ -280,7 +316,7 @@ function linkNodeModulesIntoWorktree(baseRepo, worktreePath) {
280
316
  if (legacy) {
281
317
  // Recheck immediately before removing the old link. Never unlink a
282
318
  // directory or a replacement another actor installed in the meantime.
283
- const current = authenticateWarmLink(baseRepo, worktreePath, rel);
319
+ const current = authenticateWarmLink(baseRepo, worktreePath, rel, sourceRel);
284
320
  if (!current || current.digest !== legacy.digest)
285
321
  throw new Error('dependency link changed during preparation');
286
322
  fs.unlinkSync(dest);
@@ -1 +0,0 @@
1
- import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{n as t,t as n}from"./jsx-runtime-zu2_FqZY.js";import{t as r}from"./react-dom-DiWzBDP9.js";import{a as i,c as a,l as o,m as s}from"./useSharedWebSocket-C17TGqT8.js";import{n as c}from"./createLucideIcon-Bjbbp_KS.js";import{a as l,s as u}from"./useDesktop-DpxVGfUC.js";import{dt as d,gt as f}from"./TicketDetailModalContext-CkVXuHxU.js";import{a as p,i as m,n as h,o as g,r as _,s as v,t as ee}from"./InteractiveJobComposer-Da9dvM-0.js";import{t as te}from"./loader-circle-DcygOQpC.js";import{t as ne}from"./x-CBMI9ejn.js";import{F as re,I as ie,P as ae}from"./dialog-CMgps2MY.js";import{t as oe}from"./formatDistanceToNow-DlQaXc7c.js";import{t as y}from"./button-D1xExSTF.js";import{G as se,J as b,W as x}from"./index-Czov72g7.js";import{n as S,t as C}from"./job-time-BCL7wee9.js";var ce=e(r(),1),w=e(t(),1),T=[1e3,2e3,4e3,8e3,16e3];function E(e,t){let[n,r]=(0,w.useState)(`connecting`),o=(0,w.useRef)(t);return o.current=t,(0,w.useEffect)(()=>{let t=!1,n=!1,s=null,c=0,l=null;async function u(){if(!(t||n||s)){n=!0;try{await a()}catch{}finally{n=!1}!t&&!s&&f()}}function d(){if(t)return;l!==null&&clearTimeout(l),r(`connecting`);let e=T[c++]??3e4;l=setTimeout(()=>{l=null,u()},e)}function f(){if(t||s)return;let n=i(),a;try{a=n?new WebSocket(e,[`specrails-desktop`,n]):new WebSocket(e)}catch{d();return}s=a,r(`connecting`),a.onopen=()=>{t||s!==a||(c=0,r(`connected`))},a.onmessage=e=>{if(!(t||s!==a))try{o.current(JSON.parse(e.data))}catch{}},a.onclose=()=>{t||s!==a||(s=null,d())}}let p=()=>{t||s||(l!==null&&clearTimeout(l),l=null,u())};return window.addEventListener(`online`,p),window.addEventListener(`focus`,p),f(),()=>{t=!0,window.removeEventListener(`online`,p),window.removeEventListener(`focus`,p),l!==null&&clearTimeout(l);let e=s;if(s=null,e){e.onopen=null,e.onmessage=null,e.onclose=null,e.onerror=null;try{e.close()}catch{}}}},[e]),{connectionStatus:n}}var D=n();function O({jobId:e,onClose:t,projectId:n}){let[r,i]=(0,w.useState)(e);(0,w.useEffect)(()=>{i(e)},[e]);let{t:a}=c(`jobs`),{t:T}=c(`narration`),O=(0,w.useCallback)(()=>n?`${o}/api/projects/${n}`:l(),[n]),[k,le]=(0,w.useState)(h),ue=(0,w.useCallback)(e=>{le(e),_(e)},[]),[A,j]=(0,w.useState)(null),[M,N]=(0,w.useState)([]),[P,F]=(0,w.useState)([]),[I,L]=(0,w.useState)({}),[R,z]=(0,w.useState)(!0),[B,V]=(0,w.useState)(!1),[H,U]=(0,w.useState)(!1),[W,G]=(0,w.useState)(!1);(0,w.useEffect)(()=>{async function e(){try{let e=await fetch(`${O()}/jobs/${r}`);if(e.status===404){V(!0);return}if(!e.ok)throw Error(`Failed to fetch job`);let t=await e.json();if(j(t.job),N(t.events),t.phaseDefinitions){F(t.phaseDefinitions);let e={};for(let n of t.phaseDefinitions)e[n.key]=`idle`;L(e)}}catch{V(!0)}finally{z(!1)}}e()},[r,O]);let K=(0,w.useCallback)(()=>{fetch(`${O()}/jobs/${r}`).then(e=>e.ok?e.json():null).then(e=>{e?.job&&j(e.job)}).catch(()=>{})},[r,O]),q=(0,w.useRef)([]),J=(0,w.useRef)(null),Y=(0,w.useCallback)(()=>{J.current=null;let e=q.current;e.length!==0&&(q.current=[],N(t=>{let n=[...t,...e];return n.length>1e4?n.slice(n.length-8e3):n}))},[]);(0,w.useEffect)(()=>()=>{J.current&&cancelAnimationFrame(J.current)},[]),E(f,(0,w.useCallback)(e=>{let t=e;if(t.type===`event`&&t.jobId===r){let e={id:Date.now(),job_id:r,seq:0,event_type:t.event_type,source:t.source,payload:t.payload,timestamp:t.timestamp};q.current.push(e),J.current||=requestAnimationFrame(Y)}else if(t.type===`log`&&t.processId===r){let e={id:Date.now(),job_id:r,seq:0,event_type:`log`,source:t.source,payload:JSON.stringify({line:t.line}),timestamp:t.timestamp};q.current.push(e),J.current||=requestAnimationFrame(Y)}else if(t.type===`job.finalized`&&t.jobId===r||t.type===`runtime.continuation`&&t.jobId===r||t.type===`job.interactive`&&t.jobId===r||(t.type===`loop.run_paused`||t.type===`loop.run_resumed`)&&t.loopRunId===r)K();else if(t.type===`phase`){let e=t.phase,n=t.state;L(t=>({...t,[e]:n}))}else if(t.type===`queue`){let e=t.jobs?.find(e=>e.id===r);e&&A&&j(t=>t&&{...t,status:e.status})}},[r,A,Y,K])),(0,w.useEffect)(()=>{function e(e){if(!(e.key!==`Escape`||e.defaultPrevented)){if(H){U(!1);return}t()}}return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[t,H]);let X=A?se(A):`job`,Z=X===`loop-run`;async function de(){G(!0);try{let e=await x({projectId:n??null,jobId:r,kind:X});e.ok?(u.success(a(Z?`modal.toast.stopSignalSent`:`modal.toast.cancelSignalSent`),{description:a(Z?`modal.toast.stopSignalSentDescription`:`modal.toast.cancelSignalSentDescription`)}),K()):u.error(a(`modal.toast.cancelFailed`),{description:e.error})}finally{G(!1)}}let fe=A?.status===`running`,Q=A?S(C(A)):null,{panelRef:pe,panelStyle:$,headerHandleProps:me,resizeHandles:he,isFloating:ge,guardBackdrop:_e}=re();return(0,ce.createPortal)((0,D.jsx)(b,{delayDuration:400,children:(0,D.jsxs)(`div`,{style:ie(),className:`fixed inset-0 z-[65] flex items-stretch justify-center p-[clamp(12px,2vmin,24px)]`,children:[(0,D.jsx)(`div`,{className:`absolute inset-0 bg-black/50 backdrop-blur-sm`,onClick:_e(t)}),(0,D.jsxs)(`div`,{ref:pe,className:`relative w-full min-w-0 rounded-xl glass-card border border-border/30 flex flex-col animate-in fade-in zoom-in-95 duration-200 overflow-hidden`,style:$,children:[(0,D.jsxs)(`div`,{...me,className:`flex items-center justify-between px-4 py-3 border-b border-border/30${ge?` cursor-grab active:cursor-grabbing`:``}`,children:[(0,D.jsxs)(`div`,{className:`flex items-center gap-3 min-w-0`,children:[A&&(0,D.jsxs)(D.Fragment,{children:[(0,D.jsx)(`code`,{className:`text-xs font-mono text-foreground/80 truncate`,children:A.command}),(0,D.jsx)(`span`,{className:`text-[10px] text-muted-foreground shrink-0`,children:Q?oe(Q,{addSuffix:!0,locale:s()}):`—`})]}),R&&(0,D.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:a(`common:states.loading`)})]}),(0,D.jsx)(`button`,{onClick:t,className:`h-7 w-7 shrink-0 flex items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-surface/50 transition-colors cursor-pointer`,children:(0,D.jsx)(ne,{className:`w-4 h-4`})})]}),A&&(0,D.jsx)(v,{onOpenRun:i,job:A,events:M,phases:I,phaseDefinitions:P,projectId:n,variant:`glass`,actions:fe?(0,D.jsxs)(y,{variant:`ghost`,size:`sm`,onClick:()=>U(!0),disabled:W,className:`h-7 text-destructive hover:text-destructive hover:bg-destructive/10`,children:[W&&(0,D.jsx)(te,{className:`w-3.5 h-3.5 mr-1.5 animate-spin`,"aria-hidden":!0}),a(Z?`dashboard:railControls.stop`:X===`interactive`?`common:actions.discard`:`common:actions.cancel`)]}):null}),(0,D.jsx)(`div`,{className:`min-h-0 flex-1 overflow-hidden relative`,children:B?(0,D.jsx)(`div`,{className:`flex items-center justify-center h-full`,children:(0,D.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:a(`modal.notFound`)})}):d&&A?(0,D.jsxs)(`div`,{className:`flex h-full min-h-0 flex-col`,children:[(0,D.jsx)(`div`,{className:`flex items-center gap-1 border-b border-border/40 px-3 py-1.5`,children:[`narrated`,`log`].map(e=>(0,D.jsx)(`button`,{type:`button`,onClick:()=>ue(e),"aria-pressed":k===e,"data-testid":`modal-narration-mode-${e}`,className:`rounded-md px-2 py-0.5 text-[11px] font-medium transition-colors ${k===e?`bg-accent-primary/15 text-accent-primary`:`text-muted-foreground hover:bg-surface/60`}`,children:T(e===`narrated`?`modeNarrated`:`modeLog`)},e))}),(0,D.jsx)(`div`,{className:`min-h-0 flex-1 overflow-hidden`,children:k===`narrated`?(0,D.jsx)(m,{events:M,settled:A.status!==`running`&&A.status!==`queued`,elapsedMs:A.duration_ms??null,variant:`glass`}):A.command.startsWith(`loop:`)?(0,D.jsx)(p,{events:M,jobStatus:A.status,isLoading:R,variant:`glass`,projectId:n,onOpenRun:i}):(0,D.jsx)(g,{events:M,isLoading:R,projectId:n})})]}):A?.command.startsWith(`loop:`)?(0,D.jsx)(p,{events:M,jobStatus:A.status,isLoading:R,variant:`glass`,projectId:n,onOpenRun:i}):(0,D.jsx)(g,{events:M,isLoading:R,projectId:n})}),!!A?.interactive&&A.status===`running`&&(0,D.jsx)(ee,{jobId:r,projectId:n,settleMode:A.interactiveSettleMode,initialAcceptingTurns:A.interactiveAcceptingTurns,pendingInterrupts:A.pendingInterrupts,kind:A.command.startsWith(`loop:`)?`loop-step`:`job`,variant:`glass`,onFinalized:K})]}),(0,D.jsx)(ae,{handles:he}),H&&(0,D.jsxs)(`div`,{"data-testid":`job-cancel-confirm`,className:`absolute inset-0 z-10 flex items-center justify-center p-4`,children:[(0,D.jsx)(`div`,{className:`absolute inset-0 bg-black/60 backdrop-blur-sm`,onClick:()=>U(!1)}),(0,D.jsxs)(`div`,{className:`relative w-full max-w-sm rounded-xl glass-card border border-border/30 p-5 shadow-xl animate-in fade-in zoom-in-95 duration-150`,children:[(0,D.jsx)(`h3`,{className:`text-base font-semibold text-foreground`,children:a(Z?`modal.stopRunConfirmTitle`:`modal.cancelConfirmTitle`)}),(0,D.jsx)(`p`,{className:`mt-1.5 text-sm text-muted-foreground`,children:a(Z?`modal.stopRunConfirmDescription`:`modal.cancelConfirmDescription`)}),(0,D.jsxs)(`div`,{className:`mt-4 flex justify-end gap-2`,children:[(0,D.jsx)(y,{variant:`ghost`,size:`sm`,onClick:()=>U(!1),children:a(`modal.keepRunning`)}),(0,D.jsx)(y,{variant:`destructive`,size:`sm`,onClick:()=>{U(!1),de()},children:a(Z?`modal.stopRun`:X===`interactive`?`common:actions.discard`:`modal.cancelJob`)})]})]})]})]})}),document.body)}export{O as JobDetailModal};
@@ -1 +0,0 @@
1
- import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{n as t,t as n}from"./jsx-runtime-zu2_FqZY.js";import{d as r,n as i,u as a}from"./chunk-KS7C4IRE-DEKu087k.js";import{m as o,r as ee}from"./useSharedWebSocket-C17TGqT8.js";import{n as s}from"./createLucideIcon-Bjbbp_KS.js";import{a as c,r as te,s as l}from"./useDesktop-DpxVGfUC.js";import{dt as u,n as ne}from"./TicketDetailModalContext-CkVXuHxU.js";import{n as d}from"./save-OsUqVn99.js";import{c as f}from"./MinimizedChatsContext-DKlPf54Y.js";import{n as re}from"./file-text-BcLCfcWZ.js";import{a as p,c as ie,i as ae,n as oe,o as m,r as se,s as ce,t as le}from"./InteractiveJobComposer-Da9dvM-0.js";import{t as ue}from"./download-CFHyyHcw.js";import{t as de}from"./rotate-ccw-Wh4id13q.js";import{t as h}from"./utils-BC4DHlz2.js";import{t as fe}from"./formatDistanceToNow-DlQaXc7c.js";import{t as g}from"./button-D1xExSTF.js";import{K as _,Nt as v,W as pe,Y as y,q as b}from"./index-Czov72g7.js";import{t as me}from"./format-command-C0D9IPmV.js";var x=e(t(),1),S=n(),C=4;function he({tickets:e,onTicketClick:t}){let{t:n}=s(`jobs`),[r,i]=(0,x.useState)(!1);if(e.length===0)return null;let a=e.length>=C&&!r?e.slice(0,1):e,o=e.length-a.length;return(0,S.jsxs)(`div`,{className:`rounded-xl border border-border/40 bg-card/40 px-4 py-3 space-y-1.5`,children:[a.map(e=>(0,S.jsx)(w,{ticket:e,onClick:()=>t(e.id)},e.id)),o>0&&(0,S.jsxs)(`button`,{type:`button`,onClick:()=>i(!0),className:`flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors`,children:[n(`ticketHeader.showMore`,{count:o}),(0,S.jsx)(d,{className:`w-3 h-3`})]}),r&&e.length>=C&&(0,S.jsxs)(`button`,{type:`button`,onClick:()=>i(!1),className:`flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors`,children:[n(`ticketHeader.showLess`),(0,S.jsx)(f,{className:`w-3 h-3`})]})]})}function w({ticket:e,onClick:t}){let{t:n}=s(`jobs`),r=e.title==null,i=`inline-flex items-center px-1.5 py-0.5 rounded-md text-[11px] font-mono tabular-nums shrink-0`;return r?(0,S.jsx)(`div`,{className:`flex items-center gap-2 min-w-0`,children:(0,S.jsx)(`span`,{className:h(i,`text-muted-foreground bg-muted/20 select-none`),title:n(`ticketHeader.deletedTooltip`),children:n(`ticketHeader.deletedTicket`,{id:e.id})})}):(0,S.jsxs)(`button`,{type:`button`,onClick:t,className:`flex items-center gap-2 min-w-0 w-full text-left group`,title:e.title??void 0,children:[(0,S.jsxs)(`span`,{className:h(i,`text-accent-primary bg-accent-primary/10 group-hover:bg-accent-primary/20 transition-colors`),children:[`#`,e.id]}),(0,S.jsx)(`span`,{className:`text-lg font-semibold text-foreground truncate group-hover:text-accent-primary transition-colors`,children:e.title})]})}function T(){let{t:e}=s(`jobs`),{t}=s(`narration`),{id:n}=r(),{activeProjectId:d,projects:f}=te(),C=f.find(e=>e.id===d)?.provider,w=a(),{openTicketDetail:T}=ne(),[E,D]=(0,x.useState)(null),[O,k]=(0,x.useState)([]),[ge,A]=(0,x.useState)([]),[_e,j]=(0,x.useState)({}),[ve,M]=(0,x.useState)([]),[ye,N]=(0,x.useState)(!0),[be,P]=(0,x.useState)(!1),[F,xe]=(0,x.useState)(oe),[Se,I]=(0,x.useState)(null),Ce=(0,x.useCallback)(e=>{xe(e),se(e)},[]),L=E?.command??null;(0,x.useEffect)(()=>{if(!u||F!==`narrated`||!d||!L)return;let e=!1,t=`command=${encodeURIComponent(L)}`;try{Promise.resolve(fetch(`${c()}/run-duration-range?${t}`)).then(e=>e&&e.ok?e.json():null).then(t=>{e||I(t?.range??null)}).catch(()=>{e||I(null)})}catch{I(null)}return()=>{e=!0}},[d,L,F]);let[R,z]=(0,x.useState)(!1),B=(0,x.useRef)(!1),V=(0,x.useRef)(null);(0,x.useEffect)(()=>{B.current=!1,V.current=null,z(!1)},[n]),(0,x.useEffect)(()=>{if(!n)return;let e=new AbortController;D(null),k([]),A([]),j({}),N(!0),P(!1);async function t(){try{let t=await fetch(`${c()}/jobs/${n}`,{signal:e.signal});if(t.status===404){P(!0);return}if(!t.ok)throw Error(`Failed to fetch job`);let r=await t.json();D(r.job),k(r.events)}catch(e){if(e.name===`AbortError`)return;P(!0)}finally{e.signal.aborted||N(!1)}}return t(),()=>e.abort()},[n,d]),(0,x.useEffect)(()=>{if(!E?.pipeline_id){M([]);return}let e=E.pipeline_id;fetch(`${c()}/pipelines/${e}`).then(e=>e.ok?e.json():null).then(e=>{e?.jobs&&M(e.jobs)}).catch(()=>{})},[E?.pipeline_id,E?.status]);let H=(0,x.useRef)(d);H.current=d;let U=(0,x.useRef)(null);U.current=E?.status??null;let W=(0,x.useRef)([]),G=(0,x.useRef)(null),K=(0,x.useCallback)(()=>{G.current=null;let e=W.current;e.length!==0&&(W.current=[],k(t=>{let n=[...t,...e];return n.length>1e4?n.slice(n.length-8e3):n}))},[]),q=(0,x.useCallback)(e=>{let t=W.current;t.push(e),t.length>1e4&&(W.current=t.slice(-8e3)),G.current||=requestAnimationFrame(K)},[K]);(0,x.useEffect)(()=>()=>{G.current&&cancelAnimationFrame(G.current)},[]);let J=(0,x.useCallback)(e=>{let t=e;if(!(H.current&&t.projectId&&t.projectId!==H.current)){if(t.type===`init`){let e=t.phaseDefinitions??[];A(e);let n={};for(let r of e)n[r.key]=t.phases?.[r.key]??`idle`;j(n)}else if(t.type===`event`&&t.jobId===n){let e={id:Date.now(),job_id:n??``,seq:t.seq??0,event_type:t.event_type,source:t.source,payload:t.payload,timestamp:t.timestamp};q(e)}else if(t.type===`log`&&t.processId===n){let e={id:Date.now(),job_id:n??``,seq:0,event_type:`log`,source:t.source,payload:JSON.stringify({line:t.line}),timestamp:t.timestamp};q(e)}else if(t.type===`job.finalized`&&t.jobId===n||t.type===`runtime.continuation`&&t.jobId===n||t.type===`job.interactive`&&t.jobId===n||(t.type===`loop.run_paused`||t.type===`loop.run_resumed`)&&t.loopRunId===n)fetch(`${c()}/jobs/${n}`).then(e=>e.json()).then(e=>D(e.job)).catch(()=>{});else if(t.type===`phase`){let e=t.phase,n=t.state;j(t=>({...t,[e]:n}))}else if(t.type===`queue`){let e=t.jobs?.find(e=>e.id===n);if(e){let t=e.status,r=U.current;D(e=>e&&{...e,status:t}),(t===`completed`||t===`failed`||t===`canceled`||t===`running`&&r!==`running`)&&fetch(`${c()}/jobs/${n}`).then(e=>e.json()).then(e=>D(e.job)).catch(()=>{})}}}},[n,q]),{registerHandler:Y,unregisterHandler:X}=ee();(0,x.useEffect)(()=>(Y(`job-detail-${n}`,J),()=>X(`job-detail-${n}`)),[n,J,Y,X]),(0,x.useEffect)(()=>{if(!n||!E||E.status!==`running`||E.hasTelemetry)return;let e=setInterval(()=>{fetch(`${c()}/jobs/${n}`).then(e=>e.ok?e.json():null).then(e=>{e?.job?.hasTelemetry&&D(e=>e&&{...e,hasTelemetry:!0})}).catch(()=>{})},8e3);return()=>clearInterval(e)},[n,E?.status,E?.hasTelemetry]);async function we(){if(!n)return;let t=await pe({projectId:null,jobId:n});t.ok?l.success(e(`detail.toast.cancelSignalSent`),{description:e(`detail.toast.cancelSignalSentDescription`)}):t.httpStatus===null?l.error(e(`detail.toast.networkError`)):l.error(e(`detail.toast.failed`),{description:t.error})}async function Te(){if(E)try{let t=await fetch(`${c()}/jobs/${E.id}/diagnostic`);if(!t.ok){let n=await t.json().catch(()=>({}));throw Error(n.error??e(`detail.toast.exportFailedHttp`,{status:t.status}))}let n=await t.blob(),r=URL.createObjectURL(n),i=new Date().toISOString().slice(0,10),a=document.createElement(`a`);a.href=r,a.download=`specrails-diagnostic-${E.id}-${i}.zip`,document.body.appendChild(a),a.click(),a.remove(),URL.revokeObjectURL(r)}catch(t){l.error(e(`detail.toast.exportFailed`),{description:t.message})}}async function Ee(){if(!(!E||B.current)){B.current=!0,z(!0),V.current||=typeof globalThis.crypto?.randomUUID==`function`?globalThis.crypto.randomUUID():`rerun-${E.id}-${Date.now()}-${Math.random().toString(36).slice(2)}`;try{let t=await fetch(`${c()}/spawn`,{method:`POST`,headers:{"Content-Type":`application/json`,"Idempotency-Key":V.current},body:JSON.stringify({rerunOfJobId:E.id})}),n=await t.json();if(!t.ok)throw Error(n.error??e(`detail.toast.spawnFailed`));V.current=null,l.success(e(`detail.toast.jobRequeued`)),w(`/jobs/${n.jobId}`)}catch(e){l.error(e.message)}finally{B.current=!1,z(!1)}}}if(ye)return(0,S.jsx)(`div`,{className:`max-w-5xl mx-auto px-4 py-6`,children:(0,S.jsxs)(`div`,{className:`space-y-3`,children:[(0,S.jsx)(`div`,{className:`h-4 w-48 bg-muted/30 rounded animate-pulse`}),(0,S.jsx)(`div`,{className:`h-20 bg-muted/30 rounded-lg animate-pulse`}),(0,S.jsx)(`div`,{className:`h-64 bg-muted/30 rounded-lg animate-pulse`})]})});if(be||!E)return(0,S.jsxs)(`div`,{className:`max-w-5xl mx-auto px-4 py-6 flex flex-col items-center gap-3 mt-12`,children:[(0,S.jsx)(`p`,{className:`text-lg font-semibold`,children:e(`detail.notFound`)}),(0,S.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:e(`detail.notFoundDescription`,{id:n})}),(0,S.jsx)(g,{asChild:!0,variant:`outline`,size:`sm`,children:(0,S.jsxs)(i,{to:`/`,children:[(0,S.jsx)(v,{className:`w-3.5 h-3.5 mr-1.5`}),e(`detail.backToDashboard`)]})})]});let Z=E.status===`running`,De=E.status===`completed`||E.status===`failed`,Q=!!E.interactive,Oe=Q&&Z,ke=(E.tickets?.length??0)>0,$=E.command.startsWith(`loop:`),Ae=ie(ve);return(0,S.jsxs)(`div`,{"data-job-detail-surface":!0,className:`flex flex-col h-full max-w-5xl mx-auto w-full`,children:[(0,S.jsxs)(`div`,{className:`px-4 pt-3 pb-2 space-y-2`,children:[(0,S.jsxs)(`div`,{className:`flex items-center gap-1 text-xs text-muted-foreground`,children:[(0,S.jsxs)(i,{to:`/`,className:`hover:text-foreground transition-colors flex items-center gap-1`,children:[(0,S.jsx)(v,{className:`w-3 h-3`}),e(`detail.breadcrumbDashboard`)]}),(0,S.jsx)(re,{className:`w-3 h-3`}),(0,S.jsx)(`span`,{className:`text-foreground font-mono`,children:e(`detail.breadcrumbJob`,{id:n?.slice(0,8)})})]}),ke&&(0,S.jsx)(he,{tickets:E.tickets??[],onTicketClick:T}),(0,S.jsxs)(`div`,{className:`flex items-center gap-3 text-xs text-muted-foreground flex-wrap min-w-0`,children:[(0,S.jsx)(`code`,{className:h(`font-mono text-foreground/90 truncate`,ke?`text-xs text-muted-foreground`:`text-sm`),children:me(E.command,C)}),(0,S.jsx)(`span`,{children:E.started_at?e(`detail.startedAgo`,{timeAgo:fe(new Date(E.started_at),{addSuffix:!0,locale:o()})}):e(`detail.queuedWaiting`)}),E.model&&(0,S.jsx)(`span`,{className:`text-muted-foreground/40`,children:E.model})]})]}),(0,S.jsx)(ce,{onOpenRun:e=>w(`/jobs/${e}`),job:E,events:O,phases:_e,phaseDefinitions:ge,projectId:d,variant:`page`,pipelineTotals:Ae,actions:(0,S.jsxs)(S.Fragment,{children:[E.hasTelemetry&&(0,S.jsxs)(_,{children:[(0,S.jsx)(y,{asChild:!0,children:(0,S.jsxs)(g,{variant:`outline`,size:`sm`,className:`h-7`,onClick:Te,"aria-label":e(`detail.exportDiagnosticAria`),children:[(0,S.jsx)(ue,{className:`w-3.5 h-3.5 mr-1.5`}),e(`detail.exportDiagnostic`)]})}),(0,S.jsx)(b,{children:e(`detail.exportDiagnosticTooltip`)})]}),De&&(0,S.jsxs)(_,{children:[(0,S.jsx)(y,{asChild:!0,children:(0,S.jsxs)(g,{variant:`outline`,size:`sm`,onClick:Ee,disabled:R,"aria-busy":R,className:`h-7`,children:[(0,S.jsx)(de,{className:`w-3.5 h-3.5 mr-1.5`}),e(`detail.reExecute`)]})}),(0,S.jsx)(b,{children:e(`detail.reExecuteTooltip`)})]}),Z&&(0,S.jsxs)(_,{children:[(0,S.jsx)(y,{asChild:!0,children:(0,S.jsx)(g,{variant:`outline`,size:`sm`,onClick:we,className:`h-7 border-destructive/30 text-destructive hover:bg-destructive/10`,children:e(Q?`detail.cancelInteractive`:`detail.cancelJob`)})}),(0,S.jsx)(b,{children:e(`detail.cancelJobTooltip`)})]})]})}),(0,S.jsx)(`div`,{className:`flex-1 overflow-hidden relative`,children:u?(0,S.jsxs)(`div`,{className:`flex h-full flex-col`,children:[(0,S.jsx)(`div`,{className:`flex items-center gap-1 border-b border-border/50 px-3 py-1.5`,children:[`narrated`,`log`].map(e=>(0,S.jsx)(`button`,{type:`button`,onClick:()=>Ce(e),"aria-pressed":F===e,"data-testid":`job-narration-mode-${e}`,className:`rounded-md px-2 py-0.5 text-[11px] font-medium transition-colors ${F===e?`bg-accent-primary/15 text-accent-primary`:`text-muted-foreground hover:bg-surface`}`,children:t(e===`narrated`?`modeNarrated`:`modeLog`)},e))}),(0,S.jsx)(`div`,{className:`flex-1 overflow-hidden`,children:F===`narrated`?(0,S.jsx)(ae,{events:O,settled:E.status!==`running`&&E.status!==`queued`,durationRange:Se,elapsedMs:E.duration_ms??null,variant:`page`}):$?(0,S.jsx)(p,{events:O,jobStatus:E.status,variant:`page`,onOpenRun:e=>w(`/jobs/${e}`)}):(0,S.jsx)(m,{events:O})})]}):$?(0,S.jsx)(p,{events:O,jobStatus:E.status,variant:`page`,onOpenRun:e=>w(`/jobs/${e}`)}):(0,S.jsx)(m,{events:O})}),Oe&&(0,S.jsx)(le,{jobId:E.id,settleMode:E.interactiveSettleMode,initialAcceptingTurns:E.interactiveAcceptingTurns,pendingInterrupts:E.pendingInterrupts,kind:E.command.startsWith(`loop:`)?`loop-step`:`job`,variant:`page`})]})}export{T as default};
@@ -1,2 +0,0 @@
1
- import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{n as t,t as n}from"./jsx-runtime-zu2_FqZY.js";import{l as r,r as i}from"./useSharedWebSocket-C17TGqT8.js";import{n as a,t as o}from"./createLucideIcon-Bjbbp_KS.js";import{n as s,r as c,s as l}from"./useDesktop-DpxVGfUC.js";import{s as u}from"./provider-capabilities-lePfeT_Y.js";import{t as d}from"./download-CFHyyHcw.js";import{t as f}from"./external-link-D6XxC_Cu.js";import{t as p}from"./loader-circle-DcygOQpC.js";import{t as m}from"./search-Cq_gE3Ik.js";import{t as h}from"./trash-2-BOU3WdZZ.js";import{t as g}from"./triangle-alert-H9T4jAmh.js";import{n as _,t as v}from"./jira-api-CbJvbRYx.js";import{i as y,l as b,o as x,r as S,t as C}from"./dialog-CMgps2MY.js";import{t as w}from"./utils-BC4DHlz2.js";import{t as T}from"./button-D1xExSTF.js";import{Et as E,Gt as D,Tt as O,U as k,an as A,bt as j,on as ee,qt as M,vt as te,yt as ne}from"./index-Czov72g7.js";var N=o(`earth`,[[`path`,{d:`M21.54 15H17a2 2 0 0 0-2 2v4.54`,key:`1djwo0`}],[`path`,{d:`M7 3.34V5a3 3 0 0 0 3 3a2 2 0 0 1 2 2c0 1.1.9 2 2 2a2 2 0 0 0 2-2c0-1.1.9-2 2-2h3.17`,key:`1tzkfa`}],[`path`,{d:`M11 21.95V18a2 2 0 0 0-2-2a2 2 0 0 1-2-2v-1a2 2 0 0 0-2-2H2.05`,key:`14pb5j`}],[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}]]),P=o(`folder-kanban`,[[`path`,{d:`M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z`,key:`1fr9dc`}],[`path`,{d:`M8 10v4`,key:`tgpxqk`}],[`path`,{d:`M12 10v2`,key:`hh53o1`}],[`path`,{d:`M16 10v6`,key:`1d6xys`}]]),re=o(`power`,[[`path`,{d:`M12 2v10`,key:`mnfbl`}],[`path`,{d:`M18.4 6.6a9 9 0 1 1-12.77.04`,key:`obofu9`}]]),F=o(`settings-2`,[[`path`,{d:`M14 17H5`,key:`gfn3mx`}],[`path`,{d:`M19 7h-9`,key:`6i9tg`}],[`circle`,{cx:`17`,cy:`17`,r:`3`,key:`18b49y`}],[`circle`,{cx:`7`,cy:`7`,r:`3`,key:`dfmy0x`}]]),I=e(t(),1),L=n(),ie=[`todo`,`in_progress`,`on_review`,`done`,`cancelled`],ae={todo:`todo`,in_progress:`inProgress`,on_review:`onReview`,done:`done`,cancelled:`cancelled`};function R({onConnected:e,onSkip:t,apiBase:n}){let{t:r}=a(`jira`),[i,o]=(0,I.useState)(1),[s,c]=(0,I.useState)(``),[u,d]=(0,I.useState)(``),[f,p]=(0,I.useState)(``),[m,h]=(0,I.useState)(`idle`),[g,_]=(0,I.useState)(null),[y,b]=(0,I.useState)(`cloud`),[x,S]=(0,I.useState)([]),[C,w]=(0,I.useState)(``),[E,D]=(0,I.useState)(``),[O,A]=(0,I.useState)(``),[j,ee]=(0,I.useState)(!1),[M,te]=(0,I.useState)([]),[ne,N]=(0,I.useState)({}),[P,re]=(0,I.useState)(``),[F,R]=(0,I.useState)(!1),H=()=>({baseUrl:s.trim(),accountEmail:u.trim()||null,token:f});async function U(){h(`testing`);try{let e=await v.test(H(),n);h(`ok`),_(e.displayName),b(e.deployment)}catch(e){h(`error`),l.error(V(e,r))}}async function W(){o(2),ee(!0);try{let{projects:e}=await v.discoverProjects({...H(),query:C.trim()||void 0},n);S(e)}catch(e){l.error(V(e,r))}finally{ee(!1)}}async function oe(){if(o(3),E)try{let{statuses:e}=await v.discoverStatuses({...H(),projectKey:E},n);te(e)}catch{te([])}}async function G(){R(!0);try{let t=Object.fromEntries(Object.entries(ne).filter(([,e])=>e));await v.connect({...H(),jiraProjectKey:E.trim(),statusMap:Object.keys(t).length?t:null,discardStatus:P.trim()||null},n),l.success(r(`status.connected`,{key:E.trim()})),e()}catch(e){l.error(V(e,r))}finally{R(!1)}}return(0,L.jsxs)(`div`,{className:`space-y-4`,"data-testid":`jira-wizard`,children:[(0,L.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,L.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:r(`wizard.step`,{current:i,total:4})}),t&&(0,L.jsx)(`button`,{type:`button`,onClick:t,className:`text-xs text-muted-foreground hover:text-foreground hover:underline`,"data-testid":`jira-later`,children:r(`wizard.later`)})]}),i===1&&(0,L.jsxs)(`div`,{className:`space-y-3`,children:[(0,L.jsx)(`h3`,{className:`text-sm font-semibold`,children:r(`wizard.step1Title`)}),(0,L.jsx)(z,{label:r(`creds.baseUrlLabel`),help:r(`creds.baseUrlHelp`),children:(0,L.jsx)(k,{value:s,onChange:e=>{c(e.target.value),h(`idle`)},placeholder:r(`creds.baseUrlPlaceholder`)})}),(0,L.jsx)(z,{label:r(`creds.emailLabel`),help:r(`creds.emailHelp`),children:(0,L.jsx)(k,{value:u,onChange:e=>{d(e.target.value),h(`idle`)},placeholder:r(`creds.emailPlaceholder`)})}),(0,L.jsx)(z,{label:r(`creds.tokenLabel`),help:r(`creds.tokenHelp`),children:(0,L.jsx)(k,{type:`password`,value:f,onChange:e=>{p(e.target.value),h(`idle`)}})}),(0,L.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,L.jsx)(T,{size:`sm`,variant:`outline`,onClick:U,disabled:!s.trim()||!f.trim()||m===`testing`,children:r(m===`testing`?`creds.testing`:m===`ok`?`creds.retest`:`creds.test`)}),m===`ok`&&(0,L.jsxs)(`span`,{className:`text-xs text-accent-success`,"data-testid":`jira-test-ok`,children:[`✓ `,r(`creds.testOk`,{name:g??``}),` · `,r(y===`cloud`?`review.cloud`:`review.dc`)]})]}),(0,L.jsx)(`div`,{className:`flex justify-end`,children:(0,L.jsx)(T,{size:`sm`,onClick:W,disabled:m!==`ok`,children:r(`wizard.next`)})})]}),i===2&&(0,L.jsxs)(`div`,{className:`space-y-3`,children:[(0,L.jsx)(`h3`,{className:`text-sm font-semibold`,children:r(`wizard.step2Title`)}),(0,L.jsxs)(`div`,{className:`flex gap-2`,children:[(0,L.jsx)(k,{value:C,onChange:e=>w(e.target.value),placeholder:r(`project.searchPlaceholder`)}),(0,L.jsx)(T,{size:`sm`,variant:`outline`,onClick:W,children:r(`project.searchPlaceholder`)})]}),j?(0,L.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:r(`project.loading`)}):x.length===0?(0,L.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:r(`project.none`)}):(0,L.jsx)(`div`,{className:`max-h-48 space-y-1 overflow-y-auto`,"data-testid":`jira-project-list`,children:x.map(e=>(0,L.jsxs)(`button`,{type:`button`,onClick:()=>{D(e.key),A(e.name)},className:`flex w-full items-center justify-between rounded px-2 py-1.5 text-left text-sm hover:bg-muted ${E===e.key?`bg-muted`:``}`,children:[(0,L.jsx)(`span`,{className:`font-medium`,children:e.key}),(0,L.jsx)(`span`,{className:`truncate text-xs text-muted-foreground`,children:e.name})]},e.id))}),(0,L.jsx)(z,{label:r(`project.manualLabel`),children:(0,L.jsx)(k,{value:E,onChange:e=>{D(e.target.value.toUpperCase()),A(``)},placeholder:r(`project.manualPlaceholder`)})}),(0,L.jsxs)(`div`,{className:`flex justify-between`,children:[(0,L.jsx)(T,{size:`sm`,variant:`ghost`,onClick:()=>o(1),children:r(`wizard.back`)}),(0,L.jsx)(T,{size:`sm`,onClick:oe,disabled:!E.trim(),children:r(`wizard.next`)})]})]}),i===3&&(0,L.jsxs)(`div`,{className:`space-y-3`,children:[(0,L.jsx)(`h3`,{className:`text-sm font-semibold`,children:r(`wizard.step3Title`)}),(0,L.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:r(`mapping.intro`)}),ie.map(e=>(0,L.jsx)(z,{label:r(`mapping.${ae[e]}`),children:(0,L.jsxs)(`select`,{value:ne[e]??``,onChange:t=>N(n=>({...n,[e]:t.target.value||void 0})),className:`w-full rounded-md border border-border bg-background px-2 py-1.5 text-sm`,children:[(0,L.jsx)(`option`,{value:``,children:r(`mapping.auto`)}),M.map(e=>(0,L.jsx)(`option`,{value:e.name,children:e.name},e.id))]})},e)),(0,L.jsx)(z,{label:r(`discard.configLabel`),help:r(`discard.configHelp`),children:(0,L.jsxs)(`select`,{value:P,onChange:e=>re(e.target.value),className:`w-full rounded-md border border-border bg-background px-2 py-1.5 text-sm`,"data-testid":`jira-discard-status-select`,children:[(0,L.jsx)(`option`,{value:``,children:r(`discard.configNone`)}),M.map(e=>(0,L.jsx)(`option`,{value:e.name,children:e.name},e.id))]})}),(0,L.jsxs)(`div`,{className:`flex justify-between`,children:[(0,L.jsx)(T,{size:`sm`,variant:`ghost`,onClick:()=>o(2),children:r(`wizard.back`)}),(0,L.jsx)(T,{size:`sm`,onClick:()=>o(4),children:r(`wizard.next`)})]})]}),i===4&&(0,L.jsxs)(`div`,{className:`space-y-3`,children:[(0,L.jsx)(`h3`,{className:`text-sm font-semibold`,children:r(`wizard.step4Title`)}),(0,L.jsxs)(`dl`,{className:`space-y-1 text-sm`,children:[(0,L.jsx)(B,{label:r(`review.site`),value:s}),(0,L.jsx)(B,{label:r(`review.project`),value:O?`${E} — ${O}`:E}),(0,L.jsx)(B,{label:r(`review.account`),value:g??u}),(0,L.jsx)(B,{label:r(`review.type`),value:r(y===`cloud`?`review.cloud`:`review.dc`)}),(0,L.jsx)(B,{label:r(`discard.reviewLabel`),value:P||r(`discard.configNone`)})]}),(0,L.jsxs)(`div`,{className:`flex justify-between`,children:[(0,L.jsx)(T,{size:`sm`,variant:`ghost`,onClick:()=>o(3),children:r(`wizard.back`)}),(0,L.jsx)(T,{size:`sm`,onClick:G,disabled:F,children:r(F?`wizard.connecting`:`wizard.connect`)})]})]})]})}function z({label:e,help:t,children:n}){return(0,L.jsxs)(`label`,{className:`block space-y-1`,children:[(0,L.jsx)(`span`,{className:`text-xs font-medium text-foreground`,children:e}),n,t&&(0,L.jsx)(`span`,{className:`block text-[11px] text-muted-foreground`,children:t})]})}function B({label:e,value:t}){return(0,L.jsxs)(`div`,{className:`flex justify-between gap-4`,children:[(0,L.jsx)(`dt`,{className:`text-muted-foreground`,children:e}),(0,L.jsx)(`dd`,{className:`truncate text-right font-medium`,children:t})]})}function V(e,t){return e instanceof Error?e.message:t(`errors.generic`)}var H=[`todo`,`in_progress`,`on_review`,`done`,`cancelled`],U={todo:`todo`,in_progress:`inProgress`,on_review:`onReview`,done:`done`,cancelled:`cancelled`};function W({state:e,onChanged:t,apiBase:n}){let{t:r}=a(`jira`),i=e.connection,[o,s]=(0,I.useState)(i.enabled),[c,u]=(0,I.useState)(!1),[d,f]=(0,I.useState)(!1),[p,m]=(0,I.useState)([]),[h,g]=(0,I.useState)([]),[_,y]=(0,I.useState)(i.discardStatus??``),[b,x]=(0,I.useState)(i.statusMap??{}),S=e.outbox??{pending:0,inflight:0,done:0,dead:0},C=(0,I.useCallback)(async()=>{try{let{ops:e}=n?await v.listOutbox(`dead`,n):await v.listOutbox(`dead`);m(e)}catch{m([])}},[n]);(0,I.useEffect)(()=>{C()},[C]),(0,I.useEffect)(()=>{let e=!1;return v.listStatuses(n).then(({statuses:t})=>{e||g(t)}).catch(()=>{e||g([])}),()=>{e=!0}},[n]);async function w(e){let i=_;y(e);try{n?await v.patchConnection({discardStatus:e||null},n):await v.patchConnection({discardStatus:e||null}),t()}catch(e){y(i),l.error(G(e,r))}}async function E(e,i){let a=b,o={...b,[e]:i||void 0};x(o);let s=Object.fromEntries(Object.entries(o).filter(([,e])=>e));try{n?await v.patchConnection({statusMap:Object.keys(s).length?s:null},n):await v.patchConnection({statusMap:Object.keys(s).length?s:null}),t()}catch(e){x(a),l.error(G(e,r))}}async function D(){let e=!o;s(e),f(!0);try{n?await v.setEnabled(e,n):await v.setEnabled(e)}catch(t){s(!e),l.error(G(t,r))}finally{f(!1)}}async function O(){u(!0);try{let e=n?await v.syncNow(n):await v.syncNow();l.success(r(`status.syncedToast`,{count:e.upserted}))}catch(e){l.error(G(e,r))}finally{u(!1)}}async function k(){if(window.confirm(r(`status.disconnectConfirm`))){f(!0);try{n?await v.disconnect(n):await v.disconnect(),l.success(r(`status.disconnectedToast`)),t()}catch(e){l.error(G(e,r))}finally{f(!1)}}}async function A(e){try{n?await v.retryOutbox(e,n):await v.retryOutbox(e),await C()}catch(e){l.error(G(e,r))}}let j=S.pending+S.inflight;return(0,L.jsxs)(`div`,{className:`space-y-4`,"data-testid":`jira-connected`,children:[(0,L.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`p`,{className:`text-sm font-medium text-foreground`,children:r(`status.connected`,{key:i.jiraProjectKey})}),(0,L.jsxs)(`p`,{className:`text-xs text-muted-foreground`,children:[i.baseUrl,` · `,i.deployment===`cloud`?r(`review.cloud`):r(`review.dc`)]})]}),(0,L.jsx)(`a`,{href:`${i.baseUrl}/browse/${i.jiraProjectKey}`,target:`_blank`,rel:`noreferrer`,className:`text-xs text-accent-primary hover:underline`,children:r(`badge.openInJira`)})]}),(0,L.jsxs)(`div`,{className:`flex items-center justify-between rounded-md border border-border p-3`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`p`,{className:`text-sm font-medium`,children:r(`status.enabledLabel`)}),(0,L.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:r(o?`status.enabledHelp`:`status.pausedHelp`)})]}),(0,L.jsx)(`button`,{type:`button`,role:`switch`,"aria-checked":o,"aria-label":r(`status.enabledLabel`),disabled:d,onClick:D,className:`relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ${o?`bg-accent-success`:`bg-input`}`,children:(0,L.jsx)(`span`,{className:`inline-block h-3.5 w-3.5 rounded-full bg-background shadow-sm transition-transform ${o?`translate-x-4`:`translate-x-0.5`}`})})]}),(0,L.jsxs)(`div`,{className:`rounded-md border border-border p-3`,"data-testid":`jira-status-map`,children:[(0,L.jsx)(`p`,{className:`text-sm font-medium`,children:r(`wizard.step3Title`)}),(0,L.jsx)(`p`,{className:`mt-0.5 mb-2 text-xs text-muted-foreground`,children:r(`mapping.intro`)}),(0,L.jsx)(`div`,{className:`space-y-2`,children:H.map(e=>(0,L.jsxs)(`label`,{className:`flex items-center justify-between gap-3`,children:[(0,L.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:r(`mapping.${U[e]}`)}),(0,L.jsxs)(`select`,{value:b[e]??``,onChange:t=>void E(e,t.target.value),className:`w-44 rounded-md border border-border bg-background px-2 py-1.5 text-sm`,"data-testid":`jira-statusmap-${e}`,children:[(0,L.jsx)(`option`,{value:``,children:r(`mapping.auto`)}),h.map(e=>(0,L.jsx)(`option`,{value:e.name,children:e.name},e.id)),b[e]&&!h.some(t=>t.name===b[e])&&(0,L.jsx)(`option`,{value:b[e],children:b[e]})]})]},e))})]}),(0,L.jsxs)(`div`,{className:`rounded-md border border-border p-3`,children:[(0,L.jsx)(`p`,{className:`text-sm font-medium`,children:r(`discard.configLabel`)}),(0,L.jsx)(`p`,{className:`mt-0.5 mb-2 text-xs text-muted-foreground`,children:r(`discard.configHelp`)}),(0,L.jsxs)(`select`,{value:_,onChange:e=>void w(e.target.value),className:`w-full rounded-md border border-border bg-background px-2 py-1.5 text-sm`,"data-testid":`jira-discard-status-select`,children:[(0,L.jsx)(`option`,{value:``,children:r(`discard.configNone`)}),h.map(e=>(0,L.jsx)(`option`,{value:e.name,children:e.name},e.id)),_&&!h.some(e=>e.name===_)&&(0,L.jsx)(`option`,{value:_,children:_})]})]}),(0,L.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,L.jsx)(T,{variant:`outline`,size:`sm`,onClick:O,disabled:c,children:r(c?`status.syncing`:`status.syncNow`)}),(0,L.jsx)(T,{variant:`ghost`,size:`sm`,onClick:k,disabled:d,className:`text-destructive`,children:r(`status.disconnect`)})]}),(0,L.jsxs)(`div`,{className:`rounded-md border border-border p-3`,"data-testid":`jira-outbox`,children:[(0,L.jsx)(`p`,{className:`text-sm font-medium`,children:r(`outbox.title`)}),S.dead>0?(0,L.jsx)(`p`,{className:`mt-1 text-xs text-accent-warning`,children:r(`outbox.dead`,{count:S.dead})}):j>0?(0,L.jsx)(`p`,{className:`mt-1 text-xs text-muted-foreground`,children:r(`outbox.pending`,{count:j})}):(0,L.jsx)(`p`,{className:`mt-1 text-xs text-muted-foreground`,children:r(`outbox.allSynced`)}),p.length>0&&(0,L.jsxs)(`div`,{className:`mt-2 space-y-2`,children:[(0,L.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:r(`outbox.deadHelp`)}),p.map(e=>(0,L.jsxs)(`div`,{className:`flex items-center justify-between gap-2 rounded border border-border/60 px-2 py-1`,children:[(0,L.jsxs)(`span`,{className:`truncate text-xs`,children:[oe(e,r),` · `,e.deadReason??e.lastError??``]}),(0,L.jsx)(T,{variant:`outline`,size:`sm`,onClick:()=>A(e.id),children:r(`outbox.retry`)})]},e.id))]})]})]})}function oe(e,t){return e.opType===`transition`?t(`outbox.opTransition`):e.opType===`comment`?t(`outbox.opComment`):t(`outbox.opCreate`)}function G(e,t){return e instanceof Error?e.message:t(`errors.generic`)}var se=new Set([`claude`,`codex`,`kimi`]);function K(e){return{provider:e,label:e===`codex`?`Codex`:`Claude`,active:!1,available:!1,detectedRoute:!1,requests:0,inputTokens:0,inputTokensSaved:0,outputTokens:0,outputTokensSaved:0,outputSavingsPercent:0,outputSavingsMethod:`none`,outputSavingsAllocated:!1}}function q(){return{updatedAt:null,proxyStatsAvailable:!1,durableSavingsAvailable:!1,outputSavingsAvailable:!1,outputSavingsMethod:null,outputConfidence:null,providers:{codex:K(`codex`),claude:K(`claude`)},lastIssue:null}}var J={installed:!1,installSource:null,version:null,executablePath:null,uvPath:null,port:8787,phase:`idle`,activeProviders:{codex:!1,claude:!1},availableProviders:{codex:!1,claude:!1},detectedRoutes:{codex:!1,claude:!1},proxyRunning:!1,proxyPid:null,learning:{enabled:!1,baselineReady:!1,baselineSamples:0,updatedAt:null,lastIssue:null},metrics:q(),lastIssue:null,updatedAt:null};function ce(e){let t=e?.metrics??q();return{...J,...e,activeProviders:{...J.activeProviders,...e?.activeProviders},availableProviders:{...J.availableProviders,...e?.availableProviders},detectedRoutes:{...J.detectedRoutes,...e?.detectedRoutes},learning:{...J.learning,...e?.learning},metrics:{...q(),...t,providers:{codex:{...K(`codex`),...t.providers?.codex},claude:{...K(`claude`),...t.providers?.claude}}}}}async function Y(e){let t=await e.text(),n=t?JSON.parse(t):{};if(!e.ok){let t=n.issue;throw Object.assign(Error(t?.title??n.error??`HTTP ${e.status}`),{payload:n})}return n}function X(e){return`${r}/api/projects/${e}`}function Z(e,t=`en`){if(!Number.isFinite(e))return`0`;try{return Intl.NumberFormat(t,{notation:`compact`,maximumFractionDigits:1}).format(Math.max(0,e))}catch{return Intl.NumberFormat(`en`,{notation:`compact`,maximumFractionDigits:1}).format(Math.max(0,e))}}function Q(e,t,n){return`${Z(e,t)} ${n}`}function le({onWindowOpened:e}={}){let{t}=a(`integrations`),{projects:n}=c(),{registerHandler:o,unregisterHandler:s}=i(),[u,d]=(0,I.useState)(``),[p,h]=(0,I.useState)(`all`),[g,_]=(0,I.useState)(J),[v,T]=(0,I.useState)(!0),[D,k]=(0,I.useState)(null),[M,N]=(0,I.useState)([]),[re,F]=(0,I.useState)(!1),[ie,ae]=(0,I.useState)(!1),[R,z]=(0,I.useState)(null),[B,V]=(0,I.useState)({jira:0,serena:0,projects:0}),H=(0,I.useCallback)(async()=>{try{let e=await fetch(`${r}/api/global-plugins/headroom`).then(e=>Y(e));_(ce(e.state))}finally{T(!1)}},[]);(0,I.useEffect)(()=>{H()},[H]),(0,I.useEffect)(()=>(o(`plugins-page`,e=>{let t=e;t.type===`plugin.headroom_progress`&&(t.line&&N(e=>[...e.slice(-80),t.line]),H())}),()=>s(`plugins-page`)),[o,s,H]),(0,I.useEffect)(()=>{let e=!1;async function t(){let t=await Promise.all(n.map(async e=>{let[t,n]=await Promise.all([fetch(`${X(e.id)}/jira/connection`).then(e=>Y(e)).catch(()=>({connected:!1})),fetch(`${X(e.id)}/plugins`).then(e=>Y(e)).catch(()=>({plugins:[]}))]),r=n.plugins.find(e=>e.name===`serena`);return{jira:+!!t.connected,serena:r&&r.status!==`not-installed`&&r.status!==`orphan`?1:0}}));e||V({jira:t.reduce((e,t)=>e+t.jira,0),serena:t.reduce((e,t)=>e+t.serena,0),projects:n.length})}return t(),()=>{e=!0}},[n]);async function U(e,t){k(e),N([]);try{let e=(await t()).state;e&&_(ce(e))}catch(e){let t=e.payload;t?.state&&_(ce(t.state))}finally{k(null),H()}}let W=(0,I.useMemo)(()=>[{id:`headroom-ai`,title:`Headroom AI`,scope:`global`,icon:E,description:t(`plugins.catalog.headroom.description`),installed:g.installed,status:g.lastIssue?`attention`:g.installed?g.activeProviders.codex||g.activeProviders.claude?g.installSource===`system`?`external-active`:`active`:g.installSource===`system`?`external`:`installed`:`available`,statusLabel:g.lastIssue?t(`plugins.status.attention`):g.installed?g.activeProviders.codex||g.activeProviders.claude?g.installSource===`system`?t(`plugins.status.externalActive`):t(`plugins.status.active`):g.installSource===`system`?t(`plugins.status.external`):t(`plugins.status.installed`):t(`plugins.status.available`),actionKind:g.lastIssue?`review`:g.installed?`manage`:`install`,actionLabel:g.lastIssue?t(`plugins.actions.review`):g.installed?t(`plugins.actions.manage`):t(`plugins.actions.install`),busy:D===`install`,action:()=>{if(g.lastIssue||g.installed){F(!0);return}U(`install`,()=>fetch(`${r}/api/global-plugins/headroom/install`,{method:`POST`}).then(e=>Y(e)))}}],[g,D,t]),oe=(0,I.useMemo)(()=>[{id:`jira`,title:`Jira`,scope:`project`,icon:P,description:t(`plugins.catalog.jira.description`),installed:B.jira>0,status:B.jira>0?`configured`:`available`,statusLabel:B.jira>0?t(`plugins.status.configuredCount`,{count:B.jira}):t(`plugins.status.available`),actionKind:B.jira>0?`manage`:`install`,actionLabel:B.jira>0?t(`plugins.actions.manage`):t(`plugins.actions.install`),action:()=>z(`jira`)},{id:`serena`,title:`Serena`,scope:`project`,icon:te,description:t(`plugins.catalog.serena.description`),installed:B.serena>0,status:B.serena>0?`installed`:`available`,statusLabel:B.serena>0?t(`plugins.status.installedCount`,{count:B.serena}):t(`plugins.status.available`),actionKind:B.serena>0?`manage`:`install`,actionLabel:B.serena>0?t(`plugins.actions.manage`):t(`plugins.actions.install`),action:()=>z(`serena`)}],[B,t]),G=u.trim().toLowerCase(),se=e=>`${e.title} ${e.description} ${e.statusLabel} ${t(`plugins.scope.${e.scope}`)}`.toLowerCase().includes(G),K=[...W,...oe].filter(e=>p!==`all`&&e.scope!==p?!1:se(e)),q=K.filter(e=>e.scope===`global`&&e.installed),Z=K.filter(e=>e.scope===`project`&&e.installed),Q=K.filter(e=>e.scope===`global`&&!e.installed),le=K.filter(e=>e.scope===`project`&&!e.installed),fe=q.length>0||Z.length>0,pe=Q.length>0||le.length>0,me=[g.installed,B.jira>0,B.serena>0].filter(Boolean).length;return(0,L.jsxs)(`div`,{className:`flex h-full flex-col bg-background`,children:[(0,L.jsxs)(`div`,{className:`border-b border-border px-6 py-4`,children:[(0,L.jsxs)(`div`,{className:`flex flex-wrap items-center gap-3`,children:[(0,L.jsx)(`div`,{className:`grid h-9 w-9 place-items-center rounded-lg border border-accent-primary/25 bg-accent-primary/10 text-accent-primary`,children:(0,L.jsx)(O,{className:`h-4 w-4`})}),(0,L.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,L.jsx)(`h1`,{className:`text-lg font-semibold tracking-normal`,children:t(`plugins.page.title`)}),(0,L.jsx)(`p`,{className:`mt-0.5 text-xs text-muted-foreground`,children:t(`plugins.page.subtitle`)})]}),!A()&&(0,L.jsxs)(`button`,{type:`button`,onClick:()=>void ee().then(()=>e?.()).catch(e=>l.error(String(e))),className:`inline-flex items-center gap-1.5 rounded-md border border-border px-3 py-2 text-xs hover:bg-muted`,children:[(0,L.jsx)(f,{className:`h-3.5 w-3.5`}),t(`plugins.page.openWindow`)]}),(0,L.jsxs)(`div`,{className:`mr-12 flex items-center gap-2 rounded-lg border border-border bg-card/60 px-3 py-2 text-xs`,children:[(0,L.jsx)(j,{className:`h-3.5 w-3.5 text-accent-success`}),(0,L.jsx)(`span`,{className:`text-muted-foreground`,children:t(`plugins.page.readyCount`,{count:me})})]})]}),(0,L.jsxs)(`div`,{className:`mt-4 flex flex-col gap-3 md:flex-row md:items-center`,children:[(0,L.jsxs)(`label`,{className:`relative flex-1`,children:[(0,L.jsx)(m,{className:`pointer-events-none absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground`}),(0,L.jsx)(`input`,{value:u,onChange:e=>d(e.target.value),placeholder:t(`plugins.search.placeholder`),className:`h-9 w-full rounded-lg border border-border bg-background pl-9 pr-3 text-sm outline-none transition-colors focus:border-accent-primary/60`})]}),(0,L.jsx)(`div`,{className:`inline-flex h-9 rounded-lg border border-border bg-card p-1`,children:[`all`,`global`,`project`].map(e=>(0,L.jsx)(`button`,{type:`button`,onClick:()=>h(e),className:w(`rounded-md px-3 text-xs capitalize transition-colors`,p===e?`bg-muted text-foreground`:`text-muted-foreground hover:text-foreground`),children:t(`plugins.filters.${e}`)},e))})]})]}),(0,L.jsxs)(`div`,{className:`flex-1 overflow-auto px-6 py-5`,children:[(0,L.jsxs)(`section`,{className:`mb-6`,children:[(0,L.jsxs)(`div`,{className:`mb-3 flex items-center gap-2 text-xs font-medium text-muted-foreground`,children:[(0,L.jsx)(ne,{className:`h-3.5 w-3.5`}),t(`plugins.sections.installed`)]}),fe?(0,L.jsx)(ue,{globalPlugins:q,projectPlugins:Z}):(0,L.jsx)(de,{text:t(G?`plugins.empty.installedSearch`:`plugins.empty.installed`)})]}),(0,L.jsxs)(`section`,{children:[(0,L.jsxs)(`div`,{className:`mb-3 flex items-center gap-2 text-xs font-medium text-muted-foreground`,children:[(0,L.jsx)(O,{className:`h-3.5 w-3.5`}),t(`plugins.sections.available`)]}),pe?(0,L.jsx)(ue,{globalPlugins:Q,projectPlugins:le}):(0,L.jsx)(de,{text:t(G?`plugins.empty.availableSearch`:`plugins.empty.allInstalled`)})]})]}),(0,L.jsx)(C,{open:re,onOpenChange:F,children:(0,L.jsxs)(S,{className:`w-[calc(100vw-2rem)] max-w-xl overflow-x-hidden p-4 sm:p-6`,children:[(0,L.jsxs)(x,{children:[(0,L.jsx)(b,{children:t(`plugins.headroom.title`)}),(0,L.jsx)(y,{children:t(`plugins.headroom.dialogDescription`)})]}),(0,L.jsx)(he,{state:g,loading:v,busy:D,logs:M,onInstall:()=>U(`install`,()=>fetch(`${r}/api/global-plugins/headroom/install`,{method:`POST`}).then(e=>Y(e))),onToggleProvider:(e,t)=>U(`${t?`deactivate`:`activate`}-${e}`,()=>fetch(`${r}/api/global-plugins/headroom/${t?`deactivate`:`activate`}`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({provider:e})}).then(e=>Y(e))),onDeactivateAll:()=>U(`deactivate-all`,async()=>{let e={state:g};for(let t of[`codex`,`claude`])g.activeProviders[t]&&(e=await fetch(`${r}/api/global-plugins/headroom/deactivate`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({provider:t})}).then(e=>Y(e)));return e}),onUninstall:()=>U(`uninstall`,()=>fetch(`${r}/api/global-plugins/headroom/uninstall`,{method:`POST`}).then(e=>Y(e))),onSetPort:e=>U(`port`,()=>fetch(`${r}/api/global-plugins/headroom/port`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({port:e})}).then(e=>Y(e))),onOpenDiagnostics:()=>ae(!0)})]})}),(0,L.jsx)(xe,{plugin:R,projects:n,onClose:()=>z(null)}),(0,L.jsx)(we,{open:ie,onClose:()=>ae(!1)})]})}function ue({globalPlugins:e,projectPlugins:t}){let{t:n}=a(`integrations`);return(0,L.jsxs)(`div`,{className:w(`grid grid-cols-1 gap-4`,e.length>0&&t.length>0&&`xl:grid-cols-2`),children:[e.length>0&&(0,L.jsxs)(`section`,{children:[(0,L.jsx)(pe,{title:n(`plugins.groups.global.title`),detail:n(`plugins.groups.global.detail`),scope:`global`}),(0,L.jsx)(`div`,{className:`grid grid-cols-[repeat(auto-fill,minmax(260px,320px))] justify-start gap-3`,children:e.map(e=>(0,L.jsx)(me,{plugin:e},e.id))})]}),t.length>0&&(0,L.jsxs)(`section`,{children:[(0,L.jsx)(pe,{title:n(`plugins.groups.project.title`),detail:n(`plugins.groups.project.detail`),scope:`project`}),(0,L.jsx)(`div`,{className:`grid grid-cols-[repeat(auto-fill,minmax(260px,320px))] justify-start gap-3`,children:t.map(e=>(0,L.jsx)(me,{plugin:e},e.id))})]})]})}function de({text:e}){return(0,L.jsx)(`div`,{className:`flex h-[74px] max-w-[320px] items-center rounded-lg border border-dashed border-border bg-card/35 px-3 text-xs text-muted-foreground`,children:e})}function fe({scope:e}){let{t}=a(`integrations`);return(0,L.jsx)(`span`,{className:w(`rounded px-1.5 py-0.5 text-[10px] uppercase tracking-wide`,e===`global`?`bg-accent-primary/10 text-accent-primary`:`bg-accent-info/10 text-accent-info`),children:t(`plugins.scope.${e}`)})}function pe({title:e,detail:t,scope:n}){return(0,L.jsxs)(`div`,{className:`mb-2 flex items-center gap-2`,children:[(0,L.jsx)(`div`,{className:w(`h-px flex-1`,n===`global`?`bg-accent-primary/30`:`bg-accent-info/30`)}),(0,L.jsxs)(`div`,{className:`flex items-center gap-2 rounded-md border border-border bg-card/70 px-2 py-1`,children:[(0,L.jsx)(fe,{scope:n}),(0,L.jsx)(`span`,{className:`text-xs font-medium`,children:e}),(0,L.jsx)(`span`,{className:`hidden text-[11px] text-muted-foreground sm:inline`,children:t})]}),(0,L.jsx)(`div`,{className:w(`h-px flex-1`,n===`global`?`bg-accent-primary/30`:`bg-accent-info/30`)})]})}function me({plugin:e}){let{t}=a(`integrations`),n=e.icon,r=e.scope===`global`,i=e.status===`attention`,o=e.busy?p:e.actionKind===`install`?d:e.actionKind===`review`?g:F,s=w(`shrink-0 rounded border px-1.5 py-0.5 text-[10px] leading-none`,i?`border-accent-warning/30 bg-accent-warning/10 text-accent-warning`:e.installed?`border-accent-success/25 bg-accent-success/10 text-accent-success`:`border-border bg-muted text-muted-foreground`);return(0,L.jsxs)(`article`,{className:`grid h-[88px] w-full grid-cols-[38px_minmax(0,1fr)_auto] items-center gap-3 rounded-lg border border-border bg-card/70 px-3 transition-colors hover:border-accent-primary/35 hover:bg-card`,children:[(0,L.jsx)(`div`,{className:w(`grid h-[38px] w-[38px] shrink-0 place-items-center rounded-lg border`,r?`border-accent-primary/25 bg-accent-primary/10 text-accent-primary`:`border-accent-info/25 bg-accent-info/10 text-accent-info`),children:(0,L.jsx)(n,{className:`h-4 w-4`})}),(0,L.jsxs)(`div`,{className:`min-w-0`,children:[(0,L.jsx)(`h2`,{className:`truncate text-sm font-semibold leading-5`,children:e.title}),(0,L.jsx)(`p`,{className:`mt-1 truncate text-[11px] leading-4 text-muted-foreground`,children:e.description}),(0,L.jsxs)(`div`,{className:`mt-1.5 flex items-center gap-1.5`,children:[(0,L.jsx)(fe,{scope:e.scope}),(0,L.jsx)(`span`,{className:s,children:e.statusLabel})]})]}),(0,L.jsxs)(`button`,{type:`button`,disabled:e.busy,onClick:e.action,className:`flex h-7 shrink-0 items-center gap-1.5 rounded-md border border-border bg-background px-2.5 text-xs font-medium transition-colors hover:bg-muted disabled:opacity-50`,children:[(0,L.jsx)(o,{className:w(`h-3.5 w-3.5`,e.busy&&`animate-spin`)}),e.busy?t(`plugins.actions.installing`):e.actionLabel]})]})}function he({state:e,loading:t,busy:n,logs:r,onInstall:i,onToggleProvider:o,onDeactivateAll:s,onUninstall:c,onSetPort:l,onOpenDiagnostics:u}){let{t:f}=a(`integrations`),[m,g]=(0,I.useState)(String(e.port)),[v,y]=(0,I.useState)(!1);(0,I.useEffect)(()=>g(String(e.port)),[e.port]),(0,I.useEffect)(()=>{e.installed||y(!1)},[e.installed]);let b=e.activeProviders.codex||e.activeProviders.claude,x=e.installSource===`system`;return(0,L.jsxs)(`aside`,{className:`w-full min-w-0 overflow-hidden rounded-lg border border-border bg-card/80 p-4`,children:[(0,L.jsxs)(`div`,{className:`mb-4 flex items-start gap-3`,children:[(0,L.jsx)(`div`,{className:`grid h-10 w-10 place-items-center rounded-lg border border-accent-primary/25 bg-accent-primary/10 text-accent-primary`,children:(0,L.jsx)(E,{className:`h-4 w-4`})}),(0,L.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,L.jsx)(`h2`,{className:`text-sm font-semibold`,children:f(`plugins.headroom.title`)}),(0,L.jsx)(`p`,{className:`mt-1 text-xs leading-relaxed text-muted-foreground`,children:f(`plugins.headroom.panelDescription`)})]})]}),(0,L.jsxs)(`div`,{className:`mb-4 grid grid-cols-3 gap-2 text-xs`,children:[(0,L.jsx)(ge,{label:f(`plugins.headroom.metrics.install`),value:t?f(`plugins.headroom.metrics.checking`):e.installed?f(`plugins.headroom.metrics.ready`):f(`plugins.headroom.metrics.missing`),good:e.installed}),(0,L.jsx)(ge,{label:f(`plugins.headroom.metrics.source`),value:e.installSource===`system`?f(`plugins.headroom.metrics.system`):e.installSource===`managed`?f(`plugins.headroom.metrics.specrails`):f(`plugins.headroom.metrics.none`),good:e.installed}),(0,L.jsx)(ge,{label:f(`plugins.headroom.metrics.proxy`),value:e.proxyRunning?`:${e.port}`:f(b?`plugins.headroom.metrics.starting`:`plugins.headroom.metrics.off`),good:e.proxyRunning||!b})]}),e.lastIssue&&(0,L.jsx)(_e,{issue:e.lastIssue}),e.installed?(0,L.jsxs)(`div`,{className:`mb-4 space-y-2`,children:[(0,L.jsx)(be,{provider:`codex`,active:e.activeProviders.codex,available:e.availableProviders.codex,detectedRoute:e.detectedRoutes.codex,busy:n===`activate-codex`||n===`deactivate-codex`,onClick:()=>o(`codex`,e.activeProviders.codex)}),(0,L.jsx)(be,{provider:`claude`,active:e.activeProviders.claude,available:e.availableProviders.claude,detectedRoute:e.detectedRoutes.claude,busy:n===`activate-claude`||n===`deactivate-claude`,onClick:()=>o(`claude`,e.activeProviders.claude)}),b&&(0,L.jsxs)(`button`,{type:`button`,disabled:!!n,onClick:s,className:`flex h-8 w-full items-center justify-center gap-2 rounded-lg border border-border bg-background/45 text-xs hover:bg-muted/50 disabled:opacity-50`,children:[n===`deactivate-all`?(0,L.jsx)(p,{className:`h-3.5 w-3.5 animate-spin`}):(0,L.jsx)(re,{className:`h-3.5 w-3.5`}),f(`plugins.headroom.actions.disableAllRoutes`)]})]}):(0,L.jsxs)(`button`,{type:`button`,disabled:!!n,onClick:i,className:`mb-3 flex h-9 w-full items-center justify-center gap-2 rounded-lg bg-accent-primary px-3 text-xs font-medium text-white disabled:opacity-50`,children:[n===`install`?(0,L.jsx)(p,{className:`h-3.5 w-3.5 animate-spin`}):(0,L.jsx)(d,{className:`h-3.5 w-3.5`}),f(`plugins.headroom.actions.installWithUv`)]}),e.installed&&(0,L.jsx)(ve,{state:e}),(0,L.jsxs)(`div`,{className:`mb-4 rounded-lg border border-border bg-background/45 p-3`,children:[(0,L.jsx)(`label`,{className:`mb-1 block text-[11px] font-medium text-muted-foreground`,children:f(`plugins.headroom.port.label`)}),(0,L.jsxs)(`div`,{className:`flex gap-2`,children:[(0,L.jsx)(`input`,{value:m,onChange:e=>g(e.target.value),className:`h-8 min-w-0 flex-1 rounded-md border border-border bg-background px-2 text-sm`}),(0,L.jsx)(`button`,{type:`button`,disabled:!!n||Number(m)===e.port,onClick:()=>l(Number(m)),className:`rounded-md border border-border px-2 text-xs hover:bg-muted disabled:opacity-50`,children:f(`plugins.headroom.port.apply`)})]})]}),r.length>0&&(0,L.jsx)(`pre`,{className:`mb-3 max-h-32 overflow-y-auto overflow-x-hidden whitespace-pre-wrap break-words rounded-lg border border-border bg-background/60 p-2 text-[10px] text-muted-foreground`,children:r.join(`
2
- `)}),(0,L.jsxs)(`button`,{type:`button`,onClick:u,className:`mb-2 flex h-8 w-full items-center justify-center gap-2 rounded-lg border border-border text-xs hover:bg-muted/50`,children:[(0,L.jsx)(_,{className:`h-3.5 w-3.5`}),f(`plugins.headroom.actions.viewDiagnostics`)]}),e.installed&&(0,L.jsx)(`div`,{className:`rounded-lg border border-destructive/25 bg-destructive/5 p-3`,children:v?(0,L.jsxs)(`div`,{className:`space-y-2`,children:[(0,L.jsx)(`p`,{className:`text-[11px] leading-relaxed text-muted-foreground`,children:f(x?`plugins.headroom.confirmDetach`:`plugins.headroom.confirmUninstall`)}),(0,L.jsxs)(`div`,{className:`grid grid-cols-2 gap-2`,children:[(0,L.jsx)(`button`,{type:`button`,disabled:!!n,onClick:()=>y(!1),className:`h-8 rounded-md border border-border text-xs hover:bg-muted/50 disabled:opacity-50`,children:f(`plugins.actions.cancel`)}),(0,L.jsxs)(`button`,{type:`button`,disabled:!!n,onClick:c,className:`flex h-8 items-center justify-center gap-2 rounded-md bg-destructive px-2 text-xs font-medium text-destructive-foreground disabled:opacity-50`,children:[n===`uninstall`?(0,L.jsx)(p,{className:`h-3.5 w-3.5 animate-spin`}):(0,L.jsx)(h,{className:`h-3.5 w-3.5`}),f(x?`plugins.headroom.actions.detach`:`plugins.headroom.actions.uninstall`)]})]})]}):(0,L.jsxs)(`button`,{type:`button`,disabled:!!n,onClick:()=>y(!0),className:`flex h-8 w-full items-center justify-center gap-2 rounded-md text-xs font-medium text-destructive hover:bg-destructive/10 disabled:opacity-50`,children:[(0,L.jsx)(h,{className:`h-3.5 w-3.5`}),f(x?`plugins.headroom.actions.detachHeadroom`:`plugins.headroom.actions.uninstallHeadroom`)]})})]})}function ge({label:e,value:t,good:n}){return(0,L.jsxs)(`div`,{className:`rounded-lg border border-border bg-background/45 p-2`,children:[(0,L.jsx)(`div`,{className:`text-[10px] text-muted-foreground`,children:e}),(0,L.jsx)(`div`,{className:w(`mt-0.5 font-medium`,n?`text-foreground`:`text-accent-warning`),children:t})]})}function _e({issue:e}){let{t}=a(`integrations`),n=`plugins.headroom.issues.${e.code}`,r=t(`${n}.title`,{defaultValue:e.title}),i=t(`${n}.guidance`,{defaultValue:e.guidance});return(0,L.jsx)(`div`,{className:`mb-3 rounded-lg border border-accent-warning/35 bg-accent-warning/10 p-3`,children:(0,L.jsxs)(`div`,{className:`flex items-start gap-2`,children:[(0,L.jsx)(g,{className:`mt-0.5 h-3.5 w-3.5 shrink-0 text-accent-warning`}),(0,L.jsxs)(`div`,{className:`min-w-0`,children:[(0,L.jsx)(`div`,{className:`text-xs font-medium`,children:r}),(0,L.jsx)(`p`,{className:`mt-1 text-[11px] leading-relaxed text-muted-foreground`,children:i}),(0,L.jsx)(`div`,{className:`mt-1 text-[10px] text-muted-foreground`,children:t(`plugins.headroom.issueCode`,{code:e.code})})]})]})})}function ve({state:e}){let{t,i18n:n}=a(`integrations`),r=e.metrics,i=r.outputSavingsAvailable,o=[`codex`,`claude`].map(e=>({provider:e,metric:r.providers[e]})),s=t(`plugins.headroom.savings.tokens`),c=o.reduce((e,{metric:t})=>({inputTokens:e.inputTokens+t.inputTokens,inputTokensSaved:e.inputTokensSaved+t.inputTokensSaved,outputTokens:e.outputTokens+t.outputTokens,outputTokensSaved:e.outputTokensSaved+t.outputTokensSaved}),{inputTokens:0,inputTokensSaved:0,outputTokens:0,outputTokensSaved:0}),l=o.some(({metric:e})=>e.inputTokensSaved>0),u=o.some(({metric:e})=>e.active),d=t(i?`plugins.headroom.savings.statusInputOutput`:l?`plugins.headroom.savings.statusInputOnly`:u?`plugins.headroom.savings.statusRouting`:`plugins.headroom.savings.statusInactive`),f=i||l;return(0,L.jsxs)(`div`,{className:`mb-4 rounded-lg border border-border bg-background/45 p-3`,children:[(0,L.jsxs)(`div`,{className:`mb-2 flex items-center justify-between gap-2`,children:[(0,L.jsxs)(`div`,{className:`flex min-w-0 items-center gap-2`,children:[(0,L.jsx)(M,{className:`h-3.5 w-3.5 text-accent-primary`}),(0,L.jsx)(`div`,{className:`truncate text-xs font-medium`,children:t(`plugins.headroom.savings.title`)})]}),(0,L.jsx)(`span`,{className:w(`shrink-0 rounded border px-1.5 py-0.5 text-[10px]`,f?`border-accent-success/25 bg-accent-success/10 text-accent-success`:u?`border-accent-primary/25 bg-accent-primary/10 text-accent-primary`:`border-border bg-muted text-muted-foreground`),children:d})]}),(0,L.jsxs)(`div`,{className:`mb-2 rounded-md bg-muted/35 px-2.5 py-2`,children:[(0,L.jsx)(`div`,{className:`mb-1.5 text-[10px] font-medium uppercase text-muted-foreground`,children:t(`plugins.headroom.savings.totals`)}),(0,L.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,L.jsx)($,{label:t(`plugins.headroom.savings.input`),processed:c.inputTokens>0?Q(c.inputTokens,n.language,s):t(`plugins.headroom.savings.noData`),saved:Q(c.inputTokensSaved,n.language,s)}),(0,L.jsx)($,{align:`right`,label:t(`plugins.headroom.savings.output`),processed:c.outputTokens>0?Q(c.outputTokens,n.language,s):t(`plugins.headroom.savings.noData`),saved:i?Q(c.outputTokensSaved,n.language,s):t(`plugins.headroom.savings.noData`)})]})]}),(0,L.jsx)(`div`,{className:`grid gap-2`,children:o.map(({provider:e,metric:t})=>(0,L.jsx)(ye,{metric:t,outputAvailable:r.outputSavingsAvailable},e))}),(0,L.jsx)(`div`,{className:`mt-2 flex items-center justify-end gap-2 text-[10px] text-muted-foreground`,children:r.updatedAt?t(`plugins.headroom.savings.updatedLive`):t(`plugins.headroom.savings.baselineSamples`,{count:e.learning.baselineSamples})})]})}function $({label:e,processed:t,saved:n,detail:r,align:i=`left`}){let{t:o}=a(`integrations`);return(0,L.jsxs)(`div`,{className:w(`min-w-0`,i===`right`&&`text-right`),children:[(0,L.jsx)(`div`,{className:`text-[10px] font-medium uppercase text-muted-foreground`,children:e}),(0,L.jsxs)(`div`,{className:`mt-1 space-y-0.5 text-[11px]`,children:[(0,L.jsxs)(`div`,{className:`flex min-w-0 items-baseline justify-between gap-2`,children:[(0,L.jsx)(`span`,{className:`shrink-0 text-muted-foreground`,children:o(`plugins.headroom.savings.processed`)}),(0,L.jsx)(`span`,{className:`truncate font-semibold tabular-nums text-foreground`,children:t})]}),(0,L.jsxs)(`div`,{className:`flex min-w-0 items-baseline justify-between gap-2`,children:[(0,L.jsx)(`span`,{className:`shrink-0 text-muted-foreground`,children:o(`plugins.headroom.savings.saved`)}),(0,L.jsx)(`span`,{className:`truncate font-semibold tabular-nums text-foreground`,children:n})]})]}),r&&(0,L.jsx)(`div`,{className:`mt-1 truncate text-[10px] text-muted-foreground`,children:r})]})}function ye({metric:e,outputAvailable:t}){let{t:n,i18n:r}=a(`integrations`),i=!e.available,o=i||!e.active,s=n(`plugins.headroom.savings.tokens`),c=e.inputTokens>0?Q(e.inputTokens,r.language,s):n(`plugins.headroom.savings.noData`),l=Q(e.inputTokensSaved,r.language,s),u=e.outputTokens>0?Q(e.outputTokens,r.language,s):n(`plugins.headroom.savings.noData`),d=t?Q(e.outputTokensSaved,r.language,s):e.active?n(`plugins.headroom.savings.noData`):n(`plugins.headroom.savings.off`),f=e.requests>0?n(`plugins.headroom.savings.requestCount`,{count:Z(e.requests,r.language)}):i?n(`plugins.headroom.provider.noProjectUsesProvider`):e.active?n(`plugins.headroom.savings.inputSavingsActive`):n(`plugins.headroom.savings.routeDisabled`);return(0,L.jsxs)(`div`,{className:w(`rounded-md border border-border bg-card/45 px-2.5 py-2`,o&&`opacity-70`),children:[(0,L.jsxs)(`div`,{className:`flex min-w-0 items-center gap-1.5`,children:[(0,L.jsx)(`span`,{className:`text-xs font-medium`,children:e.label}),e.detectedRoute&&(0,L.jsx)(`span`,{className:`rounded bg-accent-primary/10 px-1.5 py-0.5 text-[9px] uppercase text-accent-primary`,children:n(`plugins.headroom.savings.detected`)})]}),(0,L.jsx)(`div`,{className:`mt-0.5 truncate text-[10px] text-muted-foreground`,children:f}),(0,L.jsxs)(`div`,{className:`mt-2 grid grid-cols-2 gap-3`,children:[(0,L.jsx)($,{label:n(`plugins.headroom.savings.input`),processed:c,saved:l}),(0,L.jsx)($,{align:`right`,label:n(`plugins.headroom.savings.output`),processed:u,saved:d})]})]})}function be({provider:e,active:t,available:n,detectedRoute:r,busy:i,onClick:o}){let{t:s}=a(`integrations`),c=s(n?t?`plugins.headroom.provider.routedThroughHeadroom`:r?`plugins.headroom.provider.detectedExternalRoute`:`plugins.headroom.provider.notRouted`:`plugins.headroom.provider.noProjectUsesProvider`);return(0,L.jsxs)(`button`,{type:`button`,onClick:o,disabled:i||!n,className:`flex w-full items-center gap-3 rounded-lg border border-border bg-background/45 p-3 text-left hover:bg-muted/35 disabled:opacity-50`,children:[(0,L.jsx)(`div`,{className:w(`grid h-8 w-8 place-items-center rounded-md`,t?`bg-accent-success/15 text-accent-success`:`bg-muted text-muted-foreground`),children:i?(0,L.jsx)(p,{className:`h-3.5 w-3.5 animate-spin`}):(0,L.jsx)(N,{className:`h-3.5 w-3.5`})}),(0,L.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,L.jsx)(`div`,{className:`text-xs font-medium capitalize`,children:e}),(0,L.jsx)(`div`,{className:`text-[11px] text-muted-foreground`,children:c})]}),(0,L.jsx)(`span`,{className:w(`h-5 w-9 rounded-full p-0.5 transition-colors`,t?`bg-accent-success`:`bg-muted`),children:(0,L.jsx)(`span`,{className:w(`block h-4 w-4 rounded-full bg-white shadow transition-transform`,t&&`translate-x-4`)})})]})}function xe({plugin:e,projects:t,onClose:n}){let{t:r}=a(`integrations`),[i,o]=(0,I.useState)(``);(0,I.useEffect)(()=>o(``),[e]);let c=t.find(e=>e.id===i)??null;return(0,L.jsx)(C,{open:!!e,onOpenChange:e=>{e||n()},children:(0,L.jsxs)(S,{className:`w-[calc(100vw-2rem)] max-w-3xl overflow-x-hidden p-4 sm:p-6`,children:[(0,L.jsxs)(x,{children:[(0,L.jsx)(b,{children:r(e===`jira`?`plugins.projectWizard.configureJira`:`plugins.projectWizard.installSerena`)}),(0,L.jsx)(y,{children:r(`plugins.projectWizard.description`)})]}),(0,L.jsxs)(`div`,{className:`grid gap-4 md:grid-cols-[260px_minmax(0,1fr)]`,children:[(0,L.jsxs)(`div`,{className:`rounded-lg border border-border bg-card/70 p-3`,children:[(0,L.jsx)(`div`,{className:`mb-2 text-xs font-medium text-muted-foreground`,children:r(`plugins.projectWizard.chooseProject`)}),(0,L.jsx)(`div`,{className:`max-h-[52vh] space-y-1 overflow-auto`,children:t.map(t=>{let n=e===`serena`&&!s(t).some(e=>se.has(e));return(0,L.jsxs)(`button`,{type:`button`,disabled:n,onClick:()=>o(t.id),className:w(`w-full rounded-md px-2 py-2 text-left transition-colors`,t.id===i?`bg-muted text-foreground`:`text-muted-foreground hover:bg-muted/45 hover:text-foreground`,n&&`cursor-not-allowed opacity-45`),children:[(0,L.jsx)(`div`,{className:`truncate text-xs font-medium`,children:t.name}),(0,L.jsx)(`div`,{className:`truncate text-[10px]`,children:n?r(`plugins.projectWizard.requiresSupportedProvider`,{defaultValue:`Requires Claude or Codex`}):t.path})]},t.id)})})]}),(0,L.jsx)(`div`,{className:`min-h-[360px] rounded-lg border border-border bg-card/70 p-4`,children:c?e===`jira`?(0,L.jsx)(Se,{project:c,onDone:n}):(0,L.jsx)(Ce,{project:c,onDone:n}):(0,L.jsx)(`div`,{className:`flex h-full items-center justify-center text-sm text-muted-foreground`,children:r(`plugins.projectWizard.selectProjectToContinue`)})})]})]})})}function Se({project:e,onDone:t}){let{t:n}=a(`integrations`),r=X(e.id),[i,o]=(0,I.useState)(null),[s,c]=(0,I.useState)(!0),[l,u]=(0,I.useState)(null),d=(0,I.useCallback)(async()=>{c(!0),u(null);try{let e=await fetch(`${r}/jira/connection`).then(e=>Y(e));o(e)}catch(e){o({connected:!1}),u(e.message)}finally{c(!1)}},[r]);return(0,I.useEffect)(()=>{d()},[d]),s?(0,L.jsxs)(`div`,{className:`flex h-full items-center justify-center text-sm text-muted-foreground`,children:[(0,L.jsx)(p,{className:`mr-2 h-4 w-4 animate-spin`}),n(`plugins.jira.loading`,{defaultValue:`Loading Jira...`})]}):i?.connected&&i.connection?(0,L.jsxs)(`div`,{className:`space-y-4`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`h3`,{className:`text-sm font-semibold`,children:n(`plugins.jira.titleForProject`,{name:e.name,defaultValue:`Jira for ${e.name}`})}),(0,L.jsx)(`p`,{className:`mt-1 text-xs text-muted-foreground`,children:n(`plugins.jira.configuredDescription`,{defaultValue:`Manage the Jira project, status mapping, discard status, sync, and connection state.`})})]}),(0,L.jsx)(W,{state:i,apiBase:r,onChanged:()=>{d()}})]}):(0,L.jsxs)(`div`,{className:`space-y-4`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`h3`,{className:`text-sm font-semibold`,children:n(`plugins.jira.titleForProject`,{name:e.name,defaultValue:`Jira for ${e.name}`})}),(0,L.jsx)(`p`,{className:`mt-1 text-xs text-muted-foreground`,children:l?n(`plugins.jira.loadFailed`,{message:l,defaultValue:`Could not load the current Jira configuration: ${l}`}):n(`plugins.jira.notConfiguredDescription`,{defaultValue:`Connect this project to Jira.`})})]}),(0,L.jsx)(R,{apiBase:r,onConnected:()=>{d()},onSkip:t})]})}function Ce({project:e,onDone:t}){let{t:n}=a(`integrations`),r=(0,I.useMemo)(()=>s(e).filter(e=>se.has(e)),[e]),i=r.includes(e.provider)?e.provider:r[0]??e.provider,[o,c]=(0,I.useState)(i),[l,f]=(0,I.useState)(null),[m,h]=(0,I.useState)(null),[_,v]=(0,I.useState)(!1),[y,b]=(0,I.useState)(null),x=(0,I.useRef)(0);(0,I.useEffect)(()=>{c(i),f(null),h(null)},[e.id,i]);let S=(0,I.useCallback)(async()=>{let t=++x.current;b(null);try{let n=`?provider=${encodeURIComponent(o)}`,r=await fetch(`${X(e.id)}/plugins${n}`).then(e=>Y(e));if(t!==x.current)return;let i=r.plugins.find(e=>e.name===`serena`)??null;if(f(i),h(null),i?.status===`not-installed`){let r=await fetch(`${X(e.id)}/plugins/serena/preview-install${n}`).then(e=>Y(e));t===x.current&&h(r)}}catch(e){t===x.current&&b(e.message)}},[e.id,o]);(0,I.useEffect)(()=>{S()},[S]);async function C(){v(!0),b(null);try{await fetch(`${X(e.id)}/plugins/serena/install`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({provider:o})}).then(e=>Y(e)),await S(),t()}catch(e){b(e.message)}finally{v(!1)}}let w=(m?.requirements??[]).every(e=>e.installed&&e.executable&&e.meetsMinimum),T=l?n(`plugins.projectStatus.${l.status}`,{defaultValue:l.status}):``,E=l?.name===`serena`?n(`plugins.catalog.serena.description`):l?.description;return!l&&!y?(0,L.jsxs)(`div`,{className:`flex h-full items-center justify-center text-sm text-muted-foreground`,children:[(0,L.jsx)(p,{className:`mr-2 h-4 w-4 animate-spin`}),` `,n(`plugins.serena.loading`)]}):(0,L.jsxs)(`div`,{className:`space-y-4`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`h3`,{className:`text-sm font-semibold`,children:n(`plugins.serena.titleForProject`,{name:e.name})}),(0,L.jsx)(`p`,{className:`mt-1 text-xs text-muted-foreground`,children:n(`plugins.serena.description`)})]}),(0,L.jsxs)(`label`,{className:`block space-y-1`,children:[(0,L.jsx)(`span`,{className:`text-xs font-medium`,children:n(`plugins.serena.provider`,{defaultValue:`AI provider`})}),(0,L.jsx)(`select`,{"aria-label":n(`plugins.serena.provider`,{defaultValue:`AI provider`}),value:o,disabled:_,onChange:e=>{f(null),h(null),c(e.target.value)},className:`w-full rounded-md border border-border bg-background px-2 py-1.5 text-xs`,children:r.map(e=>(0,L.jsx)(`option`,{value:e,children:u(e)},e))})]}),l&&(0,L.jsxs)(`div`,{className:`rounded-lg border border-border bg-background/45 p-3`,children:[(0,L.jsx)(`div`,{className:`text-xs font-medium`,children:n(`plugins.serena.status`,{status:T})}),E&&(0,L.jsx)(`p`,{className:`mt-1 text-xs text-muted-foreground`,children:E})]}),m&&(0,L.jsxs)(`div`,{className:`space-y-3`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`div`,{className:`mb-1 text-xs font-medium`,children:n(`plugins.serena.files`)}),(0,L.jsx)(`ul`,{className:`space-y-1 text-xs font-mono`,children:m.files.map(e=>(0,L.jsxs)(`li`,{className:e.op===`create`?`text-accent-success`:`text-accent-info`,children:[e.op===`create`?`+ `:`~ `,e.path]},e.path))})]}),(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`div`,{className:`mb-1 text-xs font-medium`,children:n(`plugins.serena.prerequisites`)}),(0,L.jsx)(`ul`,{className:`space-y-1 text-xs`,children:m.requirements.map(e=>(0,L.jsxs)(`li`,{className:`flex items-center gap-2`,children:[e.installed&&e.executable&&e.meetsMinimum?(0,L.jsx)(D,{className:`h-3 w-3 text-accent-success`}):(0,L.jsx)(g,{className:`h-3 w-3 text-accent-warning`}),(0,L.jsxs)(`span`,{children:[e.name,e.minVersion?` >= ${e.minVersion}`:``]}),e.version&&(0,L.jsxs)(`span`,{className:`text-muted-foreground`,children:[`(`,e.version,`)`]})]},e.name))})]})]}),y&&(0,L.jsx)(`div`,{className:`rounded-lg border border-destructive/30 bg-destructive/10 p-3 text-xs text-destructive`,children:y}),(0,L.jsxs)(`div`,{className:`flex justify-end gap-2`,children:[(0,L.jsx)(`button`,{type:`button`,onClick:t,className:`rounded-md border border-border px-3 py-1.5 text-xs`,children:n(`plugins.actions.cancel`)}),(0,L.jsxs)(`button`,{type:`button`,disabled:_||l?.status!==`not-installed`||!w,onClick:C,className:`flex items-center gap-2 rounded-md bg-accent-primary px-3 py-1.5 text-xs text-white disabled:opacity-50`,children:[_?(0,L.jsx)(p,{className:`h-3.5 w-3.5 animate-spin`}):(0,L.jsx)(d,{className:`h-3.5 w-3.5`}),n(`plugins.actions.install`)]})]})]})}function we({open:e,onClose:t}){let{t:n}=a(`integrations`),[i,o]=(0,I.useState)(null);return(0,I.useEffect)(()=>{e&&fetch(`${r}/api/global-plugins/headroom/diagnostics`).then(e=>Y(e)).then(o).catch(e=>o({error:e.message}))},[e]),(0,L.jsx)(C,{open:e,onOpenChange:e=>{e||t()},children:(0,L.jsxs)(S,{className:`max-w-3xl`,children:[(0,L.jsxs)(x,{children:[(0,L.jsxs)(b,{className:`flex items-center gap-2`,children:[(0,L.jsx)(F,{className:`h-4 w-4`}),` `,n(`plugins.diagnostics.title`)]}),(0,L.jsx)(y,{children:n(`plugins.diagnostics.description`)})]}),(0,L.jsx)(`pre`,{className:`max-h-[62vh] overflow-y-auto overflow-x-hidden whitespace-pre-wrap break-words rounded-lg border border-border bg-background p-3 text-xs`,children:JSON.stringify(i,null,2)})]})})}export{le as default};