@mod-computer/cli 0.2.4 → 0.2.5

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 (74) hide show
  1. package/package.json +3 -3
  2. package/dist/app.js +0 -227
  3. package/dist/cli.bundle.js.map +0 -7
  4. package/dist/cli.js +0 -132
  5. package/dist/commands/add.js +0 -245
  6. package/dist/commands/agents-run.js +0 -71
  7. package/dist/commands/auth.js +0 -259
  8. package/dist/commands/branch.js +0 -1411
  9. package/dist/commands/claude-sync.js +0 -772
  10. package/dist/commands/comment.js +0 -568
  11. package/dist/commands/diff.js +0 -182
  12. package/dist/commands/index.js +0 -73
  13. package/dist/commands/init.js +0 -597
  14. package/dist/commands/ls.js +0 -135
  15. package/dist/commands/members.js +0 -687
  16. package/dist/commands/mv.js +0 -282
  17. package/dist/commands/recover.js +0 -207
  18. package/dist/commands/rm.js +0 -257
  19. package/dist/commands/spec.js +0 -386
  20. package/dist/commands/status.js +0 -296
  21. package/dist/commands/sync.js +0 -119
  22. package/dist/commands/trace.js +0 -1752
  23. package/dist/commands/workspace.js +0 -447
  24. package/dist/components/conflict-resolution-ui.js +0 -120
  25. package/dist/components/messages.js +0 -5
  26. package/dist/components/thread.js +0 -8
  27. package/dist/config/features.js +0 -83
  28. package/dist/containers/branches-container.js +0 -140
  29. package/dist/containers/directory-container.js +0 -92
  30. package/dist/containers/thread-container.js +0 -214
  31. package/dist/containers/threads-container.js +0 -27
  32. package/dist/containers/workspaces-container.js +0 -27
  33. package/dist/daemon/conflict-resolution.js +0 -172
  34. package/dist/daemon/content-hash.js +0 -31
  35. package/dist/daemon/file-sync.js +0 -985
  36. package/dist/daemon/index.js +0 -203
  37. package/dist/daemon/mime-types.js +0 -166
  38. package/dist/daemon/offline-queue.js +0 -211
  39. package/dist/daemon/path-utils.js +0 -64
  40. package/dist/daemon/share-policy.js +0 -83
  41. package/dist/daemon/wasm-errors.js +0 -189
  42. package/dist/daemon/worker.js +0 -557
  43. package/dist/daemon-worker.js +0 -258
  44. package/dist/errors/workspace-errors.js +0 -48
  45. package/dist/lib/auth-server.js +0 -216
  46. package/dist/lib/browser.js +0 -35
  47. package/dist/lib/diff.js +0 -284
  48. package/dist/lib/formatters.js +0 -204
  49. package/dist/lib/git.js +0 -137
  50. package/dist/lib/local-fs.js +0 -201
  51. package/dist/lib/prompts.js +0 -56
  52. package/dist/lib/storage.js +0 -213
  53. package/dist/lib/trace-formatters.js +0 -314
  54. package/dist/services/add-service.js +0 -554
  55. package/dist/services/add-validation.js +0 -124
  56. package/dist/services/automatic-file-tracker.js +0 -303
  57. package/dist/services/cli-orchestrator.js +0 -227
  58. package/dist/services/feature-flags.js +0 -187
  59. package/dist/services/file-import-service.js +0 -283
  60. package/dist/services/file-transformation-service.js +0 -218
  61. package/dist/services/logger.js +0 -44
  62. package/dist/services/mod-config.js +0 -67
  63. package/dist/services/modignore-service.js +0 -328
  64. package/dist/services/sync-daemon.js +0 -244
  65. package/dist/services/thread-notification-service.js +0 -50
  66. package/dist/services/thread-service.js +0 -147
  67. package/dist/stores/use-directory-store.js +0 -96
  68. package/dist/stores/use-threads-store.js +0 -46
  69. package/dist/stores/use-workspaces-store.js +0 -54
  70. package/dist/types/add-types.js +0 -99
  71. package/dist/types/config.js +0 -16
  72. package/dist/types/index.js +0 -2
  73. package/dist/types/workspace-connection.js +0 -53
  74. package/dist/types.js +0 -1
@@ -1,303 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import chokidar from 'chokidar';
4
- import { detectMimeType, mimeTypeToCanvasType } from '@mod/mod-core';
5
- import { ModIgnoreService } from './modignore-service.js';
6
- import crypto from 'crypto';
7
- export class AutomaticFileTracker {
8
- constructor(repo) {
9
- this.debounceTimers = new Map();
10
- this.fileHashes = new Map();
11
- this.isTracking = false;
12
- this.watchedFileCount = 0;
13
- this.maxWatchedFiles = 2000;
14
- this.filePathToDocumentId = new Map();
15
- this.repo = repo;
16
- }
17
- async enableAutoTracking(options) {
18
- if (this.isTracking) {
19
- console.log('Automatic file tracking already enabled');
20
- return;
21
- }
22
- const watchDirectory = options.watchDirectory || process.cwd();
23
- const debounceMs = options.debounceMs || 500;
24
- const verbose = options.verbose || false;
25
- const preFilterEnabled = options.preFilterEnabled !== false; // Default true
26
- const maxWatchedFiles = options.maxWatchedFiles || 2000;
27
- const resourceMonitoring = options.resourceMonitoring !== false; // Default true
28
- this.maxWatchedFiles = maxWatchedFiles;
29
- if (resourceMonitoring) {
30
- await this.checkFileDescriptorLimits();
31
- }
32
- console.log(`🔍 Starting automatic file tracking in: ${watchDirectory}`);
33
- this.modIgnoreService = new ModIgnoreService(watchDirectory);
34
- let trackableFiles = [];
35
- if (preFilterEnabled) {
36
- const filterResult = await this.modIgnoreService.preFilterDirectory(watchDirectory);
37
- trackableFiles = filterResult.trackableFiles;
38
- console.log(`📊 Pre-filter results: ${filterResult.totalFiles} total, ${filterResult.filteredFiles.length} passed filters, ${filterResult.trackableFiles.length} trackable`);
39
- console.log(`📊 Excluded ${filterResult.excludedCount} files (${((filterResult.excludedCount / filterResult.totalFiles) * 100).toFixed(1)}%)`);
40
- trackableFiles = this.validateResourceLimits(trackableFiles);
41
- }
42
- await this.initializeFileMappings(options.workspaceHandle, watchDirectory);
43
- if (preFilterEnabled && trackableFiles.length > 0) {
44
- // Watch specific pre-filtered files instead of entire directory
45
- this.watcher = chokidar.watch(trackableFiles, {
46
- ignoreInitial: true,
47
- persistent: true,
48
- followSymlinks: false
49
- });
50
- this.watchedFileCount = trackableFiles.length;
51
- }
52
- else {
53
- // Fallback to directory watching with ignore patterns
54
- this.watcher = chokidar.watch(watchDirectory, {
55
- ignored: (filePath) => {
56
- return this.modIgnoreService.shouldIgnore(filePath, watchDirectory);
57
- },
58
- ignoreInitial: true,
59
- persistent: true,
60
- followSymlinks: false
61
- });
62
- }
63
- this.watcher
64
- .on('add', filePath => {
65
- if (verbose)
66
- console.log(`[tracker] File added: ${filePath}`);
67
- this.handleFileEvent('add', filePath, options, debounceMs);
68
- })
69
- .on('change', filePath => {
70
- if (verbose)
71
- console.log(`[tracker] File changed: ${filePath}`);
72
- this.handleFileEvent('change', filePath, options, debounceMs);
73
- })
74
- .on('unlink', filePath => {
75
- if (verbose)
76
- console.log(`[tracker] File deleted: ${filePath}`);
77
- this.handleFileEvent('unlink', filePath, options, debounceMs);
78
- });
79
- this.isTracking = true;
80
- console.log(`✅ Automatic file tracking enabled`);
81
- }
82
- async initializeFileMappings(workspaceHandle, watchDirectory) {
83
- try {
84
- // Get all files currently tracked in the workspace
85
- const files = await workspaceHandle.file.list();
86
- for (const file of files) {
87
- // Create mapping from file path to document ID
88
- const filePath = path.join(watchDirectory, file.name);
89
- this.filePathToDocumentId.set(filePath, file.id);
90
- // Initialize content hash if file exists on disk
91
- if (fs.existsSync(filePath)) {
92
- const content = fs.readFileSync(filePath, 'utf8');
93
- const hash = this.getContentHash(content);
94
- this.fileHashes.set(filePath, hash);
95
- }
96
- }
97
- console.log(`📁 Initialized tracking for ${files.length} files`);
98
- }
99
- catch (error) {
100
- console.error('Failed to initialize file mappings:', error);
101
- }
102
- }
103
- handleFileEvent(eventType, filePath, options, debounceMs) {
104
- // Only track text/code files
105
- if (!this.isTrackableFile(filePath))
106
- return;
107
- // Clear existing timer
108
- const existingTimer = this.debounceTimers.get(filePath);
109
- if (existingTimer) {
110
- clearTimeout(existingTimer);
111
- }
112
- // Set new debounced timer
113
- const timer = setTimeout(async () => {
114
- try {
115
- await this.processFileChange(eventType, filePath, options);
116
- }
117
- catch (error) {
118
- console.error(`Failed to process ${eventType} for ${filePath}:`, error);
119
- }
120
- }, debounceMs);
121
- this.debounceTimers.set(filePath, timer);
122
- }
123
- async processFileChange(eventType, filePath, options) {
124
- const { workspaceHandle, verbose } = options;
125
- switch (eventType) {
126
- case 'add':
127
- await this.handleFileAdd(filePath, workspaceHandle, verbose);
128
- break;
129
- case 'change':
130
- await this.handleFileChange(filePath, workspaceHandle, verbose);
131
- break;
132
- case 'unlink':
133
- await this.handleFileDelete(filePath, workspaceHandle, verbose);
134
- break;
135
- }
136
- }
137
- async handleFileAdd(filePath, workspaceHandle, verbose) {
138
- if (!fs.existsSync(filePath))
139
- return;
140
- const stat = fs.statSync(filePath);
141
- if (stat.isDirectory()) {
142
- if (verbose)
143
- console.log(`[tracker] Skipping directory: ${filePath}`);
144
- return;
145
- }
146
- try {
147
- const content = fs.readFileSync(filePath, 'utf8');
148
- const fileName = path.basename(filePath);
149
- const hash = this.getContentHash(content);
150
- if (verbose) {
151
- console.log(`[DEBUG AUTO-TRACKER] Creating file ${fileName}...`);
152
- }
153
- const mimeType = this.getMimeType(fileName);
154
- const canvasType = mimeTypeToCanvasType(mimeType);
155
- const documentData = {
156
- text: content,
157
- metadata: {
158
- type: canvasType, // Required for workspace container routing
159
- originalFilename: fileName
160
- }
161
- };
162
- const documentHandle = await workspaceHandle.file.create(documentData, {
163
- name: fileName,
164
- mimeType: mimeType
165
- });
166
- this.filePathToDocumentId.set(filePath, documentHandle.documentId);
167
- this.fileHashes.set(filePath, hash);
168
- if (verbose) {
169
- console.log(`✅ Added file: ${fileName} (${documentHandle.documentId})`);
170
- }
171
- }
172
- catch (error) {
173
- console.error(`Failed to add file ${path.basename(filePath)}:`, error);
174
- }
175
- }
176
- async handleFileChange(filePath, workspaceHandle, verbose) {
177
- if (!fs.existsSync(filePath))
178
- return;
179
- try {
180
- const content = fs.readFileSync(filePath, 'utf8');
181
- const newHash = this.getContentHash(content);
182
- const oldHash = this.fileHashes.get(filePath);
183
- if (newHash === oldHash)
184
- return;
185
- const documentId = this.filePathToDocumentId.get(filePath);
186
- if (!documentId) {
187
- // File not tracked yet, treat as new file
188
- await this.handleFileAdd(filePath, workspaceHandle, verbose);
189
- return;
190
- }
191
- const fileName = path.basename(filePath);
192
- const mimeType = this.getMimeType(fileName);
193
- const canvasType = mimeTypeToCanvasType(mimeType);
194
- const documentData = {
195
- text: content,
196
- metadata: {
197
- type: canvasType, // Required for workspace container routing
198
- originalFilename: fileName
199
- }
200
- };
201
- await workspaceHandle.file.update(documentId, documentData);
202
- this.fileHashes.set(filePath, newHash);
203
- if (verbose) {
204
- const fileName = path.basename(filePath);
205
- console.log(`🔄 Updated file: ${fileName}`);
206
- }
207
- }
208
- catch (error) {
209
- console.error(`Failed to update file ${path.basename(filePath)}:`, error);
210
- }
211
- }
212
- async handleFileDelete(filePath, workspaceHandle, verbose) {
213
- const documentId = this.filePathToDocumentId.get(filePath);
214
- if (!documentId)
215
- return;
216
- try {
217
- // For now, clean up local mappings - file.delete() method will be implemented later
218
- this.filePathToDocumentId.delete(filePath);
219
- this.fileHashes.delete(filePath);
220
- if (verbose) {
221
- const fileName = path.basename(filePath);
222
- console.log(`🗑️ File deleted: ${fileName} (local cleanup completed)`);
223
- }
224
- }
225
- catch (error) {
226
- console.error(`Failed to handle deletion of ${path.basename(filePath)}:`, error);
227
- }
228
- }
229
- isTrackableFile(filePath) {
230
- const ext = path.extname(filePath).toLowerCase();
231
- const trackableExtensions = [
232
- '.md', '.txt', '.js', '.ts', '.jsx', '.tsx', '.json', '.yaml', '.yml',
233
- '.py', '.java', '.cpp', '.c', '.h', '.css', '.scss', '.html', '.xml',
234
- '.sql', '.go', '.rs', '.php', '.rb', '.swift', '.kt', '.scala'
235
- ];
236
- return trackableExtensions.includes(ext);
237
- }
238
- getMimeType(fileName) {
239
- // Use centralized MIME type detection from mod-core
240
- return detectMimeType(fileName);
241
- }
242
- getContentHash(content) {
243
- return crypto.createHash('sha256').update(content, 'utf8').digest('hex');
244
- }
245
- async disableAutoTracking() {
246
- if (!this.isTracking)
247
- return;
248
- if (this.watcher) {
249
- await this.watcher.close();
250
- this.watcher = undefined;
251
- }
252
- // Clear all timers
253
- for (const timer of this.debounceTimers.values()) {
254
- clearTimeout(timer);
255
- }
256
- this.debounceTimers.clear();
257
- this.isTracking = false;
258
- console.log('🛑 Automatic file tracking disabled');
259
- }
260
- getTrackingStatus() {
261
- const capacityUsed = this.watchedFileCount / this.maxWatchedFiles;
262
- return {
263
- isTracking: this.isTracking,
264
- trackedFiles: this.filePathToDocumentId.size,
265
- watchedFiles: this.watchedFileCount,
266
- maxWatchedFiles: this.maxWatchedFiles,
267
- capacityUsed: Math.round(capacityUsed * 100)
268
- };
269
- }
270
- getModIgnoreStats() {
271
- return this.modIgnoreService?.getPatternStats() || null;
272
- }
273
- async checkFileDescriptorLimits() {
274
- try {
275
- // Get current file descriptor usage (Node.js specific)
276
- const fs = await import('fs');
277
- // Check if we're approaching system limits by attempting to open a temporary file
278
- // This is a simple heuristic since Node.js doesn't expose ulimit directly
279
- const testFile = '/tmp/.mod-fd-test';
280
- try {
281
- fs.writeFileSync(testFile, 'test');
282
- fs.unlinkSync(testFile);
283
- }
284
- catch (error) {
285
- console.warn('⚠️ Warning: Potential file descriptor limit approaching');
286
- throw new Error('File descriptor limit check failed - consider reducing maxWatchedFiles');
287
- }
288
- }
289
- catch (error) {
290
- console.warn('Could not check file descriptor limits:', error);
291
- }
292
- }
293
- validateResourceLimits(trackableFiles) {
294
- if (trackableFiles.length > this.maxWatchedFiles) {
295
- console.warn(`⚠️ Warning: ${trackableFiles.length} trackable files exceeds limit of ${this.maxWatchedFiles}`);
296
- console.warn('⚠️ Consider updating .modignore patterns or increasing maxWatchedFiles limit');
297
- const limitedFiles = trackableFiles.slice(0, this.maxWatchedFiles);
298
- console.warn(`⚠️ Limiting tracking to first ${this.maxWatchedFiles} files for performance`);
299
- return limitedFiles;
300
- }
301
- return trackableFiles;
302
- }
303
- }
@@ -1,227 +0,0 @@
1
- import { ThreadService } from '@mod/mod-core/services/thread-service';
2
- import { BranchableRepo } from '@mod/mod-core/services/branchable-repo';
3
- import { streamText, stepCountIs } from 'ai';
4
- import { createOpenAI } from '@ai-sdk/openai';
5
- import { anthropic } from '@ai-sdk/anthropic';
6
- import { log } from './logger.js';
7
- function formatItemsToMessages(items, maxMessages = 20) {
8
- return items
9
- .sort((a, b) => {
10
- const aTime = new Date(a.metadata?.createdAt || 0).getTime();
11
- const bTime = new Date(b.metadata?.createdAt || 0).getTime();
12
- if (aTime !== bTime)
13
- return aTime - bTime;
14
- return String(a.id || '').localeCompare(String(b.id || ''));
15
- })
16
- .slice(-maxMessages)
17
- .map((item) => ({
18
- role: item.userType === 'user' ? 'user' : 'assistant',
19
- content: item.content,
20
- }));
21
- }
22
- export async function* chatWithAgentCli({ repo, threadId, userMessage, user = { id: 'user-1', name: 'You', avatarUrl: '' }, workspace, agent, files = [], images = [], maxMessages = 20, tools = {}, apiKey, provider, modelName, ...opts }) {
23
- const threadService = new ThreadService(repo);
24
- // 1. Load thread and history
25
- const thread = await threadService.getThread(threadId);
26
- const items = await Promise.all((thread.itemIds || []).map((id) => threadService.getThreadItem(id)));
27
- // 2. Prepare messages and persist user message
28
- let messages = formatItemsToMessages(items, maxMessages);
29
- messages = [...messages, { role: 'user', content: userMessage }];
30
- // 3. Build session context with branch and task information
31
- const context = await buildCLISessionContext({
32
- repo,
33
- threadId,
34
- workspace,
35
- user,
36
- files,
37
- images,
38
- items,
39
- agent
40
- });
41
- const instructions = typeof agent?.instructions === 'function' ? await agent.instructions(context) : (agent?.instructions || `You are ${agent?.name || 'Assistant'}. Help the user with their request.`);
42
- const resolvedApiKey = (apiKey || process.env.OPENAI_API_KEY || process.env.OPENAI_API_TOKEN || process.env.OPENAI || process.env.ANTHROPIC_API_KEY || '').trim();
43
- const selectedProvider = provider || opts.provider || 'openai';
44
- let selectedModel = modelName || opts.modelName || agent?.defaultModel || 'gpt-4o';
45
- // Normalize common aliases to real provider model IDs
46
- if (selectedProvider === 'openai' && /^gpt-5\b/i.test(selectedModel)) {
47
- selectedModel = 'gpt-4o';
48
- }
49
- const model = selectedProvider === 'openai'
50
- ? createOpenAI({ apiKey: resolvedApiKey })(selectedModel)
51
- : anthropic(selectedModel);
52
- // Merge and wrap tools with context injection (so tool.execute receives context)
53
- const mergedTools = {
54
- ...(agent?.tools || {}),
55
- ...(tools || {}),
56
- };
57
- const aiSDKTools = injectContextIntoTools(mergedTools, context);
58
- // Debug logs for visibility
59
- try {
60
- log('[CLI Orchestrator] Provider:', selectedProvider);
61
- log('[CLI Orchestrator] Model:', selectedModel);
62
- log('[CLI Orchestrator] Instructions (first 200):', String(instructions || '').slice(0, 200));
63
- log('[CLI Orchestrator] Tools:', Object.keys(aiSDKTools));
64
- log('[CLI Orchestrator] Messages:', messages);
65
- }
66
- catch { }
67
- const maxAgentSteps = Math.max(1, opts?.maxSteps ?? 6);
68
- let lastAssistantItemId;
69
- let lastReasoningItemId;
70
- const processedToolResultIds = new Set();
71
- async function* processStream(stream) {
72
- const iter = stream.fullStream ?? stream.textStream ?? stream;
73
- for await (const part of iter) {
74
- const partType = part.type;
75
- if (partType === 'reasoning' ||
76
- partType === 'reasoning-delta' ||
77
- partType === 'reasoning-start' ||
78
- partType === 'reasoning-end' ||
79
- partType === 'reasoning-part-delta' ||
80
- partType === 'reasoning-part-finish') {
81
- const delta = part.text || part.delta || '';
82
- if (delta && delta.length > 0) {
83
- if (!lastReasoningItemId) {
84
- const item = await threadService.addReasoningToThread(threadId, delta, agent?.name || 'Assistant', agent?.iconUrl);
85
- lastReasoningItemId = item.id;
86
- }
87
- else {
88
- await threadService.addChunkToThreadItem(lastReasoningItemId, delta);
89
- }
90
- }
91
- yield { type: 'reasoning', content: delta };
92
- continue;
93
- }
94
- if (part.type === 'text' || typeof part === 'string') {
95
- const content = typeof part === 'string' ? part : part.text;
96
- if (content) {
97
- if (!lastAssistantItemId) {
98
- const item = await threadService.addAssistantMessageToThread(threadId, content, agent?.name || 'Assistant', agent?.iconUrl);
99
- lastAssistantItemId = item.id;
100
- }
101
- else {
102
- await threadService.addChunkToThreadItem(lastAssistantItemId, content);
103
- }
104
- }
105
- yield { type: 'content', content };
106
- continue;
107
- }
108
- if (part.type === 'tool-call') {
109
- await threadService.addToolCallToThread(threadId, part, agent?.name || 'Assistant', agent?.iconUrl);
110
- yield part;
111
- continue;
112
- }
113
- if (part.type === 'tool-result') {
114
- const toolCallId = String(part?.toolCallId || part?.id || `tool-${Date.now()}`);
115
- if (toolCallId && processedToolResultIds.has(toolCallId)) {
116
- try {
117
- log('[CLI Orchestrator] duplicate tool-result skipped', toolCallId);
118
- }
119
- catch { }
120
- }
121
- else {
122
- if (toolCallId)
123
- processedToolResultIds.add(toolCallId);
124
- await threadService.addToolResultToThread(threadId, part, agent?.name || 'Assistant', agent?.iconUrl);
125
- }
126
- yield { type: 'tool-result', output: part };
127
- continue;
128
- }
129
- if (part.type === 'error') {
130
- yield { type: 'error', error: part.error };
131
- }
132
- }
133
- try {
134
- const summary = await stream.reasoningText?.catch(() => undefined);
135
- if (summary && summary.trim().length > 0) {
136
- if (!lastReasoningItemId) {
137
- const item = await threadService.addReasoningToThread(threadId, summary, agent?.name || 'Assistant', agent?.iconUrl);
138
- lastReasoningItemId = item.id;
139
- }
140
- else {
141
- await threadService.addChunkToThreadItem(lastReasoningItemId, summary);
142
- }
143
- yield { type: 'reasoning', content: summary };
144
- }
145
- }
146
- catch { }
147
- }
148
- const stream = streamText({
149
- model,
150
- system: instructions,
151
- tools: aiSDKTools,
152
- messages,
153
- stopWhen: stepCountIs(maxAgentSteps),
154
- });
155
- yield* processStream(stream);
156
- }
157
- function injectContextIntoTools(tools, context) {
158
- const wrapped = {};
159
- for (const [name, tool] of Object.entries(tools || {})) {
160
- if (!tool || typeof tool !== 'object')
161
- continue;
162
- const exec = tool.execute;
163
- if (exec && exec.constructor?.name === 'AsyncGeneratorFunction') {
164
- wrapped[name] = {
165
- ...tool,
166
- async *execute(args) {
167
- const result = exec.call(tool, { ...(args || {}), context });
168
- for await (const chunk of result)
169
- yield chunk;
170
- },
171
- };
172
- }
173
- else if (typeof exec === 'function') {
174
- wrapped[name] = {
175
- ...tool,
176
- async execute(args) {
177
- return exec.call(tool, { ...(args || {}), context });
178
- },
179
- };
180
- }
181
- else {
182
- // If it already looks like an ai tool with parameters, keep as-is
183
- wrapped[name] = tool;
184
- }
185
- }
186
- return wrapped;
187
- }
188
- async function buildCLISessionContext({ repo, threadId, workspace, user, files = [], images = [], items, agent }) {
189
- // Get branch context
190
- const branchableRepo = new BranchableRepo(repo);
191
- let activeBranch = undefined;
192
- let activeThread = undefined;
193
- try {
194
- const branchCtx = await branchableRepo.getBranchContext(workspace.id);
195
- if (branchCtx && branchCtx.activeBranchId && branchCtx.branchesDocId) {
196
- const branchService = branchableRepo.getBranchService();
197
- activeBranch = await branchService.getBranch(branchCtx.activeBranchId, branchCtx.branchesDocId);
198
- // Get active task ID for this branch
199
- const activeTaskId = await branchService.getActiveTaskId(branchCtx.activeBranchId, branchCtx.branchesDocId).catch(() => null);
200
- // Build active thread context
201
- activeThread = {
202
- id: threadId,
203
- name: 'CLI Thread',
204
- branchId: activeBranch?.id || branchCtx.activeBranchId,
205
- tasksDocId: activeTaskId,
206
- metadata: {
207
- type: 'thread',
208
- },
209
- };
210
- }
211
- }
212
- catch (e) {
213
- console.warn('[CLI SessionContext] Could not fetch active branch:', e);
214
- }
215
- return {
216
- repo,
217
- user,
218
- activeWorkspace: workspace, // Expected by tools
219
- workspace, // For compatibility
220
- agent,
221
- files,
222
- images,
223
- items,
224
- ...(activeBranch ? { activeBranch } : {}),
225
- ...(activeThread ? { activeThread } : {}),
226
- };
227
- }