coding-tool-x 3.8.7 → 3.8.9
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/README.md +2 -2
- package/dist/web/assets/{AgentsDrawer-C7ePByZx.js → AgentsDrawer-Cj5CJ4d9.js} +1 -1
- package/dist/web/assets/Analytics-1doS-8uC.js +1 -0
- package/dist/web/assets/AssetPathField-C9TOPl7o.js +1 -0
- package/dist/web/assets/{ChatHistoryDrawer-Ccl4rhdz.js → ChatHistoryDrawer-BTmQ1aQr.js} +1 -1
- package/dist/web/assets/{CollapseItem-MxG_UD4o.js → CollapseItem-CwqJk3bE.js} +1 -1
- package/dist/web/assets/{CommandsDrawer-DWbaxHRZ.js → CommandsDrawer-CaZWDI_G.js} +1 -1
- package/dist/web/assets/ConfigExportDrawer-C3vSZEsS.js +1 -0
- package/dist/web/assets/{ConfigTemplates-AiRPv2cd.js → ConfigTemplates-gyERg9iC.js} +1 -1
- package/dist/web/assets/{ConfigTemplatesDrawer-CKMFsfEF.js → ConfigTemplatesDrawer-B17RqFi4.js} +1 -1
- package/dist/web/assets/{ConfigTemplatesPanel-Cvbnkvpt.js → ConfigTemplatesPanel-DKIC_FqG.js} +1 -1
- package/dist/web/assets/{Divider-D28g1gwj.js → Divider-DsdbkHrG.js} +1 -1
- package/dist/web/assets/{FavoritesDrawer-DA-dR_vP.js → FavoritesDrawer-DaziQmBp.js} +1 -1
- package/dist/web/assets/Forward-DLCL7aMY.js +1 -0
- package/dist/web/assets/{GatewayConvertDrawer-402_raGb.js → GatewayConvertDrawer-OMgWUUSc.js} +1 -1
- package/dist/web/assets/Home-DOSUTfrE.js +1 -0
- package/dist/web/assets/{MarkdownEditor-BvkDECwH.js → MarkdownEditor-9TTwTS-U.js} +1 -1
- package/dist/web/assets/McpDrawer-I8rl8lGH.js +1 -0
- package/dist/web/assets/{McpFormDrawer-DPMnyt5L.js → McpFormDrawer-DEP3C7ut.js} +1 -1
- package/dist/web/assets/{PluginManager-FKVeugr4.js → PluginManager-BuRcWuBj.js} +1 -1
- package/dist/web/assets/{PluginsDrawer-MvM1R55d.js → PluginsDrawer-_zZENaQn.js} +1 -1
- package/dist/web/assets/PluginsPanel-Bs5qZ-TK.js +1 -0
- package/dist/web/assets/PluginsPanel-CzGMK-Ap.css +1 -0
- package/dist/web/assets/ProjectList-BSxuL2td.js +1 -0
- package/dist/web/assets/{ProjectList-677RLZ8O.css → ProjectList-VbIYSHbH.css} +1 -1
- package/dist/web/assets/PromptsDrawer-CoZ-ZtsY.js +1 -0
- package/dist/web/assets/RecentSessionsDrawer-B_17aJBG.js +1 -0
- package/dist/web/assets/RecentSessionsDrawer-yrAD4p1A.css +1 -0
- package/dist/web/assets/SessionCard-BL_x0d3t.js +1 -0
- package/dist/web/assets/SessionList-DhRYZ6vl.js +1 -0
- package/dist/web/assets/SessionList-qG_XpPBJ.css +1 -0
- package/dist/web/assets/{SettingsDrawer-lMjttbkD.js → SettingsDrawer-fYb0DU7X.js} +1 -1
- package/dist/web/assets/SkillManager-S698Hns8.js +1 -0
- package/dist/web/assets/{SkillsDrawer-DF6JsSyj.js → SkillsDrawer-Ms4lPCow.js} +1 -1
- package/dist/web/assets/{SkillsPanel-B3Nm5j32.css → SkillsPanel-C4ke56nF.css} +1 -1
- package/dist/web/assets/SkillsPanel-rqnqAOGL.js +1 -0
- package/dist/web/assets/{SpeedTestDrawer-BaZkZqPP.js → SpeedTestDrawer-Bbn38SfV.js} +1 -1
- package/dist/web/assets/Upload-CdyVHxCI.js +1 -0
- package/dist/web/assets/{WorkspaceDrawer-BFSwZ5yU.js → WorkspaceDrawer-L7HJRc6u.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-BHL1uYct.js → WorkspaceManager-DsoNg2z8.js} +1 -1
- package/dist/web/assets/{WorkspacePanel-B5PooBtg.js → WorkspacePanel-Be8PVZah.js} +1 -1
- package/dist/web/assets/{headers-cf-QapUq.js → headers-B5JWGn3v.js} +1 -1
- package/dist/web/assets/{icons-B2Vg-IOC.js → icons-DmFe4b5j.js} +1 -1
- package/dist/web/assets/{index-BohDDAy_.css → index-3Lp6z8TQ.css} +1 -1
- package/dist/web/assets/index-BKlAWTH-.js +2 -0
- package/dist/web/assets/{light-Cp0t7v32.js → light-0eavxNvt.js} +1 -1
- package/dist/web/assets/platforms-BmzzwnSw.js +1 -0
- package/dist/web/assets/platforms-DWXgmy2w.js +1 -0
- package/dist/web/assets/session-visibility-BAmxh3cK.js +1 -0
- package/dist/web/assets/sessions-CC0j-KH_.js +1 -0
- package/dist/web/assets/sessions-D4yQtfKb.js +1 -0
- package/dist/web/assets/use-message-DqCwefG6.js +1 -0
- package/dist/web/assets/{useResponsiveDrawer-D-DDpbnI.js → useResponsiveDrawer-V_onl9NV.js} +1 -1
- package/dist/web/index.html +7 -7
- package/package.json +1 -1
- package/src/commands/daemon.js +27 -4
- package/src/commands/search.js +17 -2
- package/src/config/paths.js +2 -0
- package/src/platforms/channel-auth-registry.js +36 -6
- package/src/platforms/channel-auth-service.js +12 -2
- package/src/platforms/driver-registry.js +9 -1
- package/src/platforms/drivers/claude/session-history-adapter.js +72 -1
- package/src/platforms/drivers/claude/sessions-implementation.js +49 -7
- package/src/platforms/drivers/claude/sessions.js +30 -6
- package/src/platforms/drivers/codex/parser.js +57 -1
- package/src/platforms/drivers/codex/session-history-adapter.js +8 -2
- package/src/platforms/drivers/codex/sessions-implementation.js +25 -3
- package/src/platforms/drivers/codex/sessions.js +18 -1
- package/src/platforms/drivers/dsh/api-operations.js +396 -0
- package/src/platforms/drivers/dsh/channels-implementation.js +458 -0
- package/src/platforms/drivers/dsh/channels.js +191 -0
- package/src/platforms/drivers/dsh/common.js +571 -0
- package/src/platforms/drivers/dsh/native-config.js +93 -0
- package/src/platforms/drivers/dsh/native-logs.js +116 -0
- package/src/platforms/drivers/dsh/plugin-manager.js +117 -0
- package/src/platforms/drivers/dsh/projects.js +23 -0
- package/src/platforms/drivers/dsh/proxy-implementation.js +260 -0
- package/src/platforms/drivers/dsh/proxy.js +23 -0
- package/src/platforms/drivers/dsh/resources.js +346 -0
- package/src/platforms/drivers/dsh/sessions.js +587 -0
- package/src/platforms/drivers/dsh/statistics-implementation.js +238 -0
- package/src/platforms/drivers/dsh/statistics.js +24 -0
- package/src/platforms/drivers/gemini/session-history-adapter.js +49 -1
- package/src/platforms/drivers/gemini/sessions-implementation.js +46 -1
- package/src/platforms/drivers/gemini/sessions.js +2 -1
- package/src/platforms/drivers/omp/prompts.js +58 -2
- package/src/platforms/drivers/omp/proxy-implementation.js +25 -1
- package/src/platforms/drivers/omp/session-history-adapter.js +42 -1
- package/src/platforms/drivers/omp/sessions-implementation.js +51 -3
- package/src/platforms/drivers/omp/sessions.js +33 -2
- package/src/platforms/drivers/opencode/channels-implementation.js +25 -22
- package/src/platforms/drivers/opencode/channels.js +6 -1
- package/src/platforms/drivers/opencode/sessions-implementation.js +89 -9
- package/src/platforms/drivers/opencode/sessions.js +37 -1
- package/src/platforms/manifest-schema.js +43 -3
- package/src/platforms/manifests/dsh.json +542 -0
- package/src/platforms/manifests/omp.json +2 -1
- package/src/platforms/manifests/opencode.json +28 -0
- package/src/platforms/native-oauth-adapters.js +318 -18
- package/src/platforms/oauth-credentials-service.js +344 -13
- package/src/platforms/path-resolver.js +2 -0
- package/src/platforms/platform-path-context.js +12 -2
- package/src/platforms/registry.js +16 -1
- package/src/platforms/runtime.js +5 -0
- package/src/platforms/session-history-adapters.js +1 -0
- package/src/server/api/platform-api-config.js +23 -2
- package/src/server/api/platform-route-factory.js +9 -2
- package/src/server/api/platforms.js +19 -1
- package/src/server/api/plugins.js +108 -13
- package/src/server/index.js +35 -1
- package/src/server/services/config-registry-service.js +10 -1
- package/src/server/services/config-templates-service.js +1 -0
- package/src/server/services/plugins-service.js +474 -103
- package/src/server/services/session-history-index.js +900 -140
- package/src/server/services/session-history-worker.js +3 -1
- package/src/server/services/skill-service.js +214 -7
- package/src/server/services/usage-log-utils.js +1 -0
- package/src/server/services/workspace-service.js +39 -10
- package/src/shared/driver-factories/api.js +22 -6
- package/src/shared/driver-factories/projects.js +8 -0
- package/dist/web/assets/Analytics-BVG6ehFv.js +0 -1
- package/dist/web/assets/AssetPathField-BZI9NvEr.js +0 -1
- package/dist/web/assets/ConfigExportDrawer-DrawKwxE.js +0 -1
- package/dist/web/assets/Home-CTF8ZWKW.js +0 -1
- package/dist/web/assets/McpDrawer-DZGHkuDm.js +0 -1
- package/dist/web/assets/PluginsPanel-CKuBs38B.js +0 -1
- package/dist/web/assets/PluginsPanel-D6ZqITcR.css +0 -1
- package/dist/web/assets/ProjectList-tsjwjXV1.js +0 -1
- package/dist/web/assets/PromptsDrawer-B-X9ci8n.js +0 -1
- package/dist/web/assets/RecentSessionsDrawer-CKFsOYDM.css +0 -1
- package/dist/web/assets/RecentSessionsDrawer-wLktMF_c.js +0 -1
- package/dist/web/assets/SessionCard-DkJPy0TY.js +0 -1
- package/dist/web/assets/SessionList-BpYRz1cX.css +0 -1
- package/dist/web/assets/SessionList-Gzhh4FOZ.js +0 -1
- package/dist/web/assets/SkillManager-CzFxWxx-.js +0 -1
- package/dist/web/assets/SkillsPanel--lLEts9y.js +0 -1
- package/dist/web/assets/Upload-BEQ-3Kqq.js +0 -1
- package/dist/web/assets/index-BFInlZGI.js +0 -2
- package/dist/web/assets/platforms-CBkB45JP.js +0 -1
- package/dist/web/assets/platforms-FTpjxAT6.js +0 -1
- package/dist/web/assets/sessions-Cmy31slD.js +0 -1
- package/dist/web/assets/sessions-yC_oVVh8.js +0 -1
- package/dist/web/assets/use-message-DoLM8C1C.js +0 -1
package/src/config/paths.js
CHANGED
|
@@ -53,6 +53,7 @@ const LEGACY_STATS_DIR = path.join(LEGACY_DIR, 'stats');
|
|
|
53
53
|
const CONTROL_MANIFEST_PATH = path.join(CONFIG_DIR, 'effective-control.json');
|
|
54
54
|
const SKILL_ARTIFACTS_DIR = path.join(STORAGE_DIR, 'skill-artifacts');
|
|
55
55
|
const SKILL_REFRESH_TASKS_PATH = path.join(RUNTIME_DIR, 'skill-refresh-tasks.json');
|
|
56
|
+
const PLUGIN_REFRESH_TASKS_PATH = path.join(RUNTIME_DIR, 'plugin-refresh-tasks.json');
|
|
56
57
|
|
|
57
58
|
// 旧目录(升级时自动合并到 ~/.cc-tool)
|
|
58
59
|
const LEGACY_BASE_DIRS = [
|
|
@@ -416,6 +417,7 @@ const PATHS = {
|
|
|
416
417
|
effectiveControlManifest: CONTROL_MANIFEST_PATH,
|
|
417
418
|
skillArtifacts: SKILL_ARTIFACTS_DIR,
|
|
418
419
|
skillRefreshTasks: SKILL_REFRESH_TASKS_PATH,
|
|
420
|
+
pluginRefreshTasks: PLUGIN_REFRESH_TASKS_PATH,
|
|
419
421
|
|
|
420
422
|
// 渠道配置
|
|
421
423
|
channelsDir: CHANNELS_DIR,
|
|
@@ -58,10 +58,32 @@ function scanNative(adapter) {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
function scanOmp() {
|
|
61
|
-
const snapshot = getOmpAuthProviderSnapshot({ forceRefresh: true, accountCheck: true });
|
|
61
|
+
const snapshot = getOmpAuthProviderSnapshot({ forceRefresh: true, accountCheck: true }) || {};
|
|
62
62
|
const hasAccounts = (snapshot.providers || []).some(provider => (
|
|
63
63
|
provider.loggedIn === true && Array.isArray(provider.accounts) && provider.accounts.length > 0
|
|
64
64
|
));
|
|
65
|
+
if (!hasAccounts) {
|
|
66
|
+
const native = readAllNativeOAuth('omp') || [];
|
|
67
|
+
let synced = null;
|
|
68
|
+
if (native.length) {
|
|
69
|
+
try {
|
|
70
|
+
synced = oauthStore.syncLocalCredential('omp');
|
|
71
|
+
} catch {
|
|
72
|
+
synced = null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const credentials = synced?.credentials || [];
|
|
76
|
+
const candidates = native.map((metadata, index) => nativeCandidate('omp', metadata, credentials[index]));
|
|
77
|
+
return {
|
|
78
|
+
candidates,
|
|
79
|
+
nativeState: {
|
|
80
|
+
available: snapshot.available === true || candidates.length > 0,
|
|
81
|
+
providers: snapshot.providers || [],
|
|
82
|
+
checkedAt: snapshot.checkedAt
|
|
83
|
+
},
|
|
84
|
+
warnings: candidates.length ? [] : [snapshot.reason || 'omp: unavailable: no logged-in local provider']
|
|
85
|
+
};
|
|
86
|
+
}
|
|
65
87
|
let synced = null;
|
|
66
88
|
if (hasAccounts) {
|
|
67
89
|
try {
|
|
@@ -81,13 +103,14 @@ function scanOmp() {
|
|
|
81
103
|
const credential = credentials.find(item => (
|
|
82
104
|
item.providerId === provider.id
|
|
83
105
|
&& (item.accountId === String(accountId || '') || item.accountEmail === String(accountEmail || ''))
|
|
84
|
-
))
|
|
106
|
+
)) || credentials
|
|
107
|
+
.filter(item => item.providerId === provider.id)[Number(accountId) - 1] || null;
|
|
85
108
|
const ref = safeRef({
|
|
86
109
|
credentialId: credential?.id,
|
|
87
110
|
providerId: provider.id,
|
|
88
|
-
accountId,
|
|
89
|
-
identityKey,
|
|
90
|
-
accountEmail
|
|
111
|
+
accountId: credential?.accountId || accountId,
|
|
112
|
+
identityKey: credential?.identityKey || identityKey,
|
|
113
|
+
accountEmail: credential?.accountEmail || accountEmail
|
|
91
114
|
});
|
|
92
115
|
if (!ref.accountId && !ref.identityKey && (provider.accounts || []).length > 1) continue;
|
|
93
116
|
candidates.push({
|
|
@@ -132,8 +155,15 @@ const adapters = Object.freeze({
|
|
|
132
155
|
},
|
|
133
156
|
omp: {
|
|
134
157
|
scan: scanOmp,
|
|
135
|
-
|
|
158
|
+
usage: (ref) => oauthStore.fetchCredentialUsage('omp', ref.credentialId),
|
|
159
|
+
quota: (ref) => oauthStore.fetchCredentialUsage('omp', ref.credentialId),
|
|
136
160
|
channelServicePath: './drivers/omp/channels-implementation'
|
|
161
|
+
},
|
|
162
|
+
opencode: {
|
|
163
|
+
scan: () => scanNative('opencode'),
|
|
164
|
+
usage: (ref) => oauthStore.fetchCredentialUsage('opencode', ref.credentialId),
|
|
165
|
+
quota: (ref) => oauthStore.fetchCredentialUsage('opencode', ref.credentialId),
|
|
166
|
+
channelServicePath: './drivers/opencode/channels-implementation'
|
|
137
167
|
}
|
|
138
168
|
});
|
|
139
169
|
|
|
@@ -37,11 +37,21 @@ function resolveChannelAuthRef(adapter, channel) {
|
|
|
37
37
|
const matched = candidates.find(candidate => matchChannel(channel, candidate));
|
|
38
38
|
if (matched) return safeRef(matched.authRef);
|
|
39
39
|
|
|
40
|
+
// A channel may retain an old account ID after the local OAuth store rotates
|
|
41
|
+
// credentials. When there is exactly one account for the same provider, it
|
|
42
|
+
// is safe to rebind the stale reference for quota lookup.
|
|
43
|
+
const soleCandidate = candidates.length === 1 ? candidates[0] : null;
|
|
44
|
+
const soleRef = safeRef(soleCandidate?.authRef);
|
|
45
|
+
const providerId = current.providerId || String(channel?.oauthProviderId || '').trim();
|
|
46
|
+
if (soleCandidate && providerId && soleRef.providerId === providerId) {
|
|
47
|
+
return soleRef;
|
|
48
|
+
}
|
|
49
|
+
|
|
40
50
|
// A channel created before OAuth credentials were synced can have no stable
|
|
41
51
|
// reference. Only auto-select when the local scan found exactly one account.
|
|
42
52
|
if (!current.credentialId && !current.providerId && !current.accountId
|
|
43
|
-
&& !current.identityKey && !current.accountEmail &&
|
|
44
|
-
return
|
|
53
|
+
&& !current.identityKey && !current.accountEmail && soleCandidate) {
|
|
54
|
+
return soleRef;
|
|
45
55
|
}
|
|
46
56
|
|
|
47
57
|
return current;
|
|
@@ -48,7 +48,15 @@ function getDriverRegistry() {
|
|
|
48
48
|
'generic-mcp': createGenericMcpDriver,
|
|
49
49
|
'generic-prompt': createGenericPromptDriver,
|
|
50
50
|
'opencode-conversion': require('./drivers/opencode/conversion').createDriver,
|
|
51
|
-
'claude-health': require('./drivers/claude/health').createDriver
|
|
51
|
+
'claude-health': require('./drivers/claude/health').createDriver,
|
|
52
|
+
'dsh-api': require('./drivers/dsh/api-operations').createDriver,
|
|
53
|
+
'dsh-projects': require('./drivers/dsh/projects').createDriver,
|
|
54
|
+
'dsh-sessions': require('./drivers/dsh/sessions').createDriver,
|
|
55
|
+
'dsh-native-config': require('./drivers/dsh/native-config').createDriver,
|
|
56
|
+
'dsh-channels': require('./drivers/dsh/channels').createDriver,
|
|
57
|
+
'dsh-proxy': require('./drivers/dsh/proxy').createDriver,
|
|
58
|
+
'dsh-statistics': require('./drivers/dsh/statistics').createDriver,
|
|
59
|
+
'dsh-native-logs': require('./drivers/dsh/native-logs').createDriver
|
|
52
60
|
}
|
|
53
61
|
});
|
|
54
62
|
registerLegacyDrivers(defaultRegistry);
|
|
@@ -150,6 +150,77 @@ async function inventory({ projectsDir } = {}) {
|
|
|
150
150
|
return descriptors;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
/** Read a bounded prefix for project/session list summaries. */
|
|
154
|
+
function readSessionSummaryHead(filePath, maxBytes = 64 * 1024) {
|
|
155
|
+
let fd;
|
|
156
|
+
try {
|
|
157
|
+
fd = fs.openSync(filePath, 'r');
|
|
158
|
+
const buffer = Buffer.alloc(maxBytes);
|
|
159
|
+
const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
|
|
160
|
+
return buffer.subarray(0, bytesRead).toString('utf8')
|
|
161
|
+
.split(/\r?\n/)
|
|
162
|
+
.filter(line => line.trim())
|
|
163
|
+
.map(line => {
|
|
164
|
+
try { return JSON.parse(line); } catch (_) { return null; }
|
|
165
|
+
})
|
|
166
|
+
.filter(Boolean);
|
|
167
|
+
} catch (_) {
|
|
168
|
+
return [];
|
|
169
|
+
} finally {
|
|
170
|
+
if (fd !== undefined) {
|
|
171
|
+
try { fs.closeSync(fd); } catch (_) {}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function summarize(descriptor) {
|
|
177
|
+
const lines = readSessionSummaryHead(descriptor.filePath);
|
|
178
|
+
const { fullPath, projectName: displayName } = parseRealProjectPath(
|
|
179
|
+
descriptor.projectHint,
|
|
180
|
+
{ projectsDir: descriptor.projectsDir }
|
|
181
|
+
);
|
|
182
|
+
let firstMessage = null;
|
|
183
|
+
let gitBranch = null;
|
|
184
|
+
let provider = null;
|
|
185
|
+
let model = null;
|
|
186
|
+
let startedAt = null;
|
|
187
|
+
for (const json of lines) {
|
|
188
|
+
const payload = json.message && typeof json.message === 'object' && !Array.isArray(json.message)
|
|
189
|
+
? json.message
|
|
190
|
+
: {};
|
|
191
|
+
const timestamp = json.timestamp || payload.timestamp || null;
|
|
192
|
+
const timestampMs = timestamp ? new Date(timestamp).getTime() : null;
|
|
193
|
+
if (timestampMs && startedAt === null) startedAt = timestampMs;
|
|
194
|
+
const metadata = json.type === 'metadata' || json.type === 'summary';
|
|
195
|
+
if (metadata) {
|
|
196
|
+
gitBranch = json.gitBranch || payload.gitBranch || gitBranch;
|
|
197
|
+
provider = json.provider || payload.provider || provider;
|
|
198
|
+
model = json.model || payload.model || model;
|
|
199
|
+
}
|
|
200
|
+
const role = json.role || payload.role
|
|
201
|
+
|| (json.type === 'user' || json.type === 'human' ? 'user' : null);
|
|
202
|
+
const rawContent = json.content !== undefined ? json.content : payload.content;
|
|
203
|
+
if (!firstMessage && role === 'user') firstMessage = getClaudeUserText(rawContent) || null;
|
|
204
|
+
if (json.model || payload.model) model = json.model || payload.model;
|
|
205
|
+
if (json.provider || payload.provider) provider = json.provider || payload.provider;
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
session: {
|
|
209
|
+
sessionId: descriptor.sessionId,
|
|
210
|
+
projectName: descriptor.projectHint || 'unknown',
|
|
211
|
+
projectDisplayName: displayName || descriptor.projectHint || 'unknown',
|
|
212
|
+
projectFullPath: fullPath || null,
|
|
213
|
+
firstMessage,
|
|
214
|
+
gitBranch,
|
|
215
|
+
provider,
|
|
216
|
+
model,
|
|
217
|
+
startedAt,
|
|
218
|
+
updatedAt: descriptor.mtimeMs,
|
|
219
|
+
extraJson: JSON.stringify({ cwd: fullPath || null })
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
153
224
|
/**
|
|
154
225
|
* Parse a Claude session file into normalized session + messages.
|
|
155
226
|
* @param {{filePath: string, size: number, mtimeMs: number, sessionId: string, projectHint: string}} descriptor
|
|
@@ -310,4 +381,4 @@ async function parse(descriptor) {
|
|
|
310
381
|
};
|
|
311
382
|
}
|
|
312
383
|
|
|
313
|
-
module.exports = { configure, inventory, parse, getClaudeUserText };
|
|
384
|
+
module.exports = { configure, inventory, summarize, parse, getClaudeUserText };
|
|
@@ -208,6 +208,10 @@ async function getProjectsWithStats(config = {}, options = {}) {
|
|
|
208
208
|
return getSessionHistoryIndex(config).listProjects('claude', { ...options, config });
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
async function getProjectsPage(config = {}, options = {}) {
|
|
212
|
+
return getSessionHistoryIndex(config).listProjectsPage('claude', { ...options, config });
|
|
213
|
+
}
|
|
214
|
+
|
|
211
215
|
// ===== Path resolution helpers (kept for mutation/conversion paths) =====
|
|
212
216
|
|
|
213
217
|
function parseRealProjectPath(encodedName, config = {}) {
|
|
@@ -327,7 +331,7 @@ function extractCwdFromSessionHeader(sessionFile) {
|
|
|
327
331
|
// ===== Counts: delegates to index =====
|
|
328
332
|
|
|
329
333
|
async function getProjectAndSessionCounts(config = {}, options = {}) {
|
|
330
|
-
const projects = await getSessionHistoryIndex().
|
|
334
|
+
const projects = await getSessionHistoryIndex().getAllProjects('claude', { ...options, config });
|
|
331
335
|
let sessionCount = 0;
|
|
332
336
|
for (const project of projects) sessionCount += project.sessionCount || 0;
|
|
333
337
|
return { projectCount: projects.length, sessionCount };
|
|
@@ -362,6 +366,23 @@ async function getSessionsForProject(config, projectName, options = {}) {
|
|
|
362
366
|
const totalSize = sessions.reduce((sum, s) => sum + (s.size || 0), 0);
|
|
363
367
|
return { sessions, totalSize };
|
|
364
368
|
}
|
|
369
|
+
|
|
370
|
+
async function getSessionsPage(config, projectName, options = {}) {
|
|
371
|
+
const payload = await getSessionHistoryIndex(config).listSessionsPage('claude', projectName, { ...options, config });
|
|
372
|
+
const forkRelations = getForkRelations();
|
|
373
|
+
return {
|
|
374
|
+
...payload,
|
|
375
|
+
sessions: payload.sessions.map(session => ({
|
|
376
|
+
sessionId: session.sessionId,
|
|
377
|
+
mtime: session.mtime,
|
|
378
|
+
size: session.size,
|
|
379
|
+
filePath: session.filePath,
|
|
380
|
+
gitBranch: session.gitBranch || null,
|
|
381
|
+
firstMessage: session.firstMessage || null,
|
|
382
|
+
forkedFrom: forkRelations[session.sessionId] || null
|
|
383
|
+
}))
|
|
384
|
+
};
|
|
385
|
+
}
|
|
365
386
|
// ===== Mutations (sync) =====
|
|
366
387
|
|
|
367
388
|
function deleteSession(config, projectName, sessionId) {
|
|
@@ -439,6 +460,7 @@ async function searchSessions(config, projectName, keyword, contextLength = 15,
|
|
|
439
460
|
const results = await getSessionHistoryIndex().searchSessions('claude', keyword, { projectName, contextLength, ...options, config });
|
|
440
461
|
const aliases = loadAliases();
|
|
441
462
|
return results.map(r => ({
|
|
463
|
+
...r,
|
|
442
464
|
sessionId: r.sessionId,
|
|
443
465
|
alias: aliases[r.sessionId] || null,
|
|
444
466
|
matchCount: r.matchCount,
|
|
@@ -465,18 +487,25 @@ async function getRecentSessions(config, limit = 5, options = {}) {
|
|
|
465
487
|
}));
|
|
466
488
|
}
|
|
467
489
|
|
|
468
|
-
async function searchSessionsAcrossProjects(config, keyword,
|
|
490
|
+
async function searchSessionsAcrossProjects(config, keyword, limit = 35, options = {}) {
|
|
469
491
|
const allResults = [];
|
|
492
|
+
const searchOptions = {
|
|
493
|
+
...options,
|
|
494
|
+
contextLength: Number(options.contextLength) || 35,
|
|
495
|
+
limit: Math.max(1, Math.min(200, Number(limit) || 35)),
|
|
496
|
+
config,
|
|
497
|
+
projectName: options.projectName || null
|
|
498
|
+
};
|
|
470
499
|
|
|
471
500
|
try {
|
|
472
|
-
const claudeResults = await getSessionHistoryIndex().searchSessions('claude', keyword,
|
|
501
|
+
const claudeResults = await getSessionHistoryIndex().searchSessions('claude', keyword, searchOptions);
|
|
473
502
|
for (const r of claudeResults) allResults.push({ ...r, channel: 'claude' });
|
|
474
503
|
} catch (_) {}
|
|
475
504
|
|
|
476
505
|
try {
|
|
477
506
|
if (fs.existsSync(CODEX_PROJECTS_DIR)) {
|
|
478
507
|
const { searchSessions: codexSearch } = require('../codex/sessions-implementation');
|
|
479
|
-
const codexResults = await codexSearch(keyword);
|
|
508
|
+
const codexResults = await codexSearch(keyword, searchOptions);
|
|
480
509
|
for (const r of codexResults) allResults.push({ ...r, channel: 'codex' });
|
|
481
510
|
}
|
|
482
511
|
} catch (_) {}
|
|
@@ -484,13 +513,24 @@ async function searchSessionsAcrossProjects(config, keyword, contextLength = 35)
|
|
|
484
513
|
try {
|
|
485
514
|
if (fs.existsSync(GEMINI_PROJECTS_DIR)) {
|
|
486
515
|
const { searchSessions: geminiSearch } = require('../gemini/sessions-implementation');
|
|
487
|
-
const geminiResults = await geminiSearch(keyword, contextLength);
|
|
516
|
+
const geminiResults = await geminiSearch(keyword, searchOptions.contextLength, searchOptions);
|
|
488
517
|
for (const r of geminiResults) allResults.push({ ...r, channel: 'gemini' });
|
|
489
518
|
}
|
|
490
519
|
} catch (_) {}
|
|
491
520
|
|
|
492
|
-
allResults.sort((a, b) =>
|
|
493
|
-
|
|
521
|
+
allResults.sort((a, b) => {
|
|
522
|
+
const updatedA = sessionTimestamp(a.updatedAt ?? a.lastUpdated ?? a.mtimeMs ?? a.mtime);
|
|
523
|
+
const updatedB = sessionTimestamp(b.updatedAt ?? b.lastUpdated ?? b.mtimeMs ?? b.mtime);
|
|
524
|
+
return updatedB - updatedA || String(a.sessionId || '').localeCompare(String(b.sessionId || ''));
|
|
525
|
+
});
|
|
526
|
+
return allResults.slice(0, searchOptions.limit);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function sessionTimestamp(value) {
|
|
530
|
+
const numeric = Number(value);
|
|
531
|
+
if (Number.isFinite(numeric) && numeric > 0) return numeric;
|
|
532
|
+
const parsed = Date.parse(String(value || ''));
|
|
533
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
494
534
|
}
|
|
495
535
|
function hasActualMessages(sessionFile) {
|
|
496
536
|
if (!sessionFile || !fs.existsSync(sessionFile)) return false;
|
|
@@ -513,7 +553,9 @@ module.exports = {
|
|
|
513
553
|
configure,
|
|
514
554
|
getProjects,
|
|
515
555
|
getProjectsWithStats,
|
|
556
|
+
getProjectsPage,
|
|
516
557
|
getSessionsForProject,
|
|
558
|
+
getSessionsPage,
|
|
517
559
|
deleteSession,
|
|
518
560
|
forkSession,
|
|
519
561
|
launch,
|
|
@@ -11,7 +11,7 @@ function createDriver(context = {}) {
|
|
|
11
11
|
servicePath: './claude/sessions-implementation',
|
|
12
12
|
localServicePath: '../platforms/drivers/claude/sessions-implementation',
|
|
13
13
|
adapterLocalPath: '../platforms/drivers/claude/session-history-adapter',
|
|
14
|
-
adapterMethods: { inventory: 'inventory', parse: 'parse' },
|
|
14
|
+
adapterMethods: { inventory: 'inventory', summarize: 'summarize', parse: 'parse' },
|
|
15
15
|
methods: {
|
|
16
16
|
launch: 'launch',
|
|
17
17
|
saveSessionOrder: 'saveSessionOrder',
|
|
@@ -44,6 +44,14 @@ function createDriver(context = {}) {
|
|
|
44
44
|
request
|
|
45
45
|
);
|
|
46
46
|
},
|
|
47
|
+
listSessionsPage: (service, projectNameOrRequest, options = {}) => {
|
|
48
|
+
const descriptorRequest = projectNameOrRequest && typeof projectNameOrRequest === 'object';
|
|
49
|
+
const request = descriptorRequest ? projectNameOrRequest : options;
|
|
50
|
+
const projectName = descriptorRequest
|
|
51
|
+
? request.params?.projectName
|
|
52
|
+
: projectNameOrRequest;
|
|
53
|
+
return service.getSessionsPage(request.config || {}, projectName, request);
|
|
54
|
+
},
|
|
47
55
|
recent: (service, limitOrRequest, options = {}) => {
|
|
48
56
|
const descriptorRequest = limitOrRequest && typeof limitOrRequest === 'object';
|
|
49
57
|
const request = descriptorRequest ? limitOrRequest : options;
|
|
@@ -59,11 +67,27 @@ function createDriver(context = {}) {
|
|
|
59
67
|
contextLength,
|
|
60
68
|
options
|
|
61
69
|
),
|
|
62
|
-
searchAcrossProjects: (service,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
searchAcrossProjects: (service, requestOrKeyword, limit, options = {}) => {
|
|
71
|
+
if (requestOrKeyword && typeof requestOrKeyword === 'object' && !Array.isArray(requestOrKeyword)) {
|
|
72
|
+
const request = requestOrKeyword;
|
|
73
|
+
const query = request.query || {};
|
|
74
|
+
const requestLimit = Number.parseInt(query.limit, 10) || 35;
|
|
75
|
+
return service.searchSessionsAcrossProjects(
|
|
76
|
+
request.config || options.config || {},
|
|
77
|
+
query.keyword || query.q || query.query || '',
|
|
78
|
+
requestLimit,
|
|
79
|
+
{
|
|
80
|
+
...request,
|
|
81
|
+
...options,
|
|
82
|
+
config: request.config || options.config || {},
|
|
83
|
+
projectName: query.projectName || query.project || null,
|
|
84
|
+
limit: requestLimit,
|
|
85
|
+
contextLength: Number(query.context) || 35
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return service.searchSessionsAcrossProjects(options.config || {}, requestOrKeyword, limit, options);
|
|
90
|
+
}
|
|
67
91
|
},
|
|
68
92
|
onSuccess: operation => {
|
|
69
93
|
if (['delete', 'fork', 'saveSessionOrder'].includes(operation)) {
|
|
@@ -339,11 +339,67 @@ function parseSession(filePath) {
|
|
|
339
339
|
};
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
+
/**
|
|
343
|
+
* Read only the beginning of a rollout for list views. Codex rollouts can be
|
|
344
|
+
* hundreds of megabytes; this helper must never call readJSONL/parseSession.
|
|
345
|
+
*/
|
|
346
|
+
function parseSessionSummary(filePath, { sessionId = null, mtimeMs = null } = {}) {
|
|
347
|
+
let fd;
|
|
348
|
+
try {
|
|
349
|
+
fd = fs.openSync(filePath, 'r');
|
|
350
|
+
const buffer = Buffer.alloc(64 * 1024);
|
|
351
|
+
const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
|
|
352
|
+
const lines = buffer.subarray(0, bytesRead).toString('utf8')
|
|
353
|
+
.split(/\r?\n/)
|
|
354
|
+
.filter(line => line.trim())
|
|
355
|
+
.map(line => {
|
|
356
|
+
try { return JSON.parse(line); } catch (_) { return null; }
|
|
357
|
+
})
|
|
358
|
+
.filter(Boolean);
|
|
359
|
+
const meta = extractSessionMeta(lines);
|
|
360
|
+
if (!meta) return null;
|
|
361
|
+
const firstUser = lines.find(line => {
|
|
362
|
+
const payload = line.type === 'response_item' ? line.payload : null;
|
|
363
|
+
return payload?.type === 'message' && payload.role === 'user' && Array.isArray(payload.content);
|
|
364
|
+
});
|
|
365
|
+
const content = firstUser?.payload?.content
|
|
366
|
+
?.map(block => block?.text || block?.input_text || '')
|
|
367
|
+
.join('\n')
|
|
368
|
+
.trim() || null;
|
|
369
|
+
const nativeSessionId = typeof meta.sessionId === 'string' && meta.sessionId.trim()
|
|
370
|
+
? meta.sessionId.trim()
|
|
371
|
+
: sessionId;
|
|
372
|
+
const projectName = extractCodexProjectName(meta);
|
|
373
|
+
return {
|
|
374
|
+
session: {
|
|
375
|
+
sessionId: nativeSessionId,
|
|
376
|
+
projectName,
|
|
377
|
+
projectDisplayName: projectName,
|
|
378
|
+
projectFullPath: typeof meta.cwd === 'string' && meta.cwd.trim() ? meta.cwd.trim() : null,
|
|
379
|
+
firstMessage: content,
|
|
380
|
+
gitBranch: meta.git?.branch || null,
|
|
381
|
+
provider: meta.provider || null,
|
|
382
|
+
model: null,
|
|
383
|
+
startedAt: meta.timestamp ? new Date(meta.timestamp).getTime() : null,
|
|
384
|
+
updatedAt: mtimeMs,
|
|
385
|
+
extraJson: JSON.stringify({ cwd: meta.cwd || null })
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
} catch (_) {
|
|
389
|
+
return null;
|
|
390
|
+
} finally {
|
|
391
|
+
if (fd !== undefined) {
|
|
392
|
+
try { fs.closeSync(fd); } catch (_) {}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
342
397
|
module.exports = {
|
|
343
398
|
readJSONL,
|
|
344
399
|
extractSessionMeta,
|
|
345
400
|
extractMessages,
|
|
346
401
|
extractTokenUsage,
|
|
347
402
|
parseSession,
|
|
348
|
-
parseSessionMeta
|
|
403
|
+
parseSessionMeta,
|
|
404
|
+
parseSessionSummary
|
|
349
405
|
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { getCodexDir } = require('./config');
|
|
6
|
-
const { parseSession } = require('./parser');
|
|
6
|
+
const { parseSession, parseSessionSummary } = require('./parser');
|
|
7
7
|
|
|
8
8
|
function configure({ pathContext } = {}) {
|
|
9
9
|
require('./config').configure?.({ pathContext });
|
|
@@ -90,6 +90,12 @@ async function inventory({ projectsDir } = {}) {
|
|
|
90
90
|
}));
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
async function summarize(descriptor) {
|
|
94
|
+
const parsed = parseSessionSummary(descriptor.filePath, descriptor);
|
|
95
|
+
if (!parsed?.session) return { sessionId: descriptor.sessionId, projectName: descriptor.projectHint || 'unknown' };
|
|
96
|
+
return parsed;
|
|
97
|
+
}
|
|
98
|
+
|
|
93
99
|
/**
|
|
94
100
|
* Parse a Codex session file.
|
|
95
101
|
*/
|
|
@@ -169,4 +175,4 @@ async function parse(descriptor) {
|
|
|
169
175
|
return { session, messages: normalizedMessages };
|
|
170
176
|
}
|
|
171
177
|
|
|
172
|
-
module.exports = { configure, inventory, parse, extractCodexProjectName };
|
|
178
|
+
module.exports = { configure, inventory, summarize, parse, extractCodexProjectName };
|
|
@@ -250,7 +250,7 @@ function normalizeSession(codexSession) {
|
|
|
250
250
|
*/
|
|
251
251
|
async function getProjects(options = {}) {
|
|
252
252
|
if (!fs.existsSync(getSessionsDir())) return [];
|
|
253
|
-
const projects = await getSessionHistoryIndex({ ...options, projectsDir: getSessionsDir() }).
|
|
253
|
+
const projects = await getSessionHistoryIndex({ ...options, projectsDir: getSessionsDir() }).getAllProjects('codex', options);
|
|
254
254
|
const savedOrder = getProjectOrder();
|
|
255
255
|
if (savedOrder.length === 0) return projects;
|
|
256
256
|
|
|
@@ -260,6 +260,11 @@ async function getProjects(options = {}) {
|
|
|
260
260
|
return [...ordered, ...byName.values()];
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
async function getProjectsPage(options = {}) {
|
|
264
|
+
return getSessionHistoryIndex({ ...options, projectsDir: getSessionsDir() })
|
|
265
|
+
.listProjectsPage('codex', options);
|
|
266
|
+
}
|
|
267
|
+
|
|
263
268
|
/**
|
|
264
269
|
* 根据项目名获取会话列表(归一化格式)
|
|
265
270
|
* @param {string} projectName - 项目名称
|
|
@@ -284,6 +289,21 @@ async function getSessionsByProject(projectName, options = {}) {
|
|
|
284
289
|
return [...byId.values(), ...ordered];
|
|
285
290
|
}
|
|
286
291
|
|
|
292
|
+
async function getSessionsPage(projectName, options = {}) {
|
|
293
|
+
const payload = await getSessionHistoryIndex().listSessionsPage('codex', projectName, options);
|
|
294
|
+
const forkRelations = require('../claude/sessions-implementation').getForkRelations();
|
|
295
|
+
const aliases = require('../../../server/services/alias').loadAliases();
|
|
296
|
+
return {
|
|
297
|
+
...payload,
|
|
298
|
+
sessions: payload.sessions.map(session => ({
|
|
299
|
+
...session,
|
|
300
|
+
mtime: new Date(session.mtime).toISOString(),
|
|
301
|
+
forkedFrom: forkRelations[session.sessionId] || null,
|
|
302
|
+
alias: aliases[session.sessionId] || null
|
|
303
|
+
}))
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
287
307
|
/**
|
|
288
308
|
* 根据 sessionId 获取会话(归一化格式)
|
|
289
309
|
* @param {string} sessionId - 会话 ID
|
|
@@ -302,8 +322,8 @@ async function getSessionById(sessionId) {
|
|
|
302
322
|
* @param {string} keyword - 搜索关键词
|
|
303
323
|
* @returns {Array} 搜索结果
|
|
304
324
|
*/
|
|
305
|
-
async function searchSessions(keyword) {
|
|
306
|
-
return getSessionHistoryIndex().searchSessions('codex', keyword);
|
|
325
|
+
async function searchSessions(keyword, options = {}) {
|
|
326
|
+
return getSessionHistoryIndex().searchSessions('codex', keyword, options);
|
|
307
327
|
}
|
|
308
328
|
|
|
309
329
|
/**
|
|
@@ -825,7 +845,9 @@ module.exports = {
|
|
|
825
845
|
scanSessionFiles,
|
|
826
846
|
getAllSessions,
|
|
827
847
|
getProjects,
|
|
848
|
+
getProjectsPage,
|
|
828
849
|
getSessionsByProject,
|
|
850
|
+
getSessionsPage,
|
|
829
851
|
getSessionById,
|
|
830
852
|
searchSessions,
|
|
831
853
|
normalizeSession,
|
|
@@ -11,9 +11,10 @@ function createDriver(context = {}) {
|
|
|
11
11
|
servicePath: './codex/sessions-implementation',
|
|
12
12
|
localServicePath: '../platforms/drivers/codex/sessions-implementation',
|
|
13
13
|
adapterLocalPath: '../platforms/drivers/codex/session-history-adapter',
|
|
14
|
-
adapterMethods: { inventory: 'inventory', parse: 'parse' },
|
|
14
|
+
adapterMethods: { inventory: 'inventory', summarize: 'summarize', parse: 'parse' },
|
|
15
15
|
methods: {
|
|
16
16
|
listSessions: 'getSessionsByProject',
|
|
17
|
+
listSessionsPage: 'getSessionsPage',
|
|
17
18
|
recent: 'getRecentSessions',
|
|
18
19
|
search: 'searchSessions',
|
|
19
20
|
getSessionById: 'getSessionById',
|
|
@@ -25,6 +26,22 @@ function createDriver(context = {}) {
|
|
|
25
26
|
status: 'getSessionStatus',
|
|
26
27
|
messages: 'getSessionMessages'
|
|
27
28
|
},
|
|
29
|
+
customMethods: {
|
|
30
|
+
searchAcrossProjects: (service, requestOrKeyword, limit, options = {}) => {
|
|
31
|
+
if (requestOrKeyword && typeof requestOrKeyword === 'object' && !Array.isArray(requestOrKeyword)) {
|
|
32
|
+
const request = requestOrKeyword;
|
|
33
|
+
const query = request.query || {};
|
|
34
|
+
return service.searchSessions(query.keyword || query.q || '', {
|
|
35
|
+
...request,
|
|
36
|
+
...options,
|
|
37
|
+
projectName: query.projectName || query.project || null,
|
|
38
|
+
limit: Number.parseInt(query.limit, 10) || 35,
|
|
39
|
+
contextLength: Number(query.context) || 35
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return service.searchSessions(requestOrKeyword, { ...options, limit });
|
|
43
|
+
}
|
|
44
|
+
},
|
|
28
45
|
onSuccess: operation => {
|
|
29
46
|
if (['delete', 'fork', 'saveSessionOrder'].includes(operation)) {
|
|
30
47
|
context.sessionHistoryIndex?.invalidateSource('codex');
|