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.
- package/client/dist/assets/{ActivityFeedPage-vAY9C71W.js → ActivityFeedPage-BGEayjTY.js} +1 -1
- package/client/dist/assets/{AgentBrowserCapture-CpwjGXQm.js → AgentBrowserCapture-Qvg4y5Tr.js} +1 -1
- package/client/dist/assets/{AgentModeAnalyticsPane-BHeoJVxQ.js → AgentModeAnalyticsPane-CR7aFeLJ.js} +2 -2
- package/client/dist/assets/{AgentModeCodePane-B0Nf3YkN.js → AgentModeCodePane-gAd88Gng.js} +2 -2
- package/client/dist/assets/{AgentModeJobsPane-CpbIItY_.js → AgentModeJobsPane-EjCDuTow.js} +1 -1
- package/client/dist/assets/{AgentsPage-BVJAFXn3.js → AgentsPage-CRHHcp1V.js} +1 -1
- package/client/dist/assets/{AnalyticsPage-DDWees-K.js → AnalyticsPage-a0b1fNEl.js} +1 -1
- package/client/dist/assets/{CodePage-CzV5tkvS.js → CodePage-BmV1m7l3.js} +1 -1
- package/client/dist/assets/{DesktopAnalyticsPage-DfRHNK7i.js → DesktopAnalyticsPage-B87WmXOQ.js} +1 -1
- package/client/dist/assets/{DocsDialog-BxduBYbh.js → DocsDialog-Co0O672q.js} +1 -1
- package/client/dist/assets/{DocsPage-DOVeRJn_.js → DocsPage-DxGI6A2T.js} +1 -1
- package/client/dist/assets/{ExportDropdown-BnKVV8UK.js → ExportDropdown-1Ce031vB.js} +1 -1
- package/client/dist/assets/{InteractiveJobComposer-Da9dvM-0.js → InteractiveJobComposer-DkqR77pO.js} +2 -2
- package/client/dist/assets/JobDetailModal-DVLDVgzz.js +1 -0
- package/client/dist/assets/JobDetailPage-RcP9N-D-.js +1 -0
- package/client/dist/assets/{JobsPage-0vdlbNik.js → JobsPage-R9M4xQnp.js} +1 -1
- package/client/dist/assets/{LoopBuilderPage-DCUHozXh.js → LoopBuilderPage-C4S-rkWp.js} +1 -1
- package/client/dist/assets/{LoopPreviewModal-Df-S8NGf.js → LoopPreviewModal-DA2JeKWX.js} +1 -1
- package/client/dist/assets/{LoopWindowSurface-sdb7QbSo.js → LoopWindowSurface-BkkeD0hx.js} +1 -1
- package/client/dist/assets/{LoopsPage-CDfCC9Px.js → LoopsPage-BHv--iI7.js} +1 -1
- package/client/dist/assets/{MinimizedChatsContext-DKlPf54Y.js → MinimizedChatsContext-Bc9xHByW.js} +1 -1
- package/client/dist/assets/PluginsPage-BEy62gxK.js +2 -0
- package/client/dist/assets/{ProjectSettingsDialog-DOkjT7xv.js → ProjectSettingsDialog-DDQ_f94T.js} +1 -1
- package/client/dist/assets/{RepositoryDeliveries-CJVpOUpZ.js → RepositoryDeliveries-DIWDFLRM.js} +1 -1
- package/client/dist/assets/{RepositoryScopeSelector-BeM-2gIZ.js → RepositoryScopeSelector-D3PlHIM-.js} +1 -1
- package/client/dist/assets/{ReviewPacketPage-EBCim2w_.js → ReviewPacketPage-BXdPwmdE.js} +1 -1
- package/client/dist/assets/{TemplatePreviewModal-1-92UGwN.js → TemplatePreviewModal-C7-G6lvI.js} +1 -1
- package/client/dist/assets/{TicketDetailModalContext-CkVXuHxU.js → TicketDetailModalContext-gGUv7gsQ.js} +1 -1
- package/client/dist/assets/{Trans-CzhPjnjB.js → Trans-Br_OIUKP.js} +1 -1
- package/client/dist/assets/{formatDistanceToNow-DlQaXc7c.js → formatDistanceToNow-Q7QPRFV4.js} +1 -1
- package/client/dist/assets/{getTimezoneOffsetInMilliseconds-DxUFGcVq.js → getTimezoneOffsetInMilliseconds-Bq0CXBaJ.js} +1 -1
- package/client/dist/assets/{index-Czov72g7.js → index-DVkWHOR3.js} +7 -7
- package/client/dist/assets/jira-B6OYw4CJ.js +1 -0
- package/client/dist/assets/jira-BTF6RaOe.js +1 -0
- package/client/dist/assets/jira-CEetIB2q.js +1 -0
- package/client/dist/assets/jira-CIzsZLw5.js +1 -0
- package/client/dist/assets/{jira-BOoaUXbb.js → jira-Cq6yvN4q.js} +1 -1
- package/client/dist/assets/jira-Daw9tmpI.js +1 -0
- package/client/dist/assets/jira-FXr7u2lh.js +1 -0
- package/client/dist/assets/jira-LaNQn7yl.js +1 -0
- package/client/dist/assets/{jira-api-CbJvbRYx.js → jira-api-C2Tzc362.js} +1 -1
- package/client/dist/assets/{jobs-DZAbh1m2.js → jobs-ARwD85B9.js} +1 -1
- package/client/dist/assets/{jobs-BD47Ov11.js → jobs-BMF1w4WT.js} +1 -1
- package/client/dist/assets/{jobs-tWjcDF_6.js → jobs-CBfzGLwm.js} +1 -1
- package/client/dist/assets/{jobs-CSrRx5lD.js → jobs-CPjiluBY.js} +1 -1
- package/client/dist/assets/{jobs-epP7gt8W.js → jobs-CQQd58pY.js} +1 -1
- package/client/dist/assets/{jobs-1G3wodhl.js → jobs-De3EqaO0.js} +1 -1
- package/client/dist/assets/{jobs-BLOoVRRl.js → jobs-Du9-Ei9a.js} +1 -1
- package/client/dist/assets/{jobs-B4pu8TdF.js → jobs-qMHbnxwC.js} +1 -1
- package/client/dist/assets/{project-repositories-BkBD3rxx.js → project-repositories-DZKmieH3.js} +1 -1
- package/client/dist/assets/{spending-CYotH-9N.js → spending-EXaboRU0.js} +1 -1
- package/client/dist/assets/{useDesktop-DpxVGfUC.js → useDesktop-ChSmmJfc.js} +1 -1
- package/client/dist/assets/{useRuntimeRuns-CfGRbhr9.js → useRuntimeRuns-w9-5OzZl.js} +1 -1
- package/client/dist/assets/{useSharedWebSocket-C17TGqT8.js → useSharedWebSocket-g0nTHfTT.js} +2 -2
- package/client/dist/index.html +16 -16
- package/docs/internals/configuration.md +45 -0
- package/docs/internals/loop-step-log-explorer.md +27 -0
- package/docs/internals/mission-rail-cards.md +7 -0
- package/docs/internals/safe-pr-review-flow.md +9 -0
- package/docs/internals/source-map.md +5 -1
- package/docs/jira-integration-plan.md +7 -4
- package/package.json +1 -1
- package/server/dist/core-execution.js +5 -18
- package/server/dist/jira/jira-db.js +33 -6
- package/server/dist/jira/jira-materializer.js +11 -9
- package/server/dist/jira/jira-status-resolver.js +64 -22
- package/server/dist/jira/jira-sync-manager.js +40 -1
- package/server/dist/jira-router.js +8 -41
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-bridge.js +18 -68
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-controls.js +7 -4
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-verification-log.js +252 -0
- package/server/dist/modules/delivery/runtime/rail-relaunch.js +10 -2
- package/server/dist/modules/loops/runtime/loop-implement-recipe.js +11 -7
- package/server/dist/modules/loops/runtime/loop-run-manager.js +5 -3
- package/server/dist/path-resolver.js +41 -20
- package/server/dist/project-env.js +22 -6
- package/server/dist/util/checkout-path.js +22 -0
- package/server/dist/worktree-node-modules.js +70 -34
- package/client/dist/assets/JobDetailModal-DhwKbZGe.js +0 -1
- package/client/dist/assets/JobDetailPage-MtPE-E_1.js +0 -1
- package/client/dist/assets/PluginsPage-iL0G3rQQ.js +0 -2
- package/client/dist/assets/jira-BNZu8f9O.js +0 -1
- package/client/dist/assets/jira-BYVN4L2u.js +0 -1
- package/client/dist/assets/jira-CKJg3HZd.js +0 -1
- package/client/dist/assets/jira-DDDsZ4ub.js +0 -1
- package/client/dist/assets/jira-DXwYjVE4.js +0 -1
- package/client/dist/assets/jira-Dn6U2nRW.js +0 -1
- 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: '
|
|
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: '
|
|
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: '
|
|
44
|
-
|
|
45
|
-
node('correction
|
|
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:
|
|
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 =
|
|
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 =
|
|
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
|
-
/**
|
|
573
|
-
*
|
|
574
|
-
|
|
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
|
|
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 =
|
|
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
|
|
607
|
-
|
|
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
|
|
12
|
-
* recovery
|
|
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
|
-
|
|
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
|
|
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, ...
|
|
158
|
+
const expected = path.join(baseRepo, ...sourceRel.split('/'));
|
|
139
159
|
if (resolveRealPath(target) !== resolveRealPath(expected))
|
|
140
160
|
return null;
|
|
141
|
-
// A link
|
|
142
|
-
//
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
|
267
|
-
const
|
|
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};
|