@sumicom/quicksave 0.6.1 → 0.6.2
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/cardBuilder.d.ts +14 -3
- package/dist/ai/cardBuilder.d.ts.map +1 -1
- package/dist/ai/cardBuilder.js +219 -66
- package/dist/ai/cardBuilder.js.map +1 -1
- package/dist/ai/claudeCliProvider.d.ts +2 -0
- package/dist/ai/claudeCliProvider.d.ts.map +1 -1
- package/dist/ai/claudeCliProvider.js +81 -20
- package/dist/ai/claudeCliProvider.js.map +1 -1
- package/dist/ai/claudeCodeProvider.d.ts +18 -0
- package/dist/ai/claudeCodeProvider.d.ts.map +1 -0
- package/dist/ai/claudeCodeProvider.js +30 -0
- package/dist/ai/claudeCodeProvider.js.map +1 -0
- package/dist/ai/claudeSdkProvider.d.ts +2 -0
- package/dist/ai/claudeSdkProvider.d.ts.map +1 -1
- package/dist/ai/claudeSdkProvider.js +13 -3
- package/dist/ai/claudeSdkProvider.js.map +1 -1
- package/dist/ai/codexMcpProvider.d.ts +15 -0
- package/dist/ai/codexMcpProvider.d.ts.map +1 -0
- package/dist/ai/codexMcpProvider.js +442 -0
- package/dist/ai/codexMcpProvider.js.map +1 -0
- package/dist/ai/codexSdkProvider.d.ts +35 -0
- package/dist/ai/codexSdkProvider.d.ts.map +1 -0
- package/dist/ai/codexSdkProvider.js +422 -0
- package/dist/ai/codexSdkProvider.js.map +1 -0
- package/dist/ai/debugLogger.d.ts +24 -0
- package/dist/ai/debugLogger.d.ts.map +1 -0
- package/dist/ai/debugLogger.js +55 -0
- package/dist/ai/debugLogger.js.map +1 -0
- package/dist/ai/provider.d.ts +4 -1
- package/dist/ai/provider.d.ts.map +1 -1
- package/dist/ai/sessionManager.d.ts +13 -8
- package/dist/ai/sessionManager.d.ts.map +1 -1
- package/dist/ai/sessionManager.js +148 -27
- package/dist/ai/sessionManager.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +12 -0
- package/dist/config.js.map +1 -1
- package/dist/connection/connection.d.ts +1 -0
- package/dist/connection/connection.d.ts.map +1 -1
- package/dist/connection/connection.js +19 -1
- package/dist/connection/connection.js.map +1 -1
- package/dist/git/operations.d.ts +20 -0
- package/dist/git/operations.d.ts.map +1 -1
- package/dist/git/operations.js +52 -0
- package/dist/git/operations.js.map +1 -1
- package/dist/handlers/messageHandler.d.ts +16 -0
- package/dist/handlers/messageHandler.d.ts.map +1 -1
- package/dist/handlers/messageHandler.js +289 -19
- package/dist/handlers/messageHandler.js.map +1 -1
- package/dist/index.js +22 -2
- package/dist/index.js.map +1 -1
- package/dist/service/debugHttpServer.d.ts +27 -0
- package/dist/service/debugHttpServer.d.ts.map +1 -0
- package/dist/service/debugHttpServer.js +189 -0
- package/dist/service/debugHttpServer.js.map +1 -0
- package/dist/service/ipcClient.js +1 -1
- package/dist/service/run.d.ts.map +1 -1
- package/dist/service/run.js +21 -1
- package/dist/service/run.js.map +1 -1
- package/package.json +4 -2
- package/dist/ai/asyncQueue.test.d.ts +0 -2
- package/dist/ai/asyncQueue.test.d.ts.map +0 -1
- package/dist/ai/asyncQueue.test.js +0 -56
- package/dist/ai/asyncQueue.test.js.map +0 -1
- package/dist/ai/sessionStore.test.d.ts +0 -2
- package/dist/ai/sessionStore.test.d.ts.map +0 -1
- package/dist/ai/sessionStore.test.js +0 -99
- package/dist/ai/sessionStore.test.js.map +0 -1
- package/dist/connection/pubsub.test.d.ts +0 -2
- package/dist/connection/pubsub.test.d.ts.map +0 -1
- package/dist/connection/pubsub.test.js +0 -96
- package/dist/connection/pubsub.test.js.map +0 -1
- package/dist/git/operations.test.d.ts +0 -2
- package/dist/git/operations.test.d.ts.map +0 -1
- package/dist/git/operations.test.js +0 -300
- package/dist/git/operations.test.js.map +0 -1
- package/dist/handlers/messageHandler.test.d.ts +0 -2
- package/dist/handlers/messageHandler.test.d.ts.map +0 -1
- package/dist/handlers/messageHandler.test.js +0 -361
- package/dist/handlers/messageHandler.test.js.map +0 -1
- package/dist/service/ipc.test.d.ts +0 -2
- package/dist/service/ipc.test.d.ts.map +0 -1
- package/dist/service/ipc.test.js +0 -140
- package/dist/service/ipc.test.js.map +0 -1
- package/dist/service/singleton.test.d.ts +0 -2
- package/dist/service/singleton.test.d.ts.map +0 -1
- package/dist/service/singleton.test.js +0 -39
- package/dist/service/singleton.test.js.map +0 -1
|
@@ -1,27 +1,33 @@
|
|
|
1
1
|
import { createMessage, generateMessageId, } from '@sumicom/quicksave-shared';
|
|
2
2
|
import { GitOperations } from '../git/operations.js';
|
|
3
|
-
import { getAnthropicApiKey, setAnthropicApiKey, hasAnthropicApiKey, addManagedRepo, addManagedCodingPath } from '../config.js';
|
|
3
|
+
import { getAnthropicApiKey, setAnthropicApiKey, hasAnthropicApiKey, addManagedRepo, removeManagedRepo, addManagedCodingPath, removeManagedCodingPath } from '../config.js';
|
|
4
4
|
import { CommitSummaryService } from '../ai/commitSummary.js';
|
|
5
5
|
import { SessionManager } from '../ai/sessionManager.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { ClaudeCodeProvider } from '../ai/claudeCodeProvider.js';
|
|
7
|
+
import { CodexSdkProvider } from '../ai/codexSdkProvider.js';
|
|
8
8
|
import { getSessionRegistry } from '../ai/sessionRegistry.js';
|
|
9
|
-
import { readdir, stat } from 'fs/promises';
|
|
9
|
+
import { readdir, stat, readFile } from 'fs/promises';
|
|
10
10
|
import { join, dirname, basename } from 'path';
|
|
11
11
|
import { homedir } from 'os';
|
|
12
12
|
const VERSION_CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000; // 12 hours
|
|
13
|
+
const CODEX_MODELS_CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000; // 12 hours
|
|
13
14
|
export class MessageHandler {
|
|
14
15
|
repos;
|
|
15
|
-
agentVersion = '0.6.
|
|
16
|
+
agentVersion = '0.6.2';
|
|
16
17
|
defaultRepoPath;
|
|
17
18
|
clientRepos = new Map(); // peerAddress -> repoPath
|
|
18
19
|
repoLocks = new Map(); // repoPath -> peerAddress holding lock
|
|
19
20
|
availableRepos;
|
|
20
21
|
codingPaths = new Map(); // path -> CodingPath
|
|
21
22
|
aiService = null;
|
|
22
|
-
claudeService = new SessionManager(
|
|
23
|
+
claudeService = new SessionManager([
|
|
24
|
+
new ClaudeCodeProvider(),
|
|
25
|
+
new CodexSdkProvider(),
|
|
26
|
+
]);
|
|
23
27
|
latestVersionCache = null;
|
|
24
28
|
versionCheckInFlight = null;
|
|
29
|
+
codexModelsCache = null;
|
|
30
|
+
codexModelsCheckInFlight = null;
|
|
25
31
|
onPeerSubscribed;
|
|
26
32
|
onPeerUnsubscribed;
|
|
27
33
|
onHistoryUpdated;
|
|
@@ -77,6 +83,59 @@ export class MessageHandler {
|
|
|
77
83
|
})();
|
|
78
84
|
return this.versionCheckInFlight;
|
|
79
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Fetch available Codex models from OpenAI /v1/models. Reads API key from
|
|
88
|
+
* ~/.codex/auth.json (ChatGPT OAuth token or OPENAI_API_KEY). Cached for 12h
|
|
89
|
+
* with concurrent-request dedup — same pattern as checkLatestVersion.
|
|
90
|
+
*/
|
|
91
|
+
async fetchCodexModels(force = false) {
|
|
92
|
+
if (!force && this.codexModelsCache &&
|
|
93
|
+
Date.now() - this.codexModelsCache.checkedAt < CODEX_MODELS_CHECK_INTERVAL_MS) {
|
|
94
|
+
return this.codexModelsCache.models;
|
|
95
|
+
}
|
|
96
|
+
if (this.codexModelsCheckInFlight)
|
|
97
|
+
return this.codexModelsCheckInFlight;
|
|
98
|
+
this.codexModelsCheckInFlight = (async () => {
|
|
99
|
+
try {
|
|
100
|
+
// Resolve API key: env var first, then ~/.codex/auth.json
|
|
101
|
+
let apiKey = process.env.OPENAI_API_KEY;
|
|
102
|
+
if (!apiKey) {
|
|
103
|
+
try {
|
|
104
|
+
const authPath = join(homedir(), '.codex', 'auth.json');
|
|
105
|
+
const raw = await readFile(authPath, 'utf-8');
|
|
106
|
+
const auth = JSON.parse(raw);
|
|
107
|
+
apiKey = auth.OPENAI_API_KEY || auth.tokens?.access_token;
|
|
108
|
+
}
|
|
109
|
+
catch { /* no auth file */ }
|
|
110
|
+
}
|
|
111
|
+
if (!apiKey)
|
|
112
|
+
return null;
|
|
113
|
+
const res = await fetch('https://api.openai.com/v1/models', {
|
|
114
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
115
|
+
signal: AbortSignal.timeout(10_000),
|
|
116
|
+
});
|
|
117
|
+
if (!res.ok)
|
|
118
|
+
return null;
|
|
119
|
+
const data = await res.json();
|
|
120
|
+
if (!Array.isArray(data.data))
|
|
121
|
+
return null;
|
|
122
|
+
// Filter to chat-capable models (gpt-*, o*) and sort descending
|
|
123
|
+
const models = data.data
|
|
124
|
+
.filter((m) => /^(gpt-|o\d)/.test(m.id))
|
|
125
|
+
.map((m) => ({ id: m.id, name: m.id }))
|
|
126
|
+
.sort((a, b) => b.id.localeCompare(a.id));
|
|
127
|
+
this.codexModelsCache = { models, checkedAt: Date.now() };
|
|
128
|
+
return models;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
this.codexModelsCheckInFlight = null;
|
|
135
|
+
}
|
|
136
|
+
})();
|
|
137
|
+
return this.codexModelsCheckInFlight;
|
|
138
|
+
}
|
|
80
139
|
addRepo(repo) {
|
|
81
140
|
if (this.repos.has(repo.path))
|
|
82
141
|
return;
|
|
@@ -181,6 +240,12 @@ export class MessageHandler {
|
|
|
181
240
|
return this.handleDiscard(message, peerAddress);
|
|
182
241
|
case 'git:untrack':
|
|
183
242
|
return this.handleUntrack(message, peerAddress);
|
|
243
|
+
case 'git:submodules':
|
|
244
|
+
return this.handleSubmodules(message, peerAddress);
|
|
245
|
+
case 'git:config-get':
|
|
246
|
+
return this.handleGitConfigGet(message, peerAddress);
|
|
247
|
+
case 'git:config-set':
|
|
248
|
+
return this.handleGitConfigSet(message, peerAddress);
|
|
184
249
|
case 'git:gitignore-add':
|
|
185
250
|
return this.handleGitignoreAdd(message, peerAddress);
|
|
186
251
|
case 'git:gitignore-read':
|
|
@@ -201,14 +266,24 @@ export class MessageHandler {
|
|
|
201
266
|
return this.handleBrowseDirectory(message);
|
|
202
267
|
case 'agent:add-repo':
|
|
203
268
|
return this.handleAddRepo(message);
|
|
269
|
+
case 'agent:remove-repo':
|
|
270
|
+
return this.handleRemoveRepo(message);
|
|
271
|
+
case 'agent:clone-repo':
|
|
272
|
+
return this.handleCloneRepo(message);
|
|
204
273
|
case 'agent:list-coding-paths':
|
|
205
274
|
return this.handleListCodingPaths(message);
|
|
206
275
|
case 'agent:add-coding-path':
|
|
207
276
|
return this.handleAddCodingPath(message);
|
|
277
|
+
case 'agent:remove-coding-path':
|
|
278
|
+
return this.handleRemoveCodingPath(message);
|
|
208
279
|
case 'agent:check-update':
|
|
209
280
|
return this.handleAgentCheckUpdate(message);
|
|
210
281
|
case 'agent:update':
|
|
211
282
|
return this.handleAgentUpdate(message);
|
|
283
|
+
case 'agent:restart':
|
|
284
|
+
return this.handleAgentRestart(message);
|
|
285
|
+
case 'codex:list-models':
|
|
286
|
+
return this.handleCodexListModels(message);
|
|
212
287
|
// Claude Code SDK
|
|
213
288
|
case 'claude:list-sessions':
|
|
214
289
|
return this.handleClaudeListSessions(message, peerAddress);
|
|
@@ -257,8 +332,9 @@ export class MessageHandler {
|
|
|
257
332
|
}
|
|
258
333
|
}
|
|
259
334
|
handleHandshake(message, peerAddress) {
|
|
260
|
-
// Fire-and-forget: check npm registry
|
|
335
|
+
// Fire-and-forget: check npm registry + OpenAI models (12h dedup each)
|
|
261
336
|
this.checkLatestVersion().catch(() => { });
|
|
337
|
+
this.fetchCodexModels().catch(() => { });
|
|
262
338
|
const response = createMessage('handshake:ack', {
|
|
263
339
|
success: true,
|
|
264
340
|
agentVersion: this.agentVersion,
|
|
@@ -268,6 +344,7 @@ export class MessageHandler {
|
|
|
268
344
|
preferences: this.claudeService.getPreferences(),
|
|
269
345
|
latestVersion: this.latestVersionCache?.version,
|
|
270
346
|
devBuild: !this.productionBuild || undefined,
|
|
347
|
+
codexModels: this.codexModelsCache?.models,
|
|
271
348
|
});
|
|
272
349
|
response.id = message.id;
|
|
273
350
|
return response;
|
|
@@ -528,6 +605,45 @@ export class MessageHandler {
|
|
|
528
605
|
this.releaseRepoLock(repoPath, peerAddress);
|
|
529
606
|
}
|
|
530
607
|
}
|
|
608
|
+
async handleSubmodules(message, peerAddress) {
|
|
609
|
+
const git = this.getGit(peerAddress);
|
|
610
|
+
const subs = await git.getSubmodules();
|
|
611
|
+
// Auto-register submodule paths so switch-repo works
|
|
612
|
+
for (const sub of subs) {
|
|
613
|
+
if (!this.repos.has(sub.path)) {
|
|
614
|
+
this.repos.set(sub.path, new GitOperations(sub.path));
|
|
615
|
+
this.availableRepos.push({ path: sub.path, name: sub.name, currentBranch: sub.branch });
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
const response = createMessage('git:submodules:response', {
|
|
619
|
+
submodules: subs.map((s) => ({ name: s.name, path: s.path, branch: s.branch })),
|
|
620
|
+
});
|
|
621
|
+
response.id = message.id;
|
|
622
|
+
return response;
|
|
623
|
+
}
|
|
624
|
+
async handleGitConfigGet(message, peerAddress) {
|
|
625
|
+
const identity = await this.getGit(peerAddress).getIdentity();
|
|
626
|
+
const response = createMessage('git:config-get:response', identity);
|
|
627
|
+
response.id = message.id;
|
|
628
|
+
return response;
|
|
629
|
+
}
|
|
630
|
+
async handleGitConfigSet(message, peerAddress) {
|
|
631
|
+
try {
|
|
632
|
+
const { name, email } = message.payload;
|
|
633
|
+
await this.getGit(peerAddress).setIdentity(name, email);
|
|
634
|
+
const response = createMessage('git:config-set:response', { success: true });
|
|
635
|
+
response.id = message.id;
|
|
636
|
+
return response;
|
|
637
|
+
}
|
|
638
|
+
catch (error) {
|
|
639
|
+
const response = createMessage('git:config-set:response', {
|
|
640
|
+
success: false,
|
|
641
|
+
error: error instanceof Error ? error.message : 'Failed to set git identity',
|
|
642
|
+
});
|
|
643
|
+
response.id = message.id;
|
|
644
|
+
return response;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
531
647
|
async handleGitignoreAdd(message, peerAddress) {
|
|
532
648
|
const repoPath = this.getClientRepoPath(peerAddress);
|
|
533
649
|
if (!this.acquireRepoLock(repoPath, peerAddress)) {
|
|
@@ -829,6 +945,81 @@ export class MessageHandler {
|
|
|
829
945
|
return response;
|
|
830
946
|
}
|
|
831
947
|
}
|
|
948
|
+
handleRemoveRepo(message) {
|
|
949
|
+
const { path: repoPath } = message.payload;
|
|
950
|
+
if (!this.repos.has(repoPath)) {
|
|
951
|
+
const response = createMessage('agent:remove-repo:response', {
|
|
952
|
+
success: false,
|
|
953
|
+
error: 'Repository not found',
|
|
954
|
+
});
|
|
955
|
+
response.id = message.id;
|
|
956
|
+
return response;
|
|
957
|
+
}
|
|
958
|
+
this.removeRepo(repoPath);
|
|
959
|
+
removeManagedRepo(repoPath);
|
|
960
|
+
const response = createMessage('agent:remove-repo:response', {
|
|
961
|
+
success: true,
|
|
962
|
+
});
|
|
963
|
+
response.id = message.id;
|
|
964
|
+
return response;
|
|
965
|
+
}
|
|
966
|
+
async handleCloneRepo(message) {
|
|
967
|
+
const { url, targetDir } = message.payload;
|
|
968
|
+
if (!url || !url.trim()) {
|
|
969
|
+
const response = createMessage('agent:clone-repo:response', {
|
|
970
|
+
success: false,
|
|
971
|
+
error: 'Repository URL is required',
|
|
972
|
+
});
|
|
973
|
+
response.id = message.id;
|
|
974
|
+
return response;
|
|
975
|
+
}
|
|
976
|
+
try {
|
|
977
|
+
const { execFile } = await import('child_process');
|
|
978
|
+
const { promisify } = await import('util');
|
|
979
|
+
const execFileAsync = promisify(execFile);
|
|
980
|
+
// Clone into the target directory
|
|
981
|
+
await execFileAsync('git', ['clone', url.trim(), targetDir], {
|
|
982
|
+
timeout: 120_000,
|
|
983
|
+
});
|
|
984
|
+
// Verify it's a valid git repo and add it
|
|
985
|
+
const git = new GitOperations(targetDir);
|
|
986
|
+
const { current } = await git.getBranches();
|
|
987
|
+
const rootPath = await git.getGitRoot();
|
|
988
|
+
// Check if already added (by resolved root path)
|
|
989
|
+
if (this.repos.has(rootPath)) {
|
|
990
|
+
const response = createMessage('agent:clone-repo:response', {
|
|
991
|
+
success: false,
|
|
992
|
+
error: 'Repository already added',
|
|
993
|
+
});
|
|
994
|
+
response.id = message.id;
|
|
995
|
+
return response;
|
|
996
|
+
}
|
|
997
|
+
// Add to maps and persist
|
|
998
|
+
this.repos.set(rootPath, git);
|
|
999
|
+
const newRepo = {
|
|
1000
|
+
path: rootPath,
|
|
1001
|
+
name: basename(rootPath),
|
|
1002
|
+
currentBranch: current,
|
|
1003
|
+
};
|
|
1004
|
+
this.availableRepos.push(newRepo);
|
|
1005
|
+
addManagedRepo(rootPath);
|
|
1006
|
+
const response = createMessage('agent:clone-repo:response', {
|
|
1007
|
+
success: true,
|
|
1008
|
+
repo: newRepo,
|
|
1009
|
+
clonedPath: rootPath,
|
|
1010
|
+
});
|
|
1011
|
+
response.id = message.id;
|
|
1012
|
+
return response;
|
|
1013
|
+
}
|
|
1014
|
+
catch (error) {
|
|
1015
|
+
const response = createMessage('agent:clone-repo:response', {
|
|
1016
|
+
success: false,
|
|
1017
|
+
error: error instanceof Error ? error.message : 'Failed to clone repository',
|
|
1018
|
+
});
|
|
1019
|
+
response.id = message.id;
|
|
1020
|
+
return response;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
832
1023
|
// ============================================================================
|
|
833
1024
|
// Coding Path Handlers
|
|
834
1025
|
// ============================================================================
|
|
@@ -865,6 +1056,19 @@ export class MessageHandler {
|
|
|
865
1056
|
return response;
|
|
866
1057
|
}
|
|
867
1058
|
}
|
|
1059
|
+
handleRemoveCodingPath(message) {
|
|
1060
|
+
const { path: codingPath } = message.payload;
|
|
1061
|
+
if (!this.codingPaths.has(codingPath)) {
|
|
1062
|
+
const response = createMessage('agent:remove-coding-path:response', { success: false, error: 'Coding path not found' });
|
|
1063
|
+
response.id = message.id;
|
|
1064
|
+
return response;
|
|
1065
|
+
}
|
|
1066
|
+
this.codingPaths.delete(codingPath);
|
|
1067
|
+
removeManagedCodingPath(codingPath);
|
|
1068
|
+
const response = createMessage('agent:remove-coding-path:response', { success: true });
|
|
1069
|
+
response.id = message.id;
|
|
1070
|
+
return response;
|
|
1071
|
+
}
|
|
868
1072
|
// ============================================================================
|
|
869
1073
|
// Agent Self-Update
|
|
870
1074
|
// ============================================================================
|
|
@@ -944,6 +1148,50 @@ export class MessageHandler {
|
|
|
944
1148
|
return response;
|
|
945
1149
|
}
|
|
946
1150
|
}
|
|
1151
|
+
async handleAgentRestart(message) {
|
|
1152
|
+
if (this.productionBuild) {
|
|
1153
|
+
const response = createMessage('agent:restart:response', { success: false, error: 'Restart is only available for dev builds' });
|
|
1154
|
+
response.id = message.id;
|
|
1155
|
+
return response;
|
|
1156
|
+
}
|
|
1157
|
+
try {
|
|
1158
|
+
const { spawn } = await import('child_process');
|
|
1159
|
+
const { fileURLToPath } = await import('url');
|
|
1160
|
+
const { resolve: resolvePath } = await import('path');
|
|
1161
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
1162
|
+
const isTs = thisFile.endsWith('.ts');
|
|
1163
|
+
const entryPath = resolvePath(dirname(thisFile), isTs ? '../index.ts' : '../../index.js');
|
|
1164
|
+
const agentRoot = resolvePath(dirname(thisFile), isTs ? '..' : '..');
|
|
1165
|
+
const node = process.execPath;
|
|
1166
|
+
const execArgv = isTs ? ['--import', 'tsx'] : [];
|
|
1167
|
+
spawn(node, [...execArgv, entryPath, '--restart'], {
|
|
1168
|
+
detached: true,
|
|
1169
|
+
stdio: 'ignore',
|
|
1170
|
+
cwd: agentRoot,
|
|
1171
|
+
}).unref();
|
|
1172
|
+
const response = createMessage('agent:restart:response', { success: true });
|
|
1173
|
+
response.id = message.id;
|
|
1174
|
+
return response;
|
|
1175
|
+
}
|
|
1176
|
+
catch (error) {
|
|
1177
|
+
const response = createMessage('agent:restart:response', { success: false, error: error instanceof Error ? error.message : 'Failed to restart agent' });
|
|
1178
|
+
response.id = message.id;
|
|
1179
|
+
return response;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
async handleCodexListModels(message) {
|
|
1183
|
+
try {
|
|
1184
|
+
const models = await this.fetchCodexModels(/* force */ true);
|
|
1185
|
+
const response = createMessage('codex:list-models:response', { models: models ?? [] });
|
|
1186
|
+
response.id = message.id;
|
|
1187
|
+
return response;
|
|
1188
|
+
}
|
|
1189
|
+
catch (error) {
|
|
1190
|
+
const response = createMessage('codex:list-models:response', { models: [], error: error instanceof Error ? error.message : 'Failed to fetch models' });
|
|
1191
|
+
response.id = message.id;
|
|
1192
|
+
return response;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
947
1195
|
// ============================================================================
|
|
948
1196
|
// Claude Code SDK Handlers
|
|
949
1197
|
// ============================================================================
|
|
@@ -962,27 +1210,41 @@ export class MessageHandler {
|
|
|
962
1210
|
}
|
|
963
1211
|
}
|
|
964
1212
|
async handleClaudeStart(message, peerAddress) {
|
|
965
|
-
const { prompt, cwd: payloadCwd, allowedTools, systemPrompt, model, permissionMode, sandboxed } = message.payload;
|
|
1213
|
+
const { prompt, cwd: payloadCwd, agent, allowedTools, systemPrompt, model, permissionMode, sandboxed } = message.payload;
|
|
1214
|
+
const legacyProvider = message.payload.provider;
|
|
966
1215
|
const cwd = payloadCwd || this.getClientRepoPath(peerAddress);
|
|
967
1216
|
const streamId = generateMessageId();
|
|
968
|
-
|
|
1217
|
+
const resolvedAgent = agent ?? (legacyProvider === 'codex-mcp' ? 'codex' : legacyProvider ? 'claude-code' : undefined);
|
|
1218
|
+
console.log(`[agent:start] agent=${resolvedAgent ?? 'default'} cwd=${cwd} prompt=${prompt.slice(0, 80)}${sandboxed ? ' [sandboxed]' : ''}`);
|
|
969
1219
|
try {
|
|
970
1220
|
const sessionId = await this.claudeService.startSession({
|
|
971
|
-
prompt,
|
|
1221
|
+
prompt,
|
|
1222
|
+
cwd,
|
|
1223
|
+
streamId,
|
|
1224
|
+
agent: resolvedAgent,
|
|
1225
|
+
allowedTools,
|
|
1226
|
+
systemPrompt,
|
|
1227
|
+
model,
|
|
1228
|
+
permissionMode,
|
|
1229
|
+
sandboxed,
|
|
972
1230
|
});
|
|
973
|
-
console.log(`[
|
|
1231
|
+
console.log(`[agent:start] session created: ${sessionId} agent=${resolvedAgent ?? 'default'}`);
|
|
974
1232
|
this.onPeerSubscribed?.(peerAddress, sessionId);
|
|
975
1233
|
// Register in session history
|
|
976
1234
|
const now = Date.now();
|
|
977
1235
|
const registry = getSessionRegistry();
|
|
978
1236
|
const gitBranch = await this.getGitBranchQuiet(cwd);
|
|
1237
|
+
const actualAgent = this.claudeService.getSessionAgent(sessionId, cwd);
|
|
979
1238
|
registry.upsertEntry({
|
|
980
1239
|
sessionId, cwd,
|
|
1240
|
+
agent: actualAgent,
|
|
981
1241
|
repoName: basename(cwd),
|
|
982
1242
|
gitBranch,
|
|
983
1243
|
firstPrompt: prompt.slice(0, 100),
|
|
984
1244
|
createdAt: now,
|
|
985
1245
|
lastAccessedAt: now,
|
|
1246
|
+
permissionMode,
|
|
1247
|
+
sandboxed: sandboxed || undefined,
|
|
986
1248
|
});
|
|
987
1249
|
this.onHistoryUpdated?.(cwd, registry.getEntry(cwd, sessionId), 'upsert');
|
|
988
1250
|
const response = createMessage('claude:start:response', { success: true, sessionId, streamId });
|
|
@@ -990,26 +1252,32 @@ export class MessageHandler {
|
|
|
990
1252
|
return response;
|
|
991
1253
|
}
|
|
992
1254
|
catch (error) {
|
|
993
|
-
console.error(`[
|
|
1255
|
+
console.error(`[agent:start] error:`, error);
|
|
994
1256
|
const response = createMessage('claude:start:response', { success: false, error: error instanceof Error ? error.message : 'Failed to start session' });
|
|
995
1257
|
response.id = message.id;
|
|
996
1258
|
return response;
|
|
997
1259
|
}
|
|
998
1260
|
}
|
|
999
1261
|
async handleClaudeResume(message, peerAddress) {
|
|
1000
|
-
const { sessionId: requestedId, prompt, cwd: payloadCwd } = message.payload;
|
|
1262
|
+
const { sessionId: requestedId, prompt, cwd: payloadCwd, agent } = message.payload;
|
|
1263
|
+
const legacyProvider = message.payload.provider;
|
|
1001
1264
|
const cwd = payloadCwd || this.getClientRepoPath(peerAddress);
|
|
1002
1265
|
const streamId = generateMessageId();
|
|
1003
|
-
|
|
1266
|
+
const resolvedAgent = agent ?? (legacyProvider === 'codex-mcp' ? 'codex' : legacyProvider ? 'claude-code' : undefined);
|
|
1267
|
+
console.log(`[agent:resume] session=${requestedId} agent=${resolvedAgent ?? 'stored'} cwd=${cwd} prompt=${prompt.slice(0, 80)}`);
|
|
1004
1268
|
// Subscribe before resumeSession starts streaming — hot resume emits events
|
|
1005
1269
|
// immediately (user_message, then assistant turns), so we must be subscribed
|
|
1006
1270
|
// before session.send() fires, not after resumeSession() returns.
|
|
1007
1271
|
this.onPeerSubscribed?.(peerAddress, requestedId);
|
|
1008
1272
|
try {
|
|
1009
1273
|
const actualSessionId = await this.claudeService.resumeSession({
|
|
1010
|
-
sessionId: requestedId,
|
|
1274
|
+
sessionId: requestedId,
|
|
1275
|
+
prompt,
|
|
1276
|
+
cwd,
|
|
1277
|
+
streamId,
|
|
1278
|
+
agent: resolvedAgent,
|
|
1011
1279
|
});
|
|
1012
|
-
console.log(`[
|
|
1280
|
+
console.log(`[agent:resume] session resumed: ${actualSessionId}`);
|
|
1013
1281
|
// Re-subscribe with actual session ID (cold resume may return a different ID)
|
|
1014
1282
|
if (actualSessionId !== requestedId) {
|
|
1015
1283
|
this.onPeerSubscribed?.(peerAddress, actualSessionId);
|
|
@@ -1018,9 +1286,11 @@ export class MessageHandler {
|
|
|
1018
1286
|
const registry = getSessionRegistry();
|
|
1019
1287
|
const existing = registry.getEntry(cwd, requestedId) ?? registry.getEntry(cwd, actualSessionId);
|
|
1020
1288
|
const gitBranch = await this.getGitBranchQuiet(cwd);
|
|
1289
|
+
const actualAgent = this.claudeService.getSessionAgent(actualSessionId, cwd);
|
|
1021
1290
|
registry.upsertEntry({
|
|
1022
1291
|
...(existing ?? { sessionId: actualSessionId, cwd, repoName: basename(cwd), createdAt: Date.now() }),
|
|
1023
1292
|
sessionId: actualSessionId,
|
|
1293
|
+
agent: actualAgent,
|
|
1024
1294
|
lastAccessedAt: Date.now(),
|
|
1025
1295
|
gitBranch,
|
|
1026
1296
|
});
|
|
@@ -1030,7 +1300,7 @@ export class MessageHandler {
|
|
|
1030
1300
|
return response;
|
|
1031
1301
|
}
|
|
1032
1302
|
catch (error) {
|
|
1033
|
-
console.error(`[
|
|
1303
|
+
console.error(`[agent:resume] error:`, error);
|
|
1034
1304
|
const response = createMessage('claude:resume:response', { success: false, error: error instanceof Error ? error.message : 'Failed to resume session' });
|
|
1035
1305
|
response.id = message.id;
|
|
1036
1306
|
return response;
|
|
@@ -1046,14 +1316,14 @@ export class MessageHandler {
|
|
|
1046
1316
|
handleClaudeClose(message) {
|
|
1047
1317
|
const { sessionId } = message.payload;
|
|
1048
1318
|
const success = this.claudeService.closeSession(sessionId);
|
|
1049
|
-
console.log(`[
|
|
1319
|
+
console.log(`[agent:close] session=${sessionId} success=${success}`);
|
|
1050
1320
|
const response = createMessage('claude:close:response', { success, error: success ? undefined : 'Session not found or already closed' });
|
|
1051
1321
|
response.id = message.id;
|
|
1052
1322
|
return response;
|
|
1053
1323
|
}
|
|
1054
1324
|
handleClaudeUserInputResponse(message) {
|
|
1055
1325
|
const resolved = this.claudeService.resolveUserInput(message.payload);
|
|
1056
|
-
console.log(`[
|
|
1326
|
+
console.log(`[agent:user-input-response] requestId=${message.payload.requestId} action=${message.payload.action} resolved=${resolved}`);
|
|
1057
1327
|
// No dedicated response type — just acknowledge
|
|
1058
1328
|
const response = createMessage('claude:user-input-response', { success: resolved });
|
|
1059
1329
|
response.id = message.id;
|