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.
- package/dist/assets/{AgentLibrary-BpW0pGgk.js → AgentLibrary-FO2ei2wB.js} +1 -1
- package/dist/assets/{Button-DZX5guWb.js → Button-CeIOXUPU.js} +1 -1
- package/dist/assets/{DataModel-WG4pG_4z.js → DataModel-D0p__Hil.js} +1 -1
- package/dist/assets/{History-D58whvn_.js → History-DVqQ5azN.js} +1 -1
- package/dist/assets/{Hooks-DCI9ezg4.js → Hooks-DkTnRHyT.js} +1 -1
- package/dist/assets/{Library-YqMi-vsA.js → Library-l7KS6pkE.js} +1 -1
- package/dist/assets/{ListDetail-ap4W5z_Z.js → ListDetail-DDw8fuRT.js} +1 -1
- package/dist/assets/{McpServers-CT0pBTF8.js → McpServers-UUNIxk_3.js} +1 -1
- package/dist/assets/{Memory-BODDUBy4.js → Memory-CNYUoQ8C.js} +1 -1
- package/dist/assets/{Panel-CeOfUS9B.js → Panel-Bb54soc2.js} +1 -1
- package/dist/assets/{Permissions-DyOqpLQx.js → Permissions-D-dNdV3f.js} +1 -1
- package/dist/assets/{Plugins-DBXzKjL2.js → Plugins-DhcIXRhQ.js} +2 -2
- package/dist/assets/{ProvenanceBadge-C47IX1Q6.js → ProvenanceBadge-DEAkDNjA.js} +1 -1
- package/dist/assets/{RemoteMachines-CtkAJvkZ.js → RemoteMachines-xCEgvTGU.js} +1 -1
- package/dist/assets/{SaveBar-YMtzcCzP.js → SaveBar-BSEs7ecA.js} +1 -1
- package/dist/assets/{Scheduler-BNiYg0YD.js → Scheduler-BIKJIU-C.js} +5 -5
- package/dist/assets/{ScopeSwitcher-Dgi8CiDi.js → ScopeSwitcher-DcaFyAV-.js} +1 -1
- package/dist/assets/Settings-vcdWy_Jx.js +4 -0
- package/dist/assets/{SkillReferenceGraph-CaFsccNI.js → SkillReferenceGraph-CLrJWTPd.js} +1 -1
- package/dist/assets/{Skills-BeCMBQ3l.js → Skills-CYM4rBEC.js} +1 -1
- package/dist/assets/{SystemPrompt-C3WmcPEF.js → SystemPrompt-BWNIu-iY.js} +1 -1
- package/dist/assets/{TagLibrary-DP4GH7Dr.js → TagLibrary-CYdwHK6m.js} +1 -1
- package/dist/assets/{TiptapBody-CXCMvRr_.js → TiptapBody-Kt90lHrD.js} +1 -1
- package/dist/assets/{Toggle-TY-Wa8hv.js → Toggle-D1OXWYOz.js} +1 -1
- package/dist/assets/{ViewTabs-CaacRnp6.js → ViewTabs-BeQR3CKh.js} +1 -1
- package/dist/assets/{historyTotals-C28IlVVQ.js → historyTotals-B0qafrRo.js} +1 -1
- package/dist/assets/{index-DdEMkmbi.js → index-Bq9uH08R.js} +201 -201
- package/dist/assets/{index-DAMBm3UN.css → index-Dd7UQabV.css} +1 -1
- package/dist/assets/{settingsSchema-7Yg6dNNB.js → settingsSchema-0ZksSAPB.js} +1 -1
- package/dist/assets/{skillFrontmatter-Dcuys1km.js → skillFrontmatter-C0nh8yfa.js} +1 -1
- package/dist/assets/{useAgentsDirWatch-B0H4M-TT.js → useAgentsDirWatch-DPkoBybS.js} +1 -1
- package/dist/assets/{useRemotePeers-Zp9jchCS.js → useRemotePeers-BDJMUihy.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/main/build-info.json +4 -4
- package/src/main/chatRunner.cjs +54 -14
- package/src/main/crashDiagnostics.cjs +18 -13
- package/src/main/health.cjs +129 -4
- package/src/main/index.cjs +57 -18
- package/src/main/ipcSchemas.cjs +18 -4
- package/src/main/lib/activeSessions.cjs +51 -34
- package/src/main/lib/claude/agentModelResolve.cjs +5 -3
- package/src/main/lib/claude/personaToolScope.cjs +68 -0
- package/src/main/lib/epic/archivedTranscript.cjs +91 -0
- package/src/main/lib/epic/archivedTranscriptMigration.cjs +84 -0
- package/src/main/lib/epic/delegationReadiness.cjs +2 -0
- package/src/main/lib/epic/epicSettle.cjs +21 -5
- package/src/main/lib/epic/epicValidationHook.cjs +7 -0
- package/src/main/lib/git/depHealth.cjs +161 -0
- package/src/main/lib/git/worktreeDeps.cjs +158 -5
- package/src/main/lib/machineProfile.cjs +2 -0
- package/src/main/lib/mcpToolCatalog.cjs +3 -1
- package/src/main/lib/opsRetentionSettings.cjs +183 -0
- package/src/main/lib/persona-macro/macroLibrary.cjs +75 -0
- package/src/main/lib/prd/prdCreate.cjs +25 -0
- package/src/main/lib/prd/prdWorktreeReach.cjs +2 -2
- package/src/main/lib/prd/workTypeLibrary.cjs +7 -3
- package/src/main/lib/process/procGroupRss.cjs +61 -0
- package/src/main/lib/remoteTranscriptTail.cjs +69 -2
- package/src/main/lib/scheduler/queueHistory.cjs +68 -9
- package/src/main/lib/scheduler/runLogRetention.cjs +129 -43
- package/src/main/lib/scheduler/schedulerBatch.cjs +7 -3
- package/src/main/lib/scheduler/staleTmpSweep.cjs +117 -0
- package/src/main/lib/telemetry/auditLog.cjs +51 -6
- package/src/main/lib/telemetry/crashTelemetry.cjs +49 -1
- package/src/main/lib/telemetry/opsErrorLog.cjs +30 -10
- package/src/main/lib/telemetry/telemetryClient.cjs +5 -2
- package/src/main/scheduler/prdParser.cjs +50 -0
- package/src/main/scheduler/prdPaths.cjs +61 -16
- package/src/main/scheduler/reviewLadder.cjs +24 -1
- package/src/main/scheduler.cjs +214 -37
- package/src/main/templates/PRD_AUTHORING.md +2 -2
- package/src/main/webRemote.cjs +82 -91
- package/src/preload/api.d.ts +41 -3
- package/src/preload/index.cjs +9 -0
- package/dist/assets/Settings-B4LuoRnd.js +0 -4
package/src/main/webRemote.cjs
CHANGED
|
@@ -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
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
|
|
1112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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)
|
|
1249
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
package/src/preload/api.d.ts
CHANGED
|
@@ -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;
|
package/src/preload/index.cjs
CHANGED
|
@@ -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};
|