@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.
- package/dist/commands/coding/agents.d.ts +8 -0
- package/dist/commands/coding/agents.d.ts.map +1 -0
- package/dist/commands/coding/agents.js +34 -0
- package/dist/commands/coding/agents.js.map +1 -0
- package/dist/commands/coding/close.d.ts.map +1 -1
- package/dist/commands/coding/close.js +13 -25
- package/dist/commands/coding/close.js.map +1 -1
- package/dist/commands/coding/list.d.ts +4 -0
- package/dist/commands/coding/list.d.ts.map +1 -1
- package/dist/commands/coding/list.js +18 -38
- package/dist/commands/coding/list.js.map +1 -1
- package/dist/commands/coding/read.d.ts +1 -0
- package/dist/commands/coding/read.d.ts.map +1 -1
- package/dist/commands/coding/read.js +36 -35
- package/dist/commands/coding/read.js.map +1 -1
- package/dist/commands/coding/resume.d.ts +1 -0
- package/dist/commands/coding/resume.d.ts.map +1 -1
- package/dist/commands/coding/resume.js +37 -31
- package/dist/commands/coding/resume.js.map +1 -1
- package/dist/commands/coding/setup.d.ts.map +1 -1
- package/dist/commands/coding/setup.js +34 -5
- package/dist/commands/coding/setup.js.map +1 -1
- package/dist/commands/coding/start.d.ts.map +1 -1
- package/dist/commands/coding/start.js +7 -32
- package/dist/commands/coding/start.js.map +1 -1
- package/dist/server/gateway-client.d.ts.map +1 -1
- package/dist/server/gateway-client.js +4 -0
- package/dist/server/gateway-client.js.map +1 -1
- package/dist/server/tools/coding-tools.d.ts +0 -22
- package/dist/server/tools/coding-tools.d.ts.map +1 -1
- package/dist/server/tools/coding-tools.js +255 -230
- package/dist/server/tools/coding-tools.js.map +1 -1
- package/dist/tui/chat.d.ts.map +1 -1
- package/dist/tui/chat.js +41 -0
- package/dist/tui/chat.js.map +1 -1
- package/dist/tui/hooks/use-conversation.d.ts +2 -0
- package/dist/tui/hooks/use-conversation.d.ts.map +1 -1
- package/dist/tui/hooks/use-conversation.js +8 -1
- package/dist/tui/hooks/use-conversation.js.map +1 -1
- package/dist/tui/utils/stream.d.ts +1 -1
- package/dist/tui/utils/stream.d.ts.map +1 -1
- package/dist/tui/utils/stream.js +2 -2
- package/dist/tui/utils/stream.js.map +1 -1
- package/dist/types/config.d.ts +2 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/utils/coding-agents/claude-code.d.ts +8 -6
- package/dist/utils/coding-agents/claude-code.d.ts.map +1 -1
- package/dist/utils/coding-agents/claude-code.js +77 -111
- package/dist/utils/coding-agents/claude-code.js.map +1 -1
- package/dist/utils/coding-agents/codex.d.ts +23 -0
- package/dist/utils/coding-agents/codex.d.ts.map +1 -0
- package/dist/utils/coding-agents/codex.js +243 -0
- package/dist/utils/coding-agents/codex.js.map +1 -0
- package/dist/utils/coding-agents/index.d.ts +12 -10
- package/dist/utils/coding-agents/index.d.ts.map +1 -1
- package/dist/utils/coding-agents/index.js +30 -23
- package/dist/utils/coding-agents/index.js.map +1 -1
- package/dist/utils/coding-agents/types.d.ts +37 -21
- package/dist/utils/coding-agents/types.d.ts.map +1 -1
- package/dist/utils/coding-agents/types.js +98 -1
- package/dist/utils/coding-agents/types.js.map +1 -1
- package/dist/utils/coding-runner.d.ts +1 -28
- package/dist/utils/coding-runner.d.ts.map +1 -1
- package/dist/utils/coding-runner.js +10 -85
- package/dist/utils/coding-runner.js.map +1 -1
- package/dist/utils/coding-sessions.d.ts +4 -57
- package/dist/utils/coding-sessions.d.ts.map +1 -1
- package/dist/utils/coding-sessions.js +9 -75
- package/dist/utils/coding-sessions.js.map +1 -1
- package/package.json +2 -2
- 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 {
|
|
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
|
-
// ============
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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'
|
|
76
|
+
required: ['sessionId'],
|
|
70
77
|
},
|
|
71
|
-
|
|
78
|
+
coding_read_session: {
|
|
72
79
|
type: 'object',
|
|
73
80
|
properties: {
|
|
74
|
-
sessionId: {
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
92
|
+
coding_list_sessions: {
|
|
82
93
|
type: 'object',
|
|
83
94
|
properties: {
|
|
84
|
-
|
|
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: '
|
|
105
|
+
description: 'Filter to a specific working directory (optional)',
|
|
87
106
|
},
|
|
88
|
-
|
|
107
|
+
limit: {
|
|
89
108
|
type: 'number',
|
|
90
|
-
description: '
|
|
109
|
+
description: 'Max sessions to return per page (default: 20)',
|
|
91
110
|
},
|
|
92
111
|
offset: {
|
|
93
112
|
type: 'number',
|
|
94
|
-
description: '
|
|
113
|
+
description: 'Sessions to skip for pagination (default: 0)',
|
|
95
114
|
},
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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: ['
|
|
134
|
+
required: ['query'],
|
|
102
135
|
},
|
|
103
|
-
|
|
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: '
|
|
114
|
-
description: '
|
|
115
|
-
inputSchema: jsonSchemas.
|
|
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: '
|
|
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
|
-
// ============
|
|
139
|
-
|
|
140
|
-
|
|
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: `
|
|
232
|
+
error: `Agent "${agentName}" not configured. Run 'corebrain coding config --agent ${agentName}' to set up.`,
|
|
145
233
|
};
|
|
146
234
|
}
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
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: `
|
|
241
|
+
error: `Session "${sessionId}" is already running. Wait for it to finish before sending another prompt.`,
|
|
153
242
|
};
|
|
154
243
|
}
|
|
155
|
-
//
|
|
156
|
-
const
|
|
157
|
-
|
|
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:
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
error: `Failed to start session: ${error}`,
|
|
177
|
-
};
|
|
262
|
+
deleteSession(sessionId);
|
|
263
|
+
return { success: false, error: `Failed to start: ${error}` };
|
|
178
264
|
}
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const
|
|
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(
|
|
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,
|
|
281
|
+
setTimeout(check, 500);
|
|
200
282
|
});
|
|
201
283
|
if (!sessionReady) {
|
|
202
284
|
stopProcess(sessionId);
|
|
203
|
-
|
|
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
|
-
|
|
219
|
-
message: '
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
//
|
|
315
|
-
const
|
|
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
|
-
|
|
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
|
|
333
|
-
// Process finished
|
|
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:
|
|
343
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
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 '
|
|
389
|
-
return await
|
|
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
|
-
|
|
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
|
}
|