@cloudcli-ai/cloudcli 1.37.0 → 1.37.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/assets/abnfDiagram-N423BO3Z-C4UAoHJb.js +1 -0
- package/dist/assets/arc-B8jF-ja9.js +1 -0
- package/dist/assets/architectureDiagram-T3A2C74G-C_Wur_CU.js +36 -0
- package/dist/assets/blockDiagram-VBNYF7ZC-Cq4P1i8t.js +132 -0
- package/dist/assets/c4Diagram-5PPSVZJV-C1wG4eUj.js +10 -0
- package/dist/assets/channel-_falYbU1.js +1 -0
- package/dist/assets/chunk-2GRJ4B5K-B8fnBG6E.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-WdfPooPJ.js +1 -0
- package/dist/assets/chunk-5RXB4S5H-DhZJuG2E.js +231 -0
- package/dist/assets/chunk-5VM5RSS4-B0rjslal.js +15 -0
- package/dist/assets/chunk-6Q2QTUOP-CG2Oj4jL.js +88 -0
- package/dist/assets/chunk-GF5L2VYU-rrl2lRxY.js +206 -0
- package/dist/assets/chunk-JWPE2WC7-CjVbrVMA.js +1 -0
- package/dist/assets/chunk-KBJHAD2P-CRzKgU8S.js +1 -0
- package/dist/assets/chunk-RYQCIY6F-DBz4GTZF.js +1 -0
- package/dist/assets/chunk-XXDRQBXY-wHxalsXk.js +1 -0
- package/dist/assets/classDiagram-JCYQIIEL-Dn-3CfpR.js +1 -0
- package/dist/assets/classDiagram-v2-OCEON4UE-Dn-3CfpR.js +1 -0
- package/dist/assets/cose-bilkent-JH36ORCC-s1hygY8e.js +1 -0
- package/dist/assets/cynefin-VYW2F7L2-C4LGV15i.js +178 -0
- package/dist/assets/cynefinDiagram-MW4NZA55-C5uhu7B3.js +62 -0
- package/dist/assets/cytoscape.esm-C10L9Icf.js +331 -0
- package/dist/assets/dagre-VZM6K2ZE-B-OBTewm.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-7IWD3JNH-BjGKQxih.js +30 -0
- package/dist/assets/diagram-B4RE2ZJO-BMKz1X2L.js +3 -0
- package/dist/assets/diagram-LBJQPF4R-DNh-GhKC.js +24 -0
- package/dist/assets/diagram-Q27KOJAE-BaTG9jPT.js +24 -0
- package/dist/assets/diagram-UB23O5K3-BUwo2OvJ.js +41 -0
- package/dist/assets/ebnfDiagram-BXEA7PRR-DAZgfzX2.js +1 -0
- package/dist/assets/erDiagram-JOGREHBK-1w1CcGEb.js +85 -0
- package/dist/assets/flowDiagram-UKHOOZJN-D4EII8Jj.js +156 -0
- package/dist/assets/ganttDiagram-PKOTCBZU-ChvJZAgB.js +292 -0
- package/dist/assets/gitGraphDiagram-DS77QQ5N-DtOn41S8.js +106 -0
- package/dist/assets/graph-Cx-EqisW.js +1 -0
- package/dist/assets/index-CzPLBvXT.css +32 -0
- package/dist/assets/index-DFkBBuSA.js +1549 -0
- package/dist/assets/infoDiagram-6WML65LV-DN_PVEJZ.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/ishikawaDiagram-WSZJBQD7-DbkF1kXb.js +70 -0
- package/dist/assets/journeyDiagram-NVQOT4AX-pJwmhddE.js +139 -0
- package/dist/assets/kanban-definition-27J2QSJJ-PPAPgNp5.js +89 -0
- package/dist/assets/layout-D6BstSzF.js +1 -0
- package/dist/assets/linear-iPR-J9f7.js +1 -0
- package/dist/assets/map-CV9uif0g.js +1 -0
- package/dist/assets/mermaid.core-BEUL86Z6.js +312 -0
- package/dist/assets/mindmap-definition-FAOFIHXS-BvZoBib7.js +96 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pegDiagram-VL7TDLO6-DhYreaUd.js +1 -0
- package/dist/assets/pieDiagram-7S7Q4E2Y-CrOHlyD0.js +39 -0
- package/dist/assets/quadrantDiagram-CIZ2JOQS-D4g5yH3t.js +7 -0
- package/dist/assets/railroadDiagram-AXF67PYL-Dj39lHlf.js +1 -0
- package/dist/assets/requirementDiagram-LRYGKXZP-1AS4Zgm_.js +84 -0
- package/dist/assets/sankeyDiagram-W5VNT64P-CKlMn19P.js +40 -0
- package/dist/assets/sequenceDiagram-SI44F4Z6-B_j3qyvo.js +162 -0
- package/dist/assets/sizeCapture-X5ZJPWSS-Bp2Op16v.js +1 -0
- package/dist/assets/stateDiagram-OKZ733FA-C0cN6DoY.js +1 -0
- package/dist/assets/stateDiagram-v2-UEYNNEHI-BvBxUUrZ.js +1 -0
- package/dist/assets/swimlanes-SLNWSIFB-BEvXOV9b.js +2 -0
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-CsE-x4YK.js +8 -0
- package/dist/assets/timeline-definition-Z64GVDOM-zdJQinvZ.js +120 -0
- package/dist/assets/vennDiagram-T6HMQDX7-DuF1uZMY.js +34 -0
- package/dist/assets/wardleyDiagram-T6FBY63Y-B9AlLQup.js +78 -0
- package/dist/assets/xychartDiagram-ELKLHX3M-DTJX48Qg.js +7 -0
- package/dist/index.html +2 -2
- package/dist-server/server/index.js +1 -2
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/agent/agent.module.js +2 -1
- package/dist-server/server/modules/agent/agent.module.js.map +1 -1
- package/dist-server/server/modules/auth/auth.middleware.js +1 -1
- package/dist-server/server/modules/auth/auth.middleware.js.map +1 -1
- package/dist-server/server/modules/browser-use/browser-use-runtime.js +10 -0
- package/dist-server/server/modules/browser-use/browser-use-runtime.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.service.js +3 -6
- package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -1
- package/dist-server/server/modules/browser-use/index.js +2 -0
- package/dist-server/server/modules/browser-use/index.js.map +1 -1
- package/dist-server/server/modules/cli/cli.js +9 -3
- package/dist-server/server/modules/cli/cli.js.map +1 -1
- package/dist-server/server/modules/cli/cli.module.js +0 -1
- package/dist-server/server/modules/cli/cli.module.js.map +1 -1
- package/dist-server/server/modules/cli/cli.service.js +0 -18
- package/dist-server/server/modules/cli/cli.service.js.map +1 -1
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js +154 -0
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js.map +1 -0
- package/dist-server/server/modules/cli/tests/cli.service.test.js +0 -3
- package/dist-server/server/modules/cli/tests/cli.service.test.js.map +1 -1
- package/dist-server/server/modules/commands/commands.routes.js +3 -3
- package/dist-server/server/modules/commands/commands.routes.js.map +1 -1
- package/dist-server/server/modules/commands/tests/commands.test.js +2 -7
- package/dist-server/server/modules/commands/tests/commands.test.js.map +1 -1
- package/dist-server/server/modules/database/index.js +2 -0
- package/dist-server/server/modules/database/index.js.map +1 -1
- package/dist-server/server/modules/database/migrations.js +18 -1
- package/dist-server/server/modules/database/migrations.js.map +1 -1
- package/dist-server/server/modules/database/repositories/provider-models.js +112 -0
- package/dist-server/server/modules/database/repositories/provider-models.js.map +1 -0
- package/dist-server/server/modules/database/repositories/sessions.db.js +63 -7
- package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
- package/dist-server/server/modules/database/schema.js +28 -3
- package/dist-server/server/modules/database/schema.js.map +1 -1
- package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +102 -0
- package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +2 -1
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -1
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +22 -0
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
- package/dist-server/server/modules/file-tree/file-tree.module.js +5 -1
- package/dist-server/server/modules/file-tree/file-tree.module.js.map +1 -1
- package/dist-server/server/modules/file-tree/file-tree.service.js +60 -20
- package/dist-server/server/modules/file-tree/file-tree.service.js.map +1 -1
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js +143 -16
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -1
- package/dist-server/server/modules/git/git-branch.service.js +15 -0
- package/dist-server/server/modules/git/git-branch.service.js.map +1 -0
- package/dist-server/server/modules/git/git.routes.js +15 -7
- package/dist-server/server/modules/git/git.routes.js.map +1 -1
- package/dist-server/server/modules/git/tests/git-branch.service.test.js +52 -0
- package/dist-server/server/modules/git/tests/git-branch.service.test.js.map +1 -0
- package/dist-server/server/modules/git/tests/git-init.routes.test.js +84 -0
- package/dist-server/server/modules/git/tests/git-init.routes.test.js.map +1 -1
- package/dist-server/server/modules/notifications/index.js +3 -1
- package/dist-server/server/modules/notifications/index.js.map +1 -1
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +22 -1
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +46 -14
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +259 -60
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +58 -71
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +1 -54
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +7 -7
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js +32 -669
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +105 -328
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +0 -12
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +144 -31
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-models.service.js +167 -199
- package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-runtime.service.js +2 -2
- package/dist-server/server/modules/providers/services/provider-runtime.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js +75 -11
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions.service.js +62 -3
- package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js +53 -17
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-models.test.js +15 -126
- package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +5 -5
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js +175 -292
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js +2 -6
- package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +1 -0
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider.routes.test.js +161 -0
- package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/sessions.service.test.js +87 -0
- package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +5 -1
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
- package/dist-server/server/shared/utils.js +8 -0
- package/dist-server/server/shared/utils.js.map +1 -1
- package/package.json +8 -4
- package/scripts/promote-dist-server.mjs +50 -0
- package/server/index.ts +1 -2
- package/server/modules/agent/agent.module.ts +2 -1
- package/server/modules/auth/auth.middleware.ts +2 -2
- package/server/modules/browser-use/browser-use-runtime.ts +10 -0
- package/server/modules/browser-use/browser-use.service.ts +5 -8
- package/server/modules/browser-use/index.ts +3 -0
- package/server/modules/cli/cli.module.ts +0 -1
- package/server/modules/cli/cli.service.ts +0 -20
- package/server/modules/cli/cli.ts +9 -3
- package/server/modules/cli/tests/cli-environment-bootstrap.test.ts +186 -0
- package/server/modules/cli/tests/cli.service.test.ts +0 -3
- package/server/modules/commands/commands.routes.ts +3 -3
- package/server/modules/commands/tests/commands.test.ts +2 -7
- package/server/modules/database/index.ts +2 -0
- package/server/modules/database/migrations.ts +20 -0
- package/server/modules/database/repositories/provider-models.ts +168 -0
- package/server/modules/database/repositories/sessions.db.ts +85 -8
- package/server/modules/database/schema.ts +29 -3
- package/server/modules/database/tests/provider-models.db.integration.test.ts +125 -0
- package/server/modules/database/tests/sessions-provider-mapping.test.ts +2 -1
- package/server/modules/database/tests/sessions.db.integration.test.ts +32 -0
- package/server/modules/file-tree/file-tree.module.ts +5 -1
- package/server/modules/file-tree/file-tree.service.ts +81 -22
- package/server/modules/file-tree/tests/file-tree.service.test.ts +167 -16
- package/server/modules/git/git-branch.service.ts +43 -0
- package/server/modules/git/git.routes.ts +16 -9
- package/server/modules/git/tests/git-branch.service.test.ts +60 -0
- package/server/modules/git/tests/git-init.routes.test.ts +83 -0
- package/server/modules/notifications/index.ts +2 -0
- package/server/modules/notifications/services/notification-orchestrator.service.js +24 -1
- package/server/modules/providers/list/claude/claude-models.provider.ts +46 -14
- package/server/modules/providers/list/claude/claude-runtime.provider.js +280 -67
- package/server/modules/providers/list/codex/codex-models.provider.ts +58 -99
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +1 -60
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +7 -7
- package/server/modules/providers/list/cursor/cursor-models.provider.ts +32 -710
- package/server/modules/providers/list/opencode/opencode-models.provider.ts +105 -413
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +0 -13
- package/server/modules/providers/provider.routes.ts +190 -33
- package/server/modules/providers/services/provider-models.service.ts +223 -288
- package/server/modules/providers/services/provider-runtime.service.ts +2 -3
- package/server/modules/providers/services/session-conversations-search.service.ts +106 -11
- package/server/modules/providers/services/sessions.service.ts +87 -3
- package/server/modules/providers/tests/codex-sessions.test.ts +57 -18
- package/server/modules/providers/tests/opencode-models.test.ts +34 -135
- package/server/modules/providers/tests/opencode-sessions.test.ts +5 -5
- package/server/modules/providers/tests/provider-models.service.test.ts +353 -450
- package/server/modules/providers/tests/provider-runtime.service.test.ts +2 -6
- package/server/modules/providers/tests/provider-token-usage.service.test.ts +1 -0
- package/server/modules/providers/tests/provider.routes.test.ts +244 -0
- package/server/modules/providers/tests/sessions.service.test.ts +127 -0
- package/server/modules/websocket/services/chat-websocket.service.ts +5 -1
- package/server/shared/interfaces.ts +4 -6
- package/server/shared/types.ts +29 -18
- package/server/shared/utils.ts +10 -0
- package/server/tsconfig.json +6 -2
- package/README.de.md +0 -257
- package/README.ja.md +0 -249
- package/README.ko.md +0 -249
- package/README.md +0 -269
- package/README.ru.md +0 -257
- package/README.tr.md +0 -258
- package/README.zh-CN.md +0 -249
- package/README.zh-TW.md +0 -249
- package/dist/assets/index-DSlds633.css +0 -32
- package/dist/assets/index-eGqPeIEu.js +0 -1531
- package/dist-server/server/constants/config.js +0 -6
- package/dist-server/server/constants/config.js.map +0 -1
- package/server/constants/config.ts +0 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import fsSync
|
|
1
|
+
import fsSync from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import readline from 'node:readline';
|
|
4
4
|
|
|
@@ -38,6 +38,15 @@ type ProjectConversationResult = {
|
|
|
38
38
|
sessions: SessionConversationResult[];
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
type SessionTitleSearchResult = {
|
|
42
|
+
sessionId: string;
|
|
43
|
+
provider: string;
|
|
44
|
+
projectId: string | null;
|
|
45
|
+
projectDisplayName: string;
|
|
46
|
+
sessionTitle: string;
|
|
47
|
+
lastActivity: string | null;
|
|
48
|
+
};
|
|
49
|
+
|
|
41
50
|
export type SessionConversationSearchProgressUpdate = {
|
|
42
51
|
projectResult: ProjectConversationResult | null;
|
|
43
52
|
totalMatches: number;
|
|
@@ -49,6 +58,7 @@ type SearchSessionConversationsInput = {
|
|
|
49
58
|
query: string;
|
|
50
59
|
limit: number;
|
|
51
60
|
signal?: AbortSignal;
|
|
61
|
+
onTitleResults?: (results: SessionTitleSearchResult[]) => void;
|
|
52
62
|
onProgress?: (update: SessionConversationSearchProgressUpdate) => void;
|
|
53
63
|
};
|
|
54
64
|
|
|
@@ -156,6 +166,67 @@ function toSummaryText(customName: string | null, fallback: string | null | unde
|
|
|
156
166
|
return trimmedFallback.length > 50 ? `${trimmedFallback.slice(0, 50)}...` : trimmedFallback;
|
|
157
167
|
}
|
|
158
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Finds visible sessions whose displayed title contains the query. Title
|
|
171
|
+
* matches are resolved from the database before transcript scanning so the UI
|
|
172
|
+
* can always present them first, including sessions without a transcript yet.
|
|
173
|
+
*/
|
|
174
|
+
function findSessionTitleResults(
|
|
175
|
+
sessions: SessionRepositoryRow[],
|
|
176
|
+
query: string,
|
|
177
|
+
limit: number,
|
|
178
|
+
): SessionTitleSearchResult[] {
|
|
179
|
+
const normalizedQuery = query.toLocaleLowerCase().replace(/\s+/g, ' ');
|
|
180
|
+
const projectCache = new Map<string, ReturnType<typeof projectsDb.getProjectPath>>();
|
|
181
|
+
|
|
182
|
+
return sessions
|
|
183
|
+
.flatMap((session) => {
|
|
184
|
+
const sessionTitle = toSummaryText(session.custom_name, session.session_id, session.session_id);
|
|
185
|
+
const normalizedTitle = sessionTitle.toLocaleLowerCase().replace(/\s+/g, ' ');
|
|
186
|
+
const matchIndex = normalizedTitle.indexOf(normalizedQuery);
|
|
187
|
+
if (matchIndex === -1) {
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const projectPath = typeof session.project_path === 'string' && session.project_path.trim()
|
|
192
|
+
? session.project_path.trim()
|
|
193
|
+
: null;
|
|
194
|
+
const projectKey = projectPath ?? UNKNOWN_PROJECT_KEY;
|
|
195
|
+
if (!projectCache.has(projectKey)) {
|
|
196
|
+
projectCache.set(
|
|
197
|
+
projectKey,
|
|
198
|
+
projectPath ? projectsDb.getProjectPath(projectPath) : null,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const project = projectCache.get(projectKey) ?? null;
|
|
203
|
+
if (project?.isArchived) {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return [{
|
|
208
|
+
sessionId: session.session_id,
|
|
209
|
+
provider: session.provider,
|
|
210
|
+
projectId: project?.project_id ?? null,
|
|
211
|
+
projectDisplayName: projectPath
|
|
212
|
+
? (project?.custom_project_name?.trim() || path.basename(projectPath) || projectPath)
|
|
213
|
+
: 'Unknown Project',
|
|
214
|
+
sessionTitle,
|
|
215
|
+
lastActivity: session.updated_at || session.created_at || null,
|
|
216
|
+
matchIndex,
|
|
217
|
+
}];
|
|
218
|
+
})
|
|
219
|
+
.sort((left, right) => {
|
|
220
|
+
if (left.matchIndex !== right.matchIndex) {
|
|
221
|
+
return left.matchIndex - right.matchIndex;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return new Date(right.lastActivity ?? 0).getTime() - new Date(left.lastActivity ?? 0).getTime();
|
|
225
|
+
})
|
|
226
|
+
.slice(0, limit)
|
|
227
|
+
.map(({ matchIndex: _matchIndex, ...result }) => result);
|
|
228
|
+
}
|
|
229
|
+
|
|
159
230
|
function isInternalContent(content: string): boolean {
|
|
160
231
|
return INTERNAL_CONTENT_PREFIXES.some((prefix) => content.startsWith(prefix));
|
|
161
232
|
}
|
|
@@ -787,11 +858,18 @@ async function parseClaudeSessionMatches(
|
|
|
787
858
|
? matchedSessionsForFile
|
|
788
859
|
: [session];
|
|
789
860
|
|
|
790
|
-
|
|
861
|
+
// Transcript lines are tagged with the provider session id (e.g. Claude's own
|
|
862
|
+
// conversation UUID), which is not necessarily the app-facing `session_id`.
|
|
863
|
+
// Match and attribute by the provider id, but map results back to the
|
|
864
|
+
// internal `session_id` the rest of the app uses to identify sessions.
|
|
865
|
+
const providerToInternalId = new Map<string, string>();
|
|
791
866
|
const customNameBySessionId = new Map<string, string | null>();
|
|
792
867
|
for (const candidate of targetSessions) {
|
|
793
|
-
|
|
868
|
+
const providerId = candidate.provider_session_id || candidate.session_id;
|
|
869
|
+
providerToInternalId.set(providerId, candidate.session_id);
|
|
870
|
+
customNameBySessionId.set(providerId, candidate.custom_name ?? null);
|
|
794
871
|
}
|
|
872
|
+
const targetSessionIds = new Set(providerToInternalId.keys());
|
|
795
873
|
|
|
796
874
|
type ClaudeSessionSearchState = {
|
|
797
875
|
matches: SessionConversationMatch[];
|
|
@@ -912,8 +990,9 @@ async function parseClaudeSessionMatches(
|
|
|
912
990
|
continue;
|
|
913
991
|
}
|
|
914
992
|
|
|
915
|
-
|
|
916
|
-
|
|
993
|
+
const internalSessionId = providerToInternalId.get(sessionId) ?? sessionId;
|
|
994
|
+
fileResults.set(internalSessionId, {
|
|
995
|
+
sessionId: internalSessionId,
|
|
917
996
|
provider: 'claude',
|
|
918
997
|
sessionSummary: toSummaryText(
|
|
919
998
|
customNameBySessionId.get(sessionId) ?? null,
|
|
@@ -1063,28 +1142,42 @@ async function parseSessionMatches(
|
|
|
1063
1142
|
return null;
|
|
1064
1143
|
}
|
|
1065
1144
|
|
|
1145
|
+
/**
|
|
1146
|
+
* Searches session titles and provider transcripts for the provider search
|
|
1147
|
+
* service and its route-level integration tests.
|
|
1148
|
+
*/
|
|
1066
1149
|
export async function searchConversations(
|
|
1067
1150
|
query: string,
|
|
1068
1151
|
limit = 50,
|
|
1069
1152
|
onProjectResult: ((update: SessionConversationSearchProgressUpdate) => void) | null = null,
|
|
1070
1153
|
signal: AbortSignal | null = null,
|
|
1071
|
-
|
|
1154
|
+
onTitleResults: ((results: SessionTitleSearchResult[]) => void) | null = null,
|
|
1155
|
+
): Promise<{
|
|
1156
|
+
results: ProjectConversationResult[];
|
|
1157
|
+
titleResults: SessionTitleSearchResult[];
|
|
1158
|
+
totalMatches: number;
|
|
1159
|
+
query: string;
|
|
1160
|
+
}> {
|
|
1072
1161
|
const safeQuery = typeof query === 'string' ? query.trim() : '';
|
|
1073
1162
|
const safeLimit = Math.max(1, Math.min(Number.isFinite(limit) ? limit : 50, 200));
|
|
1074
1163
|
const words = safeQuery.toLowerCase().split(/\s+/).filter((word) => word.length > 0);
|
|
1075
1164
|
|
|
1076
1165
|
if (words.length === 0) {
|
|
1077
|
-
return { results: [], totalMatches: 0, query: safeQuery };
|
|
1166
|
+
return { results: [], titleResults: [], totalMatches: 0, query: safeQuery };
|
|
1078
1167
|
}
|
|
1079
1168
|
|
|
1080
1169
|
const isAborted = () => signal?.aborted === true;
|
|
1081
1170
|
if (isAborted()) {
|
|
1082
|
-
return { results: [], totalMatches: 0, query: safeQuery };
|
|
1171
|
+
return { results: [], titleResults: [], totalMatches: 0, query: safeQuery };
|
|
1083
1172
|
}
|
|
1084
1173
|
|
|
1085
|
-
const
|
|
1174
|
+
const activeSessions = sessionsDb.getAllSessions();
|
|
1175
|
+
const titleResults = findSessionTitleResults(activeSessions, safeQuery, safeLimit);
|
|
1176
|
+
onTitleResults?.(titleResults);
|
|
1177
|
+
|
|
1178
|
+
const searchableSessions = normalizeSearchableSessions(activeSessions);
|
|
1086
1179
|
if (searchableSessions.length === 0) {
|
|
1087
|
-
return { results: [], totalMatches: 0, query: safeQuery };
|
|
1180
|
+
return { results: [], titleResults, totalMatches: 0, query: safeQuery };
|
|
1088
1181
|
}
|
|
1089
1182
|
|
|
1090
1183
|
const sessionsByPathKey = new Map<string, SearchableSessionRow[]>();
|
|
@@ -1115,7 +1208,7 @@ export async function searchConversations(
|
|
|
1115
1208
|
signal ?? undefined,
|
|
1116
1209
|
);
|
|
1117
1210
|
if (isAborted() || matchedFileKeys.size === 0) {
|
|
1118
|
-
return { results: [], totalMatches: 0, query: safeQuery };
|
|
1211
|
+
return { results: [], titleResults, totalMatches: 0, query: safeQuery };
|
|
1119
1212
|
}
|
|
1120
1213
|
|
|
1121
1214
|
const matchedSessionKeys = new Set<string>();
|
|
@@ -1199,6 +1292,7 @@ export async function searchConversations(
|
|
|
1199
1292
|
|
|
1200
1293
|
return {
|
|
1201
1294
|
results,
|
|
1295
|
+
titleResults,
|
|
1202
1296
|
totalMatches: runtime.totalMatches,
|
|
1203
1297
|
query: safeQuery,
|
|
1204
1298
|
};
|
|
@@ -1221,6 +1315,7 @@ export const sessionConversationsSearchService = {
|
|
|
1221
1315
|
input.limit,
|
|
1222
1316
|
input.onProgress ?? null,
|
|
1223
1317
|
input.signal ?? null,
|
|
1318
|
+
input.onTitleResults ?? null,
|
|
1224
1319
|
);
|
|
1225
1320
|
},
|
|
1226
1321
|
};
|
|
@@ -17,6 +17,7 @@ type CreateAppSessionResult = {
|
|
|
17
17
|
sessionId: string;
|
|
18
18
|
provider: LLMProvider;
|
|
19
19
|
projectPath: string;
|
|
20
|
+
sessionName: string;
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
type ArchivedSessionListItem = {
|
|
@@ -32,6 +33,17 @@ type ArchivedSessionListItem = {
|
|
|
32
33
|
isProjectArchived: boolean;
|
|
33
34
|
};
|
|
34
35
|
|
|
36
|
+
type RecentSessionListItem = Pick<
|
|
37
|
+
ArchivedSessionListItem,
|
|
38
|
+
'sessionId' | 'provider' | 'projectId' | 'projectDisplayName' | 'sessionTitle' | 'lastActivity'
|
|
39
|
+
>;
|
|
40
|
+
|
|
41
|
+
type RecentSessionsPage = {
|
|
42
|
+
conversations: RecentSessionListItem[];
|
|
43
|
+
total: number;
|
|
44
|
+
hasMore: boolean;
|
|
45
|
+
};
|
|
46
|
+
|
|
35
47
|
type SessionDetails = {
|
|
36
48
|
/** Canonical app-facing session id (may differ from the looked-up id when a provider-native id was given). */
|
|
37
49
|
sessionId: string;
|
|
@@ -51,6 +63,13 @@ type SessionDetails = {
|
|
|
51
63
|
} | null;
|
|
52
64
|
};
|
|
53
65
|
|
|
66
|
+
const MAX_CLOUDCLI_SESSION_NAME_WORDS = 4;
|
|
67
|
+
|
|
68
|
+
function buildCloudCliSessionName(initialMessage: string): string {
|
|
69
|
+
const words = initialMessage.trim().split(/\s+/).filter(Boolean);
|
|
70
|
+
return words.slice(0, MAX_CLOUDCLI_SESSION_NAME_WORDS).join(' ') || 'Untitled Session';
|
|
71
|
+
}
|
|
72
|
+
|
|
54
73
|
/**
|
|
55
74
|
* Removes one file if it exists.
|
|
56
75
|
*/
|
|
@@ -119,6 +138,40 @@ export const sessionsService = {
|
|
|
119
138
|
return chatRunRegistry.listRunningRuns();
|
|
120
139
|
},
|
|
121
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Returns the active conversation feed in true global activity order.
|
|
143
|
+
*/
|
|
144
|
+
listRecentSessions(limit: number, offset: number): RecentSessionsPage {
|
|
145
|
+
const page = sessionsDb.getRecentSessionsPage(limit, offset);
|
|
146
|
+
const projectCache = new Map<string, ReturnType<typeof projectsDb.getProjectPath>>();
|
|
147
|
+
const conversations = page.sessions.map((session) => {
|
|
148
|
+
const projectPath = session.project_path?.trim() ? session.project_path : null;
|
|
149
|
+
let project = null;
|
|
150
|
+
|
|
151
|
+
if (projectPath) {
|
|
152
|
+
if (!projectCache.has(projectPath)) {
|
|
153
|
+
projectCache.set(projectPath, projectsDb.getProjectPath(projectPath));
|
|
154
|
+
}
|
|
155
|
+
project = projectCache.get(projectPath) ?? null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
sessionId: session.session_id,
|
|
160
|
+
provider: session.provider as LLMProvider,
|
|
161
|
+
projectId: project?.project_id ?? null,
|
|
162
|
+
projectDisplayName: resolveProjectDisplayName(projectPath, project?.custom_project_name),
|
|
163
|
+
sessionTitle: session.custom_name?.trim() || session.session_id,
|
|
164
|
+
lastActivity: session.updated_at ?? session.created_at ?? null,
|
|
165
|
+
};
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
conversations,
|
|
170
|
+
total: page.total,
|
|
171
|
+
hasMore: offset + conversations.length < page.total,
|
|
172
|
+
};
|
|
173
|
+
},
|
|
174
|
+
|
|
122
175
|
/**
|
|
123
176
|
* Resolves the provider-native session id a runtime needs for resume.
|
|
124
177
|
*
|
|
@@ -154,9 +207,15 @@ export const sessionsService = {
|
|
|
154
207
|
* (via `POST /api/providers/sessions`) when the user starts a brand-new
|
|
155
208
|
* chat, navigates to the returned id immediately, and the id never changes
|
|
156
209
|
* for the lifetime of the conversation. The provider-native id is mapped to
|
|
157
|
-
* this row later, when the provider runtime announces it mid-run.
|
|
210
|
+
* this row later, when the provider runtime announces it mid-run. Its title
|
|
211
|
+
* comes directly from the first visible CloudCLI message and is limited to
|
|
212
|
+
* four whole words before any provider-owned storage exists.
|
|
158
213
|
*/
|
|
159
|
-
createAppSession(
|
|
214
|
+
createAppSession(
|
|
215
|
+
provider: LLMProvider,
|
|
216
|
+
projectPath: string,
|
|
217
|
+
initialMessage: string,
|
|
218
|
+
): CreateAppSessionResult {
|
|
160
219
|
const normalizedProjectPath = projectPath.trim();
|
|
161
220
|
if (!normalizedProjectPath) {
|
|
162
221
|
throw new AppError('projectPath is required.', {
|
|
@@ -166,15 +225,40 @@ export const sessionsService = {
|
|
|
166
225
|
}
|
|
167
226
|
|
|
168
227
|
const sessionId = randomUUID();
|
|
169
|
-
|
|
228
|
+
const sessionName = buildCloudCliSessionName(initialMessage);
|
|
229
|
+
sessionsDb.createAppSession(sessionId, provider, normalizedProjectPath, sessionName);
|
|
170
230
|
|
|
171
231
|
return {
|
|
172
232
|
sessionId,
|
|
173
233
|
provider,
|
|
174
234
|
projectPath: normalizedProjectPath,
|
|
235
|
+
sessionName,
|
|
175
236
|
};
|
|
176
237
|
},
|
|
177
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Resolves the provider-native id only for an explicit user copy action.
|
|
241
|
+
* Normal session payloads continue to expose only the stable app id.
|
|
242
|
+
*/
|
|
243
|
+
getProviderSessionId(sessionId: string): string {
|
|
244
|
+
const session = sessionsDb.getSessionById(sessionId);
|
|
245
|
+
if (!session) {
|
|
246
|
+
throw new AppError(`Session "${sessionId}" was not found.`, {
|
|
247
|
+
code: 'SESSION_NOT_FOUND',
|
|
248
|
+
statusCode: 404,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (!session.provider_session_id) {
|
|
253
|
+
throw new AppError('This session ID is not available yet.', {
|
|
254
|
+
code: 'PROVIDER_SESSION_ID_NOT_AVAILABLE',
|
|
255
|
+
statusCode: 409,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return session.provider_session_id;
|
|
260
|
+
},
|
|
261
|
+
|
|
178
262
|
/**
|
|
179
263
|
* Fetches persisted history by app session id.
|
|
180
264
|
*
|
|
@@ -64,24 +64,24 @@ const writeCodexTranscript = async (
|
|
|
64
64
|
return filePath;
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
-
test('Codex synchronizer
|
|
67
|
+
test('Codex synchronizer preserves the title assigned when CloudCLI creates a session', { concurrency: false }, async () => {
|
|
68
68
|
const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'codex-session-sync-app-'));
|
|
69
69
|
const workspacePath = path.join(tempRoot, 'workspace');
|
|
70
70
|
await mkdir(workspacePath, { recursive: true });
|
|
71
71
|
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
72
72
|
|
|
73
73
|
try {
|
|
74
|
-
await writeCodexTranscript(tempRoot, 'codex-app-1', workspacePath, '
|
|
74
|
+
await writeCodexTranscript(tempRoot, 'codex-app-1', workspacePath, 'Provider transcript title must not win');
|
|
75
75
|
await withIsolatedDatabase(async () => {
|
|
76
76
|
// The app allocates its own id and later maps the provider id onto it,
|
|
77
77
|
// exactly as a message sent from cloudcli does.
|
|
78
|
-
sessionsDb.createAppSession('app-1', 'codex', workspacePath);
|
|
78
|
+
sessionsDb.createAppSession('app-1', 'codex', workspacePath, 'Fix the login redirect');
|
|
79
79
|
sessionsDb.assignProviderSessionId('app-1', 'codex-app-1');
|
|
80
80
|
|
|
81
81
|
const synchronizer = new CodexSessionSynchronizer();
|
|
82
82
|
await synchronizer.synchronize();
|
|
83
83
|
|
|
84
|
-
assert.equal(sessionsDb.getSessionById('app-1')?.custom_name, 'Fix the login redirect
|
|
84
|
+
assert.equal(sessionsDb.getSessionById('app-1')?.custom_name, 'Fix the login redirect');
|
|
85
85
|
});
|
|
86
86
|
} finally {
|
|
87
87
|
restoreHomeDir();
|
|
@@ -152,7 +152,7 @@ test('Codex synchronizer leaves indexed sessions untitled when no name is availa
|
|
|
152
152
|
}
|
|
153
153
|
});
|
|
154
154
|
|
|
155
|
-
test('Codex history
|
|
155
|
+
test('Codex history preserves wrapped exec tool calls and results', { concurrency: false }, async () => {
|
|
156
156
|
const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'codex-exec-history-'));
|
|
157
157
|
const workspacePath = path.join(tempRoot, 'workspace');
|
|
158
158
|
await mkdir(workspacePath, { recursive: true });
|
|
@@ -161,15 +161,46 @@ test('Codex history renders Promise.all shell wrappers as Bash activity', { conc
|
|
|
161
161
|
try {
|
|
162
162
|
const providerSessionId = 'codex-exec-1';
|
|
163
163
|
const transcriptPath = await writeCodexTranscript(tempRoot, providerSessionId, workspacePath);
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
const wrappedCalls = [
|
|
165
|
+
{
|
|
166
|
+
callId: 'shell-command-1',
|
|
167
|
+
input: 'const cmds = ["echo one", "echo two"]; await Promise.all(cmds.map(command => tools.shell_command({ command })));',
|
|
168
|
+
expectedToolName: 'Bash',
|
|
169
|
+
expectedToolInput: JSON.stringify({ command: 'echo one\necho two' }),
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
callId: 'json-shell-command-1',
|
|
173
|
+
input: 'const r = await tools.shell_command({"command":"Get-Content -Raw README.md","workdir":"C:\\\\workspace","timeout_ms":10000}); text(r)',
|
|
174
|
+
expectedToolName: 'Bash',
|
|
175
|
+
expectedToolInput: JSON.stringify({ command: 'Get-Content -Raw README.md' }),
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
callId: 'exec-command-1',
|
|
179
|
+
input: 'await tools.exec_command({"command":"echo current"});',
|
|
180
|
+
expectedToolName: 'Bash',
|
|
181
|
+
expectedToolInput: JSON.stringify({ command: 'echo current' }),
|
|
182
|
+
},
|
|
183
|
+
{ callId: 'apply-patch-1', input: 'await tools.apply_patch("*** Begin Patch\\n*** End Patch");' },
|
|
184
|
+
{ callId: 'web-run-1', input: 'await tools.web__run({ search_query: [{ q: "Codex" }] });' },
|
|
185
|
+
{ callId: 'update-plan-1', input: 'await tools.update_plan({ plan: [] });' },
|
|
186
|
+
{ callId: 'unknown-1', input: 'await tools.unknown_wrapper({ value: true });' },
|
|
187
|
+
];
|
|
188
|
+
const transcriptLines = [
|
|
167
189
|
JSON.stringify({ type: 'session_meta', payload: { id: providerSessionId, cwd: workspacePath } }),
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
190
|
+
];
|
|
191
|
+
for (const call of wrappedCalls) {
|
|
192
|
+
transcriptLines.push(
|
|
193
|
+
JSON.stringify({
|
|
194
|
+
type: 'response_item',
|
|
195
|
+
payload: { type: 'custom_tool_call', name: 'exec', call_id: call.callId, input: call.input },
|
|
196
|
+
}),
|
|
197
|
+
JSON.stringify({
|
|
198
|
+
type: 'response_item',
|
|
199
|
+
payload: { type: 'custom_tool_call_output', call_id: call.callId, output: `result:${call.callId}` },
|
|
200
|
+
}),
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
await writeFile(transcriptPath, `${transcriptLines.join('\n')}\n`, 'utf8');
|
|
173
204
|
|
|
174
205
|
await withIsolatedDatabase(async () => {
|
|
175
206
|
sessionsDb.createAppSession('app-exec-1', 'codex', workspacePath);
|
|
@@ -179,11 +210,19 @@ test('Codex history renders Promise.all shell wrappers as Bash activity', { conc
|
|
|
179
210
|
const history = await new CodexSessionsProvider().fetchHistory('app-exec-1');
|
|
180
211
|
const toolUses = history.messages.filter((message) => message.kind === 'tool_use');
|
|
181
212
|
const toolResults = history.messages.filter((message) => message.kind === 'tool_result');
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
assert.equal(toolUses
|
|
186
|
-
assert.equal(toolResults.
|
|
213
|
+
const toolUsesById = new Map(toolUses.map((message) => [message.toolId, message]));
|
|
214
|
+
const toolResultsById = new Map(toolResults.map((message) => [message.toolId, message]));
|
|
215
|
+
|
|
216
|
+
assert.equal(toolUses.length, wrappedCalls.length);
|
|
217
|
+
assert.equal(toolResults.length, wrappedCalls.length);
|
|
218
|
+
for (const call of wrappedCalls) {
|
|
219
|
+
const toolUse = toolUsesById.get(call.callId);
|
|
220
|
+
assert.ok(toolUse);
|
|
221
|
+
assert.equal(toolUse.toolName, call.expectedToolName || 'exec');
|
|
222
|
+
assert.equal(toolUse.toolInput, call.expectedToolInput || call.input);
|
|
223
|
+
assert.equal(toolUse.toolResult?.content, `result:${call.callId}`);
|
|
224
|
+
assert.equal(toolResultsById.get(call.callId)?.content, `result:${call.callId}`);
|
|
225
|
+
}
|
|
187
226
|
});
|
|
188
227
|
} finally {
|
|
189
228
|
restoreHomeDir();
|
|
@@ -2,141 +2,40 @@ import assert from 'node:assert/strict';
|
|
|
2
2
|
import test from 'node:test';
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
parseOpenCodeModelsStdout,
|
|
8
|
-
parseOpenCodeVerboseModelsStdout,
|
|
5
|
+
OpenCodeProviderModels,
|
|
6
|
+
OPENCODE_PREDEFINED_MODELS,
|
|
9
7
|
} from '@/modules/providers/list/opencode/opencode-models.provider.js';
|
|
10
8
|
|
|
11
|
-
test('OpenCode
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
'
|
|
23
|
-
|
|
24
|
-
]);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
'
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
description: 'opencode - opencode/deepseek-v4-flash-free',
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
value: 'opencode/nemotron-3-super-free',
|
|
47
|
-
label: 'Nemotron 3 Super Free',
|
|
48
|
-
description: 'opencode - opencode/nemotron-3-super-free',
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
value: 'anthropic/claude-3-5-sonnet-20241022',
|
|
52
|
-
label: 'Claude 3.5 Sonnet (2024-10-22)',
|
|
53
|
-
description: 'anthropic - anthropic/claude-3-5-sonnet-20241022',
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
value: 'anthropic/claude-opus-4-7-fast',
|
|
57
|
-
label: 'Claude Opus 4.7 Fast',
|
|
58
|
-
description: 'anthropic - anthropic/claude-opus-4-7-fast',
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
value: 'openai/gpt-5.4-mini-fast',
|
|
62
|
-
label: 'GPT-5.4 Mini Fast',
|
|
63
|
-
description: 'openai - openai/gpt-5.4-mini-fast',
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
value: 'openai/gpt-5.5-pro',
|
|
67
|
-
label: 'GPT-5.5 Pro',
|
|
68
|
-
description: 'openai - openai/gpt-5.5-pro',
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
value: 'newprovider/alpha-v12-special-20261231',
|
|
72
|
-
label: 'Alpha V12 Special (2026-12-31)',
|
|
73
|
-
description: 'newprovider - newprovider/alpha-v12-special-20261231',
|
|
74
|
-
},
|
|
75
|
-
]);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('OpenCode models provider maps verbose model variants to effort options', () => {
|
|
79
|
-
const models = parseOpenCodeVerboseModelsStdout(`
|
|
80
|
-
opencode/deepseek-v4-flash-free
|
|
81
|
-
{
|
|
82
|
-
"id": "deepseek-v4-flash-free",
|
|
83
|
-
"providerID": "opencode",
|
|
84
|
-
"name": "DeepSeek V4 Flash Free",
|
|
85
|
-
"variants": {
|
|
86
|
-
"low": {
|
|
87
|
-
"reasoningEffort": "low"
|
|
88
|
-
},
|
|
89
|
-
"high": {
|
|
90
|
-
"reasoningEffort": "high"
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
anthropic/claude-sonnet-5
|
|
95
|
-
{
|
|
96
|
-
"id": "claude-sonnet-5",
|
|
97
|
-
"providerID": "anthropic",
|
|
98
|
-
"name": "Claude Sonnet 5",
|
|
99
|
-
"variants": {
|
|
100
|
-
"low": {
|
|
101
|
-
"effort": "low"
|
|
102
|
-
},
|
|
103
|
-
"max": {
|
|
104
|
-
"effort": "max"
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
google/model-alpha
|
|
109
|
-
{
|
|
110
|
-
"id": "model-alpha",
|
|
111
|
-
"providerID": "google",
|
|
112
|
-
"name": "Model Alpha"
|
|
113
|
-
}
|
|
114
|
-
`);
|
|
115
|
-
|
|
116
|
-
const definition = buildOpenCodeDefinitionFromVerboseModels(models);
|
|
117
|
-
|
|
118
|
-
assert.deepEqual(definition.OPTIONS, [
|
|
119
|
-
{
|
|
120
|
-
value: 'opencode/deepseek-v4-flash-free',
|
|
121
|
-
label: 'DeepSeek V4 Flash Free',
|
|
122
|
-
description: 'opencode - opencode/deepseek-v4-flash-free',
|
|
123
|
-
effort: {
|
|
124
|
-
values: [
|
|
125
|
-
{ value: 'low' },
|
|
126
|
-
{ value: 'high' },
|
|
127
|
-
],
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
value: 'anthropic/claude-sonnet-5',
|
|
132
|
-
label: 'Claude Sonnet 5',
|
|
133
|
-
description: 'anthropic - anthropic/claude-sonnet-5',
|
|
134
|
-
effort: {
|
|
135
|
-
values: [
|
|
136
|
-
{ value: 'low' },
|
|
137
|
-
{ value: 'max' },
|
|
138
|
-
],
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
]);
|
|
9
|
+
test('OpenCode exposes only the curated predefined catalog', async () => {
|
|
10
|
+
const adapter = new OpenCodeProviderModels();
|
|
11
|
+
|
|
12
|
+
assert.deepEqual(await adapter.getSupportedModels(), OPENCODE_PREDEFINED_MODELS);
|
|
13
|
+
assert.equal(
|
|
14
|
+
(await adapter.getCurrentActiveModel()).model,
|
|
15
|
+
OPENCODE_PREDEFINED_MODELS.DEFAULT,
|
|
16
|
+
);
|
|
17
|
+
// OpenCode routes by `<providerID>/<modelID>`, so every option has to carry a
|
|
18
|
+
// provider prefix that `opencode models --verbose` reports.
|
|
19
|
+
const providerIds = new Set(
|
|
20
|
+
OPENCODE_PREDEFINED_MODELS.OPTIONS.map((option) => option.value.split('/')[0]),
|
|
21
|
+
);
|
|
22
|
+
assert.deepEqual([...providerIds].sort(), ['anthropic', 'opencode', 'openai'].sort());
|
|
23
|
+
assert.equal(
|
|
24
|
+
OPENCODE_PREDEFINED_MODELS.OPTIONS.every((option) => /^[a-z0-9-]+\/.+/.test(option.value)),
|
|
25
|
+
true,
|
|
26
|
+
);
|
|
27
|
+
assert.equal(
|
|
28
|
+
new Set(OPENCODE_PREDEFINED_MODELS.OPTIONS.map((option) => option.value)).size,
|
|
29
|
+
OPENCODE_PREDEFINED_MODELS.OPTIONS.length,
|
|
30
|
+
);
|
|
31
|
+
assert.equal(OPENCODE_PREDEFINED_MODELS.DEFAULT, 'opencode/gpt-5.6-terra');
|
|
32
|
+
assert.ok(
|
|
33
|
+
OPENCODE_PREDEFINED_MODELS.OPTIONS.some((option) => option.value === 'opencode/claude-opus-5'),
|
|
34
|
+
);
|
|
35
|
+
assert.ok(
|
|
36
|
+
OPENCODE_PREDEFINED_MODELS.OPTIONS.some((option) => option.value === 'anthropic/claude-opus-5'),
|
|
37
|
+
);
|
|
38
|
+
assert.ok(
|
|
39
|
+
OPENCODE_PREDEFINED_MODELS.OPTIONS.some((option) => option.value === 'openai/gpt-5.6'),
|
|
40
|
+
);
|
|
142
41
|
});
|
|
@@ -470,22 +470,22 @@ const seedOpenCodeSession = async (
|
|
|
470
470
|
}
|
|
471
471
|
};
|
|
472
472
|
|
|
473
|
-
test('OpenCode synchronizer
|
|
473
|
+
test('OpenCode synchronizer preserves the title assigned when CloudCLI creates a session', { concurrency: false }, async () => {
|
|
474
474
|
const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'opencode-session-sync-app-'));
|
|
475
475
|
const workspacePath = path.join(tempRoot, 'workspace');
|
|
476
476
|
await mkdir(workspacePath, { recursive: true });
|
|
477
477
|
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
478
478
|
|
|
479
479
|
try {
|
|
480
|
-
//
|
|
481
|
-
//
|
|
480
|
+
// Both provider-owned values differ from the CloudCLI title so either one
|
|
481
|
+
// leaking through would change the assertion below.
|
|
482
482
|
await seedOpenCodeSession(tempRoot, workspacePath, {
|
|
483
483
|
sessionId: 'oc-app-1',
|
|
484
484
|
title: 'OpenCode generated title',
|
|
485
|
-
firstUserText: '
|
|
485
|
+
firstUserText: 'OpenCode first user prompt',
|
|
486
486
|
});
|
|
487
487
|
await withIsolatedDatabase(async () => {
|
|
488
|
-
sessionsDb.createAppSession('app-1', 'opencode', workspacePath);
|
|
488
|
+
sessionsDb.createAppSession('app-1', 'opencode', workspacePath, 'Fix the checkout crash');
|
|
489
489
|
sessionsDb.assignProviderSessionId('app-1', 'oc-app-1');
|
|
490
490
|
|
|
491
491
|
await new OpenCodeSessionSynchronizer().synchronize();
|