@redplanethq/corebrain 2.6.3 → 2.6.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 (71) hide show
  1. package/dist/commands/coding/agents.d.ts +8 -0
  2. package/dist/commands/coding/agents.d.ts.map +1 -0
  3. package/dist/commands/coding/agents.js +34 -0
  4. package/dist/commands/coding/agents.js.map +1 -0
  5. package/dist/commands/coding/close.d.ts.map +1 -1
  6. package/dist/commands/coding/close.js +13 -25
  7. package/dist/commands/coding/close.js.map +1 -1
  8. package/dist/commands/coding/list.d.ts +4 -0
  9. package/dist/commands/coding/list.d.ts.map +1 -1
  10. package/dist/commands/coding/list.js +18 -38
  11. package/dist/commands/coding/list.js.map +1 -1
  12. package/dist/commands/coding/read.d.ts +1 -0
  13. package/dist/commands/coding/read.d.ts.map +1 -1
  14. package/dist/commands/coding/read.js +36 -35
  15. package/dist/commands/coding/read.js.map +1 -1
  16. package/dist/commands/coding/resume.d.ts +1 -0
  17. package/dist/commands/coding/resume.d.ts.map +1 -1
  18. package/dist/commands/coding/resume.js +37 -31
  19. package/dist/commands/coding/resume.js.map +1 -1
  20. package/dist/commands/coding/setup.d.ts.map +1 -1
  21. package/dist/commands/coding/setup.js +34 -5
  22. package/dist/commands/coding/setup.js.map +1 -1
  23. package/dist/commands/coding/start.d.ts.map +1 -1
  24. package/dist/commands/coding/start.js +7 -32
  25. package/dist/commands/coding/start.js.map +1 -1
  26. package/dist/server/gateway-client.d.ts.map +1 -1
  27. package/dist/server/gateway-client.js +4 -0
  28. package/dist/server/gateway-client.js.map +1 -1
  29. package/dist/server/tools/coding-tools.d.ts +0 -22
  30. package/dist/server/tools/coding-tools.d.ts.map +1 -1
  31. package/dist/server/tools/coding-tools.js +255 -230
  32. package/dist/server/tools/coding-tools.js.map +1 -1
  33. package/dist/tui/chat.d.ts.map +1 -1
  34. package/dist/tui/chat.js +41 -0
  35. package/dist/tui/chat.js.map +1 -1
  36. package/dist/tui/hooks/use-conversation.d.ts +2 -0
  37. package/dist/tui/hooks/use-conversation.d.ts.map +1 -1
  38. package/dist/tui/hooks/use-conversation.js +8 -1
  39. package/dist/tui/hooks/use-conversation.js.map +1 -1
  40. package/dist/tui/utils/stream.d.ts +1 -1
  41. package/dist/tui/utils/stream.d.ts.map +1 -1
  42. package/dist/tui/utils/stream.js +2 -2
  43. package/dist/tui/utils/stream.js.map +1 -1
  44. package/dist/types/config.d.ts +2 -0
  45. package/dist/types/config.d.ts.map +1 -1
  46. package/dist/utils/coding-agents/claude-code.d.ts +8 -6
  47. package/dist/utils/coding-agents/claude-code.d.ts.map +1 -1
  48. package/dist/utils/coding-agents/claude-code.js +77 -111
  49. package/dist/utils/coding-agents/claude-code.js.map +1 -1
  50. package/dist/utils/coding-agents/codex.d.ts +23 -0
  51. package/dist/utils/coding-agents/codex.d.ts.map +1 -0
  52. package/dist/utils/coding-agents/codex.js +243 -0
  53. package/dist/utils/coding-agents/codex.js.map +1 -0
  54. package/dist/utils/coding-agents/index.d.ts +12 -10
  55. package/dist/utils/coding-agents/index.d.ts.map +1 -1
  56. package/dist/utils/coding-agents/index.js +30 -23
  57. package/dist/utils/coding-agents/index.js.map +1 -1
  58. package/dist/utils/coding-agents/types.d.ts +37 -21
  59. package/dist/utils/coding-agents/types.d.ts.map +1 -1
  60. package/dist/utils/coding-agents/types.js +98 -1
  61. package/dist/utils/coding-agents/types.js.map +1 -1
  62. package/dist/utils/coding-runner.d.ts +1 -28
  63. package/dist/utils/coding-runner.d.ts.map +1 -1
  64. package/dist/utils/coding-runner.js +10 -85
  65. package/dist/utils/coding-runner.js.map +1 -1
  66. package/dist/utils/coding-sessions.d.ts +4 -57
  67. package/dist/utils/coding-sessions.d.ts.map +1 -1
  68. package/dist/utils/coding-sessions.js +9 -75
  69. package/dist/utils/coding-sessions.js.map +1 -1
  70. package/package.json +2 -2
  71. package/dist/assets/assets/AppIcon.icns +0 -0
@@ -1,127 +1,154 @@
1
1
  import zod from 'zod';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { existsSync } from 'node:fs';
4
- import { getSession, updateSession, listSessions, createSession, closeSession as closeStoredSession, } from '../../utils/coding-sessions.js';
4
+ import { getPreferences } from '../../config/preferences.js';
5
+ import { getSession, upsertSession, deleteSession, listRunningSessions, } from '../../utils/coding-sessions.js';
5
6
  import { getAgentConfig, buildStartArgs, buildResumeArgs, startAgentProcess, isProcessRunning, stopProcess, } from '../../utils/coding-runner.js';
6
- import { readAgentSessionOutput, agentSessionExists, getAgentReader } from '../../utils/coding-agents/index.js';
7
- // ============ Zod Schemas ============
8
- export const StartSessionSchema = zod.object({
9
- agent: zod.string(),
7
+ import { readAgentSessionOutput, agentSessionExists, getAgentReader, scanAllSessions, searchSessions } from '../../utils/coding-agents/index.js';
8
+ // ============ Schemas ============
9
+ const AskSchema = zod.object({
10
+ agent: zod.string().optional(),
10
11
  prompt: zod.string(),
11
12
  dir: zod.string(),
13
+ sessionId: zod.string().optional(),
12
14
  model: zod.string().optional(),
13
15
  systemPrompt: zod.string().optional(),
14
16
  });
15
- export const ResumeSessionSchema = zod.object({
16
- sessionId: zod.string(),
17
- prompt: zod.string(),
18
- });
19
- export const CloseSessionSchema = zod.object({
17
+ const CloseSessionSchema = zod.object({
20
18
  sessionId: zod.string(),
21
19
  });
22
- export const ReadSessionSchema = zod.object({
20
+ const ReadSessionSchema = zod.object({
23
21
  sessionId: zod.string(),
22
+ dir: zod.string(),
24
23
  lines: zod.number().optional(),
25
24
  offset: zod.number().optional(),
26
25
  tail: zod.boolean().optional(),
27
26
  });
28
- export const ListSessionsSchema = zod.object({});
27
+ const ListSessionsSchema = zod.object({
28
+ agent: zod.string().optional(), // e.g. "claude-code" or "codex-cli"
29
+ since: zod.string().optional(), // ISO date string e.g. "2024-01-01"
30
+ dir: zod.string().optional(),
31
+ limit: zod.number().optional(),
32
+ offset: zod.number().optional(),
33
+ });
34
+ const SearchSessionsSchema = zod.object({
35
+ query: zod.string(),
36
+ dir: zod.string().optional(),
37
+ limit: zod.number().optional(),
38
+ });
29
39
  // ============ JSON Schemas ============
30
40
  const jsonSchemas = {
31
- coding_start_session: {
41
+ coding_ask: {
32
42
  type: 'object',
33
43
  properties: {
34
44
  agent: {
35
45
  type: 'string',
36
- description: 'Coding agent to use (e.g., "claude-code")',
46
+ description: 'Coding agent to use (e.g., "claude-code", "codex-cli"). Omit to use the configured default.',
37
47
  },
38
48
  prompt: {
39
49
  type: 'string',
40
- description: 'The task/prompt to send to the agent',
50
+ description: 'The question or task to send to the agent',
41
51
  },
42
52
  dir: {
43
53
  type: 'string',
44
54
  description: 'Working directory for the session (must exist)',
45
55
  },
56
+ sessionId: {
57
+ type: 'string',
58
+ description: 'Existing session ID to continue. Omit to start a new session.',
59
+ },
46
60
  model: {
47
61
  type: 'string',
48
- description: 'Model to use (optional)',
62
+ description: 'Model override (optional)',
49
63
  },
50
64
  systemPrompt: {
51
65
  type: 'string',
52
- description: 'System prompt (optional)',
66
+ description: 'System prompt override (optional, new sessions only)',
53
67
  },
54
68
  },
55
69
  required: ['agent', 'prompt', 'dir'],
56
70
  },
57
- coding_resume_session: {
71
+ coding_close_session: {
58
72
  type: 'object',
59
73
  properties: {
60
- sessionId: {
61
- type: 'string',
62
- description: 'Session ID to resume',
63
- },
64
- prompt: {
65
- type: 'string',
66
- description: 'The prompt to send to continue the session',
67
- },
74
+ sessionId: { type: 'string', description: 'Session ID to close' },
68
75
  },
69
- required: ['sessionId', 'prompt'],
76
+ required: ['sessionId'],
70
77
  },
71
- coding_close_session: {
78
+ coding_read_session: {
72
79
  type: 'object',
73
80
  properties: {
74
- sessionId: {
75
- type: 'string',
76
- description: 'Session ID to close',
81
+ sessionId: { type: 'string', description: 'Session ID to read output from' },
82
+ dir: { type: 'string', description: 'Working directory of the session' },
83
+ lines: { type: 'number', description: 'Number of lines to return' },
84
+ offset: { type: 'number', description: 'Line offset to start from (0-indexed)' },
85
+ tail: {
86
+ type: 'boolean',
87
+ description: 'If true, return the last N lines instead of first N',
77
88
  },
78
89
  },
79
- required: ['sessionId'],
90
+ required: ['sessionId', 'dir'],
80
91
  },
81
- coding_read_session: {
92
+ coding_list_sessions: {
82
93
  type: 'object',
83
94
  properties: {
84
- sessionId: {
95
+ agent: {
96
+ type: 'string',
97
+ description: 'Filter to a specific agent (e.g. "claude-code", "codex-cli")',
98
+ },
99
+ since: {
100
+ type: 'string',
101
+ description: 'ISO date string to filter sessions updated after this date (e.g. "2024-03-01")',
102
+ },
103
+ dir: {
85
104
  type: 'string',
86
- description: 'Session ID to read output from',
105
+ description: 'Filter to a specific working directory (optional)',
87
106
  },
88
- lines: {
107
+ limit: {
89
108
  type: 'number',
90
- description: 'Number of lines to return (default: all)',
109
+ description: 'Max sessions to return per page (default: 20)',
91
110
  },
92
111
  offset: {
93
112
  type: 'number',
94
- description: 'Line offset to start reading from (0-indexed, default: 0)',
113
+ description: 'Sessions to skip for pagination (default: 0)',
95
114
  },
96
- tail: {
97
- type: 'boolean',
98
- description: 'If true, return the last N lines instead of first N (default: false)',
115
+ },
116
+ required: [],
117
+ },
118
+ coding_search_sessions: {
119
+ type: 'object',
120
+ properties: {
121
+ query: {
122
+ type: 'string',
123
+ description: 'Search term to match against session titles',
124
+ },
125
+ dir: {
126
+ type: 'string',
127
+ description: 'Restrict search to a specific working directory (optional)',
128
+ },
129
+ limit: {
130
+ type: 'number',
131
+ description: 'Max results to return (default: 10)',
99
132
  },
100
133
  },
101
- required: ['sessionId'],
134
+ required: ['query'],
102
135
  },
103
- coding_list_sessions: {
136
+ coding_list_agents: {
104
137
  type: 'object',
105
138
  properties: {},
106
139
  required: [],
107
- description: 'List all coding sessions',
108
140
  },
109
141
  };
110
142
  // ============ Tool Definitions ============
111
143
  export const codingTools = [
112
144
  {
113
- name: 'coding_start_session',
114
- description: 'Start a new coding session with the specified agent (runs in background). After starting, the first coding_read_session call may return status="initializing" this is normal while the agent boots. Wait a few seconds and retry before concluding failure.',
115
- inputSchema: jsonSchemas.coding_start_session,
116
- },
117
- {
118
- name: 'coding_resume_session',
119
- description: 'Resume an existing coding session',
120
- inputSchema: jsonSchemas.coding_resume_session,
145
+ name: 'coding_ask',
146
+ description: 'Send a prompt to a coding agent. Omit sessionId to start a new session; include it to continue an existing one. After calling, use coding_read_session to check output.',
147
+ inputSchema: jsonSchemas.coding_ask,
121
148
  },
122
149
  {
123
150
  name: 'coding_close_session',
124
- description: 'Close/stop a coding session',
151
+ description: 'Stop a running coding session',
125
152
  inputSchema: jsonSchemas.coding_close_session,
126
153
  },
127
154
  {
@@ -131,79 +158,131 @@ export const codingTools = [
131
158
  },
132
159
  {
133
160
  name: 'coding_list_sessions',
134
- description: 'List all coding sessions',
161
+ description: 'List all coding sessions from Claude\'s session history. Sorted by most recent. Supports date filtering.',
135
162
  inputSchema: jsonSchemas.coding_list_sessions,
136
163
  },
164
+ {
165
+ name: 'coding_search_sessions',
166
+ description: 'Search past coding sessions by title or first message content',
167
+ inputSchema: jsonSchemas.coding_search_sessions,
168
+ },
169
+ {
170
+ name: 'coding_list_agents',
171
+ description: 'List all configured coding agents and which one is the default',
172
+ inputSchema: jsonSchemas.coding_list_agents,
173
+ },
137
174
  ];
138
- // ============ Tool Handlers ============
139
- async function handleStartSession(params, logger) {
140
- // Check if directory exists
175
+ // ============ Helpers ============
176
+ /**
177
+ * Resolve which agent to use.
178
+ * Priority: explicit param → defaultCodingAgent pref → only configured agent → error
179
+ */
180
+ function resolveAgent(agentParam) {
181
+ if (agentParam)
182
+ return { agent: agentParam };
183
+ const prefs = getPreferences();
184
+ const coding = (prefs.coding ?? {});
185
+ const configured = Object.keys(coding);
186
+ if (configured.length === 0) {
187
+ return { error: 'No coding agents configured. Run: corebrain coding setup' };
188
+ }
189
+ if (prefs.defaultCodingAgent && coding[prefs.defaultCodingAgent]) {
190
+ return { agent: prefs.defaultCodingAgent };
191
+ }
192
+ if (configured.length === 1) {
193
+ return { agent: configured[0] };
194
+ }
195
+ return {
196
+ error: `Multiple agents configured (${configured.join(', ')}). Specify which to use or set a default with: corebrain coding setup`,
197
+ };
198
+ }
199
+ /**
200
+ * Auto-detect the agent for a session ID by trying all registered readers.
201
+ * Falls back to default agent or claude-code.
202
+ */
203
+ function detectAgentForSession(sessionId, dir) {
204
+ // Check running session store first
205
+ const stored = getSession(sessionId);
206
+ if (stored?.agent)
207
+ return stored.agent;
208
+ // Try each reader's sessionExists
209
+ const readers = ['claude-code', 'codex-cli'];
210
+ for (const agentName of readers) {
211
+ const reader = getAgentReader(agentName);
212
+ if (reader?.sessionExists(dir, sessionId))
213
+ return agentName;
214
+ }
215
+ // Fall back to default
216
+ const prefs = getPreferences();
217
+ return prefs.defaultCodingAgent ?? 'claude-code';
218
+ }
219
+ // ============ Handlers ============
220
+ async function handleAsk(params, logger) {
141
221
  if (!existsSync(params.dir)) {
222
+ return { success: false, error: `Directory "${params.dir}" does not exist` };
223
+ }
224
+ const resolved = resolveAgent(params.agent);
225
+ if ('error' in resolved)
226
+ return { success: false, error: resolved.error };
227
+ const agentName = resolved.agent;
228
+ const config = getAgentConfig(agentName);
229
+ if (!config) {
142
230
  return {
143
231
  success: false,
144
- error: `Directory "${params.dir}" does not exist`,
232
+ error: `Agent "${agentName}" not configured. Run 'corebrain coding config --agent ${agentName}' to set up.`,
145
233
  };
146
234
  }
147
- // Get agent config
148
- const config = getAgentConfig(params.agent);
149
- if (!config) {
235
+ const isResume = Boolean(params.sessionId);
236
+ const sessionId = params.sessionId ?? randomUUID();
237
+ // For resume, verify process is not already running
238
+ if (isResume && isProcessRunning(sessionId)) {
150
239
  return {
151
240
  success: false,
152
- error: `Agent "${params.agent}" not configured. Run 'corebrain coding config --agent ${params.agent}' to set up.`,
241
+ error: `Session "${sessionId}" is already running. Wait for it to finish before sending another prompt.`,
153
242
  };
154
243
  }
155
- // Generate session ID and create session
156
- const sessionId = randomUUID();
157
- const session = createSession({
244
+ // Build args
245
+ const args = isResume
246
+ ? buildResumeArgs(config, { prompt: params.prompt, sessionId })
247
+ : buildStartArgs(config, {
248
+ prompt: params.prompt,
249
+ sessionId,
250
+ model: params.model,
251
+ systemPrompt: params.systemPrompt,
252
+ });
253
+ // Upsert running session record
254
+ upsertSession({
158
255
  sessionId,
159
- agent: params.agent,
160
- prompt: params.prompt,
256
+ agent: agentName,
161
257
  dir: params.dir,
258
+ startedAt: Date.now(),
162
259
  });
163
- updateSession(session);
164
- // Build command args
165
- const args = buildStartArgs(config, {
166
- prompt: params.prompt,
167
- sessionId,
168
- model: params.model,
169
- systemPrompt: params.systemPrompt,
170
- });
171
- // Start process in background
172
260
  const { pid, error } = startAgentProcess(sessionId, config, args, params.dir, logger);
173
261
  if (error) {
174
- return {
175
- success: false,
176
- error: `Failed to start session: ${error}`,
177
- };
262
+ deleteSession(sessionId);
263
+ return { success: false, error: `Failed to start: ${error}` };
178
264
  }
179
- // If this agent has a session reader, wait until the session file appears
180
- // before returning this prevents callers from seeing a false "not started" state.
181
- const hasReader = getAgentReader(params.agent) !== null;
182
- if (hasReader) {
183
- const POLL_INTERVAL_MS = 500;
184
- const STARTUP_TIMEOUT_MS = 30_000;
185
- const deadline = Date.now() + STARTUP_TIMEOUT_MS;
265
+ // Store pid
266
+ upsertSession({ sessionId, agent: agentName, dir: params.dir, pid, startedAt: Date.now() });
267
+ // For agents with a session reader, wait until the session file appears
268
+ const hasReader = getAgentReader(agentName) !== null;
269
+ if (hasReader && !isResume) {
270
+ const deadline = Date.now() + 30_000;
186
271
  const sessionReady = await new Promise((resolve) => {
187
272
  function check() {
188
- if (agentSessionExists(params.agent, params.dir, sessionId)) {
273
+ if (agentSessionExists(agentName, params.dir, sessionId))
189
274
  return resolve(true);
190
- }
191
- if (!isProcessRunning(sessionId)) {
275
+ if (!isProcessRunning(sessionId))
192
276
  return resolve(false);
193
- }
194
- if (Date.now() >= deadline) {
277
+ if (Date.now() >= deadline)
195
278
  return resolve(false);
196
- }
197
- setTimeout(check, POLL_INTERVAL_MS);
279
+ setTimeout(check, 500);
198
280
  }
199
- setTimeout(check, POLL_INTERVAL_MS);
281
+ setTimeout(check, 500);
200
282
  });
201
283
  if (!sessionReady) {
202
284
  stopProcess(sessionId);
203
- session.status = 'error';
204
- session.error = 'Session failed to start within 30 seconds';
205
- session.updatedAt = Date.now();
206
- updateSession(session);
285
+ deleteSession(sessionId);
207
286
  return {
208
287
  success: false,
209
288
  error: 'Session failed to start: agent did not produce output within 30 seconds',
@@ -215,142 +294,51 @@ async function handleStartSession(params, logger) {
215
294
  result: {
216
295
  sessionId,
217
296
  pid,
218
- status: 'running',
219
- message: 'Session started. Use coding_read_session to check output.',
220
- },
221
- };
222
- }
223
- async function handleResumeSession(params, logger) {
224
- // Get existing session
225
- const session = getSession(params.sessionId);
226
- if (!session) {
227
- return {
228
- success: false,
229
- error: `Session "${params.sessionId}" not found`,
230
- };
231
- }
232
- // Check if already running
233
- if (isProcessRunning(params.sessionId)) {
234
- return {
235
- success: false,
236
- error: `Session "${params.sessionId}" is already running`,
237
- };
238
- }
239
- // Check if directory still exists
240
- if (!existsSync(session.dir)) {
241
- return {
242
- success: false,
243
- error: `Session directory "${session.dir}" no longer exists`,
244
- };
245
- }
246
- // Get agent config
247
- const config = getAgentConfig(session.agent);
248
- if (!config) {
249
- return {
250
- success: false,
251
- error: `Agent "${session.agent}" not configured`,
252
- };
253
- }
254
- // Update session status
255
- session.status = 'running';
256
- session.prompt = params.prompt;
257
- session.updatedAt = Date.now();
258
- updateSession(session);
259
- // Build resume args
260
- const args = buildResumeArgs(config, {
261
- prompt: params.prompt,
262
- sessionId: params.sessionId,
263
- });
264
- // Start process in background
265
- const { pid, error } = startAgentProcess(params.sessionId, config, args, session.dir, logger);
266
- if (error) {
267
- return {
268
- success: false,
269
- error: `Failed to resume session: ${error}`,
270
- };
271
- }
272
- return {
273
- success: true,
274
- result: {
275
- sessionId: params.sessionId,
276
- pid,
277
- status: 'running',
278
- message: 'Session resumed. Use coding_read_session to check output.',
297
+ resumed: isResume,
298
+ message: 'Use coding_read_session to check output.',
279
299
  },
280
300
  };
281
301
  }
282
302
  function handleCloseSession(params) {
283
- const session = getSession(params.sessionId);
284
- if (!session) {
285
- return {
286
- success: false,
287
- error: `Session "${params.sessionId}" not found`,
288
- };
289
- }
290
- // Stop the process if running (this triggers auto-save in runner)
291
- const wasRunning = stopProcess(params.sessionId);
292
- // Mark as closed in sessions.json
293
- closeStoredSession(params.sessionId);
294
- return {
295
- success: true,
296
- result: {
297
- sessionId: params.sessionId,
298
- wasRunning,
299
- message: 'Session closed',
300
- },
301
- };
303
+ stopProcess(params.sessionId);
304
+ deleteSession(params.sessionId);
305
+ return { success: true, result: { sessionId: params.sessionId, message: 'Session closed' } };
302
306
  }
303
307
  async function handleReadSession(params) {
304
- // First check stored session exists
305
- const session = getSession(params.sessionId);
306
- if (!session) {
307
- return {
308
- success: false,
309
- error: `Session "${params.sessionId}" not found`,
310
- };
311
- }
312
- // Check if process is still running
313
308
  const running = isProcessRunning(params.sessionId);
314
- // Read from agent's session file using the appropriate reader
315
- const { entries, totalLines, returnedLines, fileExists, fileSizeBytes, fileSizeHuman, error: readError, } = await readAgentSessionOutput(session.agent, session.dir, params.sessionId, {
309
+ // Detect agent: running store reader probe → default
310
+ const agent = detectAgentForSession(params.sessionId, params.dir);
311
+ const { entries, totalLines, returnedLines, fileExists, fileSizeBytes, fileSizeHuman, error: readError, } = await readAgentSessionOutput(agent, params.dir, params.sessionId, {
316
312
  lines: params.lines,
317
313
  offset: params.offset,
318
314
  tail: params.tail,
319
315
  });
320
- // Determine status
321
- let status = session.status;
316
+ let status;
322
317
  let statusMessage;
323
318
  if (running && !fileExists) {
324
- // Process is alive but hasn't written its session file yet — still booting
325
319
  status = 'initializing';
326
- statusMessage =
327
- 'Session is starting up. The agent has not written output yet. Wait a few seconds and read again.';
320
+ statusMessage = 'Agent is booting. Wait a few seconds and read again.';
328
321
  }
329
322
  else if (running) {
330
323
  status = 'running';
331
324
  }
332
- else if (fileExists && status === 'running') {
333
- // Process finished, update status
325
+ else {
326
+ // Process finished clean up running session record
327
+ const stored = getSession(params.sessionId);
328
+ if (stored)
329
+ deleteSession(params.sessionId);
334
330
  status = 'completed';
335
- session.status = 'completed';
336
- session.updatedAt = Date.now();
337
- updateSession(session);
338
331
  }
339
332
  return {
340
333
  success: true,
341
334
  result: {
342
- sessionId: session.sessionId,
343
- agent: session.agent,
344
- prompt: session.prompt,
345
- dir: session.dir,
335
+ sessionId: params.sessionId,
336
+ dir: params.dir,
346
337
  status,
347
338
  ...(statusMessage ? { statusMessage } : {}),
348
339
  running,
349
340
  entries,
350
- error: readError || session.error,
351
- exitCode: null,
352
- startedAt: session.startedAt,
353
- updatedAt: session.updatedAt,
341
+ error: readError,
354
342
  totalLines,
355
343
  returnedLines,
356
344
  fileExists,
@@ -359,43 +347,80 @@ async function handleReadSession(params) {
359
347
  },
360
348
  };
361
349
  }
362
- function handleListSessions() {
363
- const sessions = listSessions();
350
+ async function handleListSessions(params) {
351
+ const since = params.since ? new Date(params.since).getTime() : undefined;
352
+ const { sessions, total, hasMore } = await scanAllSessions({
353
+ agent: params.agent,
354
+ dir: params.dir,
355
+ since,
356
+ limit: params.limit ?? 20,
357
+ offset: params.offset ?? 0,
358
+ });
359
+ const runningIds = new Set(listRunningSessions().map((s) => s.sessionId));
360
+ return {
361
+ success: true,
362
+ result: {
363
+ sessions: sessions.map((s) => ({
364
+ sessionId: s.sessionId,
365
+ agent: s.agent,
366
+ dir: s.dir,
367
+ title: s.title,
368
+ running: runningIds.has(s.sessionId),
369
+ createdAt: new Date(s.createdAt).toISOString(),
370
+ updatedAt: new Date(s.updatedAt).toISOString(),
371
+ fileSizeBytes: s.fileSizeBytes,
372
+ })),
373
+ total,
374
+ hasMore,
375
+ offset: params.offset ?? 0,
376
+ },
377
+ };
378
+ }
379
+ async function handleSearchSessions(params) {
380
+ const sessions = await searchSessions(params.query, {
381
+ dir: params.dir,
382
+ limit: params.limit ?? 10,
383
+ });
384
+ const runningIds = new Set(listRunningSessions().map((s) => s.sessionId));
364
385
  return {
365
386
  success: true,
366
387
  result: {
367
- sessions: sessions.map(s => {
368
- // Check if process is still running
369
- const running = isProcessRunning(s.sessionId);
370
- return {
371
- sessionId: s.sessionId,
372
- agent: s.agent,
373
- dir: s.dir,
374
- status: running ? 'running' : s.status,
375
- running,
376
- startedAt: s.startedAt,
377
- updatedAt: s.updatedAt,
378
- };
379
- }),
388
+ sessions: sessions.map((s) => ({
389
+ sessionId: s.sessionId,
390
+ dir: s.dir,
391
+ title: s.title,
392
+ running: runningIds.has(s.sessionId),
393
+ updatedAt: new Date(s.updatedAt).toISOString(),
394
+ })),
380
395
  count: sessions.length,
381
396
  },
382
397
  };
383
398
  }
384
- // ============ Tool Execution ============
399
+ function handleListAgents() {
400
+ const prefs = getPreferences();
401
+ const coding = (prefs.coding ?? {});
402
+ const agents = Object.keys(coding).map((name) => ({
403
+ name,
404
+ isDefault: name === (prefs.defaultCodingAgent ?? Object.keys(coding)[0]),
405
+ }));
406
+ return { success: true, result: { agents, default: prefs.defaultCodingAgent ?? agents[0]?.name ?? null } };
407
+ }
408
+ // ============ Dispatch ============
385
409
  export async function executeCodingTool(toolName, params, logger) {
386
410
  try {
387
411
  switch (toolName) {
388
- case 'coding_start_session':
389
- return await handleStartSession(StartSessionSchema.parse(params), logger);
390
- case 'coding_resume_session':
391
- return await handleResumeSession(ResumeSessionSchema.parse(params), logger);
412
+ case 'coding_ask':
413
+ return await handleAsk(AskSchema.parse(params), logger);
392
414
  case 'coding_close_session':
393
415
  return handleCloseSession(CloseSessionSchema.parse(params));
394
416
  case 'coding_read_session':
395
417
  return await handleReadSession(ReadSessionSchema.parse(params));
396
418
  case 'coding_list_sessions':
397
- ListSessionsSchema.parse(params);
398
- return handleListSessions();
419
+ return await handleListSessions(ListSessionsSchema.parse(params));
420
+ case 'coding_search_sessions':
421
+ return await handleSearchSessions(SearchSessionsSchema.parse(params));
422
+ case 'coding_list_agents':
423
+ return handleListAgents();
399
424
  default:
400
425
  return { success: false, error: `Unknown tool: ${toolName}` };
401
426
  }