claude-code-session-manager 0.119.1 → 0.121.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/assets/{AgentLibrary-BpW0pGgk.js → AgentLibrary-FO2ei2wB.js} +1 -1
  2. package/dist/assets/{Button-DZX5guWb.js → Button-CeIOXUPU.js} +1 -1
  3. package/dist/assets/{DataModel-WG4pG_4z.js → DataModel-D0p__Hil.js} +1 -1
  4. package/dist/assets/{History-D58whvn_.js → History-DVqQ5azN.js} +1 -1
  5. package/dist/assets/{Hooks-DCI9ezg4.js → Hooks-DkTnRHyT.js} +1 -1
  6. package/dist/assets/{Library-YqMi-vsA.js → Library-l7KS6pkE.js} +1 -1
  7. package/dist/assets/{ListDetail-ap4W5z_Z.js → ListDetail-DDw8fuRT.js} +1 -1
  8. package/dist/assets/{McpServers-CT0pBTF8.js → McpServers-UUNIxk_3.js} +1 -1
  9. package/dist/assets/{Memory-BODDUBy4.js → Memory-CNYUoQ8C.js} +1 -1
  10. package/dist/assets/{Panel-CeOfUS9B.js → Panel-Bb54soc2.js} +1 -1
  11. package/dist/assets/{Permissions-DyOqpLQx.js → Permissions-D-dNdV3f.js} +1 -1
  12. package/dist/assets/{Plugins-DBXzKjL2.js → Plugins-DhcIXRhQ.js} +2 -2
  13. package/dist/assets/{ProvenanceBadge-C47IX1Q6.js → ProvenanceBadge-DEAkDNjA.js} +1 -1
  14. package/dist/assets/{RemoteMachines-CtkAJvkZ.js → RemoteMachines-xCEgvTGU.js} +1 -1
  15. package/dist/assets/{SaveBar-YMtzcCzP.js → SaveBar-BSEs7ecA.js} +1 -1
  16. package/dist/assets/{Scheduler-BNiYg0YD.js → Scheduler-BIKJIU-C.js} +5 -5
  17. package/dist/assets/{ScopeSwitcher-Dgi8CiDi.js → ScopeSwitcher-DcaFyAV-.js} +1 -1
  18. package/dist/assets/Settings-vcdWy_Jx.js +4 -0
  19. package/dist/assets/{SkillReferenceGraph-CaFsccNI.js → SkillReferenceGraph-CLrJWTPd.js} +1 -1
  20. package/dist/assets/{Skills-BeCMBQ3l.js → Skills-CYM4rBEC.js} +1 -1
  21. package/dist/assets/{SystemPrompt-C3WmcPEF.js → SystemPrompt-BWNIu-iY.js} +1 -1
  22. package/dist/assets/{TagLibrary-DP4GH7Dr.js → TagLibrary-CYdwHK6m.js} +1 -1
  23. package/dist/assets/{TiptapBody-CXCMvRr_.js → TiptapBody-Kt90lHrD.js} +1 -1
  24. package/dist/assets/{Toggle-TY-Wa8hv.js → Toggle-D1OXWYOz.js} +1 -1
  25. package/dist/assets/{ViewTabs-CaacRnp6.js → ViewTabs-BeQR3CKh.js} +1 -1
  26. package/dist/assets/{historyTotals-C28IlVVQ.js → historyTotals-B0qafrRo.js} +1 -1
  27. package/dist/assets/{index-DdEMkmbi.js → index-Bq9uH08R.js} +201 -201
  28. package/dist/assets/{index-DAMBm3UN.css → index-Dd7UQabV.css} +1 -1
  29. package/dist/assets/{settingsSchema-7Yg6dNNB.js → settingsSchema-0ZksSAPB.js} +1 -1
  30. package/dist/assets/{skillFrontmatter-Dcuys1km.js → skillFrontmatter-C0nh8yfa.js} +1 -1
  31. package/dist/assets/{useAgentsDirWatch-B0H4M-TT.js → useAgentsDirWatch-DPkoBybS.js} +1 -1
  32. package/dist/assets/{useRemotePeers-Zp9jchCS.js → useRemotePeers-BDJMUihy.js} +1 -1
  33. package/dist/index.html +2 -2
  34. package/package.json +1 -1
  35. package/src/main/build-info.json +4 -4
  36. package/src/main/chatRunner.cjs +54 -14
  37. package/src/main/crashDiagnostics.cjs +18 -13
  38. package/src/main/health.cjs +129 -4
  39. package/src/main/index.cjs +57 -18
  40. package/src/main/ipcSchemas.cjs +18 -4
  41. package/src/main/lib/activeSessions.cjs +51 -34
  42. package/src/main/lib/claude/agentModelResolve.cjs +5 -3
  43. package/src/main/lib/claude/personaToolScope.cjs +68 -0
  44. package/src/main/lib/epic/archivedTranscript.cjs +91 -0
  45. package/src/main/lib/epic/archivedTranscriptMigration.cjs +84 -0
  46. package/src/main/lib/epic/delegationReadiness.cjs +2 -0
  47. package/src/main/lib/epic/epicSettle.cjs +21 -5
  48. package/src/main/lib/epic/epicValidationHook.cjs +7 -0
  49. package/src/main/lib/git/depHealth.cjs +161 -0
  50. package/src/main/lib/git/worktreeDeps.cjs +158 -5
  51. package/src/main/lib/machineProfile.cjs +2 -0
  52. package/src/main/lib/mcpToolCatalog.cjs +3 -1
  53. package/src/main/lib/opsRetentionSettings.cjs +183 -0
  54. package/src/main/lib/persona-macro/macroLibrary.cjs +75 -0
  55. package/src/main/lib/prd/prdCreate.cjs +25 -0
  56. package/src/main/lib/prd/prdWorktreeReach.cjs +2 -2
  57. package/src/main/lib/prd/workTypeLibrary.cjs +7 -3
  58. package/src/main/lib/process/procGroupRss.cjs +61 -0
  59. package/src/main/lib/remoteTranscriptTail.cjs +69 -2
  60. package/src/main/lib/scheduler/queueHistory.cjs +68 -9
  61. package/src/main/lib/scheduler/runLogRetention.cjs +129 -43
  62. package/src/main/lib/scheduler/schedulerBatch.cjs +7 -3
  63. package/src/main/lib/scheduler/staleTmpSweep.cjs +117 -0
  64. package/src/main/lib/telemetry/auditLog.cjs +51 -6
  65. package/src/main/lib/telemetry/crashTelemetry.cjs +49 -1
  66. package/src/main/lib/telemetry/opsErrorLog.cjs +30 -10
  67. package/src/main/lib/telemetry/telemetryClient.cjs +5 -2
  68. package/src/main/scheduler/prdParser.cjs +50 -0
  69. package/src/main/scheduler/prdPaths.cjs +61 -16
  70. package/src/main/scheduler/reviewLadder.cjs +24 -1
  71. package/src/main/scheduler.cjs +214 -37
  72. package/src/main/templates/PRD_AUTHORING.md +2 -2
  73. package/src/main/webRemote.cjs +82 -91
  74. package/src/preload/api.d.ts +41 -3
  75. package/src/preload/index.cjs +9 -0
  76. package/dist/assets/Settings-B4LuoRnd.js +0 -4
@@ -29,7 +29,6 @@ const logs = require('./logs.cjs');
29
29
  const { sendIfAlive } = require('./lib/sendToRenderer.cjs');
30
30
  const { claudeHome } = require('./lib/scheduler/schedulerPaths.cjs');
31
31
  const { schemas } = require('./ipcSchemas.cjs');
32
- const { summarize: _sharedSummarize } = require('./lib/summarize.cjs');
33
32
  const { makeState, confirmSas: confirmSasLogic } = require('./lib/e2eStateMachine.cjs');
34
33
  const { createClientSessions, LEGACY_CLIENT_KEY } = require('./lib/feedback-remote/remoteClientSessions.cjs');
35
34
 
@@ -497,12 +496,11 @@ async function handleTokenRevoked(deviceId) {
497
496
  sendIfAlive(_window, 'webRemote:token-revoked', { deviceId });
498
497
  }
499
498
 
500
- // ─── v2 mobile: session live state + summary push ────────────────────────────
499
+ // ─── v2 mobile: session live state push ────────────────────────────
501
500
  //
502
501
  // For each subscribed tab the agent tails its transcript JSONL (reusing the
503
502
  // canonical classifyLine + transcriptPath from transcripts.cjs — single source of
504
503
  // truth), derives a coarse state, and pushes event:session:state on change.
505
- // The last completed assistant turn drives the Haiku summary (see maybeSummarize).
506
504
 
507
505
  const SESSION_POLL_MS = 1500;
508
506
  const SESSION_INIT_TAIL_BYTES = 512 * 1024; // bound the initial read
@@ -524,7 +522,7 @@ function pushEventTo(client, type, payload) {
524
522
  }
525
523
  }
526
524
 
527
- /** Authenticated clients subscribed to a tab's session state/summary. */
525
+ /** Authenticated clients subscribed to a tab's session state. */
528
526
  function subscribersOf(tabId) {
529
527
  return _clients.authenticated().filter((c) => c.subscriptions.has(tabId));
530
528
  }
@@ -551,15 +549,6 @@ function teardownAllClients() {
551
549
  return n;
552
550
  }
553
551
 
554
- /** Extract concatenated text from an assistant transcript line, or '' if none. */
555
- function extractAssistantText(raw) {
556
- const msg = raw?.message || raw;
557
- const content = msg?.content;
558
- if (typeof content === 'string') return content;
559
- if (!Array.isArray(content)) return '';
560
- return content.filter((b) => b?.type === 'text' && typeof b.text === 'string').map((b) => b.text).join('\n').trim();
561
- }
562
-
563
552
  /** Map a classified transcript event to a coarse session state. */
564
553
  function deriveState(ev, raw) {
565
554
  // API/usage errors surface as a flagged message line.
@@ -613,6 +602,19 @@ async function tailLines(filePath, fromOffset) {
613
602
  }
614
603
 
615
604
  async function pollSessionWatcher(w) {
605
+ // The transcript may not exist yet (new session) or may live under another
606
+ // project/worktree encoding: re-resolve while the watched file is missing.
607
+ if (!fs.existsSync(w.filePath)) {
608
+ try {
609
+ const next = require('./transcripts.cjs').transcriptPath(w.cwd, w.sessionId);
610
+ if (next && next !== w.filePath) {
611
+ validatePath(next);
612
+ w.filePath = next;
613
+ w.offset = null;
614
+ w.inode = undefined;
615
+ }
616
+ } catch { /* keep the old path */ }
617
+ }
616
618
  let res;
617
619
  try {
618
620
  res = await tailLines(w.filePath, w.offset);
@@ -627,8 +629,6 @@ async function pollSessionWatcher(w) {
627
629
  w.inode = res.inode;
628
630
 
629
631
  let nextState = null;
630
- let newAssistantText = null;
631
- let newMsgId = null;
632
632
  for (const line of res.lines) {
633
633
  let obj;
634
634
  try { obj = JSON.parse(line); } catch { continue; }
@@ -636,10 +636,6 @@ async function pollSessionWatcher(w) {
636
636
  for (const ev of events) {
637
637
  const s = deriveState(ev, obj);
638
638
  if (s) nextState = s;
639
- if (ev.kind === 'assistant') {
640
- const text = extractAssistantText(obj);
641
- if (text) { newAssistantText = text; newMsgId = obj.uuid || obj.message?.id || `${w.tabId}:${res.size}`; }
642
- }
643
639
  }
644
640
  }
645
641
 
@@ -650,33 +646,20 @@ async function pollSessionWatcher(w) {
650
646
  pushEventTo(c, 'event:session:state', { tabId: w.tabId, state: w.state, since: Date.now() });
651
647
  }
652
648
  }
653
- if (newAssistantText && newMsgId !== w.lastMsgId) {
654
- w.lastAssistantText = newAssistantText;
655
- w.lastMsgId = newMsgId;
656
- }
657
- // Summarize only a COMPLETED turn (state idle) — not assistant text mid-turn that
658
- // is followed by a tool call. Cache by msgId so re-subscribe doesn't re-bill.
659
- if (w.state === 'idle' && w.lastAssistantText && w.lastMsgId !== w.summarizedMsgId) {
660
- w.summarizedMsgId = w.lastMsgId;
661
- maybeSummarize(w).catch(() => {});
662
- }
663
649
  }
664
650
 
665
- function startSessionWatch(tabId, cwd) {
651
+ function startSessionWatch(tabId, cwd, sessionId = tabId) {
666
652
  if (_sessionWatchers.has(tabId)) return;
667
- const filePath = require('./transcripts.cjs').transcriptPath(cwd, tabId);
653
+ const filePath = require('./transcripts.cjs').transcriptPath(cwd, sessionId);
668
654
  // Defense in depth: the schema restricts tabId charset, but re-validate the
669
655
  // FINAL joined path against the home-dir boundary (validatePath resolves
670
656
  // symlinks + rejects escapes) before any fs read. Throws → dispatch drops it.
671
657
  validatePath(filePath);
672
658
  const w = {
673
- tabId, cwd, filePath,
659
+ tabId, cwd, sessionId, filePath,
674
660
  offset: null, // null → first poll reads a bounded tail then tracks EOF
675
661
  inode: undefined,
676
662
  state: 'idle',
677
- lastAssistantText: null,
678
- lastMsgId: null,
679
- summarizedMsgId: null,
680
663
  timer: null,
681
664
  };
682
665
  _sessionWatchers.set(tabId, w);
@@ -732,44 +715,6 @@ function buildRemoteSessionList(tabs, readIndex) {
732
715
  return out;
733
716
  }
734
717
 
735
- // ─── SM-V2-03: mobile summary via Claude Haiku 4.5 ───────────────────────────
736
- // Summarization logic lives in lib/summarize.cjs (shared with exchanges.cjs).
737
-
738
- const SUMMARY_MIN_CHARS = 280; // below this, push raw — not worth an API call
739
-
740
- /**
741
- * Produce a mobile summary of the watcher's last completed assistant turn and push it.
742
- * Short turns are pushed raw (no API call). If no API key is configured, degrades to
743
- * the raw message so core remote control is never blocked. Cost: Haiku in+out per
744
- * completed turn per subscribed tab (~$1/$5 per 1M tokens).
745
- */
746
- async function maybeSummarize(w) {
747
- // Guard: summaries (session transcript content) go only to SAS-confirmed subscribers.
748
- if (subscribersOf(w.tabId).length === 0) return;
749
- const text = w.lastAssistantText;
750
- if (!text) return;
751
- const ofMessageId = w.lastMsgId;
752
-
753
- if (text.length < SUMMARY_MIN_CHARS) {
754
- for (const c of subscribersOf(w.tabId)) {
755
- pushEventTo(c, 'event:session:summary', { tabId: w.tabId, summary: text, ofMessageId, model: 'raw', ts: Date.now() });
756
- }
757
- return;
758
- }
759
-
760
- const { summary, model, degraded } = await _sharedSummarize(text);
761
- if (degraded) {
762
- logs.writeLine({ scope: 'webRemote', level: 'warn', message: `summary degraded: ${degraded}`, meta: {} });
763
- }
764
- for (const c of subscribersOf(w.tabId)) {
765
- pushEventTo(c, 'event:session:summary', {
766
- tabId: w.tabId, summary, ofMessageId, model,
767
- ...(degraded ? { degraded } : {}),
768
- ts: Date.now(),
769
- });
770
- }
771
- }
772
-
773
718
  /**
774
719
  * Shared finish-up for any path that transitions a client's e2e state into 'authenticated'
775
720
  * (manual SAS confirmation or pinned-key auto-trust): log, broadcast the new
@@ -1010,7 +955,7 @@ async function dispatchEnvelope(envelope, device, client) {
1010
955
  let result;
1011
956
  try {
1012
957
  result = await dispatchCommand(type, payload ?? {}, client);
1013
- await auditLog(ts, type, device.deviceId, id, 'ok', client);
958
+ await auditLog(ts, type, device.deviceId, id, auditResultFor(result), client);
1014
959
  } catch (e) {
1015
960
  const code = e?.name === 'ZodError' ? 'schema_invalid' : 'dispatch_error';
1016
961
  await auditLog(ts, type, device.deviceId, id, `error:${code}`, client);
@@ -1020,6 +965,14 @@ async function dispatchEnvelope(envelope, device, client) {
1020
965
  respond(id, result, undefined, client);
1021
966
  }
1022
967
 
968
+ /** Audit result string for a handler result: structured refusals log as refused[:reason]. */
969
+ function auditResultFor(result) {
970
+ if (result && result.ok === false && typeof result.disposition === 'string') {
971
+ return typeof result.reason === 'string' && result.reason ? `refused:${result.reason}` : 'refused';
972
+ }
973
+ return 'ok';
974
+ }
975
+
1023
976
  function respond(msgId, payload, typeOverride, client) {
1024
977
  if (!_ws || _ws.readyState !== WebSocket.OPEN) return;
1025
978
  const responseType = typeOverride || (msgId ? `resp:${msgId}` : undefined);
@@ -1102,26 +1055,34 @@ function getDispatchMap() {
1102
1055
  const parsed = schemas.remoteSessionTranscript.parse(payload);
1103
1056
  const { readActiveIndex } = require('./lib/epic/epicMint.cjs');
1104
1057
  const { resolveEpicTranscriptPath } = require('./lib/epic/epicTranscriptPath.cjs');
1105
- const { readRecentChatMessages } = require('./lib/remoteTranscriptTail.cjs');
1058
+ const { readRecentChatMessages, buildRemoteConversation } = require('./lib/remoteTranscriptTail.cjs');
1059
+ const { listExchanges } = require('./exchanges.cjs');
1106
1060
  const data = await sessionsStore.load();
1107
1061
  const tab = (data?.tabs ?? []).find((t) => t && t.cwd === parsed.cwd);
1108
1062
  if (!tab) throw new Error('cwd is not an open project');
1109
1063
  validatePath(parsed.cwd);
1064
+ const limit = parsed.limit ?? 10;
1065
+ const before = parsed.before ?? 0;
1110
1066
  let filePath = null;
1111
- if (parsed.sessionKey === (tab.id ?? tab.sessionId)) {
1112
- filePath = require('./transcripts.cjs').transcriptPath(parsed.cwd, parsed.sessionKey);
1067
+ let sessionId = null;
1068
+ if (parsed.sessionKey === tab.id || parsed.sessionKey === tab.sessionId) {
1069
+ sessionId = tab.sessionId || parsed.sessionKey;
1070
+ filePath = require('./transcripts.cjs').transcriptPath(parsed.cwd, sessionId);
1113
1071
  } else {
1114
1072
  const sessions = readActiveIndex(parsed.cwd)?.sessions ?? {};
1115
1073
  const epic = Object.values(sessions).find(
1116
1074
  (e) => e && (e.id === parsed.sessionKey || e.claudeSessionId === parsed.sessionKey),
1117
1075
  );
1118
1076
  if (epic && epic.claudeSessionId) {
1119
- filePath = resolveEpicTranscriptPath({ cwd: parsed.cwd, claudeSessionId: epic.claudeSessionId }).path;
1077
+ sessionId = epic.claudeSessionId;
1078
+ filePath = resolveEpicTranscriptPath({ cwd: parsed.cwd, claudeSessionId: sessionId }).path;
1120
1079
  }
1121
1080
  }
1122
- if (!filePath) return { messages: [] };
1081
+ if (!filePath || !sessionId) return { messages: [], hasMore: false, nextBefore: null };
1123
1082
  validatePath(filePath);
1124
- return { messages: await readRecentChatMessages(filePath, { limit: parsed.limit }) };
1083
+ const exchanges = await listExchanges({ cwd: parsed.cwd, sessionId, limit: limit + 1, offset: before });
1084
+ const tail = before === 0 ? await readRecentChatMessages(filePath, { limit: 30 }) : [];
1085
+ return buildRemoteConversation({ exchanges, tail, limit, before });
1125
1086
  },
1126
1087
 
1127
1088
  'cmd:session:prds': async (payload) => {
@@ -1209,12 +1170,15 @@ function getDispatchMap() {
1209
1170
  'cmd:app:info': async () =>
1210
1171
  ({ version: app.getVersion(), platform: process.platform }),
1211
1172
 
1212
- // v2 mobile: start/stop pushing live state + summary for a session.
1173
+ // v2 mobile: start/stop pushing live state for a session.
1213
1174
  'cmd:session:subscribe': async (payload, reqClient) => {
1214
1175
  const client = reqClient || _clients.getOrCreate(LEGACY_CLIENT_KEY);
1215
1176
  const parsed = schemas.sessionSubscribe.parse(payload);
1216
1177
  validatePath(parsed.cwd); // home-dir boundary before any fs access
1217
- startSessionWatch(parsed.tabId, parsed.cwd);
1178
+ // The phone's Main row passes the tab `id`, but the transcript is keyed by the tab's `sessionId`.
1179
+ const { tabs } = await require('./sessionsStore.cjs').load();
1180
+ const tab = (Array.isArray(tabs) ? tabs : []).find((t) => t && t.id === parsed.tabId && t.cwd === parsed.cwd);
1181
+ startSessionWatch(parsed.tabId, parsed.cwd, tab?.sessionId || parsed.tabId);
1218
1182
  client.subscriptions.add(parsed.tabId);
1219
1183
  return { ok: true };
1220
1184
  },
@@ -1236,23 +1200,28 @@ function getDispatchMap() {
1236
1200
  const data = await sessionsStore.load();
1237
1201
  const tabs = data?.tabs ?? [];
1238
1202
  // Resolve the target in main: an open tab, or an ACTIVE Epic (by id or
1239
- // claudeSessionId) of an open project. Anything else fails loudly.
1203
+ // claudeSessionId) of an open project. Anything else is a structured refusal.
1204
+ const outcome = (r) => ({
1205
+ ok: r.disposition !== 'refused',
1206
+ disposition: r.disposition,
1207
+ ...(r.reason ? { reason: r.reason } : {}),
1208
+ });
1240
1209
  if (tabs.some((t) => t.sessionId === parsed.tabId || t.id === parsed.tabId)) {
1241
- chatRunner.enqueueExternalPrompt(parsed.tabId, parsed.prompt);
1242
- return { ok: true };
1210
+ return outcome(await chatRunner.enqueueExternalPrompt(parsed.tabId, parsed.prompt));
1243
1211
  }
1244
1212
  for (const tab of tabs) {
1245
1213
  const sessions = Object.values(readActiveIndex(tab.cwd)?.sessions ?? {});
1246
1214
  const epic = sessions.find((e) => e && (e.id === parsed.tabId || e.claudeSessionId === parsed.tabId));
1247
1215
  if (!epic) continue;
1248
- if (epic.status !== 'active' || !epic.claudeSessionId) throw new Error('target is not an active session');
1249
- chatRunner.enqueueExternalPrompt(epic.id, parsed.prompt, {
1216
+ if (epic.status !== 'active' || !epic.claudeSessionId) {
1217
+ return { ok: false, disposition: 'refused', reason: epic.status === 'proposed' ? 'proposed' : 'not-active' };
1218
+ }
1219
+ return outcome(await chatRunner.enqueueExternalPrompt(epic.id, parsed.prompt, {
1250
1220
  sessionId: epic.claudeSessionId,
1251
1221
  cwd: epic.cwd || tab.cwd,
1252
- });
1253
- return { ok: true };
1222
+ }));
1254
1223
  }
1255
- throw new Error('unknown chat target');
1224
+ return { ok: false, disposition: 'refused', reason: 'unknown-target' };
1256
1225
  },
1257
1226
 
1258
1227
  'cmd:session:options': async () => {
@@ -1266,6 +1235,7 @@ function getDispatchMap() {
1266
1235
  agents: personas.map((a) => ({ name: a.name, description: a.description })),
1267
1236
  tags: EpicTagSchema.options,
1268
1237
  macros: await macroLibrary.listMacros(),
1238
+ macroInputs: true,
1269
1239
  };
1270
1240
  },
1271
1241
 
@@ -1278,7 +1248,23 @@ function getDispatchMap() {
1278
1248
  if (!tabs.some((t) => t.cwd === parsed.cwd)) throw new Error('cwd is not an open project');
1279
1249
  const personas = await agentLibrary.listPersonas();
1280
1250
  if (!personas.some((a) => a.name === parsed.agentName)) throw new Error('unknown agent');
1281
- chatRunner.requestExternalNewSession(parsed);
1251
+ let prompt = parsed.prompt;
1252
+ if (parsed.macroId) {
1253
+ const macroLibrary = require('./lib/persona-macro/macroLibrary.cjs');
1254
+ const macros = await macroLibrary.listMacros();
1255
+ const macro = macros.find((m) => m.id === parsed.macroId);
1256
+ if (!macro || !macroLibrary.macroVisibleIn(macro, parsed.cwd)) throw new Error('unknown macro');
1257
+ const result = macroLibrary.renderMacroPrompt(parsed.prompt, macro.inputs, parsed.inputs ?? {});
1258
+ if (!result.ok) throw new Error(result.error);
1259
+ prompt = result.prompt;
1260
+ }
1261
+ chatRunner.requestExternalNewSession({
1262
+ cwd: parsed.cwd,
1263
+ agentName: parsed.agentName,
1264
+ tag: parsed.tag,
1265
+ title: parsed.title,
1266
+ prompt,
1267
+ });
1282
1268
  return { ok: true };
1283
1269
  },
1284
1270
  };
@@ -1551,6 +1537,11 @@ module.exports = {
1551
1537
  },
1552
1538
  __test: {
1553
1539
  getDispatchMap,
1540
+ auditResultFor,
1541
+ startSessionWatch,
1542
+ pollSessionWatcher,
1543
+ getSessionWatcher: (tabId) => _sessionWatchers.get(tabId),
1544
+ stopAllSessionWatches,
1554
1545
  buildRemoteSessionList,
1555
1546
  setWs: (ws) => { _ws = ws; },
1556
1547
  connect,
@@ -1,6 +1,24 @@
1
1
  import type { PromptSession } from '../renderer/state/promptSessions';
2
2
 
3
3
  /** Mirrors the item shape returned by main/lib/prereqs.cjs checkPrereqs(). */
4
+ export type OpsRetentionSettings = {
5
+ runLogs: { maxAgeDays: number; keepPerSlug: number };
6
+ runsArchiveDays: number;
7
+ auditLogMonths: number;
8
+ reservedMarkersKeep: number;
9
+ };
10
+ export type OpsRetentionPatch = {
11
+ runLogs?: { maxAgeDays?: number; keepPerSlug?: number };
12
+ runsArchiveDays?: number;
13
+ auditLogMonths?: number;
14
+ reservedMarkersKeep?: number;
15
+ };
16
+ export type OpsRetentionPreview = {
17
+ runLogs: { files: number; bytes: number };
18
+ runsArchive: { entries: number; bytes: number };
19
+ tmp: { files: number; bytes: number };
20
+ };
21
+
4
22
  export type AutoUpdaterState =
5
23
  | 'unsupported' | 'disabled' | 'idle' | 'checking'
6
24
  | 'downloading' | 'ready' | 'deferred' | 'error';
@@ -257,7 +275,7 @@ export interface McpCatalogResult {
257
275
  /** Mirrors WorkType (src/main/lib/prd/workTypeLibrary.cjs) / EpicTag (tagLibrary.ts) — the
258
276
  * same Epic-mission taxonomy, reused here since an Agent persona's `tags` field is that
259
277
  * same concept, not a free-form string list. */
260
- export type AgentPersonaTag = 'feature' | 'bug' | 'discussion' | 'build' | 'project-home-builder';
278
+ export type AgentPersonaTag = 'feature' | 'bug' | 'discussion' | 'build' | 'project-home-builder' | 'art' | 'portfolio';
261
279
 
262
280
  /** One declared `{{name}}` argument of a macro prompt (macroLibrary.cjs `MacroInputSchema`). */
263
281
  export interface MacroInput {
@@ -1543,7 +1561,7 @@ export interface ChatCreatePrdPayload {
1543
1561
  /** Originating tab id — used at job completion to route a status prompt back into the tab. */
1544
1562
  sourceTabId?: string;
1545
1563
  /** User-selected Feature/Bug tag (PRD 774) carried from the originating PromptTicket. */
1546
- tag?: 'feature' | 'bug' | 'discussion' | 'build' | 'project-home-builder';
1564
+ tag?: 'feature' | 'bug' | 'discussion' | 'build' | 'project-home-builder' | 'art' | 'portfolio';
1547
1565
  }
1548
1566
 
1549
1567
  export type ChatCreatePrdResult =
@@ -1628,6 +1646,17 @@ export interface ChatExternalSendEvent {
1628
1646
  /** Epic's claudeSessionId + project cwd, resolved by main so the renderer can deliver before the Epic's project is loaded. */
1629
1647
  sessionId?: string;
1630
1648
  cwd?: string;
1649
+ /** Set by main's enqueueExternalPrompt on every push; the renderer must echo it back via `reportExternalSendResult`. */
1650
+ requestId?: string;
1651
+ }
1652
+
1653
+ export type ChatExternalSendDisposition = 'sent' | 'queued' | 'refused';
1654
+ export type ChatExternalSendRefusalReason = 'terminal-attached' | 'completed' | 'unknown-target' | 'empty-prompt';
1655
+ /** What the renderer did with a `ChatExternalSendEvent`, reported back to main. */
1656
+ export interface ChatExternalSendResult {
1657
+ requestId: string;
1658
+ disposition: ChatExternalSendDisposition;
1659
+ reason?: ChatExternalSendRefusalReason;
1631
1660
  }
1632
1661
 
1633
1662
  export type CreateProjectFolderResult =
@@ -2234,6 +2263,11 @@ export interface SessionManagerAPI {
2234
2263
  setEnabled: (enabled: boolean) => Promise<AutoUpdaterStatus>;
2235
2264
  onStatus: (handler: (status: AutoUpdaterStatus) => void) => () => void;
2236
2265
  };
2266
+ opsRetention: {
2267
+ get: () => Promise<OpsRetentionSettings>;
2268
+ set: (patch: OpsRetentionPatch) => Promise<OpsRetentionSettings>;
2269
+ preview: () => Promise<OpsRetentionPreview>;
2270
+ };
2237
2271
  webRemote: {
2238
2272
  getStatus: () => Promise<WebRemoteStatus>;
2239
2273
  enable: () => Promise<WebRemoteMutationResult>;
@@ -2274,6 +2308,8 @@ export interface SessionManagerAPI {
2274
2308
  * tool) pushes a prompt into an open tab's queue from outside the
2275
2309
  * renderer (PRD 753). */
2276
2310
  onExternalSend: (handler: (e: ChatExternalSendEvent) => void) => () => void;
2311
+ /** Reports what the renderer did with an external send (echoing its requestId) back to main. */
2312
+ reportExternalSendResult: (result: ChatExternalSendResult) => Promise<void>;
2277
2313
  /** Fires when a paired phone asks main to start a new Session (payload pre-validated in main). */
2278
2314
  onExternalNewSession: (
2279
2315
  handler: (e: { cwd: string; agentName: string; tag: AgentPersonaTag; title: string; prompt: string }) => void,
@@ -2329,6 +2365,8 @@ export interface SessionManagerAPI {
2329
2365
  getWorktreeDisabled: (payload: PromptSessionsGetWorktreeDisabledPayload) => Promise<PromptSessionsGetWorktreeDisabledResult>;
2330
2366
  /** Writes the per-project "disable Epic worktree isolation" UI toggle. */
2331
2367
  setWorktreeDisabled: (payload: PromptSessionsSetWorktreeDisabledPayload) => Promise<PromptSessionsSetWorktreeDisabledResult>;
2368
+ /** Archives the raw Claude JSONL to a gzip sidecar main-side; pointer, or null when absent/empty. */
2369
+ archiveTranscript: (payload: { cwd: string; epicId: string; claudeSessionId: string }) => Promise<{ path: string; bytes: number; gzBytes: number; sha256: string } | null>;
2332
2370
  };
2333
2371
  }
2334
2372
 
@@ -2350,7 +2388,7 @@ export interface PromptSessionsMergeActiveIndexResult {
2350
2388
  export interface PromptSessionsCreateEpicPayload {
2351
2389
  cwd: string;
2352
2390
  goalText: string;
2353
- tag?: 'feature' | 'bug' | 'discussion' | 'build' | 'project-home-builder';
2391
+ tag?: 'feature' | 'bug' | 'discussion' | 'build' | 'project-home-builder' | 'art' | 'portfolio';
2354
2392
  agentType?: string;
2355
2393
  /** Per-Epic model/effort overrides (New Session card); omitted = the persona's own. */
2356
2394
  model?: string;
@@ -439,6 +439,11 @@ contextBridge.exposeInMainWorld('api', {
439
439
  return () => ipcRenderer.removeListener('updater:status', listener);
440
440
  },
441
441
  },
442
+ opsRetention: {
443
+ get: () => ipcRenderer.invoke('opsRetention:get'),
444
+ set: (patch) => ipcRenderer.invoke('opsRetention:set', patch),
445
+ preview: () => ipcRenderer.invoke('opsRetention:preview'),
446
+ },
442
447
  webRemote: {
443
448
  /** Current connection state (no token values). */
444
449
  getStatus: () => ipcRenderer.invoke('webRemote:get-status'),
@@ -531,6 +536,8 @@ contextBridge.exposeInMainWorld('api', {
531
536
  ipcRenderer.on('chat:external-send', listener);
532
537
  return () => ipcRenderer.removeListener('chat:external-send', listener);
533
538
  },
539
+ /** Reports the renderer's disposition for an external send back to main. */
540
+ reportExternalSendResult: (result) => ipcRenderer.invoke('chat:external-send-result', result),
534
541
  /** Fires when a paired phone asks main to start a new Session. */
535
542
  onExternalNewSession: (handler) => {
536
543
  const listener = (_e, payload) => handler(payload);
@@ -596,5 +603,7 @@ contextBridge.exposeInMainWorld('api', {
596
603
  * the UI-reachable equivalent of setting SM_EPIC_WORKTREE_DISABLE=1 for
597
604
  * just this one project rather than machine-wide. */
598
605
  setWorktreeDisabled: (payload) => ipcRenderer.invoke('promptSessions:set-worktree-disabled', payload),
606
+ /** Writes the gzip transcript sidecar main-side; resolves the pointer, or null when no JSONL. */
607
+ archiveTranscript: (payload) => ipcRenderer.invoke('promptSessions:archiveTranscript', payload),
599
608
  },
600
609
  });
@@ -1,4 +0,0 @@
1
- import{aF as d,ap as e,S as I,A as Q,n as V,Y as Oe,a5 as De,aX as ge,bf as z,b6 as be,a as F,bg as Me,aZ as $e,ba as Le,b7 as _e,b0 as Fe,c as ie,aD as Ue,b4 as Be,be as le,a_ as Y,ar as Ge,aw as He,J as Je,N as oe,aO as Ke}from"./index-DdEMkmbi.js";import{P as Ve}from"./Panel-CeOfUS9B.js";import{S as We}from"./ScopeSwitcher-Dgi8CiDi.js";import{S as ve}from"./SaveBar-YMtzcCzP.js";import{u as Xe,s as ce,E as Ye}from"./settingsSchema-7Yg6dNNB.js";import{V as qe}from"./ViewTabs-CaacRnp6.js";import{T as W}from"./Toggle-TY-Wa8hv.js";import{u as ze,b as J,P as D,E as se,R as ye,a as je,S as ee}from"./useRemotePeers-Zp9jchCS.js";import"./monaco-editor-BsIlgG7c.js";import"./claude-settings-schema-COigsajx.js";const Ze=[{id:"essentials",title:"Essentials",summary:"The knobs you reach for first.",keys:["model","language","outputStyle","permissions"]},{id:"git",title:"Git & Commits",summary:"How Claude writes commits and pull requests on your behalf.",keys:["attribution","includeGitInstructions","includeCoAuthoredBy"]},{id:"memory",title:"Memory & Plans",summary:"Context Claude keeps between sessions and where it files plans.",keys:["autoMemoryEnabled","plansDirectory","claudeMdExcludes","respectGitignore"]},{id:"model",title:"Model & Reasoning",summary:"Model allow/override lists, reasoning depth, and fast-mode toggles — machine-wide, with no per-Epic override yet. An Epic's Agent persona model (Agent Library) still wins over Essentials' plain model default.",advanced:!0,keys:["availableModels","modelOverrides","effortLevel","fastMode","fastModePerSessionOptIn","alwaysThinkingEnabled","fallbackModel","enforceAvailableModels","teammateDefaultModel","advisorModel"]},{id:"interface",title:"Interface",summary:"Visual polish — spinners, progress bars, motion.",keys:["spinnerTipsEnabled","spinnerTipsOverride","spinnerVerbs","terminalProgressBarEnabled","showTurnDuration","prefersReducedMotion","companyAnnouncements","statusLine","teammateMode"]},{id:"updates",title:"Updates & Cleanup",summary:"Release channel and how long transcripts stick around.",keys:["autoUpdatesChannel","cleanupPeriodDays"]},{id:"env",title:"Environment & Auth",summary:"Environment variables and auth-helper scripts.",keys:["env","apiKeyHelper","awsCredentialExport","awsAuthRefresh"]},{id:"hooks",title:"Hooks",summary:"Scripts Claude runs before/after tool use.",keys:["hooks","disableAllHooks","allowedHttpHookUrls","httpHookAllowedEnvVars"]},{id:"mcp",title:"MCP Servers",summary:"External tool providers Claude can talk to.",keys:["enableAllProjectMcpServers","enabledMcpjsonServers","disabledMcpjsonServers"]},{id:"plugins",title:"Plugins",summary:"Installed plugins, marketplaces, and per-plugin configuration.",keys:["enabledPlugins","extraKnownMarketplaces","skippedPlugins","skippedMarketplaces","pluginConfigs"]},{id:"filesystem",title:"File Picker & Sandbox",summary:"How Claude browses files and the sandbox it runs in.",keys:["fileSuggestion","sandbox","worktree"]},{id:"login",title:"Login",summary:"Which login method to force on startup.",keys:["forceLoginMethod","forceLoginOrgUUID"]},{id:"feedback",title:"Feedback",summary:"Session-quality surveys.",keys:["feedbackSurveyRate"]},{id:"advanced",title:"Advanced / Enterprise",summary:"Managed-settings-only keys and enterprise allow/denylists.",advanced:!0,keys:["allowedMcpServers","deniedMcpServers","allowManagedHooksOnly","allowManagedPermissionRulesOnly","allowManagedMcpServersOnly","strictKnownMarketplaces","blockedMarketplaces","pluginTrustMessage","otelHeadersHelper","skipWebFetchPreflight"]}],Qe={mcp:"MCP",url:"URL",urls:"URLs",http:"HTTP",ui:"UI",id:"ID",uuid:"UUID",api:"API",aws:"AWS",otel:"OTel",json:"JSON"};function we(t){return t.replace(/^\$/,"").replace(/([a-z])([A-Z])/g,"$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1 $2").toLowerCase().split(/\s+/).filter(Boolean).map((i,a)=>{const r=Qe[i];return r||(a===0?i.charAt(0).toUpperCase()+i.slice(1):i)}).join(" ")}function Ne(t){const s=new Set,n=[];for(const a of Ze){const r=a.keys.filter(o=>t.has(o));r.forEach(o=>s.add(o)),r.length!==0&&n.push({id:a.id,title:a.title,summary:a.summary,advanced:!!a.advanced,keys:r})}const i=Array.from(t).filter(a=>!s.has(a)&&a!=="$schema").sort();return i.length>0&&n.push({id:"other",title:"Other",summary:"Keys not yet grouped.",advanced:!1,keys:i}),n}function et(t,s){return Ne(t).map(n=>({id:n.id,title:n.title,advanced:n.advanced,total:n.keys.length,setCount:n.keys.filter(i=>s.has(i)).length}))}const tt={};function ke(t,s){if(s)return{text:s,authored:!1};const n=tt[t];return n?{text:n,authored:!0}:null}const st={essentials:"star",git:"link",memory:"memory",model:"sparkle",interface:"eye",updates:"reload",env:"keys",hooks:"hooks",mcp:"mcp",plugins:"plugins",filesystem:"folder",login:"lock",feedback:"send",advanced:"shield",other:"dot"},nt=["user","project","local","default"],at={user:"user",project:"project",local:"local",default:"default"};function rt({node:t,targetScope:s,onOverride:n,schema:i}){const[a,r]=d.useState(""),[o,p]=d.useState(!1),[u,h]=d.useState(!0),m=t.kind==="object"?t:null,k=m?Object.keys(m.children):[],g=i.rootKeys(),y=new Set([...k,...g]),c=d.useMemo(()=>Ne(y),[Array.from(y).sort().join(",")]),N=d.useMemo(()=>et(y,new Set(k)),[Array.from(y).sort().join(","),k.join(",")]),[R,A]=d.useState(null),v=d.useRef(null),E=f=>{const j=c.find(l=>l.id===f);j!=null&&j.advanced&&!o&&p(!0),A(f)};d.useEffect(()=>{if(!R)return;A(null);const f=v.current,j=document.getElementById(`settings-group-${R}`);if(!f||!j)return;const l=j.getBoundingClientRect().top-f.getBoundingClientRect().top;f.scrollBy({top:l,behavior:"smooth"})},[R]);const C=d.useMemo(()=>c.filter(f=>o||!f.advanced).map(f=>({...f,keys:f.keys.filter(j=>{const l=(m==null?void 0:m.children[j])!==void 0;if(!u&&!l)return!1;if(!a.trim())return!0;const S=i.at([j]);return[j,we(j),(S==null?void 0:S.description)??""].join(" ").toLowerCase().includes(a.toLowerCase())})})).filter(f=>f.keys.length>0),[c,a,o,u,m,i]);return e.jsxs("div",{className:"h-full flex flex-col",children:[e.jsxs("div",{className:"shrink-0 border-b border-line bg-bg-elev px-3 py-1.5 flex items-center gap-3 text-xs",children:[e.jsxs("span",{className:"text-fg-faint",children:["writing overrides to"," ",e.jsx("span",{className:"text-fg-dim font-medium",children:I[s].label})]}),e.jsxs("label",{className:"flex items-center gap-1 text-fg-faint cursor-pointer",children:[e.jsx("input",{type:"checkbox",checked:u,onChange:f=>h(f.target.checked),className:"accent-accent"}),"show not-yet-configured"]}),e.jsxs("label",{className:"flex items-center gap-1 text-fg-faint cursor-pointer",children:[e.jsx("input",{type:"checkbox",checked:o,onChange:f=>p(f.target.checked),className:"accent-accent"}),"show advanced"]}),e.jsx("div",{className:"flex-1"}),e.jsx("input",{value:a,onChange:f=>r(f.target.value),placeholder:"search settings…",className:"bg-bg border border-line rounded px-2 py-1 text-xs text-fg w-64"})]}),e.jsxs("div",{className:"flex-1 flex overflow-hidden",children:[e.jsx(it,{groups:N,onJump:E}),e.jsxs("div",{ref:v,className:"flex-1 overflow-auto py-3",children:[C.map(f=>e.jsx(lt,{id:f.id,title:f.title,summary:f.summary,advanced:f.advanced,children:f.keys.map(j=>{const l=m==null?void 0:m.children[j],S=i.at([j]);return e.jsx(ot,{keyName:j,node:l,info:S,targetScope:s,onOverride:(b,M)=>n([j],b,M)},j)})},f.id)),C.length===0&&e.jsxs("div",{className:"px-6 py-12 text-center text-fg-faint text-sm",children:['no settings match "',a,'"']})]})]})]})}function it({groups:t,onJump:s}){return e.jsx("nav",{className:"shrink-0 w-48 border-r border-line bg-bg-elev/30 overflow-y-auto py-3",children:t.map(n=>e.jsxs("button",{onClick:()=>s(n.id),className:"w-full flex items-center gap-2 px-3 py-2 text-left hover:bg-bg-hi transition-colors",title:`jump to ${n.title}`,children:[e.jsx(Q,{name:st[n.id]??"dot",size:15,className:"text-fg-faint shrink-0"}),e.jsx("span",{className:"flex-1 min-w-0 truncate text-xs text-fg-dim",children:n.title}),e.jsxs("span",{className:"text-[10px] text-fg-faint font-mono shrink-0",children:[n.setCount,"/",n.total]})]},n.id))})}function lt({id:t,title:s,summary:n,advanced:i,children:a}){const[r,o]=d.useState(!0);return e.jsxs("section",{id:`settings-group-${t}`,className:"mx-4 mb-4 rounded-lg border border-line shadow-sm bg-bg-elev/50 overflow-hidden",children:[e.jsxs("header",{className:"px-4 py-3 bg-bg-elev cursor-pointer hover:bg-bg-hi flex items-center gap-3 border-b border-line",onClick:()=>o(!r),children:[e.jsx("span",{className:"text-fg-faint w-3",children:r?"▾":"▸"}),e.jsxs("div",{className:"flex-1",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("h2",{className:"text-fg text-base font-semibold",children:s}),i&&e.jsx("span",{className:"text-[9px] px-1.5 py-0 rounded border border-yellow-900/40 bg-yellow-950/30 text-yellow-500/80 uppercase tracking-wide",children:"advanced"})]}),e.jsx("p",{className:"text-fg-dim text-sm mt-0.5",children:n})]})]}),r&&e.jsx("div",{className:"divide-y divide-line/60",children:a})]})}function ot({keyName:t,node:s,info:n,targetScope:i,onOverride:a}){const[r,o]=d.useState(!1),[p,u]=d.useState(!1),h=we(t),m=s!==void 0,g=(s==null?void 0:s.kind)==="leaf"?s:null,y=(s==null?void 0:s.kind)==="object"||(s==null?void 0:s.kind)==="array",c=n==null?void 0:n.default,N=g&&c!==void 0&&Ce(g.value,c),R=ht({isSet:m,leafNode:g,defaultVal:c,isDefault:!!N,node:s}),A=ct(g,c),v=A.length>=2,E=!!g&&!N,C=ke(t,n==null?void 0:n.description);return e.jsx("div",{className:"px-4 py-3 hover:bg-bg-elev/20",children:e.jsxs("div",{className:"flex items-start gap-3",children:[e.jsx("button",{onClick:()=>o(!r),className:"text-fg-faint hover:text-fg w-3 mt-0.5",title:r?"collapse":"expand",children:r?"▾":"▸"}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsxs("div",{className:"flex items-baseline gap-2 flex-wrap",children:[e.jsx("h3",{className:"text-fg text-sm",children:h}),e.jsx(gt,{kind:R.kind,label:R.label}),v&&e.jsxs("button",{onClick:f=>{f.stopPropagation(),r||o(!0),u(j=>!j)},className:"text-[9px] px-1.5 py-0 rounded border border-blue-900/40 bg-blue-950/30 text-blue-300/90 uppercase tracking-wide hover:bg-blue-900/40",title:"this key is defined in multiple scopes — click to expand",children:["drift · ",A.map(f=>at[f]).join(" · ")]}),E&&g&&e.jsx("button",{onClick:f=>{f.stopPropagation(),a(void 0,g.winner)},className:"text-[9px] px-1.5 py-0 rounded border border-line bg-bg-elev text-fg-faint uppercase tracking-wide hover:text-fg hover:bg-bg-hi",title:`reset · delete from ${I[g.winner].label}`,children:"↺ reset"}),(n==null?void 0:n.deprecated)&&e.jsx("span",{className:"text-[9px] px-1 py-0 rounded border border-yellow-900/40 bg-yellow-950/30 text-yellow-500/80 uppercase tracking-wide",children:"deprecated"}),(n==null?void 0:n.required)&&!m&&e.jsx("span",{className:"text-[9px] px-1 py-0 rounded border border-red-900/40 bg-red-950/30 text-red-400/80 uppercase tracking-wide",children:"required"})]}),C&&e.jsx("p",{className:`text-fg-dim text-xs mt-0.5 leading-relaxed ${C.authored?"inline-block border-b border-dotted border-fg-faint/70 cursor-help":""}`,title:C.authored?"In-app-authored description — not from the official Claude Code schema":void 0,children:bt(C.text)}),r&&p&&g&&e.jsx(dt,{leaf:g,defaultVal:c,onReset:f=>a(void 0,f)}),r&&e.jsx(xt,{keyName:t,node:s,info:n,targetScope:i,onOverride:f=>a(f)})]}),e.jsx("div",{className:"shrink-0",children:!r&&e.jsx(ft,{node:s,info:n,onSet:f=>{a(f)},disabled:y&&m})})]})})}function ct(t,s){if(!t)return[];const n=new Set;n.add(t.winner);for(const i of t.shadowed)n.add(i.scope);return s!==void 0&&n.add("default"),nt.filter(i=>n.has(i))}function dt({leaf:t,defaultVal:s,onReset:n}){const i={};i[t.winner]={value:t.value,winner:!0};for(const a of t.shadowed)i[a.scope]={value:a.value,winner:!1};return e.jsxs("div",{className:"mt-2 border border-line rounded bg-bg-elev/40 text-xs",children:[e.jsx("div",{className:"px-2 py-1 text-fg-faint border-b border-line/60",children:"Values across all scopes (winner highlighted · reset removes that scope's value):"}),e.jsxs("div",{className:"divide-y divide-line/60",children:[["user","project","local"].map(a=>{const r=i[a],o=r!==void 0;return e.jsxs("div",{className:"grid grid-cols-[5rem_1fr_auto] gap-2 px-2 py-1 items-center",children:[e.jsx("span",{className:"text-fg-faint",children:I[a].label}),e.jsx("span",{className:`font-mono truncate ${r!=null&&r.winner?"text-accent":"text-fg-dim"}`,children:o?U(r.value):e.jsx("span",{className:"text-fg-faint",children:"—"})}),o?e.jsx("button",{onClick:()=>n(a),className:"text-[9px] px-1.5 py-0 rounded border border-line text-fg-faint uppercase tracking-wide hover:text-fg hover:bg-bg-hi",title:`delete this key from ${I[a].label}`,children:"↺"}):e.jsx("span",{})]},a)}),e.jsxs("div",{className:"grid grid-cols-[5rem_1fr_auto] gap-2 px-2 py-1 items-center",children:[e.jsx("span",{className:"text-fg-faint",children:"Default"}),e.jsx("span",{className:"font-mono text-fg-dim truncate",children:s!==void 0?U(s):e.jsx("span",{className:"text-fg-faint",children:"—"})}),e.jsx("span",{})]})]})]})}function xt({keyName:t,node:s,info:n,targetScope:i,onOverride:a}){const o=(s==null?void 0:s.kind)==="leaf"?s:null,p=ke(t,n==null?void 0:n.description);return e.jsxs("div",{className:"mt-3 space-y-2 text-xs",children:[p&&e.jsx("p",{className:`text-fg-dim leading-relaxed whitespace-pre-wrap ${p.authored?"inline-block border-b border-dotted border-fg-faint/70 cursor-help":""}`,title:p.authored?"In-app-authored description — not from the official Claude Code schema":void 0,children:p.text}),e.jsxs("dl",{className:"grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 text-fg-faint",children:[(n==null?void 0:n.type)&&e.jsxs(e.Fragment,{children:[e.jsx("dt",{children:"Type"}),e.jsx("dd",{className:"text-fg-dim font-mono",children:Array.isArray(n.type)?n.type.join(" | "):n.type})]}),(n==null?void 0:n.default)!==void 0&&e.jsxs(e.Fragment,{children:[e.jsx("dt",{children:"Default"}),e.jsx("dd",{className:"text-fg-dim font-mono",children:U(n.default)})]}),(n==null?void 0:n.enum)&&e.jsxs(e.Fragment,{children:[e.jsx("dt",{children:"Options"}),e.jsx("dd",{className:"text-fg-dim font-mono",children:n.enum.map(u=>String(u)).join(" | ")})]}),o&&e.jsxs(e.Fragment,{children:[e.jsx("dt",{children:"Current"}),e.jsxs("dd",{className:"font-mono",children:[e.jsx("span",{className:"text-fg-dim",children:U(o.value)}),e.jsxs("span",{className:"ml-2 text-fg-faint",children:["from ",I[o.winner].label]})]})]}),o&&o.shadowed.length>0&&e.jsxs(e.Fragment,{children:[e.jsx("dt",{children:"Shadowed"}),e.jsx("dd",{className:"space-y-0.5",children:o.shadowed.map((u,h)=>e.jsxs("div",{className:"font-mono text-fg-faint line-through",children:[U(u.value)," —"," ",e.jsx("span",{className:"no-underline",children:I[u.scope].label})]},h))})]}),(s==null?void 0:s.kind)==="array"&&e.jsxs(e.Fragment,{children:[e.jsx("dt",{children:"Accumulated"}),e.jsxs("dd",{className:"text-fg-dim",children:[s.items.length," items across all scopes"]})]})]}),e.jsx("div",{className:"pt-2 flex items-center gap-2 flex-wrap border-t border-line/60",children:e.jsx(pt,{keyName:t,node:s,info:n,onChange:a,targetScope:i})})]})}function pt({node:t,info:s,onChange:n,targetScope:i}){const r=(t==null?void 0:t.kind)==="leaf"?t.value:s==null?void 0:s.default,o=Se(s==null?void 0:s.type);if(o==="boolean"){const p=r===!0;return e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"text-fg-faint",children:"Set to:"}),e.jsx("div",{className:"inline-flex rounded border border-line overflow-hidden",children:[{v:!0,label:"on"},{v:!1,label:"off"}].map(({v:u,label:h})=>e.jsx("button",{onClick:()=>n(u),className:`px-3 py-0.5 text-xs transition-colors ${p===u?"bg-accent text-bg":"bg-bg-elev text-fg-dim hover:bg-bg-hi hover:text-fg"}`,children:h},h))}),e.jsxs("span",{className:"text-fg-faint",children:["→ ",I[i].label]})]})}return s!=null&&s.enum?e.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[e.jsx("span",{className:"text-fg-faint",children:"Choose:"}),s.enum.map(p=>{const u=Ce(r,p);return e.jsx("button",{onClick:()=>n(p),className:`px-2 py-0.5 text-xs rounded border ${u?"bg-accent text-bg border-accent":"bg-bg-elev text-fg-dim border-line hover:text-fg hover:bg-bg-hi"}`,children:String(p)},String(p))}),e.jsxs("span",{className:"text-fg-faint",children:["→ ",I[i].label]})]}):o==="integer"||o==="number"?e.jsx(ut,{value:typeof r=="number"?r:void 0,integer:o==="integer",onCommit:n,target:I[i].label}):o==="string"?e.jsx(mt,{value:typeof r=="string"?r:"",onCommit:n,target:I[i].label}):e.jsxs("div",{className:"text-fg-faint",children:["Complex value (",o??"object/array",") — use the"," ",e.jsx("span",{className:"text-fg-dim",children:"Raw"})," tab to edit."]})}function ut({value:t,integer:s,onCommit:n,target:i}){const[a,r]=d.useState(t!==void 0?String(t):""),o=()=>{const p=s?parseInt(a,10):parseFloat(a);Number.isNaN(p)||n(p)};return e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"text-fg-faint",children:"Set to:"}),e.jsx("input",{type:"number",value:a,onChange:p=>r(p.target.value),onBlur:o,onKeyDown:p=>{p.key==="Enter"&&p.target.blur()},className:"w-24 bg-bg border border-line rounded px-2 py-0.5 text-xs text-fg"}),e.jsxs("button",{onClick:o,className:"px-2 py-0.5 text-xs border border-accent/50 rounded text-accent hover:bg-accent hover:text-bg",children:["save → ",i]})]})}function mt({value:t,onCommit:s,target:n}){const[i,a]=d.useState(t);return e.jsxs("div",{className:"flex items-center gap-2 flex-1 min-w-0",children:[e.jsx("span",{className:"text-fg-faint shrink-0",children:"Set to:"}),e.jsx("input",{type:"text",value:i,onChange:r=>a(r.target.value),onKeyDown:r=>{r.key==="Enter"&&s(i)},className:"flex-1 min-w-0 bg-bg border border-line rounded px-2 py-0.5 text-xs text-fg"}),e.jsxs("button",{onClick:()=>s(i),className:"shrink-0 px-2 py-0.5 text-xs border border-accent/50 rounded text-accent hover:bg-accent hover:text-bg",children:["save → ",n]})]})}function ft({node:t,info:s,onSet:n,disabled:i}){const a=(t==null?void 0:t.kind)==="leaf",r=Se(s==null?void 0:s.type);if(i)return null;if(r==="boolean"){const o=a&&t.value===!0;return e.jsx("button",{onClick:()=>n(!o),className:`px-2.5 py-0.5 text-xs rounded border transition-colors ${o?"bg-accent/15 text-accent border-accent/40 hover:bg-accent hover:text-bg":"bg-bg-elev text-fg-dim border-line hover:text-fg hover:bg-bg-hi"}`,title:"toggle",children:o?"on":"off"})}return null}function ht({isSet:t,leafNode:s,defaultVal:n,isDefault:i,node:a}){return t?(a==null?void 0:a.kind)==="array"?{kind:"accumulated",label:`${a.items.length} items`}:(a==null?void 0:a.kind)==="object"?{kind:"set",label:"configured"}:s?i?{kind:"default",label:`= default · ${I[s.winner].label}`}:s.shadowed.length>0?{kind:"override",label:`overriding ${s.shadowed.length} scope(s)`}:{kind:"set",label:`set in ${I[s.winner].label}`}:{kind:"set",label:"configured"}:n!==void 0?{kind:"default",label:`using default: ${U(n)}`}:{kind:"unset",label:"not configured"}}function gt({kind:t,label:s}){const n={set:"bg-accent/15 text-accent border-accent/30",default:"bg-bg-hi text-fg-dim border-line",unset:"bg-bg-elev text-fg-faint border-line",override:"bg-purple-950/30 text-purple-400/80 border-purple-900/40",accumulated:"bg-blue-950/30 text-blue-400/80 border-blue-900/40"}[t];return e.jsx("span",{className:`text-[9px] px-1.5 py-0 rounded border uppercase tracking-wide ${n}`,children:s})}function Se(t){if(t)return Array.isArray(t)?t.find(n=>n!=="null")??t[0]:t}function Ce(t,s){return t===s?!0:typeof t!=typeof s?!1:t&&s&&typeof t=="object"?JSON.stringify(t)===JSON.stringify(s):!1}function bt(t){const s=t.trim().split(`
2
- `)[0],n=s.match(/^([^.!?]+[.!?])/);return n?n[1]:s.length>120?s.slice(0,120)+"…":s}function U(t){if(typeof t=="string")return t===""?'""':JSON.stringify(t);if(t===null)return"null";if(t===void 0)return"—";if(typeof t=="object"){const s=JSON.stringify(t);return s.length>60?s.slice(0,60)+"…":s}return String(t)}const vt=1440*60*1e3;function de(t){if(!t)return"never";const s=new Date(t).getTime();return Number.isNaN(s)?"never":new Date(s).toLocaleString()}function xe(t){return t?new Date(t).toLocaleString():"never"}function yt(){return e.jsxs("div",{className:"p-4 max-w-3xl space-y-8 text-sm",children:[e.jsx(jt,{}),e.jsx("hr",{className:"border-line"}),e.jsx(wt,{})]})}function jt(){const[t,s]=d.useState(null),[n,i]=d.useState(null),[a,r]=d.useState(null),[o,p]=d.useState(null),[u,h]=d.useState(!1),[m,k]=d.useState(null),[g,y]=d.useState(null);if(d.useEffect(()=>{let v=!1;return Promise.all([window.api.otel.getConfig(),window.api.otel.status(),window.api.otel.configPath()]).then(([E,C,f])=>{v||(s(E),r(E),i(C),p(f))}).catch(E=>{console.warn("[telemetry] load failed:",E==null?void 0:E.message)}),()=>{v=!0}},[]),!a||!t)return e.jsx(V,{title:"loading…"});const c=JSON.stringify(a)!==JSON.stringify(t),N=(v,E)=>{k(null),r({...a,[v]:E})},R=async()=>{h(!0),k(null);try{const v=await window.api.otel.setConfig(a);!v.ok&&v.error&&k(v.error),s(v.config),r(v.config),i(v.status),y(Date.now())}catch(v){k(v instanceof Error?v.message:String(v))}finally{h(!1)}},A=()=>{k(null),r(t)};return e.jsxs("div",{className:"space-y-5",children:[e.jsxs("header",{className:"space-y-1",children:[e.jsx("h2",{className:"text-fg text-base font-medium",children:"OpenTelemetry export"}),e.jsx("p",{className:"text-fg-dim text-xs leading-relaxed",children:"When enabled, every classified transcript event (tool_use, todo_write, plan, usage, agent_spawn) is mirrored as a 0-duration OTLP/HTTP span. Point this at a local collector (Jaeger, Grafana Tempo, SigNoz, Honeycomb) for cross-session analysis."}),o&&e.jsxs("p",{className:"text-fg-faint text-[11px]",children:["stored at ",e.jsx("span",{className:"font-mono",children:o})]})]}),e.jsxs("section",{className:"border border-line rounded p-3 space-y-3",children:[e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-fg",children:"Export transcripts to OTEL"}),e.jsx("div",{className:"text-fg-faint text-xs",children:"Toggle off to disable all span emission immediately."})]}),e.jsx(W,{checked:a.enabled,onChange:v=>N("enabled",v),label:a.enabled?"on":"off"})]}),e.jsxs("label",{className:"block space-y-1",children:[e.jsx("span",{className:"text-fg-dim text-xs",children:"OTLP/HTTP traces endpoint"}),e.jsx("input",{type:"text",value:a.endpoint,onChange:v=>N("endpoint",v.target.value),placeholder:"http://localhost:4318/v1/traces",className:"w-full bg-bg border border-line rounded px-2 py-1 text-fg font-mono text-xs"})]}),e.jsxs("label",{className:"block space-y-1",children:[e.jsxs("span",{className:"text-fg-dim text-xs",children:["Headers (newline-separated ",e.jsx("span",{className:"font-mono",children:"Key: Value"}),")"]}),e.jsx("textarea",{value:a.headers,onChange:v=>N("headers",v.target.value),placeholder:"x-honeycomb-team: YOUR_API_KEY",rows:3,className:"w-full bg-bg border border-line rounded px-2 py-1 text-fg font-mono text-xs"})]}),e.jsxs("label",{className:"block space-y-1",children:[e.jsx("span",{className:"text-fg-dim text-xs",children:"Service name"}),e.jsx("input",{type:"text",value:a.serviceName,onChange:v=>N("serviceName",v.target.value),placeholder:"session-manager",className:"w-full bg-bg border border-line rounded px-2 py-1 text-fg font-mono text-xs"})]}),e.jsxs("div",{className:"flex items-start justify-between gap-3 pt-1",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-fg",children:"Include content (PII)"}),e.jsxs("div",{className:"text-fg-faint text-xs leading-relaxed",children:["Adds tool inputs, plan text, and agent prompts to span attributes. Off by default — matches upstream Claude Code's ",e.jsx("span",{className:"font-mono",children:"OTEL_LOG_USER_PROMPTS"})," opt-in."]})]}),e.jsx(W,{checked:a.includeContent,onChange:v=>N("includeContent",v),label:a.includeContent?"on":"off"})]})]}),n&&e.jsxs("section",{className:"border border-line rounded p-3 text-xs space-y-1",children:[e.jsx("div",{className:"text-fg-dim",children:"Runtime status"}),e.jsxs("div",{children:[e.jsx("span",{className:"text-fg-faint",children:"enabled:"})," ",e.jsx("span",{className:n.enabled?"text-accent":"text-fg-dim",children:String(n.enabled)})]}),n.error&&e.jsxs("div",{className:"text-red-400 break-words",children:[e.jsx("span",{className:"text-fg-faint",children:"error:"})," ",n.error]}),!n.error&&n.enabled&&e.jsx("div",{className:"text-fg-faint",children:"spans batched and exported in the background. Verify by enabling and pointing at a local Jaeger."})]}),e.jsxs("footer",{className:"flex items-center gap-3 pt-2 border-t border-line",children:[e.jsx("button",{onClick:R,disabled:!c||u,className:"px-3 py-1 text-xs rounded bg-accent text-bg disabled:opacity-50 disabled:cursor-not-allowed",children:u?"saving…":"Save"}),e.jsx("button",{onClick:A,disabled:!c||u,className:"px-3 py-1 text-xs rounded border border-line text-fg-dim hover:text-fg disabled:opacity-50 disabled:cursor-not-allowed",children:"Revert"}),m&&e.jsx("span",{className:"text-red-400 text-xs",children:m}),!m&&g&&!c&&e.jsx("span",{className:"text-fg-faint text-xs",children:"saved"})]})]})}function wt(){const[t,s]=d.useState(null),[n,i]=d.useState(null),[a,r]=d.useState("Copy"),[o,p]=d.useState(!1),[u,h]=d.useState(null),[m,k]=d.useState(null),[g,y]=d.useState(null),[c,N]=d.useState(!1),R=()=>{window.api.telemetry.status().then(i).catch(l=>{console.warn("[product-telemetry] status reload failed:",l==null?void 0:l.message)})};if(d.useEffect(()=>{let l=!1;return Promise.all([window.api.telemetry.getConfig(),window.api.telemetry.status()]).then(([S,b])=>{l||(s(S),i(b))}).catch(S=>{console.warn("[product-telemetry] load failed:",S==null?void 0:S.message)}),()=>{l=!0}},[]),!t||!n)return e.jsx(V,{title:"loading…"});const A=async l=>{k(null);try{const S=await window.api.telemetry.setConfig(l);s(S.config),i(S.status)}catch(S){k(S instanceof Error?S.message:String(S))}},v=l=>{A({...t,enabled:l})},E=async()=>{await De(t.installId)?(r("Copied"),setTimeout(()=>r("Copy"),1500)):ge.error("Could not copy install id to the clipboard")},C=async()=>{p(!0),h(null);try{const l=await window.api.telemetry.flushNow();h(`sent ${l.sent.length}, failed ${l.failed.length}`),R()}catch(l){h(l instanceof Error?l.message:String(l))}finally{p(!1)}},f=async()=>{if(c){N(!1);return}try{const l=await window.api.telemetry.recentRecords();y(l),N(!0)}catch(l){console.warn("[product-telemetry] recentRecords failed:",l instanceof Error?l.message:l)}},j=t.lastDailyFlushAt?new Date(t.lastDailyFlushAt).getTime()+vt:null;return e.jsxs("div",{className:"space-y-5","data-testid":"product-telemetry-section",children:[e.jsxs("header",{className:"space-y-1",children:[e.jsx("h2",{className:"text-fg text-base font-medium",children:"Product telemetry (bilko.run)"}),e.jsxs("p",{className:"text-fg-dim text-xs leading-relaxed",children:["Anonymous, strictly non-identifying usage/error reporting that helps improve this app. On by default; switchable off in one click. See"," ",e.jsx("span",{className:"font-mono",children:"session-manager-operations/architecture/telemetry.md"})," for the full data model."]})]}),e.jsx(Oe,{cfg:t,onSaved:(l,S)=>{s(l),i(S)}}),e.jsxs("section",{className:"border border-line rounded p-3 space-y-3",children:[e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-fg",children:"Send anonymous product telemetry"}),e.jsx("div",{className:"text-fg-faint text-xs",children:"Sent on start-up, after an update, and once a day. Strictly anonymous — an install id only, never an email, name, or other identifier."})]}),e.jsx(W,{checked:t.enabled,onChange:v,label:t.enabled?"on":"off"})]}),e.jsxs("div",{className:"space-y-1",children:[e.jsx("div",{className:"text-fg-dim text-xs",children:"Anonymous install id"}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"text",readOnly:!0,value:t.installId,"aria-label":"Anonymous install identifier",className:"flex-1 bg-bg border border-line rounded px-2 py-1 text-fg font-mono text-xs"}),e.jsx("button",{onClick:E,className:"px-2 py-1 text-xs rounded border border-line text-fg-dim hover:text-fg",children:a})]})]}),e.jsxs("div",{className:"space-y-1 text-xs",children:[e.jsx("div",{className:"text-fg-faint",children:"Endpoint"}),e.jsx("div",{className:"font-mono text-fg-dim break-all",children:t.endpoint})]}),e.jsxs("div",{className:"space-y-1 text-xs",children:[e.jsx("div",{className:"text-fg-faint",children:"Send cadence"}),e.jsx("div",{className:"text-fg-dim",children:"on start-up, after an update, and once a day"})]}),m&&e.jsx("div",{className:"text-red-400 text-xs",children:m})]}),n.pendingCount>0&&n.sentCount===0&&e.jsxs("section",{"data-testid":"telemetry-never-delivered-warning",className:"border border-red-400 rounded p-3 text-xs space-y-1 text-red-400",children:[e.jsx("div",{className:"font-medium",children:"Never delivered"}),e.jsxs("div",{className:"leading-relaxed",children:[n.pendingCount," record",n.pendingCount===1?"":"s",' queued but none have ever been sent to bilko.run on this install. Check the endpoint/network, or click "Send now" below.']})]}),e.jsxs("section",{className:"border border-line rounded p-3 text-xs space-y-1.5",children:[e.jsx("div",{className:"text-fg-dim",children:"Runtime status"}),e.jsxs("div",{children:[e.jsx("span",{className:"text-fg-faint",children:"last send:"})," ",xe(n.lastFlushAt)]}),e.jsxs("div",{children:[e.jsx("span",{className:"text-fg-faint",children:"next daily send due:"})," ",de(j?new Date(j).toISOString():null)]}),e.jsxs("div",{children:[e.jsx("span",{className:"text-fg-faint",children:"queued records:"})," ",n.pendingCount]}),e.jsxs("div",{children:[e.jsx("span",{className:"text-fg-faint",children:"delivered so far:"})," ",n.sentCount]}),e.jsxs("div",{children:[e.jsx("span",{className:"text-fg-faint",children:"de-duplicated:"})," ",n.dedupedAppends]}),e.jsxs("div",{children:[e.jsx("span",{className:"text-fg-faint",children:"evicted:"})," ",n.evictedCount]}),n.backlog&&e.jsxs("div",{className:"text-fg-faint",children:["last backlog drain (",n.backlog.reason,", ",de(n.backlog.ranAt),"):"," ",n.backlog.linesEnqueued," enqueued, ",n.backlog.linesConfirmed," confirmed,"," ",n.backlog.watermarksRewound," watermarks rewound"]}),n.lastError&&e.jsxs("div",{className:"text-red-400 break-words",children:[e.jsx("span",{className:"text-fg-faint",children:"last error:"})," ",n.lastError.message," ","(",xe(n.lastError.at),")"]})]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("button",{onClick:C,disabled:o,className:"px-3 py-1 text-xs rounded bg-accent text-bg disabled:opacity-50 disabled:cursor-not-allowed",children:o?"sending…":"Send now"}),u&&e.jsx("span",{className:"text-fg-faint text-xs",children:u})]}),e.jsxs("section",{className:"space-y-2",children:[e.jsx("button",{onClick:f,className:"px-3 py-1 text-xs rounded border border-line text-fg-dim hover:text-fg",children:c?"Hide last records":"Inspect last records sent"}),c&&e.jsx("pre",{"data-testid":"telemetry-inspector-json",className:"bg-bg border border-line rounded p-2 text-[11px] font-mono overflow-auto max-h-96 whitespace-pre-wrap",children:JSON.stringify(g??[],null,2)})]})]})}const te="relay_client_auth_unavailable",K=t=>(t==null?void 0:t.message)||String(t),Nt=["relay_client_unpaired","token_revoked"],pe={invalid_code:"That code didn't match. Check it and try again.",code_expired:"That code expired. Mint a new one on bilko.run → Desktop clients.",invalid_code_format:"Codes are 8 letters or numbers. Check it and try again.",rate_limited:"Too many tries. Wait a minute and try again.",relay_client_unpaired:"This computer is no longer connected to your account. Enter a new code to reconnect."};function ue(t){return Object.prototype.hasOwnProperty.call(pe,t)?pe[t]:`Pairing failed (${t})`}const me=t=>{const s=/^v?(\d+)\.(\d+)\.(\d+)/.exec(t.trim());return s?[Number(s[1]),Number(s[2]),Number(s[3])]:null};function kt(t,s){const n=me(t),i=me(s);if(!n||!i)return!1;for(let a=0;a<3;a++)if(n[a]!==i[a])return n[a]>i[a];return!1}function St({peer:t,onConnect:s,onRevoke:n,onConfirmSas:i}){var g;const a=t.status==="authenticated",[r,o]=d.useState(null),p=((g=Me())==null?void 0:g.latest)??null;d.useEffect(()=>{if(!a){o(null);return}let y=!1;return window.api.remoteClient.send(t.peerId,"cmd:app:version").then(c=>{y||o(typeof c=="string"?c:null)}).catch(()=>{y||o(null)}),()=>{y=!0}},[t.peerId,a]);const u=r!=null&&p!=null&&kt(p,r),h=t.status==="failed"&&t.reason===te,m=t.status==="connecting",k=new Date(t.pairedAt).toLocaleString();return e.jsxs("div",{className:"space-y-2","data-testid":"remote-machine-row",children:[e.jsx(ye,{testId:"remote-machine",icon:"terminal",title:t.machineName,badge:a?e.jsx(F,{tone:"default",className:"!text-sage !border-sage/40",children:"online"}):void 0,subtitle:`${t.peerId} · last seen ${k}`,action:e.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:[!a&&!h&&t.status!=="pending_sas"&&e.jsx(D,{testId:"remote-machine-connect",disabled:m,onClick:()=>s(t.peerId),children:m?"Connecting…":"Connect"}),e.jsx(je,{testId:"remote-machine-revoke",onClick:()=>n(t.peerId),children:"Revoke"})]})}),r&&e.jsxs("p",{"data-testid":"remote-machine-version",className:"text-[12.5px] leading-snug text-fg-faint",children:["v",r]}),u&&e.jsx("p",{"data-testid":"remote-machine-update-available",className:"text-[12.5px] leading-snug text-fg-faint",children:`Update available on ${t.machineName} (v${r} → v${p}) — update it on that machine`}),h&&e.jsx("p",{"data-testid":"remote-machine-unavailable",className:"text-[12.5px] leading-snug text-fg-faint",children:"The bilko.run relay does not support desktop clients yet"}),t.status==="pending_sas"&&t.sas&&e.jsx(ee,{peer:"other machine",testId:"remote-machine-sas",codeTestId:"remote-machine-sas-code",confirmTestId:"remote-machine-sas-confirm",code:t.sas,onConfirm:()=>i(t.peerId)})]})}function Ct({paired:t,revoked:s,peers:n,onAuthChanged:i,onAdded:a}){const r=z(l=>l.show),[o,p]=d.useState(""),[u,h]=d.useState(!1),[m,k]=d.useState(!1),[g,y]=d.useState(null),[c,N]=d.useState({}),R=async()=>{h(!0);try{const l=await window.api.remoteClient.pair(o.trim());l.ok?(k(!1),p(""),i()):l.error===te?k(!0):r("error",ue(l.error??"unknown"))}catch(l){r("error",K(l))}finally{h(!1)}},A=async()=>{h(!0);try{const l=await window.api.remoteClient.devices();l.ok?y(l.devices??[]):l.error===te?k(!0):r("error",ue(l.error??"unknown"))}catch(l){r("error",K(l))}finally{h(!1)}},v=async l=>{try{await window.api.remoteClient.add(l,c[l]??`Machine ${l.slice(0,8)}`),a()}catch(S){r("error",K(S))}},E=async()=>{try{await window.api.remoteClient.unpair(),y(null),i()}catch(l){r("error",K(l))}},C=m&&e.jsx("p",{"data-testid":"remote-machine-unavailable",className:"text-[12.5px] leading-snug text-fg-faint",children:"The bilko.run relay does not support desktop clients yet"});if(!t)return e.jsxs("div",{className:"space-y-2 mb-3","data-testid":"remote-client-pairing",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("input",{"data-testid":"remote-client-code",value:o,maxLength:8,onChange:l=>p(l.target.value),placeholder:"8-character code",className:"px-2 py-1 text-[13px] rounded border border-line bg-transparent"}),e.jsx(D,{testId:"remote-client-pair",disabled:u||o.trim().length===0,onClick:()=>{R()},children:"Pair"})]}),e.jsx("p",{className:"text-[12.5px] leading-snug text-fg-faint",children:"Mint a desktop code on your phone: Session Manager remote → Desktop clients."}),s&&e.jsx("p",{"data-testid":"remote-client-revoked",className:"text-[12.5px] leading-snug text-fg-faint",children:"This computer was disconnected from your account on bilko.run. Enter a new code to reconnect."}),C]});const f=new Set(n.map(l=>l.deviceId)),j=(g??[]).filter(l=>!f.has(l.deviceId));return e.jsxs("div",{className:"space-y-2 mb-3","data-testid":"remote-client-pairing",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(D,{testId:"remote-client-find",disabled:u,onClick:()=>{A()},children:"Find my devices"}),e.jsx(D,{testId:"remote-client-unpair",kind:"ghost",onClick:()=>{E()},children:"Unpair"})]}),C,g&&j.length===0&&e.jsx(se,{testId:"remote-client-devices-empty",children:"No other devices to add."}),j.map(l=>e.jsxs("div",{"data-testid":"remote-client-device",className:"flex items-center gap-2",children:[e.jsx("span",{className:"text-[12.5px] text-fg-faint shrink-0",children:l.email}),e.jsx(F,{tone:"default",className:l.isOnline?"!text-sage !border-sage/40":void 0,children:l.isOnline?"online":"offline"}),e.jsx("input",{"data-testid":"remote-client-name",value:c[l.deviceId]??`Machine ${l.deviceId.slice(0,8)}`,onChange:S=>N(b=>({...b,[l.deviceId]:S.target.value})),className:"px-2 py-1 text-[13px] rounded border border-line bg-transparent min-w-0"}),e.jsx(D,{testId:"remote-client-add",onClick:()=>{v(l.deviceId)},children:"Add"})]},l.deviceId))]})}function Et(){const t=z(c=>c.show),s=be(c=>c.openPanel),[n,i]=d.useState(!1),[a,r]=d.useState(!1),o=d.useCallback(async()=>{try{const c=(await window.api.remoteClient.authStatus()).paired;i(c),c&&r(!1)}catch(c){t("error",K(c))}},[t]),p=d.useCallback(c=>{c.reason&&Nt.includes(c.reason)&&(c.reason==="token_revoked"&&r(!0),o())},[o]),{peers:u,refresh:h,connect:m,confirmSas:k,revoke:g}=ze(p);d.useEffect(()=>{o()},[o]);const y=d.useCallback(c=>g(c),[g]);return e.jsxs("div",{"data-testid":"remote-machines",children:[e.jsx(J,{right:u.length>0?e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs("span",{className:"text-[11.5px] text-fg-faint",children:[u.length," paired"]}),e.jsx(D,{testId:"remote-machines-open-view",onClick:()=>s("remote"),children:"Open Remote view"})]}):void 0,children:"Remote machines"}),e.jsx(Ct,{paired:n,revoked:a,peers:u,onAuthChanged:()=>{o()},onAdded:()=>{h()}}),u.length===0?e.jsx(se,{testId:"remote-machines-empty",children:"No machines paired yet."}):e.jsx("div",{className:"space-y-2",children:u.map(c=>e.jsx(St,{peer:c,onConnect:m,onRevoke:y,onConfirmSas:k},c.peerId))})]})}function fe({label:t,hint:s,on:n,onChange:i,warn:a,disabled:r,first:o,testId:p}){return e.jsxs("div",{className:`flex items-center justify-between gap-4 px-[18px] py-[15px] ${o?"":"border-t border-rule"} ${r?"opacity-50":""}`,children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("div",{className:"text-sm font-semibold text-fg mb-0.5",children:t}),e.jsxs("div",{className:`text-[12.5px] leading-snug flex items-center gap-1.5 ${a?"text-honey-dark":"text-fg-faint"}`,children:[a&&e.jsx("span",{"aria-hidden":!0,children:"⚠"}),s]})]}),e.jsx("span",{"data-testid":p,className:"inline-flex",children:e.jsx(W,{checked:n,onChange:r?()=>{}:i,disabled:r})})]})}function Rt({device:t,online:s,onRevoke:n,revoking:i}){const a=t.lastConnectedAt?new Date(t.lastConnectedAt).toLocaleString():`paired ${new Date(t.issuedAt).toLocaleDateString()}`;return e.jsx(ye,{testId:"settings-remote-device",icon:"wifi",title:t.deviceName,badge:s?e.jsx(F,{tone:"default",className:"!text-sage !border-sage/40",children:"online"}):void 0,subtitle:`${t.deviceId} · last seen ${a}`,action:e.jsx(je,{testId:"settings-remote-revoke",onClick:()=>n(t.deviceId),disabled:i})})}const $=t=>(t==null?void 0:t.message)||String(t);function At(){var re;const t=z(x=>x.show),[s,n]=d.useState(null),[i,a]=d.useState(!0),[r,o]=d.useState(!1),[p,u]=d.useState(!1),[h,m]=d.useState("idle"),[k,g]=d.useState(""),[y,c]=d.useState(null),[N,R]=d.useState(!1),[A,v]=d.useState([]),[E,C]=d.useState(!1),f=d.useRef(null);d.useEffect(()=>{let x=!1;const w=()=>window.api.webRemote.getStatus().then(O=>{x||n(O)}).catch(()=>{});window.api.webRemote.getStatus().then(O=>{x||(n(O),a(!1))}).catch(O=>{x||(t("error",`Remote status: ${$(O)}`),a(!1))});const Ie=window.api.webRemote.onStatus(O=>n(O)),Te=window.api.webRemote.onTokenRevoked(()=>{t("warn","Remote device token was revoked. Re-pair to reconnect."),w()}),Pe=window.api.webRemote.onRevokedAll(({revokedCount:O})=>{t("info",`Panic: all ${O} device(s) revoked and sessions torn down.`),w()});return()=>{x=!0,Ie(),Te(),Pe()}},[t]),d.useEffect(()=>{if(h!=="enter-otp")return;const x=setTimeout(()=>{var w;return(w=f.current)==null?void 0:w.focus()},50);return()=>clearTimeout(x)},[h]);const j=async()=>n(await window.api.webRemote.getStatus()),l=async()=>{if(s){o(!0);try{const x=s.enabled?await window.api.webRemote.disable():await window.api.webRemote.enable();x&&x.ok===!1&&t("error",x.error||"Failed to change remote state."),await j()}catch(x){t("error",`Failed: ${$(x)}`)}finally{o(!1)}}},S=async()=>{if(s){u(!0);try{const x=s.remoteControlEnabled?await window.api.webRemote.disableControl():await window.api.webRemote.enableControl();x&&x.ok===!1&&t("error",x.error||"Failed to change command writes."),await j()}catch(x){t("error",`Failed: ${$(x)}`)}finally{u(!1)}}},b=async()=>{const x=k.trim().toUpperCase();if(!/^[A-Z0-9]{8}$/.test(x)){t("warn","Pairing code must be 8 alphanumeric characters.");return}m("pairing");try{const w=await window.api.webRemote.pair(x);if(!w.ok){t("error",w.error||"Pairing failed."),m("enter-otp");return}t("info","Device paired successfully."),g(""),m("idle"),await j()}catch(w){t("error",`Pairing error: ${$(w)}`),m("enter-otp")}},M=async x=>{c(x);try{const w=await window.api.webRemote.revokeDevice(x);if(!w.ok){t("error",w.error||"Revoke failed.");return}await j()}catch(w){t("error",`Revoke error: ${$(w)}`)}finally{c(null)}},P=async()=>{R(!0);try{const x=await window.api.webRemote.revokeAll();x.ok||t("error",x.error||"Panic revoke failed.")}catch(x){t("error",`Panic revoke error: ${$(x)}`)}finally{R(!1)}},ne=async()=>{if(E){C(!1);return}try{const x=await window.api.webRemote.auditTail(100);if(!x.ok){t("error",`Audit log: ${x.error||"unavailable"}`);return}v(x.lines??[]),C(!0)}catch(x){t("error",`Audit log: ${$(x)}`)}},_=async x=>{try{const w=await window.api.webRemote.confirmSas(x);w!=null&&w.ok||t("error",`SAS confirm failed: ${(w==null?void 0:w.error)??"unexpected state — reconnect the mobile app"}`)}catch(w){t("error",`SAS confirm failed: ${$(w)}`)}};if(i)return e.jsx("div",{className:"p-6 text-fg-faint text-sm",children:"Loading…"});const T=(s==null?void 0:s.enabled)??!1,L=(s==null?void 0:s.remoteControlEnabled)??!1,X=(s==null?void 0:s.connected)??!1,B=(s==null?void 0:s.e2eActive)??!1,Ee=(s==null?void 0:s.e2eAuthenticated)??!1,Re=(s==null?void 0:s.e2eState)??"idle",ae=(s==null?void 0:s.pendingSas)??null,Z=((s==null?void 0:s.clients)??[]).filter(x=>x.state==="pending_sas"&&x.pendingSas),G=(s==null?void 0:s.devices)??[],H=T&&X,Ae=X?((re=G.reduce((x,w)=>w.lastConnectedAt&&(!x||(x.lastConnectedAt??"")<w.lastConnectedAt)?w:x,null))==null?void 0:re.deviceId)??null:null;return e.jsxs("div",{"data-testid":"settings-remote",className:"p-4 max-w-[760px] space-y-[22px]",children:[Z.length>0?Z.map((x,w)=>e.jsxs("div",{"data-testid":"settings-remote-sas-row",children:[Z.length>1&&e.jsx("div",{className:"text-[12px] font-semibold text-fg-dim mb-1",children:x.clientId??"Phone"}),e.jsx(ee,{testId:w===0?"settings-remote-sas":void 0,codeTestId:w===0?"settings-remote-sas-code":void 0,confirmTestId:w===0?"settings-remote-sas-confirm":void 0,code:x.pendingSas,onConfirm:()=>_(x.key)})]},x.key)):ae&&e.jsx(ee,{testId:"settings-remote-sas",codeTestId:"settings-remote-sas-code",confirmTestId:"settings-remote-sas-confirm",code:ae,onConfirm:()=>_()}),Re==="failed"&&e.jsxs("div",{className:"flex items-center gap-3.5 px-5 py-4 rounded-2xl border border-[#eccdbe] bg-[#f8e8e0]",children:[e.jsx("span",{className:"w-4 h-4 rounded-full bg-accent text-white grid place-items-center text-[11px] font-bold shrink-0",children:"!"}),e.jsxs("div",{className:"text-[13px] text-[#9a3f1f]",children:[e.jsx("strong",{className:"font-semibold",children:"E2E key exchange failed."})," ",e.jsx("span",{className:"text-fg-dim",children:"The session key could not be established. Reconnect the mobile app to retry."})]})]}),e.jsxs("div",{className:`flex items-center gap-3.5 px-5 py-4 rounded-2xl border ${H?"border-[#e8cdb9] bg-[#f5e9df]":"border-line bg-bg-hi"}`,children:[e.jsx("span",{className:`w-[38px] h-[38px] rounded-[11px] shrink-0 grid place-items-center ${H?"bg-[#f0d9c8] text-accent":"bg-[#e4ebd6] text-sage"}`,children:e.jsx(Q,{name:H?"wifi":"shield",size:20})}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx("div",{className:"text-[14.5px] font-semibold text-fg mb-0.5",children:H?"Remote control is active":T?"Remote control is idle":"Remote control is off"}),e.jsx("div",{className:"text-[13px] text-fg-dim leading-snug",children:H?"A web session can send commands to this machine. Revoke below if unexpected.":T&&(s==null?void 0:s.linkState)==="repair-needed"?"The relay no longer recognises this device. Re-pair it with a new code to reconnect.":T?"The relay is on but no device is connected. Pair a device to begin.":"The relay connection is closed — no remote commands are accepted. Enable it below only when you need it."})]}),B&&Ee?e.jsx(F,{tone:"default",className:"!text-sage !border-sage/40",children:"E2E authenticated"}):B?e.jsx(F,{tone:"warn",children:"E2E · awaiting SAS"}):e.jsx(F,{tone:"dim",children:"relay only · not E2E yet"})]}),e.jsxs("div",{children:[e.jsx(J,{children:"Remote control"}),e.jsxs("div",{className:"bg-bg-hi border border-line rounded-2xl overflow-hidden",children:[e.jsx(fe,{first:!0,testId:"settings-remote-enable",label:"Allow remote control from the web",hint:"Master switch. When off, the relay refuses every connection.",on:T,disabled:r,onChange:()=>{r||l()}}),e.jsx(fe,{testId:"settings-remote-control",label:"Allow command writes (pty + scheduler)",hint:L?"A paired device can run shell commands and queue jobs on this machine.":"Paired devices can watch session output but cannot send commands.",warn:L,disabled:!T||p,on:L&&T,onChange:()=>{T&&!p&&S()}})]})]}),e.jsxs("div",{children:[e.jsx(J,{children:"Pair a device"}),h==="idle"?e.jsxs("div",{className:"flex items-center justify-between gap-4 bg-bg-hi border border-line rounded-2xl px-5 py-[18px]",children:[e.jsxs("div",{"data-testid":"settings-remote-pair-hint",className:"text-[13.5px] text-fg-dim leading-relaxed max-w-[420px]",children:["On your phone open ",e.jsx("strong",{className:"text-fg font-semibold",children:"bilko.run/projects/session-manager"}),", sign in, and tap"," ",e.jsx("strong",{className:"text-fg font-semibold",children:"Generate pairing code"}),". Enter the 8-character code here."]}),e.jsx(D,{testId:"settings-remote-pair-start",kind:"soft",icon:"link",onClick:()=>m("enter-otp"),children:"Pair Device…"})]}):e.jsxs("div",{className:"flex items-center gap-5 bg-bg-hi border border-accent/40 rounded-2xl px-6 py-[22px]",children:[e.jsx("span",{className:"w-[52px] h-[52px] rounded-xl bg-bg border border-line grid place-items-center text-accent shrink-0",children:e.jsx(Q,{name:"link",size:24})}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx("div",{className:"text-[11.5px] font-bold uppercase tracking-[0.8px] text-fg-faint mb-2",children:"Enter the code from your phone"}),e.jsx("input",{ref:f,"data-testid":"settings-remote-otp",value:k,onChange:x=>g(x.target.value.toUpperCase().replace(/[^A-Z0-9]/g,"")),onKeyDown:x=>{x.key==="Enter"&&b()},maxLength:8,placeholder:"XXXXXXXX",disabled:h==="pairing",className:"w-full bg-transparent border-0 outline-none font-mono text-[30px] font-semibold tracking-[6px] text-fg placeholder:text-fg-faint/40 disabled:opacity-50"})]}),e.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:[e.jsx(D,{testId:"settings-remote-pair-confirm",kind:"soft",onClick:b,disabled:h==="pairing"||k.length<8,children:h==="pairing"?"Pairing…":"Confirm"}),e.jsx(D,{kind:"ghost",onClick:()=>{m("idle"),g("")},disabled:h==="pairing",children:"cancel"})]})]})]}),e.jsxs("div",{children:[e.jsx(J,{right:G.length>0?e.jsxs("span",{className:"text-[11.5px] text-fg-faint",children:[G.length," paired"]}):void 0,children:"Paired devices"}),G.length===0?e.jsx(se,{children:"No devices paired yet."}):e.jsx("div",{className:"space-y-2",children:G.map(x=>e.jsx(Rt,{device:x,online:x.deviceId===Ae,onRevoke:M,revoking:y===x.deviceId},x.deviceId))})]}),e.jsx(Et,{}),e.jsxs("div",{children:[e.jsx(J,{right:e.jsx("button",{"data-testid":"settings-remote-audit-toggle",onClick:ne,className:"text-[12px] text-fg-faint hover:text-fg-dim transition-colors",children:E?"Hide":"Show last 100 · 0600, stays on this machine"}),children:"Audit log"}),E&&e.jsx("div",{className:"bg-bg-hi border border-line rounded-xl overflow-auto max-h-64 mb-2",children:A.length===0?e.jsx("p",{className:"p-4 text-[12.5px] text-fg-faint",children:"No entries today."}):e.jsx("pre",{"data-testid":"settings-remote-audit",className:"p-4 text-[12px] text-fg-dim font-mono whitespace-pre-wrap leading-relaxed",children:A.join(`
3
- `)})}),e.jsxs("p",{className:"text-[12px] text-fg-faint",children:["Full log at"," ",e.jsx("code",{className:"font-mono text-fg-faint",children:"~/.claude/session-manager/logs/remote-audit-YYYY-MM-DD.log"})," ","(0600, never leaves this machine)"]})]}),e.jsxs("div",{className:"flex items-center justify-between gap-4 bg-[#f8e8e0] border border-[#eccdbe] rounded-2xl px-5 py-4",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsxs("div",{className:"flex items-center gap-2 mb-1",children:[e.jsx("span",{className:"w-4 h-4 rounded-full bg-accent text-white grid place-items-center text-[11px] font-bold shrink-0",children:"!"}),e.jsx("span",{className:"text-sm font-semibold text-[#9a3f1f]",children:"Panic — disconnect & revoke everything"})]}),e.jsx("div",{className:"text-[12.5px] text-fg-dim leading-snug max-w-[440px]",children:"Tears down every active session and invalidates all paired-device tokens. Devices must re-pair. Use if you suspect compromise."})]}),e.jsx("button",{"data-testid":"settings-remote-revoke-all",onClick:P,disabled:N,className:"shrink-0 rounded-lg bg-accent text-white px-[18px] py-2 text-[13px] font-semibold hover:opacity-90 disabled:opacity-50 transition-opacity",children:N?"Revoking…":"Revoke all"})]})]})}const q="px-2 py-1 rounded border border-line text-xs text-fg hover:bg-bg-hi disabled:opacity-50";function It(){const t=$e(),s=z(p=>p.show),n=async p=>{try{const h=await p();h&&h.ok===!1&&s("error",h.error??"Update action failed")}catch(u){s("error",u instanceof Error?u.message:String(u))}};if(!t)return null;const{state:i}=t,a=i==="unsupported",r=t.latest?`v${t.latest}`:"—",o=()=>{window.confirm("Installing now will stop all running jobs and terminals. Continue?")&&n(()=>window.api.updater.install(!0))};return e.jsxs("section",{"data-testid":"settings-updates",className:"border border-line rounded p-3 space-y-3",children:[e.jsxs("div",{className:"space-y-1",children:[e.jsx("span",{className:"text-fg text-xs font-medium",children:"Updates"}),e.jsxs("div",{className:"text-fg-dim text-xs flex gap-4",children:[e.jsxs("span",{children:["Current v",t.current]}),e.jsxs("span",{children:["Latest ",r]})]})]}),a?e.jsx("p",{className:"text-fg-faint text-[11px]",children:"Auto-update runs only in the installed Windows app or the Linux AppImage. npx builds update via npx."}):e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx(W,{checked:t.enabled,onChange:p=>void n(()=>window.api.updater.setEnabled(p)),label:"Update automatically"}),e.jsx("button",{type:"button","data-testid":"updates-check",className:q,disabled:i==="checking"||i==="downloading",onClick:()=>void n(()=>window.api.updater.check()),children:"Check for updates"})]}),i==="checking"&&e.jsx("p",{className:"text-fg-dim text-xs",children:"Checking…"}),i==="downloading"&&e.jsxs("p",{className:"text-fg-dim text-xs",children:["Downloading… ",t.progress!=null?`${Math.round(t.progress)}%`:""]}),i==="ready"&&e.jsxs("button",{type:"button","data-testid":"updates-install",className:q,onClick:()=>void n(()=>window.api.updater.install(!1)),children:["Restart to install v",t.latest]}),i==="deferred"&&e.jsxs("div",{className:"space-y-2",children:[e.jsxs("p",{className:"text-fg-dim text-xs",children:["Waiting for ",t.busyReason??"work"," to finish"]}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("button",{type:"button",className:q,onClick:o,children:"Install now"}),e.jsx("button",{type:"button",className:q,onClick:()=>void n(()=>window.api.updater.cancelDeferred()),children:"Cancel"})]})]}),i==="error"&&e.jsx("p",{className:"text-red-400 text-xs",children:t.error})]})}const Tt={compact:"Compact",roomy:"Roomy"};function Pt(){const{model:t,setModel:s}=Le(),{catalog:n}=_e(null),{density:i,setDensity:a}=Fe();return e.jsxs("div",{className:"p-4 max-w-3xl space-y-5 text-sm",children:[e.jsxs("header",{className:"space-y-1",children:[e.jsx("h2",{className:"text-fg text-base font-medium",children:"Session Manager preferences"}),e.jsx("p",{className:"text-fg-dim text-xs leading-relaxed",children:"Native app preferences, stored locally — not part of Claude Code's own settings."})]}),e.jsxs("section",{className:"border border-line rounded p-3 space-y-3",children:[e.jsxs("div",{className:"block space-y-1",children:[e.jsx("span",{className:"text-fg-dim text-xs",children:"Default model for raw sessions"}),e.jsx(ie,{options:Ue(n),value:t,onChange:s,mono:!0}),e.jsx("span",{className:"text-fg-faint text-[11px]",children:"Used when opening a raw interactive session without an explicit per-launch override."})]}),e.jsxs("div",{className:"block space-y-1",children:[e.jsx("span",{className:"text-fg-dim text-xs",children:"Density"}),e.jsx(ie,{options:["compact","roomy"],value:i,onChange:r=>a(r),labels:Tt}),e.jsx("span",{className:"text-fg-faint text-[11px]",children:"Compact tightens spacing across the app's chrome; Roomy is the default."})]})]}),e.jsx(It,{})]})}function Ot({cwd:t}){const[s,n]=d.useState(!1),[i,a]=d.useState(!1),[r,o]=d.useState(!1),[p,u]=d.useState(!1);d.useEffect(()=>{var y,c;let g=!1;return n(!1),Promise.resolve((c=(y=window.api.promptSessions)==null?void 0:y.getWorktreeDisabled)==null?void 0:c.call(y,{cwd:t})).then(N=>{g||(a((N==null?void 0:N.disabled)??!1),o((N==null?void 0:N.disabled)??!1),n(!0))},()=>{g||n(!0)}),()=>{g=!0}},[t]);const h=s&&r!==i,m=()=>{var g,y;u(!0),Promise.resolve((y=(g=window.api.promptSessions)==null?void 0:g.setWorktreeDisabled)==null?void 0:y.call(g,{cwd:t,disabled:r})).then(c=>{a((c==null?void 0:c.disabled)??r),o((c==null?void 0:c.disabled)??r)}).catch(c=>ge.error(c instanceof Error?c.message:String(c))).finally(()=>u(!1))},k=()=>o(i);return e.jsxs("div",{"data-testid":"settings-epic-isolation",className:"border-b border-line bg-bg-elev px-3 py-2.5 text-[11.5px] leading-snug",children:[e.jsxs("label",{className:"flex items-start gap-2",children:[e.jsx("input",{type:"checkbox",checked:r,disabled:!s||p,onChange:g=>o(g.target.checked),"data-testid":"settings-epic-isolation-checkbox",className:"mt-0.5"}),e.jsxs("span",{children:[e.jsx("span",{className:"font-semibold text-fg",children:"Disable Epic worktree isolation for this project"}),e.jsxs("span",{className:"block text-fg-dim",children:["Off by default — each active Epic runs in its own isolated ",e.jsx("code",{children:"git worktree"})," (branch"," ",e.jsx("code",{children:"sm-epic/<id>"}),"), merged back to main at an explicit checkpoint. Turning this on makes every new Epic in this project run directly in the shared working tree instead, same as before this feature existed."]})]})]}),h&&e.jsx(ve,{dirty:h,busy:p,onSave:m,onRevert:k})]})}const he={user:{title:"Global settings",body:"This is the real ~/.claude/settings.json on disk — the same file the claude CLI reads on every session it starts. Edits apply machine-wide, to every project and every session."},project:{title:"Project settings",body:"This is the real <project>/.claude/settings.json on disk, checked into git. Edits apply to every current and future session in this project, for anyone who clones it."},local:{title:"Local settings",body:"This is the real <project>/.claude/settings.local.json on disk, gitignored. Edits apply to every current and future session in this project, on this machine only."}};function Dt(){const t=Be(),s=le(b=>b.tabs),n=le(b=>b.activeTabId),i=s.find(b=>b.id===n)??null,a=(i==null?void 0:i.cwd)??null,r=be(b=>b.navFace),o=d.useRef(!1),p=d.useRef(r),[u,h]=d.useState(()=>r==="project"&&a?"project":"user"),[m,k]=d.useState("effective");d.useEffect(()=>{if(p.current!==r){if(p.current=r,o.current){o.current=!1;return}h(r==="project"&&a?"project":"user")}},[r,a]),d.useEffect(()=>{r==="project"&&(m==="telemetry"||m==="remote"||m==="app")&&k("effective")},[r,m]);const g=b=>{o.current=!0,h(b)},y=Xe(oe,t,a),c=y[u]??null,N=Y(b=>b.files),R=Y(b=>b.setDraft),A=Y(b=>b.saveJson),v=Y(b=>b.revert),[E,C]=d.useState(null),f=d.useMemo(()=>Ge(He(N,y)),[N,y]),j=(b,M,P)=>{var B;const _=y[P??u];if(!_)return;C(null);const T=((B=N[_])==null?void 0:B.draftRaw)??"";let L={};if(T.trim())try{L=JSON.parse(T)}catch{L={}}const X=Ke(L,b,M);R(_,JSON.stringify(X,null,2)+`
4
- `)};if(!t)return e.jsx(V,{title:"loading…"});const l=c?N[c]:null,S=b=>b!=="user"&&!a;return e.jsx(Ve,{toolbar:e.jsxs(e.Fragment,{children:[e.jsx(qe,{options:[{key:"effective",label:"Guided"},{key:"tree",label:"Tree"},{key:"raw",label:"Raw"},...r==="home"?[{key:"telemetry",label:"Telemetry"},{key:"remote",label:"Phone remote"},{key:"app",label:"Session Manager"}]:[]],active:m,onChange:k}),m!=="telemetry"&&m!=="remote"&&m!=="app"&&e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"mx-2 text-fg-faint",children:"·"}),e.jsx(We,{scopes:oe.scopes,active:u,onChange:g,annotate:b=>{if(S(b))return{exists:!1};const M=y[b];if(!M)return null;const P=N[M];return{exists:(P==null?void 0:P.exists)??!1,dirty:(P==null?void 0:P.dirty)??!1}}}),e.jsx("span",{className:"ml-3 text-fg-faint truncate",children:m==="effective"?`overrides → ${c??"—"}`:c??"—"})]}),e.jsx("div",{className:"flex-1"}),e.jsx("a",{href:"https://code.claude.com/docs/en/settings",target:"_blank",rel:"noreferrer",className:"text-fg-faint hover:text-fg-dim underline-offset-2 hover:underline",children:"schema reference ↗"})]}),footer:m!=="telemetry"&&m!=="remote"&&m!=="app"&&c&&l?e.jsx(ve,{dirty:l.dirty,busy:l.busy,parseError:E||l.parseError,lastSavedAt:l.lastSavedAt,leading:l.exists?null:e.jsx("span",{children:"file will be created on save"}),onSave:async()=>{C(null);const b=await A(c);b.ok||C(b.error??"save failed")},onRevert:()=>{C(null),v(c)}}):null,children:m==="telemetry"?e.jsx(yt,{}):m==="remote"?e.jsx(At,{}):m==="app"?e.jsx(Pt,{}):e.jsxs(e.Fragment,{children:[e.jsxs("div",{"data-testid":"settings-scope-note",className:"sticky top-0 z-10 border-b border-line bg-bg-elev px-3 py-2 text-[11.5px] leading-snug",children:[e.jsx("span",{className:"font-semibold text-fg",children:he[u].title}),e.jsx("span",{className:"mx-1.5 text-fg-faint",children:"·"}),e.jsx("span",{className:"text-fg-dim",children:he[u].body}),c&&e.jsx("span",{className:"ml-1.5 font-mono text-fg-faint",children:c})]}),u==="project"&&a&&e.jsx(Ot,{cwd:a}),S(u)?e.jsx(V,{title:"no active project",hint:"open a tab in a project directory to edit project or local settings"}):m==="effective"?e.jsx(rt,{node:f,targetScope:u,onOverride:j,schema:ce()}):m==="tree"?e.jsx(Ye,{node:f,targetScope:u,onOverride:j,schema:ce()}):c&&l?e.jsx(Je,{path:c,value:l.draftRaw,onChange:b=>{C(null),R(c,b)}}):e.jsx(V,{title:"resolving…"})]})})}const Kt=d.memo(Dt);export{he as SETTINGS_SCOPE_NOTES,Kt as Settings};