claude-code-session-manager 0.120.0 → 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-LBg3mVL8.js → AgentLibrary-FO2ei2wB.js} +1 -1
- package/dist/assets/{Button-C4BN5ISR.js → Button-CeIOXUPU.js} +1 -1
- package/dist/assets/{DataModel-CHhjk61s.js → DataModel-D0p__Hil.js} +1 -1
- package/dist/assets/{History-CL-T6Lv3.js → History-DVqQ5azN.js} +1 -1
- package/dist/assets/{Hooks-ByWVgXIf.js → Hooks-DkTnRHyT.js} +1 -1
- package/dist/assets/{Library-QvYVAEmY.js → Library-l7KS6pkE.js} +1 -1
- package/dist/assets/{ListDetail-FvNe6fo5.js → ListDetail-DDw8fuRT.js} +1 -1
- package/dist/assets/{McpServers-C7EmPm8B.js → McpServers-UUNIxk_3.js} +1 -1
- package/dist/assets/{Memory-nyLR4Yw4.js → Memory-CNYUoQ8C.js} +1 -1
- package/dist/assets/{Panel-thElCbLX.js → Panel-Bb54soc2.js} +1 -1
- package/dist/assets/{Permissions-FQ9GM6Ca.js → Permissions-D-dNdV3f.js} +1 -1
- package/dist/assets/{Plugins-D9KE7XTI.js → Plugins-DhcIXRhQ.js} +2 -2
- package/dist/assets/{ProvenanceBadge-lAViG8yV.js → ProvenanceBadge-DEAkDNjA.js} +1 -1
- package/dist/assets/{RemoteMachines-AWpARk7N.js → RemoteMachines-xCEgvTGU.js} +1 -1
- package/dist/assets/{SaveBar-c15EEGB_.js → SaveBar-BSEs7ecA.js} +1 -1
- package/dist/assets/{Scheduler-FvduP1FG.js → Scheduler-BIKJIU-C.js} +1 -1
- package/dist/assets/{ScopeSwitcher-BLep1rtB.js → ScopeSwitcher-DcaFyAV-.js} +1 -1
- package/dist/assets/{Settings-D1kTUIDB.js → Settings-vcdWy_Jx.js} +1 -1
- package/dist/assets/{SkillReferenceGraph-_40_f0t3.js → SkillReferenceGraph-CLrJWTPd.js} +1 -1
- package/dist/assets/{Skills-DaTkSOuU.js → Skills-CYM4rBEC.js} +1 -1
- package/dist/assets/{SystemPrompt-DxNf-soM.js → SystemPrompt-BWNIu-iY.js} +1 -1
- package/dist/assets/{TagLibrary-L6KpBV-x.js → TagLibrary-CYdwHK6m.js} +1 -1
- package/dist/assets/{TiptapBody-C_XjtJ82.js → TiptapBody-Kt90lHrD.js} +1 -1
- package/dist/assets/{Toggle-H0rERKNI.js → Toggle-D1OXWYOz.js} +1 -1
- package/dist/assets/{ViewTabs-ZUWphDat.js → ViewTabs-BeQR3CKh.js} +1 -1
- package/dist/assets/{historyTotals-BxFKOztb.js → historyTotals-B0qafrRo.js} +1 -1
- package/dist/assets/{index-B5wj0DEX.js → index-Bq9uH08R.js} +200 -200
- package/dist/assets/{settingsSchema-BRcYkXJN.js → settingsSchema-0ZksSAPB.js} +1 -1
- package/dist/assets/{skillFrontmatter-DFGp4DST.js → skillFrontmatter-C0nh8yfa.js} +1 -1
- package/dist/assets/{useAgentsDirWatch-VvrgalfC.js → useAgentsDirWatch-DPkoBybS.js} +1 -1
- package/dist/assets/{useRemotePeers-mvXKNEE3.js → useRemotePeers-BDJMUihy.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/main/build-info.json +4 -4
- package/src/main/chatRunner.cjs +43 -6
- package/src/main/ipcSchemas.cjs +15 -1
- package/src/main/lib/persona-macro/macroLibrary.cjs +75 -0
- package/src/main/lib/prd/prdCreate.cjs +25 -0
- package/src/main/lib/remoteTranscriptTail.cjs +69 -2
- package/src/main/webRemote.cjs +82 -91
- package/src/preload/api.d.ts +13 -0
- package/src/preload/index.cjs +2 -0
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
|
@@ -1646,6 +1646,17 @@ export interface ChatExternalSendEvent {
|
|
|
1646
1646
|
/** Epic's claudeSessionId + project cwd, resolved by main so the renderer can deliver before the Epic's project is loaded. */
|
|
1647
1647
|
sessionId?: string;
|
|
1648
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;
|
|
1649
1660
|
}
|
|
1650
1661
|
|
|
1651
1662
|
export type CreateProjectFolderResult =
|
|
@@ -2297,6 +2308,8 @@ export interface SessionManagerAPI {
|
|
|
2297
2308
|
* tool) pushes a prompt into an open tab's queue from outside the
|
|
2298
2309
|
* renderer (PRD 753). */
|
|
2299
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>;
|
|
2300
2313
|
/** Fires when a paired phone asks main to start a new Session (payload pre-validated in main). */
|
|
2301
2314
|
onExternalNewSession: (
|
|
2302
2315
|
handler: (e: { cwd: string; agentName: string; tag: AgentPersonaTag; title: string; prompt: string }) => void,
|
package/src/preload/index.cjs
CHANGED
|
@@ -536,6 +536,8 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
536
536
|
ipcRenderer.on('chat:external-send', listener);
|
|
537
537
|
return () => ipcRenderer.removeListener('chat:external-send', listener);
|
|
538
538
|
},
|
|
539
|
+
/** Reports the renderer's disposition for an external send back to main. */
|
|
540
|
+
reportExternalSendResult: (result) => ipcRenderer.invoke('chat:external-send-result', result),
|
|
539
541
|
/** Fires when a paired phone asks main to start a new Session. */
|
|
540
542
|
onExternalNewSession: (handler) => {
|
|
541
543
|
const listener = (_e, payload) => handler(payload);
|