@luckydraw/cumulus 0.5.1

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 (142) hide show
  1. package/README.md +148 -0
  2. package/dist/cli/cumulus.d.ts +3 -0
  3. package/dist/cli/cumulus.d.ts.map +1 -0
  4. package/dist/cli/cumulus.js +233 -0
  5. package/dist/cli/cumulus.js.map +1 -0
  6. package/dist/index.d.ts +33 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +43 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/lib/config.d.ts +86 -0
  11. package/dist/lib/config.d.ts.map +1 -0
  12. package/dist/lib/config.js +241 -0
  13. package/dist/lib/config.js.map +1 -0
  14. package/dist/lib/content-detector.d.ts +46 -0
  15. package/dist/lib/content-detector.d.ts.map +1 -0
  16. package/dist/lib/content-detector.js +359 -0
  17. package/dist/lib/content-detector.js.map +1 -0
  18. package/dist/lib/content-store.d.ts +255 -0
  19. package/dist/lib/content-store.d.ts.map +1 -0
  20. package/dist/lib/content-store.js +955 -0
  21. package/dist/lib/content-store.js.map +1 -0
  22. package/dist/lib/context-budget.d.ts +83 -0
  23. package/dist/lib/context-budget.d.ts.map +1 -0
  24. package/dist/lib/context-budget.js +101 -0
  25. package/dist/lib/context-budget.js.map +1 -0
  26. package/dist/lib/embeddings.d.ts +64 -0
  27. package/dist/lib/embeddings.d.ts.map +1 -0
  28. package/dist/lib/embeddings.js +176 -0
  29. package/dist/lib/embeddings.js.map +1 -0
  30. package/dist/lib/history.d.ts +120 -0
  31. package/dist/lib/history.d.ts.map +1 -0
  32. package/dist/lib/history.js +205 -0
  33. package/dist/lib/history.js.map +1 -0
  34. package/dist/lib/image-utils.d.ts +41 -0
  35. package/dist/lib/image-utils.d.ts.map +1 -0
  36. package/dist/lib/image-utils.js +288 -0
  37. package/dist/lib/image-utils.js.map +1 -0
  38. package/dist/lib/migrate.d.ts +35 -0
  39. package/dist/lib/migrate.d.ts.map +1 -0
  40. package/dist/lib/migrate.js +196 -0
  41. package/dist/lib/migrate.js.map +1 -0
  42. package/dist/lib/retriever.d.ts +56 -0
  43. package/dist/lib/retriever.d.ts.map +1 -0
  44. package/dist/lib/retriever.js +644 -0
  45. package/dist/lib/retriever.js.map +1 -0
  46. package/dist/lib/revert.d.ts +23 -0
  47. package/dist/lib/revert.d.ts.map +1 -0
  48. package/dist/lib/revert.js +75 -0
  49. package/dist/lib/revert.js.map +1 -0
  50. package/dist/lib/session.d.ts +65 -0
  51. package/dist/lib/session.d.ts.map +1 -0
  52. package/dist/lib/session.js +289 -0
  53. package/dist/lib/session.js.map +1 -0
  54. package/dist/lib/snapshots.d.ts +39 -0
  55. package/dist/lib/snapshots.d.ts.map +1 -0
  56. package/dist/lib/snapshots.js +99 -0
  57. package/dist/lib/snapshots.js.map +1 -0
  58. package/dist/lib/stream-processor.d.ts +149 -0
  59. package/dist/lib/stream-processor.d.ts.map +1 -0
  60. package/dist/lib/stream-processor.js +389 -0
  61. package/dist/lib/stream-processor.js.map +1 -0
  62. package/dist/lib/summarizer.d.ts +67 -0
  63. package/dist/lib/summarizer.d.ts.map +1 -0
  64. package/dist/lib/summarizer.js +213 -0
  65. package/dist/lib/summarizer.js.map +1 -0
  66. package/dist/mcp/index.d.ts +3 -0
  67. package/dist/mcp/index.d.ts.map +1 -0
  68. package/dist/mcp/index.js +16 -0
  69. package/dist/mcp/index.js.map +1 -0
  70. package/dist/mcp/proxy.d.ts +19 -0
  71. package/dist/mcp/proxy.d.ts.map +1 -0
  72. package/dist/mcp/proxy.js +120 -0
  73. package/dist/mcp/proxy.js.map +1 -0
  74. package/dist/mcp/server.d.ts +6 -0
  75. package/dist/mcp/server.d.ts.map +1 -0
  76. package/dist/mcp/server.js +29 -0
  77. package/dist/mcp/server.js.map +1 -0
  78. package/dist/mcp/shared-server.d.ts +21 -0
  79. package/dist/mcp/shared-server.d.ts.map +1 -0
  80. package/dist/mcp/shared-server.js +210 -0
  81. package/dist/mcp/shared-server.js.map +1 -0
  82. package/dist/mcp/tool-handler.d.ts +20 -0
  83. package/dist/mcp/tool-handler.d.ts.map +1 -0
  84. package/dist/mcp/tool-handler.js +1405 -0
  85. package/dist/mcp/tool-handler.js.map +1 -0
  86. package/dist/tui/components/App.d.ts +11 -0
  87. package/dist/tui/components/App.d.ts.map +1 -0
  88. package/dist/tui/components/App.js +607 -0
  89. package/dist/tui/components/App.js.map +1 -0
  90. package/dist/tui/components/DebugContextView.d.ts +13 -0
  91. package/dist/tui/components/DebugContextView.d.ts.map +1 -0
  92. package/dist/tui/components/DebugContextView.js +78 -0
  93. package/dist/tui/components/DebugContextView.js.map +1 -0
  94. package/dist/tui/components/IncludeMenu.d.ts +12 -0
  95. package/dist/tui/components/IncludeMenu.d.ts.map +1 -0
  96. package/dist/tui/components/IncludeMenu.js +127 -0
  97. package/dist/tui/components/IncludeMenu.js.map +1 -0
  98. package/dist/tui/components/InputArea.d.ts +27 -0
  99. package/dist/tui/components/InputArea.d.ts.map +1 -0
  100. package/dist/tui/components/InputArea.js +366 -0
  101. package/dist/tui/components/InputArea.js.map +1 -0
  102. package/dist/tui/components/MarkdownText.d.ts +38 -0
  103. package/dist/tui/components/MarkdownText.d.ts.map +1 -0
  104. package/dist/tui/components/MarkdownText.js +234 -0
  105. package/dist/tui/components/MarkdownText.js.map +1 -0
  106. package/dist/tui/components/MessageBubble.d.ts +11 -0
  107. package/dist/tui/components/MessageBubble.d.ts.map +1 -0
  108. package/dist/tui/components/MessageBubble.js +16 -0
  109. package/dist/tui/components/MessageBubble.js.map +1 -0
  110. package/dist/tui/components/MessageHistory.d.ts +11 -0
  111. package/dist/tui/components/MessageHistory.d.ts.map +1 -0
  112. package/dist/tui/components/MessageHistory.js +12 -0
  113. package/dist/tui/components/MessageHistory.js.map +1 -0
  114. package/dist/tui/components/RevertMenu.d.ts +17 -0
  115. package/dist/tui/components/RevertMenu.d.ts.map +1 -0
  116. package/dist/tui/components/RevertMenu.js +144 -0
  117. package/dist/tui/components/RevertMenu.js.map +1 -0
  118. package/dist/tui/components/StatusBar.d.ts +14 -0
  119. package/dist/tui/components/StatusBar.d.ts.map +1 -0
  120. package/dist/tui/components/StatusBar.js +13 -0
  121. package/dist/tui/components/StatusBar.js.map +1 -0
  122. package/dist/tui/components/StreamingResponse.d.ts +15 -0
  123. package/dist/tui/components/StreamingResponse.d.ts.map +1 -0
  124. package/dist/tui/components/StreamingResponse.js +52 -0
  125. package/dist/tui/components/StreamingResponse.js.map +1 -0
  126. package/dist/tui/hooks/useAppState.d.ts +147 -0
  127. package/dist/tui/hooks/useAppState.d.ts.map +1 -0
  128. package/dist/tui/hooks/useAppState.js +110 -0
  129. package/dist/tui/hooks/useAppState.js.map +1 -0
  130. package/dist/tui/hooks/useClaudeProcess.d.ts +19 -0
  131. package/dist/tui/hooks/useClaudeProcess.d.ts.map +1 -0
  132. package/dist/tui/hooks/useClaudeProcess.js +185 -0
  133. package/dist/tui/hooks/useClaudeProcess.js.map +1 -0
  134. package/dist/tui/index.d.ts +10 -0
  135. package/dist/tui/index.d.ts.map +1 -0
  136. package/dist/tui/index.js +11 -0
  137. package/dist/tui/index.js.map +1 -0
  138. package/dist/tui/utils/streamParser.d.ts +31 -0
  139. package/dist/tui/utils/streamParser.d.ts.map +1 -0
  140. package/dist/tui/utils/streamParser.js +63 -0
  141. package/dist/tui/utils/streamParser.js.map +1 -0
  142. package/package.json +94 -0
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Revert orchestrator: truncates conversation history, cleans derived data
3
+ * (embeddings, summaries), and optionally restores git state.
4
+ */
5
+ import type { HistoryStore } from './history.js';
6
+ import { type GitSnapshot, type RestoreResult } from './snapshots.js';
7
+ export interface RevertResult {
8
+ success: boolean;
9
+ removedCount: number;
10
+ removedIds: string[];
11
+ gitResult?: RestoreResult;
12
+ error?: string;
13
+ }
14
+ export interface RevertOptions {
15
+ restoreGit?: boolean;
16
+ gitSnapshot?: GitSnapshot;
17
+ }
18
+ /**
19
+ * Execute a full revert: truncate history, clean embeddings/summaries,
20
+ * and optionally restore git state.
21
+ */
22
+ export declare function executeRevert(historyStore: HistoryStore, targetMessageId: string, options?: RevertOptions): Promise<RevertResult>;
23
+ //# sourceMappingURL=revert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revert.d.ts","sourceRoot":"","sources":["../../src/lib/revert.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAmB,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGvF,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,YAAY,EAAE,YAAY,EAC1B,eAAe,EAAE,MAAM,EACvB,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,YAAY,CAAC,CAoEvB"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Revert orchestrator: truncates conversation history, cleans derived data
3
+ * (embeddings, summaries), and optionally restores git state.
4
+ */
5
+ import * as fs from 'fs/promises';
6
+ import { getEmbeddingsPath } from './embeddings.js';
7
+ import { restoreSnapshot } from './snapshots.js';
8
+ import { loadSummaries, saveSummaries } from './summarizer.js';
9
+ /**
10
+ * Execute a full revert: truncate history, clean embeddings/summaries,
11
+ * and optionally restore git state.
12
+ */
13
+ export async function executeRevert(historyStore, targetMessageId, options) {
14
+ const threadPath = historyStore.threadPath;
15
+ // 1. Truncate history
16
+ let removedIds;
17
+ try {
18
+ removedIds = await historyStore.truncateTo(targetMessageId);
19
+ }
20
+ catch (err) {
21
+ return {
22
+ success: false,
23
+ removedCount: 0,
24
+ removedIds: [],
25
+ error: err instanceof Error ? err.message : 'Failed to truncate history',
26
+ };
27
+ }
28
+ const removedSet = new Set(removedIds);
29
+ // 2. Clean embeddings: remove entries for deleted messages
30
+ try {
31
+ const embeddingsPath = getEmbeddingsPath(threadPath);
32
+ const content = await fs.readFile(embeddingsPath, 'utf-8');
33
+ const data = JSON.parse(content);
34
+ const originalCount = data.entries.length;
35
+ data.entries = data.entries.filter(e => !removedSet.has(e.messageId));
36
+ if (data.entries.length !== originalCount) {
37
+ await fs.writeFile(embeddingsPath, JSON.stringify(data), 'utf-8');
38
+ }
39
+ }
40
+ catch {
41
+ // Embeddings file may not exist - not an error
42
+ }
43
+ // 3. Clean summaries: remove summaries that cover removed messages
44
+ try {
45
+ const summaryFile = await loadSummaries(threadPath);
46
+ if (summaryFile) {
47
+ // Get new last index from the kept messages
48
+ const keptMessages = await historyStore.getAll();
49
+ const newLastIndex = keptMessages.length - 1;
50
+ const originalCount = summaryFile.summaries.length;
51
+ summaryFile.summaries = summaryFile.summaries.filter(s => s.fromIndex <= newLastIndex);
52
+ summaryFile.lastMessageIndex = Math.min(summaryFile.lastMessageIndex, newLastIndex);
53
+ summaryFile.updatedAt = Date.now();
54
+ if (summaryFile.summaries.length !== originalCount ||
55
+ summaryFile.lastMessageIndex !== newLastIndex) {
56
+ await saveSummaries(summaryFile, threadPath);
57
+ }
58
+ }
59
+ }
60
+ catch {
61
+ // Summary cleanup failure is non-fatal
62
+ }
63
+ // 4. Restore git state if requested
64
+ let gitResult;
65
+ if (options?.restoreGit && options.gitSnapshot) {
66
+ gitResult = await restoreSnapshot(options.gitSnapshot);
67
+ }
68
+ return {
69
+ success: true,
70
+ removedCount: removedIds.length,
71
+ removedIds,
72
+ gitResult,
73
+ };
74
+ }
75
+ //# sourceMappingURL=revert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revert.js","sourceRoot":"","sources":["../../src/lib/revert.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ export interface SessionChunk {
2
+ index: number;
3
+ content: string;
4
+ tokenEstimate: number;
5
+ }
6
+ export interface SessionSearchResult {
7
+ sessionId: string;
8
+ isCurrentSession: boolean;
9
+ content_snippet: string;
10
+ score: number;
11
+ chunkIndex: number;
12
+ }
13
+ /**
14
+ * Manages a single session document — an append-only markdown file
15
+ * that accumulates conversation context, chunked and embedded for
16
+ * semantic search alongside regular messages.
17
+ */
18
+ export declare class SessionManager {
19
+ readonly sessionId: string;
20
+ readonly sessionsDir: string;
21
+ readonly sessionPath: string;
22
+ readonly embeddingsPath: string;
23
+ readonly startedAt: number;
24
+ private exchangeCount;
25
+ private threadName;
26
+ constructor(sessionsDir: string, threadName: string, existingSessionId?: string);
27
+ getSessionId(): string;
28
+ /**
29
+ * Create the session file with its header.
30
+ */
31
+ initialize(): Promise<void>;
32
+ /**
33
+ * Append an exchange to the session document.
34
+ * Does not re-embed — that happens lazily at search time.
35
+ */
36
+ appendExchange(userMsg: string, assistantMsg: string): Promise<void>;
37
+ /**
38
+ * Read the session file and chunk it on exchange boundaries,
39
+ * accumulating exchanges until the chunk reaches ~DEFAULT_CHUNK_SIZE tokens.
40
+ */
41
+ getChunks(): Promise<SessionChunk[]>;
42
+ /**
43
+ * Generate embeddings for new/changed chunks.
44
+ * Only the last chunk can change (since we only append),
45
+ * so we invalidate and regenerate it if needed.
46
+ */
47
+ generateMissingEmbeddings(): Promise<void>;
48
+ /**
49
+ * Search this session's chunks against a query embedding.
50
+ */
51
+ search(queryEmbedding: number[], threshold?: number): Promise<SessionSearchResult[]>;
52
+ /**
53
+ * Search ALL session docs in a sessions directory.
54
+ * Generates missing embeddings for each session before searching.
55
+ */
56
+ static searchAllSessions(sessionsDir: string, queryEmbedding: number[], currentSessionId: string, threshold?: number): Promise<SessionSearchResult[]>;
57
+ /**
58
+ * Reconstruct chunk content from parts given a chunk index.
59
+ * Mirrors the chunking logic in getChunks().
60
+ */
61
+ private static getChunkContent;
62
+ private loadEmbeddings;
63
+ private saveEmbeddings;
64
+ }
65
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/lib/session.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,qBAAa,cAAc;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,UAAU,CAAS;gBAEf,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM;IAS/E,YAAY,IAAI,MAAM;IAItB;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAMjC;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM1E;;;OAGG;IACG,SAAS,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IA4C1C;;;;OAIG;IACG,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC;IAiChD;;OAEG;IACG,MAAM,CACV,cAAc,EAAE,MAAM,EAAE,EACxB,SAAS,GAAE,MAA2B,GACrC,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAyBjC;;;OAGG;WACU,iBAAiB,CAC5B,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EAAE,EACxB,gBAAgB,EAAE,MAAM,EACxB,SAAS,GAAE,MAA2B,GACrC,OAAO,CAAC,mBAAmB,EAAE,CAAC;IA+EjC;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;YA8BhB,cAAc;YAed,cAAc;CAuB7B"}
@@ -0,0 +1,289 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { nanoid } from 'nanoid';
4
+ import { estimateTokens } from './context-budget.js';
5
+ import { cosineSimilarity, embeddingsAvailable, getEmbeddingProvider, } from './embeddings.js';
6
+ const DEFAULT_CHUNK_SIZE = 4000; // tokens
7
+ const EXCHANGE_DELIMITER = '\n## Exchange ';
8
+ const SEMANTIC_THRESHOLD = 0.3;
9
+ /**
10
+ * Manages a single session document — an append-only markdown file
11
+ * that accumulates conversation context, chunked and embedded for
12
+ * semantic search alongside regular messages.
13
+ */
14
+ export class SessionManager {
15
+ sessionId;
16
+ sessionsDir;
17
+ sessionPath;
18
+ embeddingsPath;
19
+ startedAt;
20
+ exchangeCount = 0;
21
+ threadName;
22
+ constructor(sessionsDir, threadName, existingSessionId) {
23
+ this.sessionId = existingSessionId ?? `ses_${nanoid(8)}`;
24
+ this.sessionsDir = sessionsDir;
25
+ this.threadName = threadName;
26
+ this.sessionPath = path.join(sessionsDir, `${this.sessionId}.md`);
27
+ this.embeddingsPath = path.join(sessionsDir, `${this.sessionId}.embeddings.json`);
28
+ this.startedAt = Date.now();
29
+ }
30
+ getSessionId() {
31
+ return this.sessionId;
32
+ }
33
+ /**
34
+ * Create the session file with its header.
35
+ */
36
+ async initialize() {
37
+ await fs.mkdir(this.sessionsDir, { recursive: true });
38
+ const header = `# Session on thread "${this.threadName}" | ${new Date(this.startedAt).toISOString()}\n`;
39
+ await fs.writeFile(this.sessionPath, header, 'utf-8');
40
+ }
41
+ /**
42
+ * Append an exchange to the session document.
43
+ * Does not re-embed — that happens lazily at search time.
44
+ */
45
+ async appendExchange(userMsg, assistantMsg) {
46
+ this.exchangeCount++;
47
+ const block = `${EXCHANGE_DELIMITER}${this.exchangeCount}\nUser: ${userMsg}\nAssistant: ${assistantMsg}\n`;
48
+ await fs.appendFile(this.sessionPath, block, 'utf-8');
49
+ }
50
+ /**
51
+ * Read the session file and chunk it on exchange boundaries,
52
+ * accumulating exchanges until the chunk reaches ~DEFAULT_CHUNK_SIZE tokens.
53
+ */
54
+ async getChunks() {
55
+ let content;
56
+ try {
57
+ content = await fs.readFile(this.sessionPath, 'utf-8');
58
+ }
59
+ catch (error) {
60
+ if (error.code === 'ENOENT')
61
+ return [];
62
+ throw error;
63
+ }
64
+ // Split on exchange delimiters, keeping the delimiter with the section
65
+ const parts = content.split(/(?=\n## Exchange )/);
66
+ const chunks = [];
67
+ let currentLines = [];
68
+ let currentTokens = 0;
69
+ for (const part of parts) {
70
+ const partTokens = estimateTokens(part);
71
+ if (currentTokens + partTokens > DEFAULT_CHUNK_SIZE && currentLines.length > 0) {
72
+ chunks.push({
73
+ index: chunks.length,
74
+ content: currentLines.join(''),
75
+ tokenEstimate: currentTokens,
76
+ });
77
+ currentLines = [];
78
+ currentTokens = 0;
79
+ }
80
+ currentLines.push(part);
81
+ currentTokens += partTokens;
82
+ }
83
+ // Final chunk
84
+ if (currentLines.length > 0) {
85
+ chunks.push({
86
+ index: chunks.length,
87
+ content: currentLines.join(''),
88
+ tokenEstimate: currentTokens,
89
+ });
90
+ }
91
+ return chunks;
92
+ }
93
+ /**
94
+ * Generate embeddings for new/changed chunks.
95
+ * Only the last chunk can change (since we only append),
96
+ * so we invalidate and regenerate it if needed.
97
+ */
98
+ async generateMissingEmbeddings() {
99
+ if (!(await embeddingsAvailable()))
100
+ return;
101
+ const chunks = await this.getChunks();
102
+ if (chunks.length === 0)
103
+ return;
104
+ const existing = await this.loadEmbeddings();
105
+ const provider = getEmbeddingProvider();
106
+ const newEntries = [];
107
+ for (const chunk of chunks) {
108
+ const key = `ses:${this.sessionId}:${chunk.index}`;
109
+ const isLastChunk = chunk.index === chunks.length - 1;
110
+ if (existing.has(key) && !isLastChunk) {
111
+ // Earlier chunks are immutable, skip
112
+ continue;
113
+ }
114
+ // Last chunk may have grown — re-embed it
115
+ const [embedding] = await provider.embed([chunk.content]);
116
+ newEntries.push({
117
+ messageId: key,
118
+ embedding: embedding,
119
+ timestamp: new Date().toISOString(),
120
+ });
121
+ }
122
+ if (newEntries.length > 0) {
123
+ await this.saveEmbeddings(newEntries);
124
+ }
125
+ }
126
+ /**
127
+ * Search this session's chunks against a query embedding.
128
+ */
129
+ async search(queryEmbedding, threshold = SEMANTIC_THRESHOLD) {
130
+ const embeddings = await this.loadEmbeddings();
131
+ const chunks = await this.getChunks();
132
+ const results = [];
133
+ for (const chunk of chunks) {
134
+ const key = `ses:${this.sessionId}:${chunk.index}`;
135
+ const embedding = embeddings.get(key);
136
+ if (!embedding)
137
+ continue;
138
+ const score = cosineSimilarity(queryEmbedding, embedding);
139
+ if (score >= threshold) {
140
+ results.push({
141
+ sessionId: this.sessionId,
142
+ isCurrentSession: true,
143
+ content_snippet: chunk.content.slice(0, 300),
144
+ score,
145
+ chunkIndex: chunk.index,
146
+ });
147
+ }
148
+ }
149
+ return results;
150
+ }
151
+ /**
152
+ * Search ALL session docs in a sessions directory.
153
+ * Generates missing embeddings for each session before searching.
154
+ */
155
+ static async searchAllSessions(sessionsDir, queryEmbedding, currentSessionId, threshold = SEMANTIC_THRESHOLD) {
156
+ let files;
157
+ try {
158
+ files = await fs.readdir(sessionsDir);
159
+ }
160
+ catch (error) {
161
+ if (error.code === 'ENOENT')
162
+ return [];
163
+ throw error;
164
+ }
165
+ // Generate missing embeddings for all session .md files
166
+ const sessionFiles = files.filter(f => f.endsWith('.md'));
167
+ for (const file of sessionFiles) {
168
+ const sid = file.replace('.md', '');
169
+ const manager = new SessionManager(sessionsDir, '', sid);
170
+ await manager.generateMissingEmbeddings();
171
+ }
172
+ const embeddingFiles = files.filter(f => f.endsWith('.embeddings.json'));
173
+ // Also pick up freshly generated embedding files
174
+ try {
175
+ const refreshedFiles = await fs.readdir(sessionsDir);
176
+ const newEmbFiles = refreshedFiles.filter(f => f.endsWith('.embeddings.json') && !embeddingFiles.includes(f));
177
+ embeddingFiles.push(...newEmbFiles);
178
+ }
179
+ catch {
180
+ // ignore
181
+ }
182
+ const results = [];
183
+ for (const file of embeddingFiles) {
184
+ const sessionId = file.replace('.embeddings.json', '');
185
+ const embeddingsPath = path.join(sessionsDir, file);
186
+ const sessionPath = path.join(sessionsDir, `${sessionId}.md`);
187
+ let embeddingsData;
188
+ try {
189
+ const content = await fs.readFile(embeddingsPath, 'utf-8');
190
+ embeddingsData = JSON.parse(content);
191
+ }
192
+ catch {
193
+ continue;
194
+ }
195
+ // Load session content for snippets
196
+ let sessionContent;
197
+ try {
198
+ sessionContent = await fs.readFile(sessionPath, 'utf-8');
199
+ }
200
+ catch {
201
+ continue;
202
+ }
203
+ // Split into chunks to get snippet for matching chunk
204
+ const parts = sessionContent.split(/(?=\n## Exchange )/);
205
+ for (const entry of embeddingsData.entries) {
206
+ const score = cosineSimilarity(queryEmbedding, entry.embedding);
207
+ if (score < threshold)
208
+ continue;
209
+ // Parse chunk index from key (ses:sessionId:chunkIndex)
210
+ const keyParts = entry.messageId.split(':');
211
+ const chunkIndex = parseInt(keyParts[2] ?? '0', 10);
212
+ // Reconstruct which parts belong to this chunk
213
+ const chunkContent = this.getChunkContent(parts, chunkIndex);
214
+ results.push({
215
+ sessionId,
216
+ isCurrentSession: sessionId === currentSessionId,
217
+ content_snippet: chunkContent.slice(0, 300),
218
+ score,
219
+ chunkIndex,
220
+ });
221
+ }
222
+ }
223
+ return results.sort((a, b) => b.score - a.score);
224
+ }
225
+ /**
226
+ * Reconstruct chunk content from parts given a chunk index.
227
+ * Mirrors the chunking logic in getChunks().
228
+ */
229
+ static getChunkContent(parts, targetChunkIndex) {
230
+ let chunkIndex = 0;
231
+ let currentLines = [];
232
+ let currentTokens = 0;
233
+ for (const part of parts) {
234
+ const partTokens = estimateTokens(part);
235
+ if (currentTokens + partTokens > DEFAULT_CHUNK_SIZE && currentLines.length > 0) {
236
+ if (chunkIndex === targetChunkIndex) {
237
+ return currentLines.join('');
238
+ }
239
+ chunkIndex++;
240
+ currentLines = [];
241
+ currentTokens = 0;
242
+ }
243
+ currentLines.push(part);
244
+ currentTokens += partTokens;
245
+ }
246
+ if (chunkIndex === targetChunkIndex) {
247
+ return currentLines.join('');
248
+ }
249
+ return '';
250
+ }
251
+ // -- Private embedding I/O --
252
+ async loadEmbeddings() {
253
+ try {
254
+ const content = await fs.readFile(this.embeddingsPath, 'utf-8');
255
+ const data = JSON.parse(content);
256
+ const map = new Map();
257
+ for (const entry of data.entries) {
258
+ map.set(entry.messageId, entry.embedding);
259
+ }
260
+ return map;
261
+ }
262
+ catch (error) {
263
+ if (error.code === 'ENOENT')
264
+ return new Map();
265
+ throw error;
266
+ }
267
+ }
268
+ async saveEmbeddings(newEntries) {
269
+ const provider = getEmbeddingProvider();
270
+ const existing = await this.loadEmbeddings();
271
+ // Merge: new entries overwrite existing keys (for last-chunk re-embedding)
272
+ for (const entry of newEntries) {
273
+ existing.set(entry.messageId, entry.embedding);
274
+ }
275
+ const data = {
276
+ version: 1,
277
+ model: provider.name,
278
+ dimensions: provider.dimensions,
279
+ entries: Array.from(existing.entries()).map(([messageId, embedding]) => ({
280
+ messageId,
281
+ embedding,
282
+ timestamp: new Date().toISOString(),
283
+ })),
284
+ };
285
+ await fs.mkdir(path.dirname(this.embeddingsPath), { recursive: true });
286
+ await fs.writeFile(this.embeddingsPath, JSON.stringify(data), 'utf-8');
287
+ }
288
+ }
289
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/lib/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Git snapshot capture and restore for conversation revert.
3
+ * Captures HEAD, branch, and working tree state before each turn.
4
+ */
5
+ export interface GitSnapshot {
6
+ /** Current HEAD commit hash */
7
+ head: string;
8
+ /** Current branch name */
9
+ branch: string;
10
+ /** Stash ref for uncommitted changes (undefined if clean tree) */
11
+ stash?: string;
12
+ }
13
+ /**
14
+ * Check if the current directory is inside a git repository.
15
+ */
16
+ export declare function isGitRepo(cwd?: string): Promise<boolean>;
17
+ /**
18
+ * Capture a snapshot of the current git state: HEAD, branch, and working tree.
19
+ * Uses `git stash create -u` to capture uncommitted changes (including untracked files)
20
+ * without modifying the working tree.
21
+ *
22
+ * Never throws - returns null if not in a git repo or on failure.
23
+ */
24
+ export declare function captureSnapshot(cwd?: string): Promise<GitSnapshot | null>;
25
+ export interface RestoreResult {
26
+ success: boolean;
27
+ warning?: string;
28
+ error?: string;
29
+ }
30
+ /**
31
+ * Restore git state from a snapshot.
32
+ * 1. Checkout the branch
33
+ * 2. Reset to the snapshot HEAD
34
+ * 3. Apply stashed changes if any
35
+ *
36
+ * If only stash apply fails, returns success with a warning.
37
+ */
38
+ export declare function restoreSnapshot(snapshot: GitSnapshot, cwd?: string): Promise<RestoreResult>;
39
+ //# sourceMappingURL=snapshots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshots.d.ts","sourceRoot":"","sources":["../../src/lib/snapshots.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,MAAM,WAAW,WAAW;IAC1B,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAiBD;;GAEG;AACH,wBAAsB,SAAS,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAG9D;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAsB/E;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAiCjG"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Git snapshot capture and restore for conversation revert.
3
+ * Captures HEAD, branch, and working tree state before each turn.
4
+ */
5
+ import { execFile } from 'child_process';
6
+ import { promisify } from 'util';
7
+ const execFileAsync = promisify(execFile);
8
+ /**
9
+ * Run a git command, returning stdout trimmed. Returns null on failure.
10
+ */
11
+ async function git(args, cwd) {
12
+ try {
13
+ const { stdout } = await execFileAsync('git', args, {
14
+ cwd: cwd ?? process.cwd(),
15
+ timeout: 10_000,
16
+ });
17
+ return stdout.trim();
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ }
23
+ /**
24
+ * Check if the current directory is inside a git repository.
25
+ */
26
+ export async function isGitRepo(cwd) {
27
+ const result = await git(['rev-parse', '--is-inside-work-tree'], cwd);
28
+ return result === 'true';
29
+ }
30
+ /**
31
+ * Capture a snapshot of the current git state: HEAD, branch, and working tree.
32
+ * Uses `git stash create -u` to capture uncommitted changes (including untracked files)
33
+ * without modifying the working tree.
34
+ *
35
+ * Never throws - returns null if not in a git repo or on failure.
36
+ */
37
+ export async function captureSnapshot(cwd) {
38
+ try {
39
+ if (!(await isGitRepo(cwd)))
40
+ return null;
41
+ const head = await git(['rev-parse', 'HEAD'], cwd);
42
+ if (!head)
43
+ return null;
44
+ const branch = await git(['rev-parse', '--abbrev-ref', 'HEAD'], cwd);
45
+ if (!branch)
46
+ return null;
47
+ // git stash create -u: creates a stash commit without removing changes.
48
+ // Returns empty string if working tree is clean.
49
+ const stash = await git(['stash', 'create', '-u'], cwd);
50
+ return {
51
+ head,
52
+ branch,
53
+ ...(stash ? { stash } : {}),
54
+ };
55
+ }
56
+ catch {
57
+ return null;
58
+ }
59
+ }
60
+ /**
61
+ * Restore git state from a snapshot.
62
+ * 1. Checkout the branch
63
+ * 2. Reset to the snapshot HEAD
64
+ * 3. Apply stashed changes if any
65
+ *
66
+ * If only stash apply fails, returns success with a warning.
67
+ */
68
+ export async function restoreSnapshot(snapshot, cwd) {
69
+ try {
70
+ if (!(await isGitRepo(cwd))) {
71
+ return { success: false, error: 'Not a git repository' };
72
+ }
73
+ // Checkout the branch
74
+ const checkoutResult = await git(['checkout', snapshot.branch], cwd);
75
+ if (checkoutResult === null) {
76
+ return { success: false, error: `Failed to checkout branch: ${snapshot.branch}` };
77
+ }
78
+ // Reset to the snapshot HEAD
79
+ const resetResult = await git(['reset', '--hard', snapshot.head], cwd);
80
+ if (resetResult === null) {
81
+ return { success: false, error: `Failed to reset to: ${snapshot.head}` };
82
+ }
83
+ // Apply stashed changes if any
84
+ if (snapshot.stash) {
85
+ const stashResult = await git(['stash', 'apply', snapshot.stash], cwd);
86
+ if (stashResult === null) {
87
+ return {
88
+ success: true,
89
+ warning: 'Code restored to commit, but uncommitted changes could not be re-applied.',
90
+ };
91
+ }
92
+ }
93
+ return { success: true };
94
+ }
95
+ catch {
96
+ return { success: false, error: 'Unexpected error during restore' };
97
+ }
98
+ }
99
+ //# sourceMappingURL=snapshots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshots.js","sourceRoot":"","sources":["../../src/lib/snapshots.ts"],"names":[],"mappings":"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"}