@loom-framework/backend 0.1.0-alpha.2 → 0.1.0-alpha.4
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/ai/button-resolver.d.ts +17 -0
- package/dist/ai/button-resolver.d.ts.map +1 -0
- package/dist/ai/button-resolver.js +40 -0
- package/dist/ai/button-resolver.js.map +1 -0
- package/dist/ai/engine.d.ts +14 -0
- package/dist/ai/engine.d.ts.map +1 -1
- package/dist/ai/engine.js +71 -22
- package/dist/ai/engine.js.map +1 -1
- package/dist/ai/index.d.ts +3 -1
- package/dist/ai/index.d.ts.map +1 -1
- package/dist/ai/index.js +1 -0
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/output-parser.d.ts +10 -1
- package/dist/ai/output-parser.d.ts.map +1 -1
- package/dist/ai/output-parser.js +18 -1
- package/dist/ai/output-parser.js.map +1 -1
- package/dist/ai/session-manager.d.ts +11 -2
- package/dist/ai/session-manager.d.ts.map +1 -1
- package/dist/ai/session-manager.js +88 -12
- package/dist/ai/session-manager.js.map +1 -1
- package/dist/bin.js +0 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -7
- package/dist/index.js.map +1 -1
- package/dist/routes/chat.d.ts +31 -0
- package/dist/routes/chat.d.ts.map +1 -0
- package/dist/routes/chat.js +216 -0
- package/dist/routes/chat.js.map +1 -0
- package/dist/routes/index.d.ts +3 -0
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +2 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/upload.d.ts +24 -0
- package/dist/routes/upload.d.ts.map +1 -0
- package/dist/routes/upload.js +67 -0
- package/dist/routes/upload.js.map +1 -0
- package/package.json +12 -13
- package/dist/websocket/index.d.ts +0 -32
- package/dist/websocket/index.d.ts.map +0 -1
- package/dist/websocket/index.js +0 -337
- package/dist/websocket/index.js.map +0 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Button Resolver - Resolve AI button prompts from configuration
|
|
3
|
+
*
|
|
4
|
+
* Shared between the SSE chat route and other modules.
|
|
5
|
+
*/
|
|
6
|
+
import type { LoomConfig } from '@loom-framework/core';
|
|
7
|
+
/**
|
|
8
|
+
* Resolve prompt from AI button configuration.
|
|
9
|
+
* - Static buttons: return prompt directly
|
|
10
|
+
* - Template buttons: substitute {{var}} with context values
|
|
11
|
+
* Returns null if button not found or context is incomplete.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveButtonPrompt(buttonId: string, context: Record<string, string> | undefined, config: LoomConfig): {
|
|
14
|
+
prompt: string;
|
|
15
|
+
error?: string;
|
|
16
|
+
} | null;
|
|
17
|
+
//# sourceMappingURL=button-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"button-resolver.d.ts","sourceRoot":"","sources":["../../src/ai/button-resolver.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EAC3C,MAAM,EAAE,UAAU,GACjB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAiC3C"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Button Resolver - Resolve AI button prompts from configuration
|
|
3
|
+
*
|
|
4
|
+
* Shared between the SSE chat route and other modules.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Resolve prompt from AI button configuration.
|
|
8
|
+
* - Static buttons: return prompt directly
|
|
9
|
+
* - Template buttons: substitute {{var}} with context values
|
|
10
|
+
* Returns null if button not found or context is incomplete.
|
|
11
|
+
*/
|
|
12
|
+
export function resolveButtonPrompt(buttonId, context, config) {
|
|
13
|
+
const button = config.aiButtons?.find((b) => b.id === buttonId);
|
|
14
|
+
if (!button) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
// Static prompt
|
|
18
|
+
if (button.prompt) {
|
|
19
|
+
return { prompt: button.prompt };
|
|
20
|
+
}
|
|
21
|
+
// Template prompt
|
|
22
|
+
if (button.promptTemplate) {
|
|
23
|
+
const missing = (button.contextVars || []).filter((v) => !context || context[v] === undefined);
|
|
24
|
+
if (missing.length > 0) {
|
|
25
|
+
return {
|
|
26
|
+
prompt: '',
|
|
27
|
+
error: `Missing context variables: ${missing.join(', ')}. Required: ${button.contextVars.join(', ')}`,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
let resolved = button.promptTemplate;
|
|
31
|
+
if (context) {
|
|
32
|
+
for (const [key, value] of Object.entries(context)) {
|
|
33
|
+
resolved = resolved.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { prompt: resolved };
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=button-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"button-resolver.js","sourceRoot":"","sources":["../../src/ai/button-resolver.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAAgB,EAChB,OAA2C,EAC3C,MAAkB;IAElB,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC;IAChE,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC;IACd,CAAC;IAED,gBAAgB;IAChB,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;IACnC,CAAC;IAED,kBAAkB;IAClB,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,MAAM,CAC/C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,SAAS,CAC5C,CAAC;QACF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,OAAO;gBACL,MAAM,EAAE,EAAE;gBACV,KAAK,EAAE,8BAA8B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,MAAM,CAAC,WAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;aACvG,CAAC;QACJ,CAAC;QAED,IAAI,QAAQ,GAAG,MAAM,CAAC,cAAc,CAAC;QACrC,IAAI,OAAO,EAAE,CAAC;YACZ,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;gBACnD,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,SAAS,GAAG,QAAQ,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;YAC5E,CAAC;QACH,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IAC9B,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/ai/engine.d.ts
CHANGED
|
@@ -5,9 +5,21 @@
|
|
|
5
5
|
* with support for --resume, --model, and MCP config.
|
|
6
6
|
*/
|
|
7
7
|
import type { AIEngine, AICallOptions, AIChunk, ClaudeCodeConfig } from '@loom-framework/core';
|
|
8
|
+
/** Log levels for engine diagnostics */
|
|
9
|
+
export type EngineLogLevel = 'COMMAND' | 'PROMPT' | 'STDOUT_CHUNK' | 'RESPONSE_PARSED' | 'STDERR' | 'PROCESS_EXIT';
|
|
10
|
+
export interface EngineLogEntry {
|
|
11
|
+
level: EngineLogLevel;
|
|
12
|
+
sessionId: string;
|
|
13
|
+
timestamp: string;
|
|
14
|
+
message: string;
|
|
15
|
+
data?: unknown;
|
|
16
|
+
}
|
|
17
|
+
export type EngineLogger = (entry: EngineLogEntry) => void;
|
|
8
18
|
export interface ClaudeCodeEngineOptions {
|
|
9
19
|
config: ClaudeCodeConfig;
|
|
10
20
|
projectRoot: string;
|
|
21
|
+
/** Optional diagnostic logger for engine operations */
|
|
22
|
+
logger?: EngineLogger;
|
|
11
23
|
}
|
|
12
24
|
/**
|
|
13
25
|
* ClaudeCodeEngine - Spawns Claude Code in headless mode
|
|
@@ -20,7 +32,9 @@ export declare class ClaudeCodeEngine implements AIEngine {
|
|
|
20
32
|
private config;
|
|
21
33
|
private projectRoot;
|
|
22
34
|
private activeProcesses;
|
|
35
|
+
private logger?;
|
|
23
36
|
constructor(options: ClaudeCodeEngineOptions);
|
|
37
|
+
private log;
|
|
24
38
|
/**
|
|
25
39
|
* Call Claude Code with a prompt and stream back AIChunk objects
|
|
26
40
|
*/
|
package/dist/ai/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/ai/engine.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAM/F,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,gBAAgB,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/ai/engine.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAM/F,wCAAwC;AACxC,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,QAAQ,GAAG,cAAc,GAAG,iBAAiB,GAAG,QAAQ,GAAG,cAAc,CAAC;AAEnH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,cAAc,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;AAE3D,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,gBAAgB,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED;;;;;GAKG;AACH,qBAAa,gBAAiB,YAAW,QAAQ;IAC/C,QAAQ,CAAC,IAAI,iBAAiB;IAE9B,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAwC;IAC/D,OAAO,CAAC,MAAM,CAAC,CAAe;gBAElB,OAAO,EAAE,uBAAuB;IAM5C,OAAO,CAAC,GAAG;IAUX;;OAEG;IACI,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,cAAc,CAAC,OAAO,CAAC;IAgI5E;;OAEG;IACH,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAUvC;;OAEG;IACH,OAAO,IAAI,IAAI;CAQhB"}
|
package/dist/ai/engine.js
CHANGED
|
@@ -20,9 +20,20 @@ export class ClaudeCodeEngine {
|
|
|
20
20
|
config;
|
|
21
21
|
projectRoot;
|
|
22
22
|
activeProcesses = new Map();
|
|
23
|
+
logger;
|
|
23
24
|
constructor(options) {
|
|
24
25
|
this.config = options.config;
|
|
25
26
|
this.projectRoot = options.projectRoot;
|
|
27
|
+
this.logger = options.logger;
|
|
28
|
+
}
|
|
29
|
+
log(level, sessionId, message, data) {
|
|
30
|
+
this.logger?.({
|
|
31
|
+
level,
|
|
32
|
+
sessionId,
|
|
33
|
+
timestamp: new Date().toISOString(),
|
|
34
|
+
message,
|
|
35
|
+
data,
|
|
36
|
+
});
|
|
26
37
|
}
|
|
27
38
|
/**
|
|
28
39
|
* Call Claude Code with a prompt and stream back AIChunk objects
|
|
@@ -32,10 +43,13 @@ export class ClaudeCodeEngine {
|
|
|
32
43
|
const timeout = options.timeout ?? this.config.timeout ?? DEFAULT_PROCESS_TIMEOUT_MS;
|
|
33
44
|
const pluginRoot = this.config.pluginRoot || path.join(this.projectRoot, '.claude');
|
|
34
45
|
const mcpConfigPath = path.join(this.projectRoot, '.loom', 'mcp.json');
|
|
46
|
+
// Build prompt with file references if provided
|
|
47
|
+
const fullPrompt = buildPromptWithFiles(prompt, options.files);
|
|
35
48
|
const args = [
|
|
36
49
|
'-p',
|
|
37
50
|
'--output-format', 'json',
|
|
38
51
|
'--mcp-config', mcpConfigPath,
|
|
52
|
+
'--strict-mcp-config',
|
|
39
53
|
];
|
|
40
54
|
if (this.config.skipPermissions !== false) {
|
|
41
55
|
args.push('--dangerously-skip-permissions');
|
|
@@ -48,6 +62,11 @@ export class ClaudeCodeEngine {
|
|
|
48
62
|
}
|
|
49
63
|
// Read prompt from stdin to avoid ARG_MAX
|
|
50
64
|
args.push('-');
|
|
65
|
+
this.log('COMMAND', options.sessionId, `Spawning claude process`, { claudePath, args });
|
|
66
|
+
this.log('PROMPT', options.sessionId, `Prompt length: ${fullPrompt.length}`, {
|
|
67
|
+
promptPreview: fullPrompt.slice(0, 200),
|
|
68
|
+
filesCount: options.files?.length ?? 0,
|
|
69
|
+
});
|
|
51
70
|
const childProcess = spawn(claudePath, args, {
|
|
52
71
|
cwd: this.projectRoot,
|
|
53
72
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -58,12 +77,49 @@ export class ClaudeCodeEngine {
|
|
|
58
77
|
});
|
|
59
78
|
// Track active process for cleanup
|
|
60
79
|
this.activeProcesses.set(options.sessionId, childProcess);
|
|
80
|
+
// Capture stderr for error reporting
|
|
81
|
+
let stderrOutput = '';
|
|
82
|
+
childProcess.stderr.on('data', (data) => {
|
|
83
|
+
const text = data.toString();
|
|
84
|
+
stderrOutput += text;
|
|
85
|
+
this.log('STDERR', options.sessionId, text.trim());
|
|
86
|
+
});
|
|
87
|
+
// Wait for process completion with timeout
|
|
88
|
+
let timeoutHandle;
|
|
89
|
+
const completionPromise = new Promise((resolve, reject) => {
|
|
90
|
+
timeoutHandle = setTimeout(() => {
|
|
91
|
+
childProcess.kill('SIGTERM');
|
|
92
|
+
setTimeout(() => childProcess.kill('SIGKILL'), 5_000);
|
|
93
|
+
reject(new Error(`Claude Code process timed out after ${timeout}ms`));
|
|
94
|
+
}, timeout);
|
|
95
|
+
childProcess.on('close', (code) => {
|
|
96
|
+
if (timeoutHandle)
|
|
97
|
+
clearTimeout(timeoutHandle);
|
|
98
|
+
this.activeProcesses.delete(options.sessionId);
|
|
99
|
+
if (code !== null && code !== 0) {
|
|
100
|
+
this.log('PROCESS_EXIT', options.sessionId, `Process exited with code ${code}`, { exitCode: code, stderr: stderrOutput });
|
|
101
|
+
}
|
|
102
|
+
resolve();
|
|
103
|
+
});
|
|
104
|
+
childProcess.on('error', (err) => {
|
|
105
|
+
if (timeoutHandle)
|
|
106
|
+
clearTimeout(timeoutHandle);
|
|
107
|
+
this.activeProcesses.delete(options.sessionId);
|
|
108
|
+
reject(err);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
61
111
|
// Send prompt via stdin then close
|
|
62
|
-
childProcess.stdin.write(
|
|
112
|
+
childProcess.stdin.write(fullPrompt);
|
|
63
113
|
childProcess.stdin.end();
|
|
64
114
|
// Parse output stream
|
|
65
115
|
try {
|
|
66
|
-
|
|
116
|
+
const parserLogger = this.logger
|
|
117
|
+
? (entry) => {
|
|
118
|
+
this.log('STDOUT_CHUNK', options.sessionId, entry.message, entry.data);
|
|
119
|
+
}
|
|
120
|
+
: undefined;
|
|
121
|
+
for await (const chunk of parseClaudeOutput(childProcess.stdout, parserLogger)) {
|
|
122
|
+
this.log('RESPONSE_PARSED', options.sessionId, `Chunk type: ${chunk.type}`, { chunkType: chunk.type });
|
|
67
123
|
// For result-type content chunks that carry sessionId, split into two chunks
|
|
68
124
|
if (chunk.type === 'content' && chunk.sessionId) {
|
|
69
125
|
const { sessionId, usage, ...contentChunk } = chunk;
|
|
@@ -88,26 +144,7 @@ export class ClaudeCodeEngine {
|
|
|
88
144
|
};
|
|
89
145
|
}
|
|
90
146
|
// Wait for process completion with timeout
|
|
91
|
-
await
|
|
92
|
-
const timeoutHandle = setTimeout(() => {
|
|
93
|
-
childProcess.kill('SIGTERM');
|
|
94
|
-
// Force kill after 5s
|
|
95
|
-
setTimeout(() => {
|
|
96
|
-
childProcess.kill('SIGKILL');
|
|
97
|
-
}, 5_000);
|
|
98
|
-
reject(new Error(`Claude Code process timed out after ${timeout}ms`));
|
|
99
|
-
}, timeout);
|
|
100
|
-
childProcess.on('close', () => {
|
|
101
|
-
clearTimeout(timeoutHandle);
|
|
102
|
-
this.activeProcesses.delete(options.sessionId);
|
|
103
|
-
resolve();
|
|
104
|
-
});
|
|
105
|
-
childProcess.on('error', (err) => {
|
|
106
|
-
clearTimeout(timeoutHandle);
|
|
107
|
-
this.activeProcesses.delete(options.sessionId);
|
|
108
|
-
reject(err);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
147
|
+
await completionPromise;
|
|
111
148
|
yield { type: 'done' };
|
|
112
149
|
}
|
|
113
150
|
/**
|
|
@@ -134,4 +171,16 @@ export class ClaudeCodeEngine {
|
|
|
134
171
|
}
|
|
135
172
|
}
|
|
136
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Build prompt with file references injected for Claude Code.
|
|
176
|
+
* Formats file paths as inline references so Claude Code can access them.
|
|
177
|
+
*/
|
|
178
|
+
function buildPromptWithFiles(prompt, files) {
|
|
179
|
+
if (!files || files.length === 0)
|
|
180
|
+
return prompt;
|
|
181
|
+
const fileList = files
|
|
182
|
+
.map((f) => ` - ${f.name}: ${f.path}`)
|
|
183
|
+
.join('\n');
|
|
184
|
+
return `${prompt}\n\nAttached files:\n${fileList}`;
|
|
185
|
+
}
|
|
137
186
|
//# sourceMappingURL=engine.js.map
|
package/dist/ai/engine.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/ai/engine.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,KAAK,EAAqB,MAAM,eAAe,CAAC;AACzD,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,iBAAiB,
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/ai/engine.ts"],"names":[],"mappings":"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"}
|
package/dist/ai/index.d.ts
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
* AI Module - Barrel Export
|
|
3
3
|
*/
|
|
4
4
|
export { ClaudeCodeEngine } from './engine.js';
|
|
5
|
-
export type { ClaudeCodeEngineOptions } from './engine.js';
|
|
5
|
+
export type { ClaudeCodeEngineOptions, EngineLogger, EngineLogLevel, EngineLogEntry } from './engine.js';
|
|
6
6
|
export { SessionManager } from './session-manager.js';
|
|
7
7
|
export type { SessionRecord, SessionMessage, SessionFileRef } from './session-manager.js';
|
|
8
8
|
export { parseClaudeOutput } from './output-parser.js';
|
|
9
|
+
export type { ParserLogger, ParserLogEntry } from './output-parser.js';
|
|
10
|
+
export { resolveButtonPrompt } from './button-resolver.js';
|
|
9
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/ai/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ai/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ai/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,uBAAuB,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACzG,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC1F,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/ai/index.js
CHANGED
package/dist/ai/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ai/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ai/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -3,8 +3,17 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Parses `claude -p --output-format json` output stream into AIChunk objects.
|
|
5
5
|
* Handles both newline-delimited JSON (streaming) and single JSON object (batch) modes.
|
|
6
|
+
* Supports tool_use / tool_result event types for tool call visualization.
|
|
6
7
|
*/
|
|
7
8
|
import type { AIChunk } from '@loom-framework/core';
|
|
9
|
+
/** Parser log entry for diagnostics */
|
|
10
|
+
export interface ParserLogEntry {
|
|
11
|
+
type: 'CHUNK_RECEIVED' | 'LINE_PARSED' | 'PARSE_ERROR';
|
|
12
|
+
timestamp: string;
|
|
13
|
+
message: string;
|
|
14
|
+
data?: unknown;
|
|
15
|
+
}
|
|
16
|
+
export type ParserLogger = (entry: ParserLogEntry) => void;
|
|
8
17
|
/**
|
|
9
18
|
* Parse Claude Code stdout stream into typed AIChunk objects
|
|
10
19
|
*
|
|
@@ -15,5 +24,5 @@ import type { AIChunk } from '@loom-framework/core';
|
|
|
15
24
|
* 4. Fall back to parsing the entire buffer as a single JSON object
|
|
16
25
|
* 5. Ultimate fallback: yield raw text as content chunk
|
|
17
26
|
*/
|
|
18
|
-
export declare function parseClaudeOutput(stdout: NodeJS.ReadableStream): AsyncGenerator<AIChunk>;
|
|
27
|
+
export declare function parseClaudeOutput(stdout: NodeJS.ReadableStream, logger?: ParserLogger): AsyncGenerator<AIChunk>;
|
|
19
28
|
//# sourceMappingURL=output-parser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output-parser.d.ts","sourceRoot":"","sources":["../../src/ai/output-parser.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"output-parser.d.ts","sourceRoot":"","sources":["../../src/ai/output-parser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAW,MAAM,sBAAsB,CAAC;AAE7D,uCAAuC;AACvC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,gBAAgB,GAAG,aAAa,GAAG,aAAa,CAAC;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;AAkC3D;;;;;;;;;GASG;AACH,wBAAuB,iBAAiB,CACtC,MAAM,EAAE,MAAM,CAAC,cAAc,EAC7B,MAAM,CAAC,EAAE,YAAY,GACpB,cAAc,CAAC,OAAO,CAAC,CAmCzB"}
|
package/dist/ai/output-parser.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Parses `claude -p --output-format json` output stream into AIChunk objects.
|
|
5
5
|
* Handles both newline-delimited JSON (streaming) and single JSON object (batch) modes.
|
|
6
|
+
* Supports tool_use / tool_result event types for tool call visualization.
|
|
6
7
|
*/
|
|
7
8
|
/** ANSI escape code pattern */
|
|
8
9
|
const ANSI_ESCAPE = /\x1b\[[0-9;]*m/g;
|
|
@@ -16,7 +17,7 @@ const ANSI_ESCAPE = /\x1b\[[0-9;]*m/g;
|
|
|
16
17
|
* 4. Fall back to parsing the entire buffer as a single JSON object
|
|
17
18
|
* 5. Ultimate fallback: yield raw text as content chunk
|
|
18
19
|
*/
|
|
19
|
-
export async function* parseClaudeOutput(stdout) {
|
|
20
|
+
export async function* parseClaudeOutput(stdout, logger) {
|
|
20
21
|
let buffer = '';
|
|
21
22
|
for await (const chunk of stdout) {
|
|
22
23
|
buffer += chunk.toString();
|
|
@@ -24,6 +25,7 @@ export async function* parseClaudeOutput(stdout) {
|
|
|
24
25
|
const cleanOutput = buffer.replace(ANSI_ESCAPE, '').trim();
|
|
25
26
|
if (!cleanOutput)
|
|
26
27
|
return;
|
|
28
|
+
logger?.({ type: 'CHUNK_RECEIVED', timestamp: new Date().toISOString(), message: `Received ${cleanOutput.length} chars` });
|
|
27
29
|
// Strategy 1: Try newline-delimited JSON
|
|
28
30
|
const lines = cleanOutput.split('\n').map((l) => l.trim()).filter(Boolean);
|
|
29
31
|
const parsedChunks = tryParseJsonLines(lines);
|
|
@@ -82,6 +84,21 @@ function tryParseSingleJson(output) {
|
|
|
82
84
|
*/
|
|
83
85
|
function mapJsonToChunk(data) {
|
|
84
86
|
switch (data.type) {
|
|
87
|
+
case 'tool_use': {
|
|
88
|
+
return {
|
|
89
|
+
type: 'tool_call',
|
|
90
|
+
toolUseId: data.tool_use_id || '',
|
|
91
|
+
toolName: data.tool_name || 'unknown',
|
|
92
|
+
toolInput: data.tool_input,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
case 'tool_result': {
|
|
96
|
+
return {
|
|
97
|
+
type: 'tool_result',
|
|
98
|
+
toolUseId: data.tool_use_id || '',
|
|
99
|
+
toolResult: data.tool_result,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
85
102
|
case 'result': {
|
|
86
103
|
if (data.is_error) {
|
|
87
104
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output-parser.js","sourceRoot":"","sources":["../../src/ai/output-parser.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"output-parser.js","sourceRoot":"","sources":["../../src/ai/output-parser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA2CH,+BAA+B;AAC/B,MAAM,WAAW,GAAG,iBAAiB,CAAC;AAEtC;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,iBAAiB,CACtC,MAA6B,EAC7B,MAAqB;IAErB,IAAI,MAAM,GAAG,EAAE,CAAC;IAEhB,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;IAC7B,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3D,IAAI,CAAC,WAAW;QAAE,OAAO;IAEzB,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,YAAY,WAAW,CAAC,MAAM,QAAQ,EAAE,CAAC,CAAC;IAE3H,yCAAyC;IACzC,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC3E,MAAM,YAAY,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAE9C,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;YACjC,MAAM,KAAK,CAAC;QACd,CAAC;QACD,OAAO;IACT,CAAC;IAED,qCAAqC;IACrC,MAAM,WAAW,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAC;IACpD,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,WAAW,CAAC;QAClB,OAAO;IACT,CAAC;IAED,gCAAgC;IAChC,MAAM;QACJ,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,WAAW;KACrB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,iBAAiB,CAAC,KAAe;IACxC,MAAM,MAAM,GAAc,EAAE,CAAC;IAC7B,IAAI,YAAY,GAAG,KAAK,CAAC;IAEzB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAqB,CAAC;YAClD,YAAY,GAAG,IAAI,CAAC;YACpB,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;YACnC,IAAI,KAAK;gBAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,oCAAoC;QACtC,CAAC;IACH,CAAC;IAED,OAAO,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;AACpC,CAAC;AAED;;GAEG;AACH,SAAS,kBAAkB,CAAC,MAAc;IACxC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAqB,CAAC;QACpD,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,cAAc,CAAC,IAAsB;IAC5C,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,OAAO;gBACL,IAAI,EAAE,WAAW;gBACjB,SAAS,EAAE,IAAI,CAAC,WAAW,IAAI,EAAE;gBACjC,QAAQ,EAAE,IAAI,CAAC,SAAS,IAAI,SAAS;gBACrC,SAAS,EAAE,IAAI,CAAC,UAAU;aAC3B,CAAC;QACJ,CAAC;QAED,KAAK,aAAa,CAAC,CAAC,CAAC;YACnB,OAAO;gBACL,IAAI,EAAE,aAAa;gBACnB,SAAS,EAAE,IAAI,CAAC,WAAW,IAAI,EAAE;gBACjC,UAAU,EAAE,IAAI,CAAC,WAAW;aAC7B,CAAC;QACJ,CAAC;QAED,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAClB,OAAO;oBACL,IAAI,EAAE,OAAO;oBACb,KAAK,EAAE,IAAI,CAAC,MAAM,IAAI,gCAAgC;iBACvD,CAAC;YACJ,CAAC;YAED,+DAA+D;YAC/D,iDAAiD;YACjD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAChB,OAAO;oBACL,IAAI,EAAE,SAAS;oBACf,OAAO,EAAE,IAAI,CAAC,MAAM;oBACpB,SAAS,EAAE,IAAI,CAAC,UAAU;oBAC1B,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC;iBAC1B,CAAC;YACJ,CAAC;YAED,kCAAkC;YAClC,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QAED,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,IAAI,IAAI,CAAC,OAAO,KAAK,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBAC5C,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;YACpD,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QAED;YACE,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,kBAAkB,CAAC,IAAsB;IAChD,IAAI,CAAC,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAElC,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IACjC,OAAO;QACL,IAAI,EAAE,cAAc;QACpB,SAAS,EAAE,IAAI,CAAC,UAAU;QAC1B,KAAK;KACN,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,YAAY,CAAC,IAAsB;IAC1C,sCAAsC;IACtC,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QACpB,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QACjD,MAAM,UAAU,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACpE,IAAI,UAAU,EAAE,CAAC;YACf,OAAO;gBACL,WAAW,EAAE,UAAU,CAAC,WAAW;gBACnC,YAAY,EAAE,UAAU,CAAC,YAAY;gBACrC,aAAa,EAAE,UAAU,CAAC,aAAa;aACxC,CAAC;QACJ,CAAC;IACH,CAAC;IAED,8BAA8B;IAC9B,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,OAAO;YACL,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY;YACpC,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,aAAa;YACtC,aAAa,EAAE,CAAC;SACjB,CAAC;IACJ,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -33,10 +33,12 @@ export declare class SessionManager {
|
|
|
33
33
|
private sessionsDir;
|
|
34
34
|
/** In-memory index of active sessions for stale detection */
|
|
35
35
|
private activeIndex;
|
|
36
|
+
/** In-memory session index for fast listing */
|
|
37
|
+
private sessionIndex;
|
|
36
38
|
private cleanupTimer?;
|
|
37
39
|
constructor(projectRoot: string);
|
|
38
40
|
/**
|
|
39
|
-
* Ensure sessions directory exists
|
|
41
|
+
* Ensure sessions directory exists and load index
|
|
40
42
|
*/
|
|
41
43
|
initialize(): Promise<void>;
|
|
42
44
|
/**
|
|
@@ -60,9 +62,13 @@ export declare class SessionManager {
|
|
|
60
62
|
*/
|
|
61
63
|
addMessages(id: string, messages: SessionMessage[], claudeSessionId?: string, usage?: AIUsage): Promise<SessionRecord>;
|
|
62
64
|
/**
|
|
63
|
-
* List all sessions sorted by most recent first
|
|
65
|
+
* List all sessions sorted by most recent first (from index, O(1))
|
|
64
66
|
*/
|
|
65
67
|
listSessions(): Promise<SessionRecord[]>;
|
|
68
|
+
/**
|
|
69
|
+
* List full session records from files (fallback, used during initialization)
|
|
70
|
+
*/
|
|
71
|
+
private listSessionsFromFiles;
|
|
66
72
|
/**
|
|
67
73
|
* Delete a session
|
|
68
74
|
*/
|
|
@@ -77,5 +83,8 @@ export declare class SessionManager {
|
|
|
77
83
|
private cleanStaleSessions;
|
|
78
84
|
private getSessionPath;
|
|
79
85
|
private writeSessionFile;
|
|
86
|
+
private getIndexFilePath;
|
|
87
|
+
private loadIndex;
|
|
88
|
+
private saveIndex;
|
|
80
89
|
}
|
|
81
90
|
//# sourceMappingURL=session-manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../src/ai/session-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAiB,OAAO,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../src/ai/session-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAiB,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAmBnE,6BAA6B;AAC7B,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,WAAW,CAAS;IAC5B,6DAA6D;IAC7D,OAAO,CAAC,WAAW,CAAyC;IAC5D,+CAA+C;IAC/C,OAAO,CAAC,YAAY,CAAmD;IACvE,OAAO,CAAC,YAAY,CAAC,CAAiC;gBAE1C,WAAW,EAAE,MAAM;IAI/B;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA+BjC;;OAEG;IACH,YAAY,CAAC,UAAU,GAAE,MAAe,GAAG,IAAI;IAM/C;;OAEG;IACH,WAAW,IAAI,IAAI;IAOnB;;OAEG;IACG,aAAa,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAgCxD;;OAEG;IACG,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAU5D;;OAEG;IACG,WAAW,CACf,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,cAAc,EAAE,EAC1B,eAAe,CAAC,EAAE,MAAM,EACxB,KAAK,CAAC,EAAE,OAAO,GACd,OAAO,CAAC,aAAa,CAAC;IA8CzB;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;IAiB9C;;OAEG;YACW,qBAAqB;IA2BnC;;OAEG;IACG,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBjD;;OAEG;IACG,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAMrD;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,cAAc;YAIR,gBAAgB;IAM9B,OAAO,CAAC,gBAAgB;YAIV,SAAS;YAUT,SAAS;CAYxB"}
|
|
@@ -12,24 +12,39 @@ export class SessionManager {
|
|
|
12
12
|
sessionsDir;
|
|
13
13
|
/** In-memory index of active sessions for stale detection */
|
|
14
14
|
activeIndex = new Map();
|
|
15
|
+
/** In-memory session index for fast listing */
|
|
16
|
+
sessionIndex = { sessions: [], lastUpdated: '' };
|
|
15
17
|
cleanupTimer;
|
|
16
18
|
constructor(projectRoot) {
|
|
17
19
|
this.sessionsDir = path.join(projectRoot, '.loom', 'sessions');
|
|
18
20
|
}
|
|
19
21
|
/**
|
|
20
|
-
* Ensure sessions directory exists
|
|
22
|
+
* Ensure sessions directory exists and load index
|
|
21
23
|
*/
|
|
22
24
|
async initialize() {
|
|
23
25
|
await fs.mkdir(this.sessionsDir, { recursive: true });
|
|
24
|
-
//
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
this.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
// Try to load index from disk
|
|
27
|
+
const indexLoaded = await this.loadIndex();
|
|
28
|
+
if (!indexLoaded) {
|
|
29
|
+
// Fallback: rebuild index from session files
|
|
30
|
+
const sessions = await this.listSessionsFromFiles();
|
|
31
|
+
this.sessionIndex = {
|
|
32
|
+
sessions: sessions.map((s) => ({
|
|
33
|
+
id: s.id,
|
|
34
|
+
title: s.title,
|
|
35
|
+
updatedAt: s.updatedAt,
|
|
36
|
+
createdAt: s.createdAt,
|
|
37
|
+
})),
|
|
38
|
+
lastUpdated: new Date().toISOString(),
|
|
39
|
+
};
|
|
40
|
+
await this.saveIndex();
|
|
41
|
+
}
|
|
42
|
+
// Load into active index for stale detection
|
|
43
|
+
for (const entry of this.sessionIndex.sessions) {
|
|
44
|
+
this.activeIndex.set(entry.id, {
|
|
45
|
+
id: entry.id,
|
|
46
|
+
lastActivity: new Date(entry.updatedAt).getTime(),
|
|
47
|
+
createdAt: new Date(entry.createdAt).getTime(),
|
|
33
48
|
});
|
|
34
49
|
}
|
|
35
50
|
}
|
|
@@ -69,6 +84,15 @@ export class SessionManager {
|
|
|
69
84
|
lastActivity: Date.now(),
|
|
70
85
|
createdAt: Date.now(),
|
|
71
86
|
});
|
|
87
|
+
// Update index
|
|
88
|
+
this.sessionIndex.sessions.push({
|
|
89
|
+
id: sessionId,
|
|
90
|
+
title: session.title,
|
|
91
|
+
updatedAt: session.updatedAt,
|
|
92
|
+
createdAt: session.createdAt,
|
|
93
|
+
});
|
|
94
|
+
this.sessionIndex.lastUpdated = new Date().toISOString();
|
|
95
|
+
await this.saveIndex();
|
|
72
96
|
return session;
|
|
73
97
|
}
|
|
74
98
|
/**
|
|
@@ -115,16 +139,43 @@ export class SessionManager {
|
|
|
115
139
|
lastActivity: Date.now(),
|
|
116
140
|
createdAt: new Date(session.createdAt).getTime(),
|
|
117
141
|
});
|
|
142
|
+
// Sync index entry (title may have changed)
|
|
143
|
+
const idxEntry = this.sessionIndex.sessions.find((e) => e.id === id);
|
|
144
|
+
if (idxEntry) {
|
|
145
|
+
idxEntry.title = session.title;
|
|
146
|
+
idxEntry.updatedAt = session.updatedAt;
|
|
147
|
+
}
|
|
148
|
+
this.sessionIndex.lastUpdated = new Date().toISOString();
|
|
149
|
+
await this.saveIndex();
|
|
118
150
|
return session;
|
|
119
151
|
}
|
|
120
152
|
/**
|
|
121
|
-
* List all sessions sorted by most recent first
|
|
153
|
+
* List all sessions sorted by most recent first (from index, O(1))
|
|
122
154
|
*/
|
|
123
155
|
async listSessions() {
|
|
156
|
+
// Use index for fast path: only return summary data
|
|
157
|
+
// For full records, callers should use readSession(id)
|
|
158
|
+
const entries = [...this.sessionIndex.sessions];
|
|
159
|
+
entries.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
160
|
+
// Map index entries to minimal SessionRecord shape
|
|
161
|
+
return entries.map((e) => ({
|
|
162
|
+
id: e.id,
|
|
163
|
+
title: e.title,
|
|
164
|
+
createdAt: e.createdAt,
|
|
165
|
+
updatedAt: e.updatedAt,
|
|
166
|
+
messages: [],
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* List full session records from files (fallback, used during initialization)
|
|
171
|
+
*/
|
|
172
|
+
async listSessionsFromFiles() {
|
|
124
173
|
try {
|
|
125
174
|
const files = await fs.readdir(this.sessionsDir);
|
|
126
175
|
const sessions = [];
|
|
127
176
|
for (const file of files) {
|
|
177
|
+
if (file === 'index.json')
|
|
178
|
+
continue;
|
|
128
179
|
if (!file.endsWith('.json'))
|
|
129
180
|
continue;
|
|
130
181
|
const filePath = path.join(this.sessionsDir, file);
|
|
@@ -136,7 +187,6 @@ export class SessionManager {
|
|
|
136
187
|
// Skip malformed files
|
|
137
188
|
}
|
|
138
189
|
}
|
|
139
|
-
// Sort by updatedAt descending
|
|
140
190
|
sessions.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
141
191
|
return sessions;
|
|
142
192
|
}
|
|
@@ -152,6 +202,10 @@ export class SessionManager {
|
|
|
152
202
|
try {
|
|
153
203
|
await fs.unlink(filePath);
|
|
154
204
|
this.activeIndex.delete(id);
|
|
205
|
+
// Sync index
|
|
206
|
+
this.sessionIndex.sessions = this.sessionIndex.sessions.filter((e) => e.id !== id);
|
|
207
|
+
this.sessionIndex.lastUpdated = new Date().toISOString();
|
|
208
|
+
await this.saveIndex();
|
|
155
209
|
return true;
|
|
156
210
|
}
|
|
157
211
|
catch {
|
|
@@ -186,6 +240,28 @@ export class SessionManager {
|
|
|
186
240
|
await fs.mkdir(this.sessionsDir, { recursive: true });
|
|
187
241
|
await fs.writeFile(filePath, JSON.stringify(session, null, 2), 'utf-8');
|
|
188
242
|
}
|
|
243
|
+
getIndexFilePath() {
|
|
244
|
+
return path.join(this.sessionsDir, 'index.json');
|
|
245
|
+
}
|
|
246
|
+
async loadIndex() {
|
|
247
|
+
try {
|
|
248
|
+
const content = await fs.readFile(this.getIndexFilePath(), 'utf-8');
|
|
249
|
+
this.sessionIndex = JSON.parse(content);
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
async saveIndex() {
|
|
257
|
+
try {
|
|
258
|
+
await fs.mkdir(this.sessionsDir, { recursive: true });
|
|
259
|
+
await fs.writeFile(this.getIndexFilePath(), JSON.stringify(this.sessionIndex, null, 2), 'utf-8');
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
// Non-critical: index is in-memory, will rebuild on next init
|
|
263
|
+
}
|
|
264
|
+
}
|
|
189
265
|
}
|
|
190
266
|
function generateSessionId() {
|
|
191
267
|
return `session_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|