claude-code-session-manager 0.33.1 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +5 -1
  2. package/dist/assets/{TiptapBody-BqQFXHkk.js → TiptapBody-BqDK21Pr.js} +51 -51
  3. package/dist/assets/index-CPTin6qz.css +32 -0
  4. package/dist/assets/index-zepGuf8m.js +3536 -0
  5. package/dist/index.html +2 -2
  6. package/package.json +1 -1
  7. package/plugins/session-manager-dev/skills/discover-features/SKILL.md +95 -0
  8. package/plugins/session-manager-dev/skills/optimize-kpi/SKILL.md +9 -9
  9. package/plugins/session-manager-dev/skills/process-feedback/SKILL.md +77 -62
  10. package/plugins/session-manager-dev/skills/project-status/SKILL.md +13 -0
  11. package/src/main/__tests__/chat-queue.test.cjs +65 -0
  12. package/src/main/__tests__/chat-stop-signal.test.cjs +52 -0
  13. package/src/main/__tests__/exchanges.test.cjs +177 -0
  14. package/src/main/__tests__/files-reject-credentials.test.cjs +40 -0
  15. package/src/main/__tests__/kg-augment.test.cjs +195 -0
  16. package/src/main/__tests__/memoryAggregate.test.cjs +70 -0
  17. package/src/main/agentMemory.cjs +0 -21
  18. package/src/main/chatRunner.cjs +398 -0
  19. package/src/main/exchanges.cjs +125 -0
  20. package/src/main/files.cjs +20 -8
  21. package/src/main/historyAggregator.cjs +0 -162
  22. package/src/main/index.cjs +76 -57
  23. package/src/main/ipcSchemas.cjs +70 -30
  24. package/src/main/lib/kgExchangePairing.cjs +75 -0
  25. package/src/main/lib/reaperHelpers.cjs +7 -1
  26. package/src/main/lib/summarize.cjs +114 -0
  27. package/src/main/lib/toolUseClassify.cjs +19 -0
  28. package/src/main/memoryAggregate.cjs +250 -0
  29. package/src/main/pluginInstall.cjs +4 -2
  30. package/src/main/scheduler.cjs +67 -21
  31. package/src/main/supervisor.cjs +16 -0
  32. package/src/main/transcripts.cjs +22 -2
  33. package/src/main/voiceHotkey.cjs +0 -2
  34. package/src/main/webRemote.cjs +11 -78
  35. package/src/preload/api.d.ts +140 -125
  36. package/src/preload/index.cjs +50 -29
  37. package/dist/assets/index-1PpZBVUr.js +0 -3535
  38. package/dist/assets/index-AKeGl-VM.css +0 -32
  39. package/src/main/kg.cjs +0 -792
  40. package/src/main/lib/kgLite.cjs +0 -195
  41. package/src/main/lib/kgPrune.cjs +0 -87
@@ -28,6 +28,7 @@ const { writeTextAtomic, validatePath } = require('./config.cjs');
28
28
  const logs = require('./logs.cjs');
29
29
  const { sendIfAlive } = require('./lib/sendToRenderer.cjs');
30
30
  const { schemas } = require('./ipcSchemas.cjs');
31
+ const { summarize: _sharedSummarize } = require('./lib/summarize.cjs');
31
32
  const { makeState, confirmSas: confirmSasLogic } = require('./lib/e2eStateMachine.cjs');
32
33
 
33
34
  // ─── Constants ───────────────────────────────────────────────────────────────
@@ -740,70 +741,9 @@ function startSessionListPush() {
740
741
  }
741
742
 
742
743
  // ─── SM-V2-03: mobile summary via Claude Haiku 4.5 ───────────────────────────
744
+ // Summarization logic lives in lib/summarize.cjs (shared with exchanges.cjs).
743
745
 
744
- const SUMMARY_MIN_CHARS = 280; // below this, push raw — not worth an API call
745
- const SUMMARY_MODEL = 'claude-haiku-4-5';
746
- const SUMMARY_MAX_INPUT_CHARS = 24_000; // cap the turn text sent to Haiku (~6k tokens)
747
- const SUMMARY_SYSTEM =
748
- 'Summarize this Claude Code assistant turn for a phone screen in 2 sentences max, ' +
749
- 'followed by an optional list of up to 3 short action items. Plain text only — no ' +
750
- 'markdown headers, no code blocks. Lead with what was done or decided.';
751
-
752
- let _anthropicKeyCache = null; // memoized found key only (string); null = re-resolve
753
-
754
- /** Resolve the Anthropic API key: env → web-remote.json → null (degrade to raw).
755
- * Only a FOUND key is cached — if absent we re-resolve each call (cheap, loadConfig
756
- * is TTL-cached) so adding the key to web-remote.json later takes effect without a restart. */
757
- async function resolveAnthropicKey() {
758
- if (_anthropicKeyCache) return _anthropicKeyCache;
759
- const fromEnv = process.env.ANTHROPIC_API_KEY;
760
- if (fromEnv && fromEnv.trim()) { _anthropicKeyCache = fromEnv.trim(); return _anthropicKeyCache; }
761
- try {
762
- const cfg = await loadConfig();
763
- const k = cfg.anthropicApiKey;
764
- if (typeof k === 'string' && k.trim()) { _anthropicKeyCache = k.trim(); return _anthropicKeyCache; }
765
- } catch { /* fall through to null → re-resolve next time */ }
766
- return null;
767
- }
768
-
769
- /** POST to the Anthropic Messages API. Returns the first text block, or throws. */
770
- function anthropicSummarize(apiKey, text) {
771
- const body = JSON.stringify({
772
- model: SUMMARY_MODEL,
773
- max_tokens: 320,
774
- system: SUMMARY_SYSTEM,
775
- messages: [{ role: 'user', content: text.slice(0, SUMMARY_MAX_INPUT_CHARS) }],
776
- });
777
- return new Promise((resolve, reject) => {
778
- const req = https.request('https://api.anthropic.com/v1/messages', {
779
- method: 'POST',
780
- headers: {
781
- 'content-type': 'application/json',
782
- 'x-api-key': apiKey,
783
- 'anthropic-version': '2023-06-01',
784
- 'content-length': Buffer.byteLength(body),
785
- },
786
- timeout: 20_000,
787
- }, (res) => {
788
- let data = '';
789
- res.on('data', (c) => { data += c; });
790
- res.on('end', () => {
791
- if (res.statusCode < 200 || res.statusCode >= 300) {
792
- return reject(new Error(`anthropic HTTP ${res.statusCode}`));
793
- }
794
- try {
795
- const json = JSON.parse(data);
796
- const block = Array.isArray(json.content) ? json.content.find((b) => b.type === 'text') : null;
797
- if (!block?.text) return reject(new Error('no text in response'));
798
- resolve(block.text.trim());
799
- } catch (e) { reject(e); }
800
- });
801
- });
802
- req.on('error', reject);
803
- req.on('timeout', () => req.destroy(new Error('anthropic request timed out')));
804
- req.end(body);
805
- });
806
- }
746
+ const SUMMARY_MIN_CHARS = 280; // below this, push raw — not worth an API call
807
747
 
808
748
  /**
809
749
  * Produce a mobile summary of the watcher's last completed assistant turn and push it.
@@ -823,22 +763,15 @@ async function maybeSummarize(w) {
823
763
  return;
824
764
  }
825
765
 
826
- const apiKey = await resolveAnthropicKey();
827
- if (!apiKey) {
828
- // Degrade gracefully: push a trimmed raw message + a hint flag the app can surface.
829
- pushEvent('event:session:summary', {
830
- tabId: w.tabId, summary: text.slice(0, 600), ofMessageId, model: 'raw', degraded: 'no_api_key', ts: Date.now(),
831
- });
832
- return;
833
- }
834
-
835
- try {
836
- const summary = await anthropicSummarize(apiKey, text);
837
- pushEvent('event:session:summary', { tabId: w.tabId, summary, ofMessageId, model: SUMMARY_MODEL, ts: Date.now() });
838
- } catch (e) {
839
- logs.writeLine({ scope: 'webRemote', level: 'warn', message: 'summary failed; pushing raw', meta: { error: e?.message } });
840
- pushEvent('event:session:summary', { tabId: w.tabId, summary: text.slice(0, 600), ofMessageId, model: 'raw', degraded: 'api_error', ts: Date.now() });
766
+ const { summary, model, degraded } = await _sharedSummarize(text);
767
+ if (degraded) {
768
+ logs.writeLine({ scope: 'webRemote', level: 'warn', message: `summary degraded: ${degraded}`, meta: {} });
841
769
  }
770
+ pushEvent('event:session:summary', {
771
+ tabId: w.tabId, summary, ofMessageId, model,
772
+ ...(degraded ? { degraded } : {}),
773
+ ts: Date.now(),
774
+ });
842
775
  }
843
776
 
844
777
  // ─── Message handling & command dispatch ─────────────────────────────────────
@@ -443,24 +443,6 @@ export interface HistoryAggregateResult {
443
443
  scannedMs: number;
444
444
  }
445
445
 
446
- export interface ConversationSummary {
447
- /** ISO 8601 timestamp of the first event in the conversation (or file mtime fallback). */
448
- timestamp: string;
449
- /** Decoded project cwd, e.g. /home/user/Projects/foo. */
450
- projectFolder: string;
451
- stats: {
452
- /** Wall-clock duration in ms (first event ts → last event ts). Omitted when unknown. */
453
- duration?: number;
454
- /** Sum of input + output tokens across the file. 0 when no usage blocks present. */
455
- estimatedTokens: number;
456
- };
457
- }
458
-
459
- export interface ListConversationsResult {
460
- conversations: ConversationSummary[];
461
- scannedMs: number;
462
- }
463
-
464
446
  export interface SessionScanEntry {
465
447
  sessionId: string;
466
448
  projectEncoded: string;
@@ -490,7 +472,6 @@ export interface FilesWriteResult { ok: boolean; error: string | null }
490
472
  export interface FilesCreateResult { ok: boolean; path?: string; error: string | null }
491
473
  export interface FilesRenameResult { ok: boolean; newPath?: string; error: string | null }
492
474
  export interface FilesDeleteResult { ok: boolean; error: string | null }
493
- export interface FilesShellResult { ok: boolean; error?: string }
494
475
 
495
476
  export interface SearchFileEntry {
496
477
  name: string;
@@ -681,6 +662,29 @@ export interface MemoryMutationResult {
681
662
  error: string | null;
682
663
  }
683
664
 
665
+ export interface MemoryClusterLink {
666
+ from: string;
667
+ to: string;
668
+ label?: string;
669
+ }
670
+
671
+ export interface MemoryCluster {
672
+ id: string;
673
+ name: string;
674
+ summary: string;
675
+ memberSlugs: string[];
676
+ links: MemoryClusterLink[];
677
+ }
678
+
679
+ export interface MemoryAggregateResult {
680
+ workspace: string;
681
+ generatedAt: number | null;
682
+ clusters: MemoryCluster[];
683
+ orphans: string[];
684
+ cached: boolean;
685
+ error?: string;
686
+ }
687
+
684
688
  // ────────────────────────────────────────────── Per-subagent memory
685
689
  // Stored at ~/.claude/session-manager/agent-memory/<agentId>.json. Keyed by
686
690
  // agent name (the .md filename in ~/.claude/agents/), not by workspace cwd.
@@ -712,17 +716,6 @@ export interface AgentMemoryMutationResult {
712
716
  error: string | null;
713
717
  }
714
718
 
715
- export interface AgentMemoryAgentSummary {
716
- agentId: string;
717
- bytes: number;
718
- mtimeMs: number;
719
- }
720
-
721
- export interface AgentMemoryListAgentsResult {
722
- agents: AgentMemoryAgentSummary[];
723
- error: string | null;
724
- }
725
-
726
719
  // ────────────────────────────────────────────── Git status (richer than app:git-branch)
727
720
 
728
721
  /** Mirrors the status returned by src/main/git.cjs mapStatus(). */
@@ -845,6 +838,67 @@ export interface WebRemoteAuditTailResult {
845
838
  error?: string;
846
839
  }
847
840
 
841
+ // ────────────────────────────────────────────── Chat runner (PRD 319)
842
+
843
+ export interface ChatRunPayload {
844
+ tabId: string;
845
+ /** UUID for the claude session — created by the caller; stays the same across
846
+ * resumes so transcript context carries forward. */
847
+ sessionId: string;
848
+ prompt: string;
849
+ cwd: string;
850
+ /** When true, use --resume <sessionId> instead of --session-id. */
851
+ resume?: boolean;
852
+ }
853
+
854
+ export interface ChatRunQueuedEvent {
855
+ tabId: string;
856
+ sessionId: string;
857
+ /** 1-based position in the FIFO queue while waiting behind a busy lane. */
858
+ position: number;
859
+ }
860
+
861
+ export interface ChatRunStartedEvent {
862
+ tabId: string;
863
+ sessionId: string;
864
+ }
865
+
866
+ export interface ChatRunOutputEvent {
867
+ tabId: string;
868
+ /** Incremental text delta from one assistant content block. */
869
+ delta: string;
870
+ }
871
+
872
+ export interface ChatRunToolUseEvent {
873
+ tabId: string;
874
+ /** tool_use content block id from the stream-json event. */
875
+ id: string;
876
+ kind: 'skill' | 'mcp' | 'tool';
877
+ label: string;
878
+ }
879
+
880
+ export interface ChatRunNeedsInputEvent {
881
+ tabId: string;
882
+ sessionId: string;
883
+ /** Questions the agent is blocked on. */
884
+ questions: string[];
885
+ /** Full raw assistant text including the stop-signal sentinel. */
886
+ raw: string;
887
+ }
888
+
889
+ export interface ChatRunCompleteEvent {
890
+ tabId: string;
891
+ sessionId: string;
892
+ /** The run's own final assistant message verbatim. */
893
+ finalMessage: string;
894
+ }
895
+
896
+ export interface ChatRunErrorEvent {
897
+ tabId: string;
898
+ sessionId: string;
899
+ message: string;
900
+ }
901
+
848
902
  export interface SessionManagerAPI {
849
903
  app: {
850
904
  version: () => Promise<string>;
@@ -867,16 +921,6 @@ export interface SessionManagerAPI {
867
921
  homeSelfCheck: () => Promise<{ ok: boolean; error?: string; realCwd?: string }>;
868
922
  onNewSession: (handler: () => void) => () => void;
869
923
  onRebootSession: (handler: () => void) => () => void;
870
- openInEditor: (cwd: string, editor?: string | null) => Promise<{ ok: boolean; opener?: string; error?: string }>;
871
- /** Open an http/https URL in the OS default browser. file://, javascript:,
872
- * and other schemes are rejected with `ok:false` to prevent abuse. */
873
- openExternal: (url: string) => Promise<{ ok: boolean; error?: string }>;
874
- /** Open a specific file at line:col in the user's editor. Editors with
875
- * goto-line support (code/cursor/subl) get the `-g file:line:col` form;
876
- * others open the file alone. Image files are routed to the OS default viewer. */
877
- openFileInEditor: (filePath: string, line?: number, col?: number, editor?: string | null) => Promise<{ ok: boolean; opener?: string; error?: string }>;
878
- openInFinder: (cwd: string) => Promise<{ ok: boolean; opener?: string; error?: string }>;
879
- openInTerminal: (cwd: string) => Promise<{ ok: boolean; opener?: string; error?: string }>;
880
924
  archiveProject: (encoded: string) => Promise<{ ok: boolean; error?: string }>;
881
925
  };
882
926
  pty: {
@@ -929,7 +973,6 @@ export interface SessionManagerAPI {
929
973
  onHotkeyConfigChanged: (handler: (cfg: VoiceHotkeyConfig) => void) => () => void;
930
974
  getHotkeyConfig: () => Promise<VoiceHotkeyConfig>;
931
975
  setHotkeyConfig: (cfg: VoiceHotkeyConfig) => Promise<VoiceSetHotkeyResult>;
932
- getHotkeyConfigPath: () => Promise<string>;
933
976
  setRecording: (recording: boolean) => void;
934
977
  /** F5: read persisted audio-input device preference. */
935
978
  getDevicePref: () => Promise<VoiceDevicePref>;
@@ -965,8 +1008,21 @@ export interface SessionManagerAPI {
965
1008
  create: (parentPath: string, name: string, kind: 'file' | 'folder') => Promise<FilesCreateResult>;
966
1009
  rename: (path: string, newName: string) => Promise<FilesRenameResult>;
967
1010
  delete: (path: string) => Promise<FilesDeleteResult>;
968
- openExternal: (path: string) => Promise<FilesShellResult>;
969
- showInFinder: (path: string) => Promise<FilesShellResult>;
1011
+ };
1012
+ /** Consolidated shell open/reveal. One method, discriminated on `as`, replaces
1013
+ * the former app.openIn* / app.openExternal / files.openExternal / files.showInFinder.
1014
+ * Each variant's boundary guard (home-scope / http(s)-only) runs in the main handler. */
1015
+ shell: {
1016
+ open: (
1017
+ opts:
1018
+ | { as: 'editor'; cwd: string; editor?: string | null }
1019
+ | { as: 'fileInEditor'; path: string; line?: number; col?: number; editor?: string | null }
1020
+ | { as: 'finder'; cwd: string }
1021
+ | { as: 'terminal'; cwd: string }
1022
+ | { as: 'external'; url: string }
1023
+ | { as: 'openPath'; path: string }
1024
+ | { as: 'revealPath'; path: string }
1025
+ ) => Promise<{ ok: boolean; opener?: string; error?: string }>;
970
1026
  };
971
1027
  search: {
972
1028
  files: (cwd: string, query?: string, opts?: { limit?: number }) => Promise<SearchFilesResult>;
@@ -983,7 +1039,6 @@ export interface SessionManagerAPI {
983
1039
  };
984
1040
  history: {
985
1041
  aggregate: (req?: HistoryAggregateRequest) => Promise<HistoryAggregateResult>;
986
- listConversations: () => Promise<ListConversationsResult>;
987
1042
  scanProjects: () => Promise<SessionScanResult>;
988
1043
  };
989
1044
  schedule: {
@@ -993,7 +1048,6 @@ export interface SessionManagerAPI {
993
1048
  runNow: () => Promise<{ ok: boolean }>;
994
1049
  forceTick: () => Promise<{ ok: boolean }>;
995
1050
  resume: () => Promise<{ ok: boolean }>;
996
- refreshReset: () => Promise<{ ok: boolean; nextReset: string | null }>;
997
1051
  /** Re-scan prds/ and merge into queue.json; broadcasts updated state. */
998
1052
  rescan: () => Promise<{ ok: boolean }>;
999
1053
  /** Move all pending+failed PRDs to prds-archived/<ISO>/ and drop their
@@ -1057,6 +1111,8 @@ export interface SessionManagerAPI {
1057
1111
  delete: (name: string, workspace?: string) => Promise<MemoryMutationResult>;
1058
1112
  /** Create a new memory entry with starter frontmatter + body. */
1059
1113
  create: (name: string, description?: string, workspace?: string) => Promise<MemoryMutationResult>;
1114
+ /** Aggregate workspace memories into semantic clusters. `refresh:true` fires a cost-gated `claude -p` pass; otherwise returns the cache only. */
1115
+ aggregate: (workspace: string, refresh?: boolean) => Promise<MemoryAggregateResult>;
1060
1116
  };
1061
1117
  agentMemory: {
1062
1118
  /** List all memory entries for one subagent. Sorted newest first. */
@@ -1072,8 +1128,6 @@ export interface SessionManagerAPI {
1072
1128
  ) => Promise<AgentMemoryMutationResult>;
1073
1129
  /** Delete one entry. Removes the file outright when last entry is removed. */
1074
1130
  delete: (agentId: string, entryId: string) => Promise<AgentMemoryMutationResult>;
1075
- /** List all agents that currently have a memory file on disk. */
1076
- listAgents: () => Promise<AgentMemoryListAgentsResult>;
1077
1131
  };
1078
1132
  docEditor: {
1079
1133
  pickFile: (payload?: { lastDir?: string }) => Promise<{ path: string | null; error?: string }>;
@@ -1121,88 +1175,49 @@ export interface SessionManagerAPI {
1121
1175
  * E2E session as authenticated and unblocks MUTATE-tier commands. */
1122
1176
  confirmSas: () => Promise<WebRemoteMutationResult>;
1123
1177
  };
1124
- kg: {
1125
- /** Distilled knowledge graph + ingest status for ONE project (`cwd`).
1126
- * Omit `cwd` for the most-active project. */
1127
- get: (cwd?: string) => Promise<KgState>;
1128
- /** Projects seen in the prompt log, with per-project graph stats. */
1129
- projects: () => Promise<KgProject[]>;
1130
- /** Process new prompt-log lines into per-project graphs (incremental, global). */
1131
- ingest: () => Promise<{ ok: boolean; added?: number; projects?: number; stopped?: boolean; error?: string; note?: string }>;
1132
- /** Ask a question answered from ONE project's graph + prompts via claude -p. */
1133
- ask: (question: string, cwd?: string) => Promise<{ ok: boolean; answer?: string; cited?: { ts: string; prompt: string }[]; error?: string }>;
1134
- /** Purge graphs: one project (`{ cwd }`) or all (`{ all: true }`). */
1135
- clear: (arg?: { cwd?: string; all?: boolean }) => Promise<{ ok: boolean; cleared?: string; removed?: number }>;
1136
- /** Toggle the recurring claude -p extraction on/off (sets captureMode 'llm'/'off'). */
1137
- setExtraction: (enabled: boolean) => Promise<{ ok: boolean; extractionEnabled: boolean }>;
1138
- /** Set capture mode: 'llm' = claude -p extraction; 'lite' = heuristic (free); 'off' = disabled. */
1139
- setCaptureMode: (mode: 'llm' | 'lite' | 'off') => Promise<{ ok: boolean; captureMode?: string; error?: string }>;
1140
- onIngestProgress: (handler: (ev: KgIngestProgress) => void) => () => void;
1178
+ chat: {
1179
+ /** Spawn a headless `claude -p` run for a tab. Results arrive via the on* listeners. */
1180
+ run: (payload: ChatRunPayload) => Promise<{ ok: boolean }>;
1181
+ /** Cancel the in-flight run for a tab (SIGTERM→SIGKILL). No-op when idle. */
1182
+ cancel: (tabId: string) => void;
1183
+ onQueued: (handler: (e: ChatRunQueuedEvent) => void) => () => void;
1184
+ onRunStarted: (handler: (e: ChatRunStartedEvent) => void) => () => void;
1185
+ onOutput: (handler: (e: ChatRunOutputEvent) => void) => () => void;
1186
+ onToolUse: (handler: (e: ChatRunToolUseEvent) => void) => () => void;
1187
+ onNeedsInput: (handler: (e: ChatRunNeedsInputEvent) => void) => () => void;
1188
+ onComplete: (handler: (e: ChatRunCompleteEvent) => void) => () => void;
1189
+ onError: (handler: (e: ChatRunErrorEvent) => void) => () => void;
1190
+ };
1191
+ exchanges: {
1192
+ /** Durable per-exchange log entries for a project, newest-first (max 100 by default). */
1193
+ list: (payload: {
1194
+ cwd: string;
1195
+ sessionId?: string;
1196
+ limit?: number;
1197
+ offset?: number;
1198
+ }) => Promise<Exchange[]>;
1141
1199
  };
1142
1200
  }
1143
1201
 
1144
- export interface KgProject {
1145
- cwd: string;
1146
- /** Last path segment of `cwd`, for display. */
1147
- label: string;
1148
- /** Prompts logged for this project (real prompts only). */
1149
- total: number;
1150
- /** Prompts already distilled into this project's graph. */
1151
- processed: number;
1152
- /** total - processed. */
1153
- pending: number;
1154
- nodes: number;
1155
- edges: number;
1156
- lastIngest: string | null;
1157
- }
1158
-
1159
- export interface KgNode {
1160
- id: string;
1161
- key: string;
1162
- name: string;
1163
- type: string;
1164
- description: string;
1165
- count: number;
1166
- firstTs: string | null;
1167
- lastTs: string | null;
1168
- }
1169
- export interface KgEdge {
1170
- src: string;
1171
- dst: string;
1172
- relation: string;
1173
- weight: number;
1174
- lastTs: string | null;
1175
- }
1176
- export interface KgState {
1177
- /** The project this graph belongs to. */
1202
+ // ────────────────────────────────────────────── Exchanges (PRD 324 read path)
1203
+
1204
+ export interface Exchange {
1205
+ /** ISO 8601 timestamp when the exchange was recorded. */
1206
+ ts: string;
1207
+ /** Claude session UUID. */
1208
+ sessionId: string;
1209
+ /** Project cwd. */
1178
1210
  cwd: string;
1179
- /** Last path segment of `cwd`, for display. */
1180
- label: string;
1181
- nodes: KgNode[];
1182
- edges: KgEdge[];
1183
- status: {
1184
- promptCount: number;
1185
- totalPrompts: number;
1186
- pending: number;
1187
- lastIngest: string | null;
1188
- ingesting: boolean;
1189
- logPath: string;
1190
- extractionEnabled: boolean;
1191
- /** Active capture mode: 'llm' | 'lite' | 'off'. */
1192
- captureMode: 'llm' | 'lite' | 'off';
1193
- /** Node cap from kg-config.json; 0 = disabled; default 300. */
1194
- maxGraphNodes: number;
1195
- };
1196
- }
1197
- export interface KgIngestProgress {
1198
- phase: 'start' | 'extract' | 'batch' | 'done' | 'error';
1199
- ingesting: boolean;
1200
- batch?: number;
1201
- totalBatches?: number;
1202
- added?: number;
1203
- /** Set on `batch`: the project cwd whose graph just committed. */
1204
- cwd?: string;
1205
- error?: string;
1211
+ /** The user prompt that started the exchange. */
1212
+ prompt: string;
1213
+ /** The verbatim assistant result. */
1214
+ result: string;
1215
+ /** Haiku-generated one-sentence summary. */
1216
+ summary: string;
1217
+ /** Haiku model used for the summary (informational). */
1218
+ model?: string;
1219
+ /** Set when summarization failed — `result` is available but `summary` may be empty. */
1220
+ degraded?: boolean;
1206
1221
  }
1207
1222
 
1208
1223
  declare global {
@@ -9,11 +9,6 @@ contextBridge.exposeInMainWorld('api', {
9
9
  pickDirectory: () => ipcRenderer.invoke('app:pick-directory'),
10
10
  gitBranch: (cwd) => ipcRenderer.invoke('app:git-branch', { cwd }),
11
11
  rebootApp: () => ipcRenderer.send('app:reboot-app'),
12
- openInEditor: (cwd, editor) => ipcRenderer.invoke('app:open-in-editor', { cwd, editor }),
13
- openExternal: (url) => ipcRenderer.invoke('app:open-external', { url }),
14
- openFileInEditor: (filePath, line, col, editor) => ipcRenderer.invoke('app:open-file-in-editor', { path: filePath, line, col, editor }),
15
- openInFinder: (cwd) => ipcRenderer.invoke('app:open-in-finder', { cwd }),
16
- openInTerminal: (cwd) => ipcRenderer.invoke('app:open-in-terminal', { cwd }),
17
12
  archiveProject: (encoded) => ipcRenderer.invoke('app:archive-project', { encoded }),
18
13
  testFireHook: (args) => ipcRenderer.invoke('app:test-fire-hook', args),
19
14
  // F7: lets the renderer suppress the wizard auto-trigger under SM_E2E=1.
@@ -121,7 +116,6 @@ contextBridge.exposeInMainWorld('api', {
121
116
  },
122
117
  getHotkeyConfig: () => ipcRenderer.invoke('voice:get-hotkey-config'),
123
118
  setHotkeyConfig: (cfg) => ipcRenderer.invoke('voice:set-hotkey', cfg),
124
- getHotkeyConfigPath: () => ipcRenderer.invoke('voice:get-hotkey-config-path'),
125
119
  setRecording: (recording) => ipcRenderer.send('voice:set-recording', !!recording),
126
120
  // F5 device picker prefs (~/.config/session-manager/voice.json `device` key).
127
121
  getDevicePref: () => ipcRenderer.invoke('voice:get-device-pref'),
@@ -158,7 +152,6 @@ contextBridge.exposeInMainWorld('api', {
158
152
  },
159
153
  history: {
160
154
  aggregate: (req) => ipcRenderer.invoke('history:aggregate', req),
161
- listConversations: () => ipcRenderer.invoke('history:list-conversations'),
162
155
  scanProjects: () => ipcRenderer.invoke('history:scan-projects'),
163
156
  },
164
157
  files: {
@@ -168,8 +161,11 @@ contextBridge.exposeInMainWorld('api', {
168
161
  create: (parentPath, name, kind) => ipcRenderer.invoke('files:create', { parentPath, name, kind }),
169
162
  rename: (path, newName) => ipcRenderer.invoke('files:rename', { path, newName }),
170
163
  delete: (path) => ipcRenderer.invoke('files:delete', { path }),
171
- openExternal: (path) => ipcRenderer.invoke('files:open-external', { path }),
172
- showInFinder: (path) => ipcRenderer.invoke('files:show-in-finder', { path }),
164
+ },
165
+ // Consolidated shell open/reveal — see shell:open in index.cjs.
166
+ // as: 'editor' | 'fileInEditor' | 'finder' | 'terminal' | 'external' | 'openPath' | 'revealPath'
167
+ shell: {
168
+ open: (opts) => ipcRenderer.invoke('shell:open', opts),
173
169
  },
174
170
  search: {
175
171
  files: (cwd, query, opts) => ipcRenderer.invoke('search:files', { cwd, query, opts }),
@@ -191,7 +187,6 @@ contextBridge.exposeInMainWorld('api', {
191
187
  runNow: () => ipcRenderer.invoke('schedule:run-now'),
192
188
  forceTick: () => ipcRenderer.invoke('schedule:force-tick'),
193
189
  resume: () => ipcRenderer.invoke('schedule:resume'),
194
- refreshReset: () => ipcRenderer.invoke('schedule:refresh-reset'),
195
190
  rescan: () => ipcRenderer.invoke('schedule:rescan'),
196
191
  clearQueue: () => ipcRenderer.invoke('schedule:clear-queue'),
197
192
  openFolder: () => ipcRenderer.invoke('schedule:open-folder'),
@@ -244,6 +239,8 @@ contextBridge.exposeInMainWorld('api', {
244
239
  if (workspace) payload.workspace = workspace;
245
240
  return ipcRenderer.invoke('memory:create', payload);
246
241
  },
242
+ aggregate: (workspace, refresh) =>
243
+ ipcRenderer.invoke('memory:aggregate', refresh ? { workspace, refresh: true } : { workspace }),
247
244
  },
248
245
  agentMemory: {
249
246
  list: (agentId) => ipcRenderer.invoke('agent-memory:list', { agentId }),
@@ -254,7 +251,6 @@ contextBridge.exposeInMainWorld('api', {
254
251
  return ipcRenderer.invoke('agent-memory:set', payload);
255
252
  },
256
253
  delete: (agentId, entryId) => ipcRenderer.invoke('agent-memory:delete', { agentId, entryId }),
257
- listAgents: () => ipcRenderer.invoke('agent-memory:list-agents'),
258
254
  },
259
255
  docEditor: {
260
256
  pickFile: (payload) => ipcRenderer.invoke('doc-editor:pick-file', payload),
@@ -320,25 +316,50 @@ contextBridge.exposeInMainWorld('api', {
320
316
  return () => ipcRenderer.removeListener('webRemote:revoked-all', listener);
321
317
  },
322
318
  },
323
- kg: {
324
- /** Knowledge Graph distilled from the prompt log (~/.claude/knowledge-log),
325
- * segregated per project. `cwd` selects which project's graph; omit for the
326
- * most-active project. */
327
- get: (cwd) => ipcRenderer.invoke('kg:get', { cwd }),
328
- /** Projects seen in the log, with per-project graph stats (for the selector). */
329
- projects: () => ipcRenderer.invoke('kg:projects'),
330
- ingest: () => ipcRenderer.invoke('kg:ingest'),
331
- ask: (question, cwd) => ipcRenderer.invoke('kg:ask', { question, cwd }),
332
- /** Purge graphs: one project (cwd) or all (`{ all: true }`). */
333
- clear: (arg) => ipcRenderer.invoke('kg:clear', arg ?? {}),
334
- /** Toggle the recurring `claude -p` extraction on/off. */
335
- setExtraction: (enabled) => ipcRenderer.invoke('kg:set-extraction', { enabled }),
336
- /** Set capture mode: 'llm' | 'lite' | 'off'. */
337
- setCaptureMode: (mode) => ipcRenderer.invoke('kg:set-capture-mode', { mode }),
338
- onIngestProgress: (handler) => {
319
+ chat: {
320
+ /** Spawn a headless claude -p job. Results arrive via the on* listeners. */
321
+ run: (payload) => ipcRenderer.invoke('chat:run', payload),
322
+ /** Cancel an in-flight run for the given tabId. No-op if none running. */
323
+ cancel: (tabId) => ipcRenderer.send('chat:cancel', { tabId }),
324
+ onQueued: (handler) => {
325
+ const listener = (_e, payload) => handler(payload);
326
+ ipcRenderer.on('chat:run:queued', listener);
327
+ return () => ipcRenderer.removeListener('chat:run:queued', listener);
328
+ },
329
+ onRunStarted: (handler) => {
330
+ const listener = (_e, payload) => handler(payload);
331
+ ipcRenderer.on('chat:run:started', listener);
332
+ return () => ipcRenderer.removeListener('chat:run:started', listener);
333
+ },
334
+ onOutput: (handler) => {
335
+ const listener = (_e, payload) => handler(payload);
336
+ ipcRenderer.on('chat:run:output', listener);
337
+ return () => ipcRenderer.removeListener('chat:run:output', listener);
338
+ },
339
+ onToolUse: (handler) => {
339
340
  const listener = (_e, payload) => handler(payload);
340
- ipcRenderer.on('kg:ingest-progress', listener);
341
- return () => ipcRenderer.removeListener('kg:ingest-progress', listener);
341
+ ipcRenderer.on('chat:run:tool-use', listener);
342
+ return () => ipcRenderer.removeListener('chat:run:tool-use', listener);
342
343
  },
344
+ onNeedsInput: (handler) => {
345
+ const listener = (_e, payload) => handler(payload);
346
+ ipcRenderer.on('chat:run:needs-input', listener);
347
+ return () => ipcRenderer.removeListener('chat:run:needs-input', listener);
348
+ },
349
+ onComplete: (handler) => {
350
+ const listener = (_e, payload) => handler(payload);
351
+ ipcRenderer.on('chat:run:complete', listener);
352
+ return () => ipcRenderer.removeListener('chat:run:complete', listener);
353
+ },
354
+ onError: (handler) => {
355
+ const listener = (_e, payload) => handler(payload);
356
+ ipcRenderer.on('chat:run:error', listener);
357
+ return () => ipcRenderer.removeListener('chat:run:error', listener);
358
+ },
359
+ },
360
+ exchanges: {
361
+ /** List exchanges for a project (durable chat-run log), newest-first.
362
+ * `sessionId` filters to one session; `limit`/`offset` for pagination. */
363
+ list: (payload) => ipcRenderer.invoke('exchanges:list', payload),
343
364
  },
344
365
  });