claude-code-session-manager 0.122.1 → 0.123.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-C2qUX9WU.js → AgentLibrary-DxWN3FSN.js} +2 -2
- package/dist/assets/{Button-ssBzvFha.js → Button-CISLFSKb.js} +1 -1
- package/dist/assets/DataModel-CYPbhGrx.js +1 -0
- package/dist/assets/{History-BRdRp1J_.js → History-Fhouektw.js} +2 -2
- package/dist/assets/{Hooks--hiGPBQy.js → Hooks-COfIcyYV.js} +3 -3
- package/dist/assets/{Library-0XYvkhh2.js → Library-Bt5WOyyu.js} +7 -7
- package/dist/assets/ListDetail-DH7uzYd5.js +1 -0
- package/dist/assets/{McpServers-CCjHSwFm.js → McpServers-CwyVEzMN.js} +2 -2
- package/dist/assets/{Memory-C7RXoVO4.js → Memory-k9TWT9N4.js} +8 -8
- package/dist/assets/{Panel-C92O_aAH.js → Panel-Di8JqCYN.js} +1 -1
- package/dist/assets/{Permissions-DuTJ1FUb.js → Permissions-FFs6Rq1H.js} +3 -3
- package/dist/assets/{Plugins-DSXsP4hM.js → Plugins-uK5vl_RE.js} +2 -2
- package/dist/assets/{ProvenanceBadge-CWdDkU1m.js → ProvenanceBadge-CGDyGDoN.js} +1 -1
- package/dist/assets/{SaveBar-ob2dJJjH.js → SaveBar-Cnwc_XfF.js} +1 -1
- package/dist/assets/{Scheduler-Du4HgPKY.js → Scheduler-aACa6_mC.js} +7 -7
- package/dist/assets/{ScopeSwitcher-Dv_6M_Ci.js → ScopeSwitcher-CoG3CSlC.js} +1 -1
- package/dist/assets/Settings-BG0RM6Di.js +4 -0
- package/dist/assets/{SkillReferenceGraph-Dx1Qs8Y0.js → SkillReferenceGraph-COffGu_A.js} +1 -1
- package/dist/assets/Skills--qQpFjjf.js +2 -0
- package/dist/assets/SystemPrompt-Cr9RRSiZ.js +1 -0
- package/dist/assets/TagLibrary-D_ZbXwBv.js +1 -0
- package/dist/assets/{TiptapBody-Cihe5er3.js → TiptapBody-CCHsa8Jv.js} +1 -1
- package/dist/assets/{Toggle-CpYVC8bF.js → Toggle-BUOj4pws.js} +1 -1
- package/dist/assets/{ViewTabs-C3FN6P_C.js → ViewTabs-BdpsL7yr.js} +1 -1
- package/dist/assets/{historyTotals-C4Rs37Cd.js → historyTotals-Dx6ca881.js} +1 -1
- package/dist/assets/{index-Dd7UQabV.css → index-BnKaYERU.css} +1 -1
- package/dist/assets/{index-S-F5jp_h.js → index-CkNGj8FA.js} +151 -151
- package/dist/assets/{settingsSchema-BZcdzHTQ.js → settingsSchema-BZhkpr9e.js} +1 -1
- package/dist/assets/{skillFrontmatter-EfNERt4b.js → skillFrontmatter-N13KGqbs.js} +1 -1
- package/dist/assets/{useAgentsDirWatch-BfbrFvB3.js → useAgentsDirWatch-De9UsbDt.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/plugins/session-manager-dev/skills/develop/SKILL.md +29 -25
- package/scripts/scheduler-mcp-server.cjs +124 -74
- package/src/main/agentLibrary.cjs +8 -3
- package/src/main/build-info.json +4 -4
- package/src/main/crashDiagnostics.cjs +24 -0
- package/src/main/historyAggregator.cjs +41 -7
- package/src/main/index.cjs +13 -3
- package/src/main/ipcSchemas.cjs +30 -22
- package/src/main/lib/activeSessions.cjs +93 -32
- package/src/main/lib/epic/delegationReadiness.cjs +2 -0
- package/src/main/lib/epic/delegationSelfHeal.cjs +77 -0
- package/src/main/lib/epic/epicMint.cjs +1 -1
- package/src/main/lib/epic/epicStandingInstructions.cjs +73 -0
- package/src/main/lib/epic/guardSpawnSettings.cjs +76 -0
- package/src/main/lib/feedback-remote/crossProjectFeedback.cjs +9 -2
- package/src/main/lib/feedback-remote/remoteClientSessions.cjs +3 -11
- package/src/main/lib/feedback-remote/remoteE2eCrypto.cjs +1 -1
- package/src/main/lib/git/gitWorktree.cjs +9 -4
- package/src/main/lib/git/worktreeDeps.cjs +13 -2
- package/src/main/lib/git/worktreeSetup.cjs +212 -0
- package/src/main/lib/mcpToolCatalog.cjs +31 -2
- package/src/main/lib/persona-macro/agentPersonaSchema.cjs +4 -0
- package/src/main/lib/persona-macro/shippedPersonaSeeds.cjs +23 -0
- package/src/main/lib/prd/prdAgentType.cjs +28 -0
- package/src/main/lib/prd/prdBodyPatch.cjs +138 -0
- package/src/main/lib/prd/prdCreate.cjs +205 -32
- package/src/main/lib/prd/prdGateFiles.cjs +31 -0
- package/src/main/lib/scheduler/definitionOfDone.cjs +24 -3
- package/src/main/lib/scheduler/queueStore.cjs +5 -4
- package/src/main/lib/scheduler/scheduleJobSchema.cjs +1 -0
- package/src/main/lib/scheduler/schedulerBatch.cjs +43 -5
- package/src/main/lib/scheduler/schedulerPaths.cjs +2 -0
- package/src/main/lib/scheduler/watchdogHelpers.cjs +114 -22
- package/src/main/lib/telemetry/mainHeapSnapshot.cjs +69 -0
- package/src/main/lib/telemetry/telemetryBoot.cjs +25 -3
- package/src/main/lib/telemetry/telemetryClient.cjs +12 -0
- package/src/main/lib/telemetry/telemetryCounters.cjs +25 -2
- package/src/main/pty.cjs +0 -53
- package/src/main/runVerify.cjs +38 -11
- package/src/main/scheduler/prdParser.cjs +3 -0
- package/src/main/scheduler/prdPaths.cjs +48 -2
- package/src/main/scheduler.cjs +124 -29
- package/src/main/seedDevPlugin.cjs +1 -1
- package/src/main/seedSchedulerMcp.cjs +1 -1
- package/src/main/templates/PRD_AUTHORING.md +8 -4
- package/src/main/webRemote.cjs +38 -132
- package/src/preload/api.d.ts +4 -52
- package/src/preload/index.cjs +0 -24
- package/src/seed/agents/dev-lead.md +2 -1
- package/src/seed/agents/validator.md +2 -1
- package/dist/assets/DataModel-CENoTvc2.js +0 -1
- package/dist/assets/ListDetail-CkKXAM59.js +0 -1
- package/dist/assets/RemoteMachines-BFmbIGpW.js +0 -3
- package/dist/assets/Settings-DMoI0cqB.js +0 -4
- package/dist/assets/Skills-DYWQTQB1.js +0 -2
- package/dist/assets/SystemPrompt-Df0IfOuI.js +0 -1
- package/dist/assets/TagLibrary-BuMsGx-2.js +0 -1
- package/dist/assets/useRemotePeers-CKVakQPc.js +0 -1
- package/src/main/lib/feedback-remote/relayClientAuth.cjs +0 -133
- package/src/main/remoteClient.cjs +0 -334
- package/src/main/remoteClientIpc.cjs +0 -133
package/src/main/webRemote.cjs
CHANGED
|
@@ -98,6 +98,31 @@ function defaultConfig() {
|
|
|
98
98
|
return { remoteEnabled: false, remoteControlEnabled: false, devices: [] };
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
// Retired desktop-to-desktop clients: drop pins that came from one (clientId is a
|
|
102
|
+
// string) so a leftover keypair is never auto-trusted; strip clientId from the rest.
|
|
103
|
+
function pruneDesktopPins(cfg) {
|
|
104
|
+
if (!cfg || !Array.isArray(cfg.devices)) return cfg;
|
|
105
|
+
const devices = cfg.devices.map((d) => {
|
|
106
|
+
if (!d || !Array.isArray(d.verifiedPeerPubKeys)) return d;
|
|
107
|
+
const verifiedPeerPubKeys = d.verifiedPeerPubKeys
|
|
108
|
+
.filter((p) => !(p && typeof p === 'object' && typeof p.clientId === 'string'))
|
|
109
|
+
.map((p) => {
|
|
110
|
+
if (!p || typeof p !== 'object') return p;
|
|
111
|
+
const { clientId: _drop, ...rest } = p;
|
|
112
|
+
return rest;
|
|
113
|
+
});
|
|
114
|
+
return { ...d, verifiedPeerPubKeys };
|
|
115
|
+
});
|
|
116
|
+
return { ...cfg, devices };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Delete the orphaned desktop-client files. Never throws.
|
|
120
|
+
function removeOrphanedClientFiles(dir) {
|
|
121
|
+
for (const name of ['remote-peers.json', 'relay-client.json']) {
|
|
122
|
+
try { fs.rmSync(path.join(dir, name), { force: true }); } catch { /* best effort */ }
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
101
126
|
function loadConfigSync() {
|
|
102
127
|
const now = Date.now();
|
|
103
128
|
if (_configCache && now - _configCacheAt < CONFIG_CACHE_TTL_MS) {
|
|
@@ -105,7 +130,7 @@ function loadConfigSync() {
|
|
|
105
130
|
}
|
|
106
131
|
try {
|
|
107
132
|
const raw = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
108
|
-
_configCache = { ...defaultConfig(), ...JSON.parse(raw) };
|
|
133
|
+
_configCache = pruneDesktopPins({ ...defaultConfig(), ...JSON.parse(raw) });
|
|
109
134
|
} catch {
|
|
110
135
|
_configCache = defaultConfig();
|
|
111
136
|
}
|
|
@@ -120,7 +145,7 @@ async function loadConfig() {
|
|
|
120
145
|
}
|
|
121
146
|
try {
|
|
122
147
|
const raw = await fsp.readFile(CONFIG_PATH, 'utf8');
|
|
123
|
-
_configCache = { ...defaultConfig(), ...JSON.parse(raw) };
|
|
148
|
+
_configCache = pruneDesktopPins({ ...defaultConfig(), ...JSON.parse(raw) });
|
|
124
149
|
} catch {
|
|
125
150
|
_configCache = defaultConfig();
|
|
126
151
|
}
|
|
@@ -142,14 +167,14 @@ async function saveConfig(data) {
|
|
|
142
167
|
|
|
143
168
|
// ─── Audit log ────────────────────────────────────────────────────────────────
|
|
144
169
|
|
|
145
|
-
// Format: <ISO> <type> deviceId=<id> client=<
|
|
170
|
+
// Format: <ISO> <type> deviceId=<id> client=<connKey|-> msgId=<uuid> result=ok|error:<code>
|
|
146
171
|
// NEVER log token values or payload content.
|
|
147
172
|
async function auditLog(ts, type, deviceId, msgId, result, client) {
|
|
148
173
|
try {
|
|
149
174
|
const ymd = ts.slice(0, 10);
|
|
150
175
|
const logPath = path.join(AUDIT_LOG_DIR, `remote-audit-${ymd}.log`);
|
|
151
176
|
const clean = (s) => String(s ?? '').replace(/[\r\n\t]+/g, ' ').slice(0, 200);
|
|
152
|
-
const line = `${ts} ${clean(type)} deviceId=${clean(deviceId) || '-'} client=${clean(client && (client.
|
|
177
|
+
const line = `${ts} ${clean(type)} deviceId=${clean(deviceId) || '-'} client=${clean(client && (client.key)) || '-'} msgId=${clean(msgId) || '-'} result=${result}\n`;
|
|
153
178
|
const handle = await fsp.open(logPath, 'a', 0o600);
|
|
154
179
|
try {
|
|
155
180
|
await handle.write(line);
|
|
@@ -257,10 +282,10 @@ function isPinnedPeerKey(device, pubKey) {
|
|
|
257
282
|
}
|
|
258
283
|
|
|
259
284
|
// Append a pin (never removing another client's), dedupe by pubKey, keep the newest MAX.
|
|
260
|
-
function addPinnedPeerKey(device, pubKey
|
|
285
|
+
function addPinnedPeerKey(device, pubKey) {
|
|
261
286
|
const prev = Array.isArray(device.verifiedPeerPubKeys) ? device.verifiedPeerPubKeys : [];
|
|
262
287
|
const next = prev.filter((p) => p && p.pubKey !== pubKey);
|
|
263
|
-
next.push({ pubKey,
|
|
288
|
+
next.push({ pubKey, pinnedAt: new Date().toISOString() });
|
|
264
289
|
return next.slice(-MAX_PINNED_PEER_KEYS);
|
|
265
290
|
}
|
|
266
291
|
|
|
@@ -286,7 +311,6 @@ function computeStatus(cfg) {
|
|
|
286
311
|
pendingSas: connected && !authed && pending ? pending.e2e.pendingSas : null,
|
|
287
312
|
clients: _clients.values().map((r) => ({
|
|
288
313
|
key: r.key,
|
|
289
|
-
clientId: r.clientId ?? null,
|
|
290
314
|
state: r.e2e.state,
|
|
291
315
|
pendingSas: r.e2e.state === 'pending_sas' ? r.e2e.pendingSas : null,
|
|
292
316
|
})),
|
|
@@ -680,41 +704,6 @@ function stopAllSessionWatches() {
|
|
|
680
704
|
for (const tabId of Array.from(_sessionWatchers.keys())) stopSessionWatch(tabId);
|
|
681
705
|
}
|
|
682
706
|
|
|
683
|
-
/**
|
|
684
|
-
* Pure builder for the phone's session list: one project entry per tab, then
|
|
685
|
-
* each tab's ACTIVE Epics (from active-index.json via readIndex). Never throws.
|
|
686
|
-
*/
|
|
687
|
-
function buildRemoteSessionList(tabs, readIndex) {
|
|
688
|
-
const out = [];
|
|
689
|
-
const seenEpics = new Set();
|
|
690
|
-
for (const tab of Array.isArray(tabs) ? tabs : []) {
|
|
691
|
-
if (!tab) continue;
|
|
692
|
-
const project = tab.label || path.basename(tab.cwd || '');
|
|
693
|
-
out.push({ kind: 'project', tabId: tab.sessionId, cwd: tab.cwd, title: tab.label || tab.cwd, project });
|
|
694
|
-
let sessions = null;
|
|
695
|
-
try { sessions = readIndex(tab.cwd)?.sessions; } catch { sessions = null; }
|
|
696
|
-
if (!sessions || typeof sessions !== 'object') continue;
|
|
697
|
-
for (const epic of Object.values(sessions)) {
|
|
698
|
-
if (!epic || epic.status !== 'active') continue;
|
|
699
|
-
if (typeof epic.claudeSessionId !== 'string' || !epic.claudeSessionId) continue;
|
|
700
|
-
if (seenEpics.has(epic.id)) continue;
|
|
701
|
-
seenEpics.add(epic.id);
|
|
702
|
-
const firstLine = typeof epic.goalText === 'string' ? epic.goalText.split('\n')[0].trim().slice(0, 80).trim() : '';
|
|
703
|
-
out.push({
|
|
704
|
-
kind: 'epic',
|
|
705
|
-
tabId: epic.claudeSessionId,
|
|
706
|
-
epicId: epic.id,
|
|
707
|
-
cwd: epic.worktree?.dir || epic.cwd,
|
|
708
|
-
title: firstLine || epic.id,
|
|
709
|
-
project,
|
|
710
|
-
tag: epic.tag,
|
|
711
|
-
agentType: epic.agentType,
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
return out;
|
|
716
|
-
}
|
|
717
|
-
|
|
718
707
|
/**
|
|
719
708
|
* Shared finish-up for any path that transitions a client's e2e state into 'authenticated'
|
|
720
709
|
* (manual SAS confirmation or pinned-key auto-trust): log, broadcast the new
|
|
@@ -780,7 +769,7 @@ async function handleMessage(raw, device) {
|
|
|
780
769
|
}
|
|
781
770
|
|
|
782
771
|
// Per-client E2E record, keyed by the relay-stamped fromConn (else 'legacy').
|
|
783
|
-
const client = _clients.getOrCreate(_clients.clientKeyOf(envelope)
|
|
772
|
+
const client = _clients.getOrCreate(_clients.clientKeyOf(envelope));
|
|
784
773
|
|
|
785
774
|
// ── E2E key exchange ───────────────────────────────────────────────────────
|
|
786
775
|
// Browser sends e2e:hello with its ephemeral P-256 public key (SPKI base64url).
|
|
@@ -1000,38 +989,16 @@ function respond(msgId, payload, typeOverride, client) {
|
|
|
1000
989
|
}
|
|
1001
990
|
}
|
|
1002
991
|
|
|
1003
|
-
function stopClientPtyStream(client, tabId) {
|
|
1004
|
-
const unsub = client.ptyStreams.get(tabId);
|
|
1005
|
-
client.ptyStreams.delete(tabId);
|
|
1006
|
-
if (unsub) {
|
|
1007
|
-
try { unsub(); } catch { /* listener already gone */ }
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
992
|
// Lazy-loaded dispatch map — avoids circular require at module load time.
|
|
1012
993
|
let _dispatchMap = null;
|
|
1013
994
|
|
|
1014
995
|
function getDispatchMap() {
|
|
1015
996
|
if (_dispatchMap) return _dispatchMap;
|
|
1016
997
|
|
|
1017
|
-
const { manager: ptyManager } = require('./pty.cjs');
|
|
1018
998
|
const sessionsStore = require('./sessionsStore.cjs');
|
|
1019
|
-
const { remote: histRemote } = require('./historyAggregator.cjs');
|
|
1020
999
|
const chatRunner = require('./chatRunner.cjs');
|
|
1021
1000
|
|
|
1022
1001
|
_dispatchMap = {
|
|
1023
|
-
'cmd:sessions:load': async () =>
|
|
1024
|
-
sessionsStore.load(),
|
|
1025
|
-
|
|
1026
|
-
'cmd:sessions:list': async () => {
|
|
1027
|
-
const { readActiveIndex } = require('./lib/epic/epicMint.cjs');
|
|
1028
|
-
const data = await sessionsStore.load();
|
|
1029
|
-
return {
|
|
1030
|
-
sessions: buildRemoteSessionList(data?.tabs ?? [], readActiveIndex),
|
|
1031
|
-
activeTabId: data?.activeTabId ?? null,
|
|
1032
|
-
};
|
|
1033
|
-
},
|
|
1034
|
-
|
|
1035
1002
|
'cmd:projects:list': async () => {
|
|
1036
1003
|
const { readActiveIndex } = require('./lib/epic/epicMint.cjs');
|
|
1037
1004
|
const { listRemoteProjects } = require('./lib/remoteDrilldown.cjs');
|
|
@@ -1104,71 +1071,9 @@ function getDispatchMap() {
|
|
|
1104
1071
|
return { prds };
|
|
1105
1072
|
},
|
|
1106
1073
|
|
|
1107
|
-
'
|
|
1108
|
-
const parsed = schemas.sessionsPayload.parse(payload);
|
|
1109
|
-
return sessionsStore.save(parsed);
|
|
1110
|
-
},
|
|
1111
|
-
|
|
1112
|
-
'cmd:pty:spawn': async (payload, reqClient) => {
|
|
1113
|
-
const client = reqClient || _clients.getOrCreate(LEGACY_CLIENT_KEY);
|
|
1114
|
-
const parsed = schemas.ptySpawn.parse(payload);
|
|
1115
|
-
// Path safety — validatePath rejects anything outside home dir.
|
|
1116
|
-
validatePath(parsed.cwd);
|
|
1117
|
-
// Strip startupCommand: it is ignored by pty.cjs today, but passing a
|
|
1118
|
-
// remotely-controlled 8 KiB string to spawn is a landmine if pty.cjs
|
|
1119
|
-
// ever uses it. Remote callers have no legitimate need for it.
|
|
1120
|
-
const { startupCommand: _ignored, ...safePayload } = parsed;
|
|
1121
|
-
const result = await ptyManager.spawn(safePayload);
|
|
1122
|
-
// Stream output to the peer — replaces any prior subscription for this tab.
|
|
1123
|
-
// Chunk contents are sensitive: never logged.
|
|
1124
|
-
const tabId = safePayload.tabId;
|
|
1125
|
-
stopClientPtyStream(client, tabId);
|
|
1126
|
-
const unsub = ptyManager.addOutputListener(
|
|
1127
|
-
tabId,
|
|
1128
|
-
(_id, data) => {
|
|
1129
|
-
if (client.e2e.state !== 'authenticated' || !loadConfigSync().remoteEnabled) return;
|
|
1130
|
-
pushEventTo(client, 'event:pty:data', { tabId, data });
|
|
1131
|
-
},
|
|
1132
|
-
(_id, info) => {
|
|
1133
|
-
if (client.ptyStreams.get(tabId) === unsub) client.ptyStreams.delete(tabId);
|
|
1134
|
-
if (client.e2e.state !== 'authenticated' || !loadConfigSync().remoteEnabled) return;
|
|
1135
|
-
pushEventTo(client, 'event:pty:exit', { tabId, exitCode: info?.exitCode, signal: info?.signal });
|
|
1136
|
-
},
|
|
1137
|
-
);
|
|
1138
|
-
client.ptyStreams.set(tabId, unsub);
|
|
1139
|
-
return result;
|
|
1140
|
-
},
|
|
1141
|
-
|
|
1142
|
-
'cmd:pty:write': async (payload) => {
|
|
1143
|
-
const parsed = schemas.ptyWrite.parse(payload);
|
|
1144
|
-
return ptyManager.write(parsed);
|
|
1145
|
-
},
|
|
1146
|
-
|
|
1147
|
-
'cmd:pty:resize': async (payload) => {
|
|
1148
|
-
const parsed = schemas.ptyResize.parse(payload);
|
|
1149
|
-
ptyManager.resize(parsed);
|
|
1150
|
-
return { ok: true };
|
|
1151
|
-
},
|
|
1152
|
-
|
|
1153
|
-
'cmd:pty:kill': async (payload, reqClient) => {
|
|
1154
|
-
const parsed = schemas.ptyTabId.parse(payload);
|
|
1155
|
-
// Kill ends the PTY itself, so every client's stream for it ends.
|
|
1156
|
-
for (const c of _clients.values()) stopClientPtyStream(c, parsed.tabId);
|
|
1157
|
-
void reqClient;
|
|
1158
|
-
ptyManager.kill(parsed.tabId);
|
|
1159
|
-
return { ok: true };
|
|
1160
|
-
},
|
|
1161
|
-
|
|
1162
|
-
'cmd:history:aggregate': async (payload) => {
|
|
1163
|
-
const parsed = schemas.historyAggregate.parse(payload);
|
|
1164
|
-
return histRemote.aggregate(parsed);
|
|
1165
|
-
},
|
|
1166
|
-
|
|
1167
|
-
'cmd:app:version': async () =>
|
|
1168
|
-
app.getVersion(),
|
|
1169
|
-
|
|
1074
|
+
// Web app's multi-desktop picker (SAS-gated: hostname identifies the machine).
|
|
1170
1075
|
'cmd:app:info': async () =>
|
|
1171
|
-
({ version: app.getVersion(), platform: process.platform }),
|
|
1076
|
+
({ version: app.getVersion(), platform: process.platform, hostname: os.hostname() }),
|
|
1172
1077
|
|
|
1173
1078
|
// v2 mobile: start/stop pushing live state for a session.
|
|
1174
1079
|
'cmd:session:subscribe': async (payload, reqClient) => {
|
|
@@ -1388,6 +1293,7 @@ async function revokeAllDevices() {
|
|
|
1388
1293
|
// ─── IPC handlers ─────────────────────────────────────────────────────────────
|
|
1389
1294
|
|
|
1390
1295
|
function registerRemoteHandlers() {
|
|
1296
|
+
removeOrphanedClientFiles(path.join(claudeHome(), 'session-manager'));
|
|
1391
1297
|
const { validated } = require('./ipcSchemas.cjs');
|
|
1392
1298
|
|
|
1393
1299
|
// Returns current status without tokens — safe to expose to renderer.
|
|
@@ -1425,7 +1331,7 @@ function registerRemoteHandlers() {
|
|
|
1425
1331
|
const cfg = await loadConfig();
|
|
1426
1332
|
const devices = (cfg.devices || []).map((d) =>
|
|
1427
1333
|
d.deviceId === target.deviceId
|
|
1428
|
-
? { ...d, verifiedPeerPubKeys: addPinnedPeerKey(d, target.peerPubKey
|
|
1334
|
+
? { ...d, verifiedPeerPubKeys: addPinnedPeerKey(d, target.peerPubKey) }
|
|
1429
1335
|
: d
|
|
1430
1336
|
);
|
|
1431
1337
|
await saveConfig({ ...cfg, devices });
|
|
@@ -1536,13 +1442,14 @@ module.exports = {
|
|
|
1536
1442
|
getE2eState: (clientKey = LEGACY_CLIENT_KEY) => (_clients.get(clientKey) || { e2e: makeState() }).e2e,
|
|
1537
1443
|
},
|
|
1538
1444
|
__test: {
|
|
1445
|
+
pruneDesktopPins,
|
|
1446
|
+
removeOrphanedClientFiles,
|
|
1539
1447
|
getDispatchMap,
|
|
1540
1448
|
auditResultFor,
|
|
1541
1449
|
startSessionWatch,
|
|
1542
1450
|
pollSessionWatcher,
|
|
1543
1451
|
getSessionWatcher: (tabId) => _sessionWatchers.get(tabId),
|
|
1544
1452
|
stopAllSessionWatches,
|
|
1545
|
-
buildRemoteSessionList,
|
|
1546
1453
|
setWs: (ws) => { _ws = ws; },
|
|
1547
1454
|
connect,
|
|
1548
1455
|
hasReconnectTimer: () => _reconnectTimer !== null,
|
|
@@ -1556,6 +1463,5 @@ module.exports = {
|
|
|
1556
1463
|
getClient: (clientKey = LEGACY_CLIENT_KEY) => _clients.getOrCreate(clientKey),
|
|
1557
1464
|
revokeAllDevices,
|
|
1558
1465
|
pair,
|
|
1559
|
-
streamCount: () => _clients.values().reduce((n, c) => n + c.ptyStreams.size, 0),
|
|
1560
1466
|
},
|
|
1561
1467
|
};
|
package/src/preload/api.d.ts
CHANGED
|
@@ -342,6 +342,8 @@ export interface AgentPersona {
|
|
|
342
342
|
title?: string | null;
|
|
343
343
|
/** Bundled-persona version stamp (seedAgentPersonas.cjs). Null for a persona that was never seeded, or has no stamp. */
|
|
344
344
|
seedVersion?: number | null;
|
|
345
|
+
/** `executorOnly: true` frontmatter — persona only executes a scoped PRD; New Epic warns when it is picked as Actor. */
|
|
346
|
+
executorOnly?: boolean;
|
|
345
347
|
path: string;
|
|
346
348
|
body: string;
|
|
347
349
|
/** Project names (basename of cwd) whose currently-open tab overlays this agent. */
|
|
@@ -374,6 +376,8 @@ export interface AgentPersonaSaveInput {
|
|
|
374
376
|
projectName?: string;
|
|
375
377
|
/** Bundled-persona version stamp (seedAgentPersonas.cjs). Omit to carry over the existing file's stamp unchanged. */
|
|
376
378
|
seedVersion?: number;
|
|
379
|
+
/** Omit to keep the existing file's value. */
|
|
380
|
+
executorOnly?: boolean;
|
|
377
381
|
body: string;
|
|
378
382
|
}
|
|
379
383
|
|
|
@@ -1671,43 +1675,6 @@ export interface WebRemoteDevice {
|
|
|
1671
1675
|
lastConnectedAt: string | null;
|
|
1672
1676
|
}
|
|
1673
1677
|
|
|
1674
|
-
export interface RemoteMachinePeer {
|
|
1675
|
-
peerId: string;
|
|
1676
|
-
deviceId: string;
|
|
1677
|
-
machineName: string;
|
|
1678
|
-
pairedAt: string;
|
|
1679
|
-
clientPublicKey: string;
|
|
1680
|
-
verifiedDevicePubKey: string | null;
|
|
1681
|
-
/** process.platform of the remote machine, last known (null until first authenticated). */
|
|
1682
|
-
os: string | null;
|
|
1683
|
-
status: string;
|
|
1684
|
-
reason: string | null;
|
|
1685
|
-
sas: string | null;
|
|
1686
|
-
}
|
|
1687
|
-
|
|
1688
|
-
export interface RemoteClientDevice {
|
|
1689
|
-
deviceId: string;
|
|
1690
|
-
email: string;
|
|
1691
|
-
issuedAt: string;
|
|
1692
|
-
/** null = never expires (relay keeps tokens until revoked). */
|
|
1693
|
-
expiresAt: string | null;
|
|
1694
|
-
isOnline: boolean;
|
|
1695
|
-
devicePubKey: string;
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
export interface RemoteClientStatus {
|
|
1699
|
-
peerId: string;
|
|
1700
|
-
status: string;
|
|
1701
|
-
reason: string | null;
|
|
1702
|
-
sas: string | null;
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
|
-
export interface RemoteClientEvent {
|
|
1706
|
-
peerId: string;
|
|
1707
|
-
type: string;
|
|
1708
|
-
payload: unknown;
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
1678
|
export interface WebRemoteStatus {
|
|
1712
1679
|
enabled: boolean;
|
|
1713
1680
|
remoteControlEnabled: boolean;
|
|
@@ -1728,7 +1695,6 @@ export interface WebRemoteStatus {
|
|
|
1728
1695
|
|
|
1729
1696
|
export interface WebRemoteClientStatus {
|
|
1730
1697
|
key: string;
|
|
1731
|
-
clientId: string | null;
|
|
1732
1698
|
state: 'idle' | 'pending_sas' | 'authenticated' | 'failed';
|
|
1733
1699
|
pendingSas: string | null;
|
|
1734
1700
|
}
|
|
@@ -2242,20 +2208,6 @@ export interface SessionManagerAPI {
|
|
|
2242
2208
|
* renderer needs per-row badges without a separate git call per file. */
|
|
2243
2209
|
fileStatus: (cwd: string) => Promise<GitFileStatusMap>;
|
|
2244
2210
|
};
|
|
2245
|
-
remoteClient: {
|
|
2246
|
-
list: () => Promise<RemoteMachinePeer[]>;
|
|
2247
|
-
connect: (peerId: string) => Promise<{ status: string; reason: string | null; sas: string | null }>;
|
|
2248
|
-
confirmSas: (peerId: string) => Promise<{ ok: boolean; reason?: string }>;
|
|
2249
|
-
remove: (peerId: string) => Promise<{ ok: boolean }>;
|
|
2250
|
-
authStatus: () => Promise<{ paired: boolean; clientId: string | null }>;
|
|
2251
|
-
pair: (code: string, clientName?: string) => Promise<{ ok: boolean; clientId?: string; error?: string }>;
|
|
2252
|
-
unpair: () => Promise<{ ok: boolean }>;
|
|
2253
|
-
devices: () => Promise<{ ok: boolean; devices?: RemoteClientDevice[]; error?: string }>;
|
|
2254
|
-
add: (deviceId: string, machineName: string) => Promise<RemoteMachinePeer>;
|
|
2255
|
-
send: (peerId: string, type: string, payload?: unknown) => Promise<unknown>;
|
|
2256
|
-
onStatus: (handler: (status: RemoteClientStatus) => void) => () => void;
|
|
2257
|
-
onEvent: (handler: (ev: RemoteClientEvent) => void) => () => void;
|
|
2258
|
-
};
|
|
2259
2211
|
updater: {
|
|
2260
2212
|
status: () => Promise<AutoUpdaterStatus>;
|
|
2261
2213
|
check: () => Promise<AutoUpdaterStatus>;
|
package/src/preload/index.cjs
CHANGED
|
@@ -404,30 +404,6 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
404
404
|
status: (cwd) => ipcRenderer.invoke('git:status', { cwd }),
|
|
405
405
|
fileStatus: (cwd) => ipcRenderer.invoke('git:file-status', { cwd }),
|
|
406
406
|
},
|
|
407
|
-
remoteClient: {
|
|
408
|
-
/** Paired remote machines merged with live connection status. */
|
|
409
|
-
list: () => ipcRenderer.invoke('remoteClient:list'),
|
|
410
|
-
connect: (peerId) => ipcRenderer.invoke('remoteClient:connect', { peerId }),
|
|
411
|
-
/** Confirm the short authentication string matches the remote machine. */
|
|
412
|
-
confirmSas: (peerId) => ipcRenderer.invoke('remoteClient:confirm-sas', { peerId }),
|
|
413
|
-
remove: (peerId) => ipcRenderer.invoke('remoteClient:remove', { peerId }),
|
|
414
|
-
authStatus: () => ipcRenderer.invoke('remoteClient:auth-status'),
|
|
415
|
-
pair: (code, clientName) => ipcRenderer.invoke('remoteClient:pair', { code, clientName }),
|
|
416
|
-
unpair: () => ipcRenderer.invoke('remoteClient:unpair'),
|
|
417
|
-
devices: () => ipcRenderer.invoke('remoteClient:devices'),
|
|
418
|
-
add: (deviceId, machineName) => ipcRenderer.invoke('remoteClient:add', { deviceId, machineName }),
|
|
419
|
-
send: (peerId, type, payload) => ipcRenderer.invoke('remoteClient:send', { peerId, type, payload }),
|
|
420
|
-
onStatus: (handler) => {
|
|
421
|
-
const listener = (_e, payload) => handler(payload);
|
|
422
|
-
ipcRenderer.on('remoteClient:status', listener);
|
|
423
|
-
return () => ipcRenderer.removeListener('remoteClient:status', listener);
|
|
424
|
-
},
|
|
425
|
-
onEvent: (handler) => {
|
|
426
|
-
const listener = (_e, payload) => handler(payload);
|
|
427
|
-
ipcRenderer.on('remoteClient:event', listener);
|
|
428
|
-
return () => ipcRenderer.removeListener('remoteClient:event', listener);
|
|
429
|
-
},
|
|
430
|
-
},
|
|
431
407
|
updater: {
|
|
432
408
|
status: () => ipcRenderer.invoke('updater:status'),
|
|
433
409
|
check: () => ipcRenderer.invoke('updater:check'),
|
|
@@ -5,7 +5,8 @@ tools: Read, Grep, Glob, Bash, Edit, Write
|
|
|
5
5
|
model: sonnet
|
|
6
6
|
effort: medium
|
|
7
7
|
title: Engineering — Software Engineer
|
|
8
|
-
seedVersion:
|
|
8
|
+
seedVersion: 5
|
|
9
|
+
executorOnly: true
|
|
9
10
|
---
|
|
10
11
|
|
|
11
12
|
You are dev-lead. You execute one PRD — nothing above it, nothing beyond it. You don't decide
|
|
@@ -4,7 +4,8 @@ description: Validates a finished PLAN (the PRDs its validate PRD lists) once, a
|
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
5
|
model: sonnet
|
|
6
6
|
title: Engineering — Plan Validator
|
|
7
|
-
seedVersion:
|
|
7
|
+
seedVersion: 4
|
|
8
|
+
executorOnly: true
|
|
8
9
|
---
|
|
9
10
|
|
|
10
11
|
You are validator. You judge whether a plan's PRDs actually landed what they promised. You do not fix anything, you do not re-implement, you do not queue PRDs — your output is evidence and verdicts, read by the architect who owns the plan.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{aF as d,j,aq as $,a9 as g,ap as e,n as N,at as E,aa as b,aJ as S,a as f,a4 as T,k as v,i as D}from"./index-S-F5jp_h.js";import{P as R}from"./Panel-C92O_aAH.js";import{V}from"./ViewTabs-C3FN6P_C.js";import"./monaco-editor-BsIlgG7c.js";const C=[{key:"diagram",label:"Diagram"},{key:"tables",label:"Tables"}];function G(){const[s,t]=d.useState("diagram"),[a,i]=d.useState(null),[l,n]=d.useState(()=>new Set),c=d.useMemo(()=>j.filter(r=>!l.has(r.group)),[l]),m=d.useMemo(()=>$(c),[c]),o=r=>{n(x=>{const h=new Set(x);return h.has(r)?h.delete(r):h.add(r),h}),i(null)},u=a?g(a):null,y=r=>c.some(x=>x.id===r);return e.jsx(R,{toolbar:e.jsxs(e.Fragment,{children:[e.jsx(V,{options:C,active:s,onChange:t}),e.jsx(M,{hiddenGroups:l,onToggle:o}),e.jsxs("span",{className:"ml-auto text-fg-faint",children:[j.length," entities"]})]}),children:s==="diagram"?e.jsxs("div",{className:"h-full flex",children:[e.jsx("div",{className:"flex-1 min-w-0 overflow-auto",children:e.jsx(O,{layout:m,selectedId:a,onSelect:r=>i(x=>x===r?null:r)})}),e.jsx("div",{className:"w-96 shrink-0 border-l border-line overflow-y-auto",children:u?e.jsx(F,{entity:u,onSelectRelated:r=>y(r)?i(r):void 0}):e.jsx(N,{title:"select an entity",hint:"click a box in the diagram to see its storage, fields, and relations"})})]}):e.jsx(P,{})})}const X=d.memo(G);function M({hiddenGroups:s,onToggle:t}){return e.jsx("div",{className:"flex items-center gap-1.5 flex-wrap",children:v.map(a=>{const i=s.has(a.id);return e.jsxs("button",{onClick:()=>t(a.id),title:`${a.blurb} (${a.realm})`,className:`flex items-center gap-1 px-1.5 py-0.5 rounded border text-[10.5px] ${i?"border-line text-fg-faint opacity-50":"border-line text-fg-dim hover:text-fg"}`,children:[e.jsx("span",{className:`w-1.5 h-1.5 rounded-full ${a.tone.dot}`}),a.label,e.jsx("span",{className:"text-fg-faint",children:a.realm})]},a.id)})})}function O({layout:s,selectedId:t,onSelect:a}){const i=d.useMemo(()=>t?new Set(E(t)):null,[t]),l=n=>!!t&&(n.relation.from===t||n.relation.to===t);return s.boxes.length===0?e.jsx(N,{title:"no entities in view",hint:"toggle a group in the legend to show its entities"}):e.jsxs("div",{className:"relative p-4",style:{width:s.width+32,height:s.height+32},children:[e.jsxs("svg",{width:s.width,height:s.height,className:"absolute left-4 top-4 overflow-visible",children:[s.edges.map(n=>{const c=l(n);return e.jsx("path",{"data-testid":`erd-edge-${n.relation.from}-${n.relation.to}`,d:n.d,fill:"none",className:c?"stroke-accent":"stroke-line",strokeWidth:c?2:1,opacity:t&&!c?.25:1},`${n.relation.from}-${n.relation.to}-${n.relation.label}`)}),s.edges.map(n=>l(n)?e.jsx("text",{x:n.mx,y:n.my,textAnchor:"middle",className:"fill-fg-dim text-[10px]",children:n.relation.label},`label-${n.relation.from}-${n.relation.to}-${n.relation.label}`):null)]}),s.boxes.map(n=>e.jsx(_,{box:n,selected:n.id===t,dimmed:!!t&&n.id!==t&&!(i!=null&&i.has(n.id)),onClick:()=>a(n.id)},n.id))]})}function _({box:s,selected:t,dimmed:a,onClick:i}){const l=g(s.id),n=b(s.group),c=l.fields.slice(0,D),m=l.fields.length-c.length;return e.jsxs("button",{type:"button","data-testid":`erd-box-${s.id}`,onClick:i,className:`absolute text-left rounded border overflow-hidden bg-bg-hi ${t?"border-accent ring-1 ring-accent":"border-line"} ${a?"opacity-30":""}`,style:{left:s.x,top:s.y,width:s.w,height:s.h},children:[e.jsxs("div",{className:"px-2 py-1 border-b border-line flex items-center gap-1.5",children:[e.jsx("span",{className:`w-1.5 h-1.5 rounded-full shrink-0 ${n.tone.dot}`}),e.jsx("span",{className:"text-[11px] font-semibold text-fg truncate",children:l.name})]}),l.aka&&e.jsx("div",{className:"px-2 pt-0.5 text-[9.5px] text-fg-faint truncate",children:l.aka}),e.jsxs("div",{className:"px-2 py-0.5",children:[c.map(o=>e.jsxs("div",{className:"text-[9.5px] font-mono text-fg-dim truncate flex items-center gap-1",children:[o.key&&e.jsx("span",{className:"text-fg-faint",children:o.key}),e.jsx("span",{className:"truncate",children:o.name})]},o.name)),m>0&&e.jsxs("div",{className:"text-[9.5px] text-fg-faint italic",children:["+",m," more"]})]})]})}function w({fields:s}){return e.jsxs("table",{className:"w-full text-[11px] font-mono",children:[e.jsx("thead",{className:"text-fg-faint uppercase tracking-wide text-[9.5px]",children:e.jsxs("tr",{className:"border-b border-line",children:[e.jsx("th",{className:"text-left font-normal px-2 py-1",children:"name"}),e.jsx("th",{className:"text-left font-normal px-2 py-1",children:"type"}),e.jsx("th",{className:"text-left font-normal px-2 py-1",children:"key"}),e.jsx("th",{className:"text-left font-normal px-2 py-1",children:"note"})]})}),e.jsx("tbody",{children:s.map(t=>e.jsxs("tr",{className:"border-b border-line/50",children:[e.jsxs("td",{className:"px-2 py-1 text-fg",children:[t.name,t.optional&&e.jsx("span",{className:"text-fg-faint",children:"?"})]}),e.jsx("td",{className:"px-2 py-1 text-fg-dim",children:t.type}),e.jsx("td",{className:"px-2 py-1",children:t.key&&e.jsxs(f,{tone:t.key==="pk"?"accent":"dim",children:[t.key,t.ref?` → ${t.ref}`:""]})}),e.jsx("td",{className:"px-2 py-1 text-fg-faint",children:t.note})]},t.name))})]})}function k({entity:s}){return e.jsxs("div",{className:"space-y-1 text-[11px]",children:[e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx("span",{className:"text-fg-faint w-20 shrink-0",children:"path"}),e.jsx("span",{className:"font-mono text-fg-dim break-all",children:s.store.path})]}),e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx("span",{className:"text-fg-faint w-20 shrink-0",children:"format"}),e.jsx("span",{className:"font-mono text-fg-dim",children:s.store.format})]}),s.store.writer&&e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx("span",{className:"text-fg-faint w-20 shrink-0",children:"writer"}),e.jsxs(f,{tone:"accent",children:["single-writer: ",s.store.writer]})]}),e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx("span",{className:"text-fg-faint w-20 shrink-0",children:"definedIn"}),e.jsx("span",{className:"font-mono text-fg-dim break-all",children:s.store.definedIn})]})]})}function F({entity:s,onSelectRelated:t}){const a=b(s.group),i=S(s.id);return e.jsxs("div",{className:"p-3 space-y-3",children:[e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx("span",{className:`w-2 h-2 rounded-full ${a.tone.dot}`}),e.jsx("span",{className:"text-sm font-semibold text-fg",children:s.name})]}),s.aka&&e.jsx("div",{className:"text-[10.5px] text-fg-faint",children:s.aka}),e.jsx(f,{tone:"dim",className:"mt-1",children:a.label})]}),e.jsx("div",{className:"text-xs text-fg-dim leading-relaxed",children:s.summary}),e.jsxs("div",{children:[e.jsx(p,{children:"storage"}),e.jsx(k,{entity:s})]}),e.jsxs("div",{children:[e.jsx(p,{children:"fields"}),e.jsx(w,{fields:s.fields})]}),s.notes&&s.notes.length>0&&e.jsxs("div",{children:[e.jsx(p,{children:"notes"}),e.jsx("ul",{className:"list-disc pl-4 space-y-0.5 text-xs text-fg-dim",children:s.notes.map(l=>e.jsx("li",{children:l},l))})]}),e.jsxs("div",{children:[e.jsx(p,{children:"relations"}),i.length===0?e.jsx("div",{className:"text-xs text-fg-faint italic",children:"no relations"}):e.jsx("ul",{className:"space-y-1",children:i.map(l=>{const n=l.from===s.id?l.to:l.from,c=g(n);return e.jsxs("li",{className:"flex items-center gap-1.5 text-xs",children:[e.jsx("span",{className:"font-mono text-fg-faint w-10 shrink-0",children:T(l.cardinality)}),e.jsx("span",{className:"text-fg-dim",children:l.label}),l.via&&e.jsxs("span",{className:"font-mono text-[10px] text-fg-faint",children:["via ",l.via]}),e.jsx("button",{type:"button",onClick:()=>t(n),className:"ml-auto text-accent hover:underline font-mono",children:c.name})]},`${l.from}-${l.to}-${l.label}`)})})]})]})}function p({children:s}){return e.jsx("div",{className:"text-[10.5px] uppercase tracking-wide text-fg-faint mb-1 font-mono",children:s})}function P(){return e.jsx("div",{className:"p-4 space-y-6",children:v.map(s=>{const t=j.filter(a=>a.group===s.id);return t.length===0?null:e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center gap-1.5 mb-2",children:[e.jsx("span",{className:`w-2 h-2 rounded-full ${s.tone.dot}`}),e.jsx("span",{className:"text-xs font-semibold text-fg",children:s.label}),e.jsx("span",{className:"text-[10.5px] text-fg-faint",children:s.realm})]}),e.jsx("div",{className:"grid grid-cols-1 xl:grid-cols-2 gap-3",children:t.map(a=>e.jsxs("div",{className:"border border-line rounded bg-bg-hi p-3 space-y-2",children:[e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"text-sm font-semibold text-fg",children:a.name}),a.aka&&e.jsx("span",{className:"text-[10.5px] text-fg-faint",children:a.aka})]}),e.jsx(f,{tone:"dim",className:"mt-1",children:s.label})]}),e.jsx("div",{className:"text-xs text-fg-dim leading-relaxed",children:a.summary}),e.jsx(k,{entity:a}),e.jsx(w,{fields:a.fields}),a.notes&&a.notes.length>0&&e.jsx("ul",{className:"list-disc pl-4 space-y-0.5 text-xs text-fg-dim",children:a.notes.map(i=>e.jsx("li",{children:i},i))})]},a.id))})]},s.id)})})}export{X as DataModel};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{ap as e}from"./index-S-F5jp_h.js";function o({sidebar:r,detail:s,sidebarWidth:i="18rem"}){return e.jsxs("div",{className:"h-full flex",children:[e.jsx("div",{className:"shrink-0 border-r border-line overflow-y-auto",style:{width:i},children:r}),e.jsx("div",{className:"flex-1 min-w-0 overflow-auto",children:s})]})}export{o as L};
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{bf as _,aF as d,a2 as P,ap as e,a as N,bn as D,X as O,D as A,a0 as H,a3 as U}from"./index-S-F5jp_h.js";import{b as L,P as E,E as T,R as z,u as W,S as G}from"./useRemotePeers-CKVakQPc.js";import"./monaco-editor-BsIlgG7c.js";function M(o){return o instanceof Error?o.message:String(o)}function V({peerId:o,machineName:i,onOpenTerminal:y}){const c=_(a=>a.show),[x,m]=d.useState([]),[p,w]=d.useState("loading"),[h,k]=d.useState(null),[t,j]=d.useState(""),[C,I]=d.useState(!1),f=d.useRef(0),v=d.useCallback(async()=>{const a=++f.current;w("loading");try{let u;try{const s=await window.api.remoteClient.send(o,"cmd:sessions:list",{});u=Array.isArray(s==null?void 0:s.sessions)?s.sessions:[]}catch{if(a!==f.current)return;const s=await window.api.remoteClient.send(o,"cmd:sessions:load",{});u=(Array.isArray(s==null?void 0:s.tabs)?s.tabs:[]).map(b=>({kind:"project",tabId:b.sessionId,cwd:b.cwd,title:b.label||b.cwd,project:b.label||P(b.cwd)}))}if(a!==f.current)return;m(u),w("ready")}catch(u){if(a!==f.current)return;w("error"),c("error",`Could not load sessions from ${i}: ${M(u)}`)}},[o,i,c]);d.useEffect(()=>(v(),()=>{f.current++}),[v]);const S=d.useMemo(()=>{const a=new Map;for(const s of x){const g=s.project||P(s.cwd);(a.get(g)??a.set(g,[]).get(g)).push(s)}const u=s=>s.kind==="project"?0:1;for(const s of a.values())s.sort((g,b)=>u(g)-u(b));return[...a.entries()].sort((s,g)=>s[0].localeCompare(g[0]))},[x]),n=t.trim(),$=h!==null&&n.length>0&&!C,R=d.useCallback(async()=>{if(!(h===null||t.trim().length===0)){I(!0);try{await window.api.remoteClient.send(o,"cmd:chat:send",{tabId:h,prompt:t}),j("")}catch(a){c("error",`Could not send prompt to ${i}: ${M(a)}`)}finally{I(!1)}}},[o,i,h,t,c]);return e.jsxs("div",{"data-testid":"remote-sessions-panel",className:"flex flex-col gap-3",children:[e.jsx(L,{right:e.jsx(N,{tone:"accent",className:"",title:`Sessions on ${i}`,children:e.jsxs("span",{"data-testid":"remote-sessions-badge",children:["Remote: ",i]})}),children:"Remote sessions"}),e.jsx("div",{className:"flex justify-end",children:e.jsx(E,{icon:"history",testId:"remote-sessions-refresh",onClick:()=>{v()},disabled:p==="loading",children:"Refresh"})}),p==="loading"&&e.jsx(T,{testId:"remote-sessions-loading",children:"Loading sessions…"}),p==="error"&&e.jsxs(T,{testId:"remote-sessions-error",children:["Could not load sessions from ",i,"."]}),p==="ready"&&x.length===0&&e.jsxs(T,{testId:"remote-sessions-empty",children:["No sessions open on ",i,"."]}),p==="ready"&&S.map(([a,u])=>e.jsx("div",{"data-testid":"remote-sessions-group",className:"flex flex-col gap-2",children:u.map(s=>e.jsx(z,{testId:`remote-session-${s.tabId}`,icon:"terminal",title:s.title||s.cwd,subtitle:s.cwd,badge:s.kind==="epic"&&s.tag?e.jsx(N,{tone:"accent",children:s.tag}):void 0,action:e.jsxs("div",{className:"flex items-center gap-2",children:[s.kind==="project"&&y&&e.jsx(E,{icon:"terminal",kind:"ghost",testId:`remote-session-terminal-${s.tabId}`,onClick:()=>y(s.cwd),children:"Terminal here"}),e.jsx(E,{kind:h===s.tabId?"soft":"ghost",testId:`remote-session-select-${s.tabId}`,onClick:()=>k(s.tabId),children:h===s.tabId?"Selected":"Select"})]})},s.tabId))},a)),p==="ready"&&x.length>0&&e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx("textarea",{"data-testid":"remote-sessions-prompt",value:t,onChange:a=>j(a.target.value),rows:3,placeholder:h?`Prompt for the selected session on ${i}`:"Select a session first",className:"w-full rounded-lg bg-bg-hi border border-line px-3 py-2 text-[13px] text-fg placeholder:text-fg-faint"}),e.jsx("div",{className:"flex justify-end",children:e.jsx(E,{icon:"play",testId:"remote-sessions-send",onClick:()=>{R()},disabled:!$,children:"Send"})})]})]})}const X=260;function q({peerId:o,machineName:i,cwd:y,onClose:c}){const x=d.useRef(null),m=d.useRef(null),p=_(t=>t.show),[w,h]=d.useState(!1);d.useEffect(()=>{const t=x.current;if(!t)return;const j=crypto.randomUUID();let C=!0,I=!1,f=!1;const v=(r,l)=>{C&&p("error",`Remote terminal ${r} failed: ${l instanceof Error?l.message:String(l)}`)},S=(r,l)=>window.api.remoteClient.send(o,r,l),n=new D.Terminal({fontFamily:'"IBM Plex Mono", JetBrains Mono, ui-monospace, Menlo, monospace',fontSize:A.fontSize,lineHeight:1.2,cursorBlink:!0,allowProposedApi:!0,theme:O[A.theme]}),$=new H.FitAddon;n.loadAddon($);const R=()=>{if(U(t.clientWidth,t.clientHeight))try{$.fit()}catch{}};n.open(t),R(),n.focus();const a=window.api.remoteClient.onEvent(r=>{if(r.peerId!==o)return;const l=r.payload??{};l.tabId===j&&(r.type==="event:pty:data"?typeof l.data=="string"&&n.write(l.data):r.type==="event:pty:exit"&&(I=!0,n.options.disableStdin=!0,n.write(`\r
|
|
2
|
-
\x1B[38;5;240m[remote session exited code=${l.exitCode??"?"}]\x1B[0m\r
|
|
3
|
-
`),h(!0)))}),u=n.onData(r=>{I||S("cmd:pty:write",{tabId:j,data:r}).catch(l=>v("write",l))}),s=n.onResize(({cols:r,rows:l})=>{I||S("cmd:pty:resize",{tabId:j,cols:r,rows:l}).catch(F=>v("resize",F))});S("cmd:pty:spawn",{tabId:j,cwd:y,cols:n.cols,rows:n.rows}).catch(r=>v("spawn",r)),m.current=()=>{f||(f=!0,S("cmd:pty:kill",{tabId:j}).catch(r=>v("kill",r)))};const g=()=>R();window.addEventListener("resize",g);const b=new ResizeObserver(()=>R());return b.observe(t),()=>{var r;window.removeEventListener("resize",g),b.disconnect(),a(),u.dispose(),s.dispose(),(r=m.current)==null||r.call(m),m.current=null,C=!1,n.dispose()}},[o,y,p]);const k=()=>{var t;(t=m.current)==null||t.call(m),c()};return e.jsxs("div",{"data-testid":"remote-terminal-panel",className:"bg-bg-hi border border-line rounded-xl overflow-hidden",children:[e.jsxs("div",{className:"flex items-center justify-between gap-3 px-[18px] pt-3.5 pb-2",children:[e.jsxs("div",{className:"flex items-center gap-2.5 min-w-0",children:[e.jsx(L,{children:"Remote terminal"}),e.jsx(N,{tone:"accent",children:e.jsxs("span",{"data-testid":"remote-terminal-badge",children:["Remote: ",i]})}),w&&e.jsx(N,{tone:"dim",children:"exited"})]}),e.jsx(E,{kind:"ghost",onClick:k,testId:"remote-terminal-close",children:"Close"})]}),e.jsx("div",{ref:x,"data-testid":"remote-terminal-host",className:"w-full bg-bg",style:{height:X}})]})}const J="relay_client_auth_unavailable",K="device_offline",Q={win32:"Windows",darwin:"macOS",linux:"Linux"},B=o=>o?Q[o]??o:"OS unknown";function Y({peers:o,selectedId:i,onSelect:y}){return e.jsxs("div",{className:"w-[240px] shrink-0 border-r border-line p-3 space-y-2 overflow-y-auto","data-testid":"remote-view-switcher",children:[e.jsx(L,{children:"Machines"}),o.map(c=>{const x=c.status==="authenticated";return e.jsxs("button",{type:"button","data-testid":"remote-view-machine","aria-pressed":c.peerId===i,onClick:()=>y(c.peerId),className:`w-full text-left rounded-lg border px-3 py-2 transition-colors ${c.peerId===i?"bg-bg-hi border-accent/40":"bg-bg border-line hover:bg-bg-hi"}`,children:[e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"text-sm font-semibold text-fg truncate",children:c.machineName}),x?e.jsx(N,{tone:"good",children:"online"}):e.jsx(N,{tone:"dim",children:"offline"})]}),e.jsx("div",{className:"text-[11.5px] text-fg-faint",children:B(c.os)})]},c.peerId)})]})}function se({onOpenSettings:o}){const{peers:i,connect:y,confirmSas:c}=W(),[x,m]=d.useState(null),[p,w]=d.useState("sessions"),[h,k]=d.useState("");if(i.length===0)return e.jsxs("div",{className:"p-6 space-y-3","data-testid":"remote-view",children:[e.jsx(T,{testId:"remote-view-empty",children:"No machines paired yet. Pair a machine in Settings → Phone remote."}),e.jsx(E,{testId:"remote-view-open-settings",onClick:o,children:"Open Settings"})]});const t=i.find(n=>n.peerId===x)??i[0],j=t.status==="authenticated",C=t.status==="failed"&&t.reason===J,I=t.status==="connecting",f=`Remote: ${t.machineName}`,v=n=>{m(n),w("sessions"),k("")},S=n=>{k(n),w("terminal")};return e.jsxs("div",{className:"flex h-full min-h-0","data-testid":"remote-view",children:[e.jsx(Y,{peers:i,selectedId:t.peerId,onSelect:v}),e.jsxs("div",{className:"flex-1 min-w-0 flex flex-col p-4 gap-3 overflow-y-auto",children:[e.jsxs("div",{"data-testid":"remote-view-header",className:"flex items-center gap-2.5",children:[e.jsx(N,{tone:"accent",className:"",title:t.peerId,children:e.jsx("span",{"data-testid":"remote-view-header-badge",children:f})}),e.jsx("span",{className:"text-[12.5px] text-fg-faint",children:B(t.os)})]}),t.status==="pending_sas"&&t.sas&&e.jsx(G,{peer:"other machine",testId:"remote-view-sas",code:t.sas,onConfirm:()=>{c(t.peerId)}}),j?e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"flex gap-2 border-b border-line pb-2",children:["sessions","terminal"].map(n=>e.jsxs("button",{type:"button","data-testid":`remote-view-tab-${n}`,onClick:()=>w(n),className:`inline-flex items-center gap-2 rounded-lg px-3 py-1.5 text-[13px] font-semibold ${p===n?"bg-bg-hi text-fg border border-line":"text-fg-dim hover:text-fg"}`,children:[n==="sessions"?"Sessions":"Terminal",e.jsx(N,{tone:"accent",children:f})]},n))}),p==="sessions"?e.jsx(V,{peerId:t.peerId,machineName:t.machineName,onOpenTerminal:S},t.peerId):e.jsx(q,{peerId:t.peerId,machineName:t.machineName,cwd:h,onClose:()=>w("sessions")},t.peerId)]}):e.jsxs("div",{className:"space-y-2","data-testid":"remote-view-offline",children:[e.jsx(T,{children:C?"The bilko.run relay does not support desktop clients yet":t.reason===K?`${t.machineName} is offline.`:`Not connected to ${t.machineName}.`}),!C&&t.status!=="pending_sas"&&e.jsx(z,{icon:"terminal",title:t.machineName,subtitle:t.peerId,action:e.jsx(E,{testId:"remote-view-connect",disabled:I,onClick:()=>{y(t.peerId)},children:I?"Connecting…":"Connect"})})]})]})]})}export{se as RemoteMachines};
|