@jingyi0605/codingns 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/bin/codingns.mjs +15 -2
  2. package/dist/public/assets/{AdaptiveButlerPage-uFwDdN-F.js → AdaptiveButlerPage-Dw72U3hG.js} +3 -3
  3. package/dist/public/assets/{App-BZvapsi8.js → App-Dsf3ooXU.js} +3 -3
  4. package/dist/public/assets/{BootstrapPage-gHSoa4JN.js → BootstrapPage-CE0m1qSR.js} +1 -1
  5. package/dist/public/assets/ConversationPage-8wOY7SX-.js +4 -0
  6. package/dist/public/assets/{DesktopDetachPreviewPage-4eMRxiBW.js → DesktopDetachPreviewPage-Dxarr_Wf.js} +1 -1
  7. package/dist/public/assets/DesktopWindowPage-VytPwJ4c.js +2 -0
  8. package/dist/public/assets/FileContextPanel-DwFzLsOp.js +1 -0
  9. package/dist/public/assets/GitSidebar-CH6WqTrM.js +6 -0
  10. package/dist/public/assets/MobileCreateSessionSheet-DcxKM00P.js +1 -0
  11. package/dist/public/assets/{MobileTopHeaderFrame-Bwv8Ovm_.js → MobileTopHeaderFrame-C5rIKQT6.js} +1 -1
  12. package/dist/public/assets/MobileWorkspaceSwitcherHeader-CfUnHgv_.js +1 -0
  13. package/dist/public/assets/{RelayConnectEntryPage-D_4YL-YH.js → RelayConnectEntryPage-CgMvVZwa.js} +1 -1
  14. package/dist/public/assets/{ServerSettingsModal-CMSm3BZU.js → ServerSettingsModal-CFul__z1.js} +1 -1
  15. package/dist/public/assets/SessionIndexPage-B-tRhBXC.js +1 -0
  16. package/dist/public/assets/SettingsPage-C9LGxSQZ.js +1 -0
  17. package/dist/public/assets/TerminalManagerPanel-BbORd-ee.js +1 -0
  18. package/dist/public/assets/{TerminalPage-DaooFaJ4.js → TerminalPage-DWHv6mlu.js} +1 -1
  19. package/dist/public/assets/TerminalRuntimeFallbackModal-B29YxbQe.js +1 -0
  20. package/dist/public/assets/{ToolFilesPage-CGxBvYG0.js → ToolFilesPage-Dx9cv9hu.js} +1 -1
  21. package/dist/public/assets/ToolGitPage-D7H3vAia.js +1 -0
  22. package/dist/public/assets/ToolProcessesPage-PqQWxsy-.js +1 -0
  23. package/dist/public/assets/ToolsHomePage-CX05Pe_4.js +1 -0
  24. package/dist/public/assets/WorkbenchLandingPage-CchkAC75.js +1 -0
  25. package/dist/public/assets/WorkbenchLayout-pOZvEqp7.js +3 -0
  26. package/dist/public/assets/{WorkbenchModal-0tPIIhca.js → WorkbenchModal-ColqvV6a.js} +1 -1
  27. package/dist/public/assets/WorkbenchShellRoute-C0_h4lP6.js +1 -0
  28. package/dist/public/assets/WorkbenchShellRoute-RGZpA0_J.css +1 -0
  29. package/dist/public/assets/WorkspaceDebugDetailPage-Deqy2_pO.js +1 -0
  30. package/dist/public/assets/WorkspaceDetailPage-Cvf-ZdlB.js +1 -0
  31. package/dist/public/assets/WorkspaceHomePage-Dsyvqyk1.js +1 -0
  32. package/dist/public/assets/{client-runtime-manager-BZpL17fc.js → client-runtime-manager-DROQJ9v3.js} +1 -1
  33. package/dist/public/assets/{file-tree-icon-Db5LXC8h.js → file-tree-icon-Bp3Ntt7u.js} +1 -1
  34. package/dist/public/assets/index-B84Po2NA.css +1 -0
  35. package/dist/public/assets/index-C-0oeG_5.js +42 -0
  36. package/dist/public/assets/legna-code-6TqgZ4Ls.png +0 -0
  37. package/dist/public/assets/{login-direct-candidate-resolver-1mxe_Oh8.js → login-direct-candidate-resolver-DotM530R.js} +1 -1
  38. package/dist/public/assets/model-switch-api-Bh9nYslz.js +1 -0
  39. package/dist/public/assets/{preferences-service-DWnzl5a0.js → preferences-service-BG6GKG29.js} +1 -1
  40. package/dist/public/assets/{relay-entry-C5_Iay0I.js → relay-entry-pmr-c42O.js} +1 -1
  41. package/dist/public/assets/session-runtime-machine-YN84QBlr.js +21 -0
  42. package/dist/public/assets/{terminal-runtime-meta-cdtWVfCm.js → terminal-runtime-meta-8_uRZf7h.js} +1 -1
  43. package/dist/public/assets/useRegisteredDebugTemplates-DWX7LXQu.js +1 -0
  44. package/dist/public/index.html +2 -2
  45. package/dist/server/config/env.d.ts +2 -0
  46. package/dist/server/config/env.js +7 -0
  47. package/dist/server/config/env.js.map +1 -1
  48. package/dist/server/modules/model-switch/cc-switch-adapter.d.ts +7 -0
  49. package/dist/server/modules/model-switch/cc-switch-adapter.js +17 -0
  50. package/dist/server/modules/model-switch/cc-switch-adapter.js.map +1 -1
  51. package/dist/server/modules/parallel-sessions/parallel-session-controller.d.ts +4 -0
  52. package/dist/server/modules/parallel-sessions/parallel-session-controller.js +7 -0
  53. package/dist/server/modules/parallel-sessions/parallel-session-controller.js.map +1 -1
  54. package/dist/server/modules/parallel-sessions/parallel-session-group-service.d.ts +6 -1
  55. package/dist/server/modules/parallel-sessions/parallel-session-group-service.js +36 -2
  56. package/dist/server/modules/parallel-sessions/parallel-session-group-service.js.map +1 -1
  57. package/dist/server/modules/provider/opencode-model-options.d.ts +1 -0
  58. package/dist/server/modules/provider/opencode-model-options.js +54 -12
  59. package/dist/server/modules/provider/opencode-model-options.js.map +1 -1
  60. package/dist/server/modules/provider/provider-controller.d.ts +6 -1
  61. package/dist/server/modules/provider/provider-controller.js +24 -2
  62. package/dist/server/modules/provider/provider-controller.js.map +1 -1
  63. package/dist/server/modules/provider/provider-discovery-helper-client.d.ts +2 -0
  64. package/dist/server/modules/provider/provider-discovery-helper-client.js.map +1 -1
  65. package/dist/server/modules/provider/provider-discovery-helper-process.js +1 -1
  66. package/dist/server/modules/provider/provider-discovery-helper-process.js.map +1 -1
  67. package/dist/server/modules/provider/provider-discovery-runtime.js +5 -1
  68. package/dist/server/modules/provider/provider-discovery-runtime.js.map +1 -1
  69. package/dist/server/modules/sessions/claude-runtime-helper-client.d.ts +1 -0
  70. package/dist/server/modules/sessions/claude-runtime-helper-client.js +14 -0
  71. package/dist/server/modules/sessions/claude-runtime-helper-client.js.map +1 -1
  72. package/dist/server/modules/sessions/codex-app-server-helper-client.d.ts +1 -0
  73. package/dist/server/modules/sessions/codex-app-server-helper-client.js +10 -0
  74. package/dist/server/modules/sessions/codex-app-server-helper-client.js.map +1 -1
  75. package/dist/server/modules/sessions/provider-session-delete-cli.js +2 -0
  76. package/dist/server/modules/sessions/provider-session-delete-cli.js.map +1 -1
  77. package/dist/server/modules/sessions/session-controller.d.ts +7 -0
  78. package/dist/server/modules/sessions/session-controller.js +22 -0
  79. package/dist/server/modules/sessions/session-controller.js.map +1 -1
  80. package/dist/server/modules/sessions/session-history-service.d.ts +12 -2
  81. package/dist/server/modules/sessions/session-history-service.js +284 -16
  82. package/dist/server/modules/sessions/session-history-service.js.map +1 -1
  83. package/dist/server/modules/sessions/session-live-runtime-service.d.ts +15 -2
  84. package/dist/server/modules/sessions/session-live-runtime-service.js +265 -50
  85. package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
  86. package/dist/server/modules/sessions/session-message-attachment-service.js +2 -2
  87. package/dist/server/modules/sessions/session-message-attachment-service.js.map +1 -1
  88. package/dist/server/modules/sessions/session-provider-config-service.d.ts +66 -0
  89. package/dist/server/modules/sessions/session-provider-config-service.js +821 -0
  90. package/dist/server/modules/sessions/session-provider-config-service.js.map +1 -0
  91. package/dist/server/modules/sessions/session-provider-error-mapper.d.ts +2 -0
  92. package/dist/server/modules/sessions/session-provider-error-mapper.js +42 -0
  93. package/dist/server/modules/sessions/session-provider-error-mapper.js.map +1 -1
  94. package/dist/server/server/create-server.js +11 -8
  95. package/dist/server/server/create-server.js.map +1 -1
  96. package/dist/server/storage/repositories/session-binding-repository.js +44 -5
  97. package/dist/server/storage/repositories/session-binding-repository.js.map +1 -1
  98. package/dist/server/storage/repositories/session-index-repository.js +6 -0
  99. package/dist/server/storage/repositories/session-index-repository.js.map +1 -1
  100. package/dist/server/storage/sqlite/client.js +19 -0
  101. package/dist/server/storage/sqlite/client.js.map +1 -1
  102. package/dist/server/storage/sqlite/schema.sql +5 -0
  103. package/dist/server/types/domain.d.ts +6 -0
  104. package/node_modules/@codingns/session-sync-core/dist/claude-message-utils.d.ts +5 -2
  105. package/node_modules/@codingns/session-sync-core/dist/claude-message-utils.js +40 -8
  106. package/node_modules/@codingns/session-sync-core/dist/claude-message-utils.js.map +1 -1
  107. package/node_modules/@codingns/session-sync-core/dist/index.d.ts +2 -0
  108. package/node_modules/@codingns/session-sync-core/dist/index.js +2 -0
  109. package/node_modules/@codingns/session-sync-core/dist/index.js.map +1 -1
  110. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.d.ts +10 -1
  111. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js +110 -35
  112. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js.map +1 -1
  113. package/node_modules/@codingns/session-sync-core/dist/providers/claude-session-store.d.ts +11 -0
  114. package/node_modules/@codingns/session-sync-core/dist/providers/claude-session-store.js +105 -0
  115. package/node_modules/@codingns/session-sync-core/dist/providers/claude-session-store.js.map +1 -0
  116. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js +131 -39
  117. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js.map +1 -1
  118. package/node_modules/@codingns/session-sync-core/dist/providers/legna-code.d.ts +9 -0
  119. package/node_modules/@codingns/session-sync-core/dist/providers/legna-code.js +17 -0
  120. package/node_modules/@codingns/session-sync-core/dist/providers/legna-code.js.map +1 -0
  121. package/node_modules/@codingns/session-sync-core/dist/providers/opencode-shared.d.ts +8 -1
  122. package/node_modules/@codingns/session-sync-core/dist/providers/opencode-shared.js +19 -6
  123. package/node_modules/@codingns/session-sync-core/dist/providers/opencode-shared.js.map +1 -1
  124. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.d.ts +1 -0
  125. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +13 -8
  126. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js.map +1 -1
  127. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.d.ts +5 -1
  128. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js +103 -51
  129. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js.map +1 -1
  130. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.d.ts +2 -1
  131. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +41 -21
  132. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
  133. package/node_modules/@codingns/session-sync-core/dist/runtime/gemini-runtime.js +32 -8
  134. package/node_modules/@codingns/session-sync-core/dist/runtime/gemini-runtime.js.map +1 -1
  135. package/node_modules/@codingns/session-sync-core/dist/runtime/legna-runtime.d.ts +10 -0
  136. package/node_modules/@codingns/session-sync-core/dist/runtime/legna-runtime.js +16 -0
  137. package/node_modules/@codingns/session-sync-core/dist/runtime/legna-runtime.js.map +1 -0
  138. package/node_modules/@codingns/session-sync-core/dist/runtime/opencode-runtime.js +167 -10
  139. package/node_modules/@codingns/session-sync-core/dist/runtime/opencode-runtime.js.map +1 -1
  140. package/node_modules/@codingns/session-sync-core/dist/runtime/types.d.ts +2 -0
  141. package/node_modules/@codingns/session-sync-core/dist/types.d.ts +1 -1
  142. package/node_modules/@codingns/session-sync-core/dist/types.js +1 -1
  143. package/node_modules/@codingns/session-sync-core/dist/types.js.map +1 -1
  144. package/package.json +1 -1
  145. package/dist/public/assets/ConversationPage-z3sXtKZ7.js +0 -4
  146. package/dist/public/assets/DesktopWindowPage-CZcoGApB.js +0 -2
  147. package/dist/public/assets/FileContextPanel-C3qex8bb.js +0 -1
  148. package/dist/public/assets/GitSidebar-BK6H16XU.js +0 -6
  149. package/dist/public/assets/MobileCreateSessionSheet-BYfbvK8o.js +0 -1
  150. package/dist/public/assets/MobileSheet-Ckug8hTb.js +0 -1
  151. package/dist/public/assets/MobileWorkspaceSwitcherHeader-RqWrBdn2.js +0 -1
  152. package/dist/public/assets/SessionIndexPage-DuK10DL5.js +0 -1
  153. package/dist/public/assets/SettingsPage-fyD-xaHL.js +0 -1
  154. package/dist/public/assets/TerminalManagerPanel-CCLr1Ypk.js +0 -1
  155. package/dist/public/assets/TerminalRuntimeFallbackModal-aUzjEBwP.js +0 -1
  156. package/dist/public/assets/ToolGitPage-C264yjS9.js +0 -1
  157. package/dist/public/assets/ToolProcessesPage-BOP4A1cb.js +0 -1
  158. package/dist/public/assets/ToolsHomePage-CQxGiKQA.js +0 -1
  159. package/dist/public/assets/WorkbenchLandingPage-CvAY68ca.js +0 -1
  160. package/dist/public/assets/WorkbenchLayout-DGm8Tc5M.js +0 -3
  161. package/dist/public/assets/WorkbenchShellRoute-BF0nHWOk.css +0 -1
  162. package/dist/public/assets/WorkbenchShellRoute-DBBOsJo9.js +0 -1
  163. package/dist/public/assets/WorkspaceDebugDetailPage-CDerFYd2.js +0 -1
  164. package/dist/public/assets/WorkspaceDetailPage-BlJc1CHE.js +0 -1
  165. package/dist/public/assets/WorkspaceHomePage-BUsKJ3lv.js +0 -1
  166. package/dist/public/assets/default-session-permission-mode-DT4SGiwp.js +0 -1
  167. package/dist/public/assets/index-BZLcEHW3.js +0 -42
  168. package/dist/public/assets/index-BbspQPC2.css +0 -1
  169. package/dist/public/assets/session-runtime-machine-DdLeDqQr.js +0 -17
  170. package/dist/public/assets/useRegisteredDebugTemplates-oFAQNIqh.js +0 -1
@@ -1,5 +1,5 @@
1
1
  import { existsSync, readFileSync, statSync } from "node:fs";
2
- import { CapabilityService, ClaudeCodeAdapter, CodexAdapter, GeminiAdapter, KimiAdapter, OpenCodeAdapter, ProviderRegistry, SessionSyncService } from "@codingns/session-sync-core";
2
+ import { CapabilityService, ClaudeCodeAdapter, CodexAdapter, GeminiAdapter, KimiAdapter, LegnaCodeAdapter, OpenCodeAdapter, ProviderRegistry, SessionSyncService } from "@codingns/session-sync-core";
3
3
  import { AppError } from "../../shared/errors/app-error.js";
4
4
  import { hashContent } from "../../shared/utils/hash.js";
5
5
  import { createId } from "../../shared/utils/id.js";
@@ -25,15 +25,18 @@ const RECONSTRUCTED_FORK_TARGET_PROVIDERS = new Set(["codex", "claude-code", "op
25
25
  const FORK_RECONSTRUCTION_PAGE_SIZE = 200;
26
26
  const MAX_FORK_DEPTH = 4;
27
27
  const SYNTHETIC_CODEX_SESSION_CLEANUP_GRACE_MS = 120_000;
28
+ const GEMINI_RUNTIME_CHAT_DISCOVERY_GRACE_MS = 30_000;
28
29
  const SESSION_START_DEFERRED_PROVIDERS = new Set([
29
30
  "codex",
30
31
  "claude-code",
32
+ "legna-code",
31
33
  "opencode",
32
34
  "gemini",
33
35
  "kimi"
34
36
  ]);
35
37
  const MUTABLE_HISTORY_TAIL_PROVIDERS = new Set([
36
38
  "claude-code",
39
+ "legna-code",
37
40
  "codex",
38
41
  "gemini",
39
42
  "kimi",
@@ -75,6 +78,7 @@ export class SessionHistoryService {
75
78
  sessionIsolatedWorkspaceRepository;
76
79
  providerDiscoveryHelperClient = getSharedProviderDiscoveryHelperClient();
77
80
  providerSessionDiscoveryConfig;
81
+ sessionProviderConfigService;
78
82
  taskManager;
79
83
  workspaceDiscoveryStatuses = new Map();
80
84
  workspaceStateRefreshStatuses = new Map();
@@ -84,7 +88,7 @@ export class SessionHistoryService {
84
88
  sessionDeletedObservers = new Set();
85
89
  workspaceSessionRelations = new Map();
86
90
  workspaceStateRefreshTaskSequence = 0;
87
- constructor(db, workspaceRepository, sessionBindingRepository, sessionChangedFileService, sessionIndexRepository, sessionMessageAttachmentService, sessionStateRepository, sessionStatusSnapshotRepository, config, sessionActivityAuthorityService = new SessionActivityAuthorityService(), sessionMessageOriginRepository = null, sessionForkRepository = null, adapterOverrides = {}, taskManager = createTaskManager(), parallelSessionGroupRepository = null, parallelSessionMemberRepository = null, sessionIsolatedWorkspaceRepository = null) {
91
+ constructor(db, workspaceRepository, sessionBindingRepository, sessionChangedFileService, sessionIndexRepository, sessionMessageAttachmentService, sessionStateRepository, sessionStatusSnapshotRepository, config, sessionActivityAuthorityService = new SessionActivityAuthorityService(), sessionMessageOriginRepository = null, sessionForkRepository = null, adapterOverrides = {}, taskManager = createTaskManager(), parallelSessionGroupRepository = null, parallelSessionMemberRepository = null, sessionIsolatedWorkspaceRepository = null, sessionProviderConfigService = null) {
88
92
  this.db = db;
89
93
  this.workspaceRepository = workspaceRepository;
90
94
  this.sessionBindingRepository = sessionBindingRepository;
@@ -102,9 +106,11 @@ export class SessionHistoryService {
102
106
  this.parallelSessionGroupRepository = parallelSessionGroupRepository;
103
107
  this.parallelSessionMemberRepository = parallelSessionMemberRepository;
104
108
  this.sessionIsolatedWorkspaceRepository = sessionIsolatedWorkspaceRepository;
109
+ this.sessionProviderConfigService = sessionProviderConfigService;
105
110
  this.claudeCodeHomeDir = config.claudeCodeHomeDir;
106
111
  this.providerCliCommandPaths = {
107
112
  "claude-code": process.platform === "win32" ? "claude.cmd" : "claude",
113
+ "legna-code": config.legnaCodeCliPath,
108
114
  codex: config.codexCliPath,
109
115
  gemini: config.geminiCliPath,
110
116
  kimi: config.kimiCliPath
@@ -113,8 +119,10 @@ export class SessionHistoryService {
113
119
  this.providerCliAvailability = buildProviderCliAvailabilitySnapshot(this.providerCliCommandPaths);
114
120
  this.providerSessionDiscoveryConfig = {
115
121
  claudeCodeHomeDir: config.claudeCodeHomeDir,
122
+ legnaCodeHomeDir: config.legnaCodeHomeDir,
116
123
  codexCliPath: config.codexCliPath,
117
124
  codexHomeDir: config.codexHomeDir,
125
+ legnaCodeCliPath: config.legnaCodeCliPath,
118
126
  geminiCliPath: config.geminiCliPath,
119
127
  geminiHomeDir: config.geminiHomeDir,
120
128
  kimiDefaultModel: config.kimiDefaultModel,
@@ -125,6 +133,10 @@ export class SessionHistoryService {
125
133
  };
126
134
  this.providerRegistry = new ProviderRegistry([
127
135
  new ClaudeCodeAdapter({ homeDir: config.claudeCodeHomeDir }),
136
+ new LegnaCodeAdapter({
137
+ homeDir: config.legnaCodeHomeDir,
138
+ legacyClaudeHomeDir: config.claudeCodeHomeDir
139
+ }),
128
140
  new CodexAdapter({
129
141
  homeDir: config.codexHomeDir,
130
142
  forkTransportFactory: adapterOverrides.codexForkTransportFactory
@@ -323,8 +335,8 @@ export class SessionHistoryService {
323
335
  ? current?.syncCursor ?? page.cursor
324
336
  : page.cursor,
325
337
  lastSyncAt: nowIso(),
326
- lastErrorCode: current?.lastErrorCode ?? null,
327
- lastErrorDetail: current?.lastErrorDetail ?? null,
338
+ lastErrorCode: clearSuccessfulProviderReadErrorCode(current?.lastErrorCode ?? null),
339
+ lastErrorDetail: clearSuccessfulProviderReadErrorDetail(current?.lastErrorCode ?? null, current?.lastErrorDetail ?? null),
328
340
  resumedAt: current?.resumedAt ?? null
329
341
  });
330
342
  snapshotIdleMs = Date.now() - snapshotIdleStartedAt;
@@ -455,6 +467,15 @@ export class SessionHistoryService {
455
467
  try {
456
468
  const workspacePath = workspaceId ? this.getWorkspaceOrThrow(workspaceId).path : null;
457
469
  const baseCapabilities = this.applyProviderCliAvailability(this.capabilityService.getProviderCapabilities(provider));
470
+ if (baseCapabilities.provider === "opencode" && workspacePath) {
471
+ const refreshed = await this.enrichProviderCapabilities(baseCapabilities, workspacePath);
472
+ const cacheKey = buildProviderCapabilityCacheKey(baseCapabilities.provider, workspacePath);
473
+ this.providerCapabilityCache.set(cacheKey, {
474
+ refreshedAt: Date.now(),
475
+ value: refreshed
476
+ });
477
+ return refreshed;
478
+ }
458
479
  this.scheduleProviderCapabilityRefresh(baseCapabilities, workspacePath);
459
480
  return this.resolveProviderCapabilitiesImmediate(baseCapabilities, workspacePath);
460
481
  }
@@ -470,6 +491,17 @@ export class SessionHistoryService {
470
491
  .getSessionCapabilities(binding.provider, binding.providerSessionId)
471
492
  .then((capabilities) => {
472
493
  const normalizedCapabilities = this.applyProviderCliAvailability(capabilities);
494
+ if (normalizedCapabilities.provider === "opencode") {
495
+ return this.enrichProviderCapabilities(normalizedCapabilities, workspacePath)
496
+ .then((refreshed) => {
497
+ const cacheKey = buildProviderCapabilityCacheKey(normalizedCapabilities.provider, workspacePath);
498
+ this.providerCapabilityCache.set(cacheKey, {
499
+ refreshedAt: Date.now(),
500
+ value: refreshed
501
+ });
502
+ return refreshed;
503
+ });
504
+ }
473
505
  this.scheduleProviderCapabilityRefresh(normalizedCapabilities, workspacePath);
474
506
  return this.resolveProviderCapabilitiesImmediate(normalizedCapabilities, workspacePath);
475
507
  })
@@ -610,11 +642,17 @@ export class SessionHistoryService {
610
642
  async startSessionDirect(input) {
611
643
  const workspace = this.getWorkspaceOrThrow(input.workspaceId);
612
644
  this.assertProviderCapabilityEnabled(input.provider, "canStartSession", "当前 provider 不支持创建会话");
645
+ const sessionId = createId();
646
+ const providerBinding = this.prepareDirectSessionBinding({
647
+ sessionId,
648
+ provider: input.provider,
649
+ providerConfigMode: input.providerConfigMode ?? null,
650
+ providerPresetId: input.providerPresetId ?? null
651
+ });
613
652
  try {
614
- const result = await this.sessionSyncService.startSession(input.provider, workspace.path, {
653
+ const result = await this.startProviderSessionWithBinding(input.provider, workspace.path, providerBinding.runtimeHomeDir, {
615
654
  initialPrompt: input.initialPrompt
616
655
  });
617
- const sessionId = createId();
618
656
  const timestamp = nowIso();
619
657
  const persist = this.db.transaction(() => {
620
658
  this.sessionBindingRepository.upsert({
@@ -623,6 +661,9 @@ export class SessionHistoryService {
623
661
  provider: result.session.provider,
624
662
  providerSessionId: result.session.providerSessionId,
625
663
  rawStoreRef: result.session.rawStoreRef,
664
+ providerConfigMode: providerBinding.providerConfigMode,
665
+ providerPresetId: providerBinding.providerPresetId,
666
+ runtimeHomeDir: providerBinding.runtimeHomeDir,
626
667
  createdAt: timestamp,
627
668
  updatedAt: timestamp
628
669
  });
@@ -690,10 +731,18 @@ export class SessionHistoryService {
690
731
  });
691
732
  }
692
733
  this.assertForkDepthWithinLimit(input.sessionId);
693
- if (targetProvider !== binding.provider) {
734
+ const requestedTargetSelection = resolveRequestedProviderSelection({
735
+ existingBinding: targetProvider === binding.provider ? binding : null,
736
+ providerConfigMode: input.providerConfigMode ?? undefined,
737
+ providerPresetId: input.providerPresetId ?? undefined
738
+ });
739
+ if (targetProvider !== binding.provider
740
+ || !areEquivalentProviderBindingSelection(binding, requestedTargetSelection)) {
694
741
  return this.forkSessionAcrossProviders({
695
742
  ...input,
696
- targetProvider
743
+ targetProvider,
744
+ providerConfigMode: requestedTargetSelection.providerConfigMode,
745
+ providerPresetId: requestedTargetSelection.providerPresetId
697
746
  }, binding, sourceMessageId);
698
747
  }
699
748
  try {
@@ -713,6 +762,9 @@ export class SessionHistoryService {
713
762
  provider: result.session.provider,
714
763
  providerSessionId: result.session.providerSessionId,
715
764
  rawStoreRef: result.session.rawStoreRef,
765
+ providerConfigMode: binding.providerConfigMode,
766
+ providerPresetId: binding.providerPresetId,
767
+ runtimeHomeDir: binding.runtimeHomeDir,
716
768
  createdAt: timestamp,
717
769
  updatedAt: timestamp
718
770
  });
@@ -807,6 +859,8 @@ export class SessionHistoryService {
807
859
  userId: input.userId,
808
860
  provider: input.targetProvider,
809
861
  initialPrompt: inheritedPrompt,
862
+ providerConfigMode: input.providerConfigMode ?? null,
863
+ providerPresetId: input.providerPresetId ?? null,
810
864
  parentSessionId: input.sessionId,
811
865
  sessionKind: input.sessionKind ?? "default",
812
866
  annotationSourceMessageId: input.annotationSourceMessageId ?? null,
@@ -854,6 +908,40 @@ export class SessionHistoryService {
854
908
  this.workspaceSessionRelations.set(input.targetWorkspaceId?.trim() || sourceBinding.workspaceId, relationMap);
855
909
  return this.getSessionListItemOrThrow(startedSession.sessionId, input.userId);
856
910
  }
911
+ prepareDirectSessionBinding(input) {
912
+ if (!this.sessionProviderConfigService) {
913
+ return {
914
+ providerConfigMode: "global-default",
915
+ providerPresetId: null,
916
+ runtimeHomeDir: null
917
+ };
918
+ }
919
+ return this.sessionProviderConfigService.prepareSessionBinding({
920
+ sessionId: input.sessionId,
921
+ provider: input.provider,
922
+ providerConfigMode: input.providerConfigMode ?? undefined,
923
+ providerPresetId: input.providerPresetId ?? null
924
+ });
925
+ }
926
+ startProviderSessionWithBinding(provider, workspacePath, runtimeHomeDir, options) {
927
+ const scopedRuntimeHomeDir = runtimeHomeDir?.trim() || null;
928
+ if (!scopedRuntimeHomeDir) {
929
+ return this.sessionSyncService.startSession(provider, workspacePath, options);
930
+ }
931
+ switch (provider) {
932
+ case "claude-code":
933
+ return new ClaudeCodeAdapter({ homeDir: scopedRuntimeHomeDir }).startSession(workspacePath, options);
934
+ case "codex":
935
+ return new CodexAdapter({ homeDir: scopedRuntimeHomeDir }).startSession(workspacePath, options);
936
+ case "gemini":
937
+ return new GeminiAdapter({
938
+ homeDir: scopedRuntimeHomeDir,
939
+ commandPath: this.providerSessionDiscoveryConfig.geminiCliPath
940
+ }).startSession(workspacePath, options);
941
+ default:
942
+ return this.sessionSyncService.startSession(provider, workspacePath, options);
943
+ }
944
+ }
857
945
  async readForkSourceMessages(sessionId, binding, sourceType, sourceMessageId, sourceMessageSnapshot = null) {
858
946
  const messages = [];
859
947
  let cursor = null;
@@ -1024,13 +1112,14 @@ export class SessionHistoryService {
1024
1112
  return null;
1025
1113
  }
1026
1114
  await this.syncSessionTitleFromProvider(sessionId, binding);
1115
+ const snapshot = this.sessionStatusSnapshotRepository.findBySessionId(sessionId);
1027
1116
  this.upsertSnapshot(sessionId, {
1028
1117
  syncStatus: "idle",
1029
1118
  syncCursor: page.cursor,
1030
1119
  lastSyncAt: nowIso(),
1031
- lastErrorCode: this.sessionStatusSnapshotRepository.findBySessionId(sessionId)?.lastErrorCode ?? null,
1032
- lastErrorDetail: this.sessionStatusSnapshotRepository.findBySessionId(sessionId)?.lastErrorDetail ?? null,
1033
- resumedAt: this.sessionStatusSnapshotRepository.findBySessionId(sessionId)?.resumedAt ?? null
1120
+ lastErrorCode: clearSuccessfulProviderReadErrorCode(snapshot?.lastErrorCode ?? null),
1121
+ lastErrorDetail: clearSuccessfulProviderReadErrorDetail(snapshot?.lastErrorCode ?? null, snapshot?.lastErrorDetail ?? null),
1122
+ resumedAt: snapshot?.resumedAt ?? null
1034
1123
  });
1035
1124
  return {
1036
1125
  type: "session.delta",
@@ -1244,6 +1333,15 @@ export class SessionHistoryService {
1244
1333
  provider: resolvedSnapshot.provider,
1245
1334
  providerSessionId: resolvedSnapshot.providerSessionId,
1246
1335
  rawStoreRef: resolvedSnapshot.rawStoreRef,
1336
+ providerConfigMode: currentBinding?.providerConfigMode
1337
+ ?? duplicateBinding?.providerConfigMode
1338
+ ?? "global-default",
1339
+ providerPresetId: currentBinding?.providerPresetId
1340
+ ?? duplicateBinding?.providerPresetId
1341
+ ?? null,
1342
+ runtimeHomeDir: currentBinding?.runtimeHomeDir
1343
+ ?? duplicateBinding?.runtimeHomeDir
1344
+ ?? null,
1247
1345
  createdAt: pickEarlierIso(currentBinding?.createdAt ?? null, duplicateBinding?.createdAt ?? null)
1248
1346
  ?? timestamp,
1249
1347
  updatedAt: timestamp
@@ -1335,6 +1433,9 @@ export class SessionHistoryService {
1335
1433
  provider: session.provider,
1336
1434
  providerSessionId: session.providerSessionId,
1337
1435
  rawStoreRef: session.rawStoreRef,
1436
+ providerConfigMode: existing?.providerConfigMode ?? "global-default",
1437
+ providerPresetId: existing?.providerPresetId ?? null,
1438
+ runtimeHomeDir: existing?.runtimeHomeDir ?? null,
1338
1439
  createdAt,
1339
1440
  updatedAt: timestamp
1340
1441
  };
@@ -1614,9 +1715,24 @@ export class SessionHistoryService {
1614
1715
  total: 0
1615
1716
  };
1616
1717
  }
1718
+ if (this.shouldTreatMissingGeminiRuntimeHistoryAsEmpty(sessionId, provider, error)) {
1719
+ return {
1720
+ messages: [],
1721
+ cursor,
1722
+ nextCursor: null,
1723
+ total: 0
1724
+ };
1725
+ }
1617
1726
  throw mapSessionProviderError(error);
1618
1727
  });
1619
1728
  }
1729
+ shouldTreatMissingGeminiRuntimeHistoryAsEmpty(sessionId, provider, error) {
1730
+ if (provider !== "gemini" || !isGeminiChatNotFoundError(error)) {
1731
+ return false;
1732
+ }
1733
+ const sessionIndex = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
1734
+ return this.listSessionStatesBySessionId(sessionId).some((state) => shouldTreatMissingGeminiRuntimeHistoryStateAsTransient(state, sessionIndex));
1735
+ }
1620
1736
  enrichMessagesWithOrigin(sessionId, messages) {
1621
1737
  return this.resolveMessageOrigins(sessionId, messages);
1622
1738
  }
@@ -1909,8 +2025,8 @@ export class SessionHistoryService {
1909
2025
  syncStatus: "idle",
1910
2026
  syncCursor: page.cursor,
1911
2027
  lastSyncAt: nowIso(),
1912
- lastErrorCode: snapshot?.lastErrorCode ?? null,
1913
- lastErrorDetail: snapshot?.lastErrorDetail ?? null,
2028
+ lastErrorCode: clearSuccessfulProviderReadErrorCode(snapshot?.lastErrorCode ?? null),
2029
+ lastErrorDetail: clearSuccessfulProviderReadErrorDetail(snapshot?.lastErrorCode ?? null, snapshot?.lastErrorDetail ?? null),
1914
2030
  resumedAt: snapshot?.resumedAt ?? null
1915
2031
  });
1916
2032
  await onEnvelope({
@@ -1936,7 +2052,12 @@ export class SessionHistoryService {
1936
2052
  provider: binding.provider,
1937
2053
  providerSessionId: binding.providerSessionId,
1938
2054
  rawStoreRef: binding.rawStoreRef
1939
- }, signal)).trim();
2055
+ }, signal).catch((error) => {
2056
+ if (this.shouldTreatMissingGeminiRuntimeHistoryAsEmpty(sessionId, binding.provider, error)) {
2057
+ return "";
2058
+ }
2059
+ throw error;
2060
+ })).trim();
1940
2061
  const resolvedTitle = resolvePersistedSessionTitle(binding.provider, nextTitle, currentIndex.title);
1941
2062
  if (resolvedTitle.length === 0 || resolvedTitle === currentIndex.title) {
1942
2063
  return;
@@ -2371,6 +2492,9 @@ export class SessionHistoryService {
2371
2492
  provider: input.provider,
2372
2493
  providerSessionId: buildPendingBindingValue(input.provider, input.targetSessionId),
2373
2494
  rawStoreRef: buildPendingBindingValue(input.provider, input.targetSessionId),
2495
+ providerConfigMode: sourceBinding.providerConfigMode,
2496
+ providerPresetId: sourceBinding.providerPresetId,
2497
+ runtimeHomeDir: sourceBinding.runtimeHomeDir,
2374
2498
  createdAt: sourceBinding.createdAt,
2375
2499
  updatedAt: input.timestamp
2376
2500
  });
@@ -2452,6 +2576,9 @@ export class SessionHistoryService {
2452
2576
  provider: sourceBinding.provider,
2453
2577
  providerSessionId: buildAliasBindingValue(input.provider, input.targetSessionId, input.sourceSessionId),
2454
2578
  rawStoreRef: buildAliasBindingValue(input.provider, input.targetSessionId, input.sourceSessionId),
2579
+ providerConfigMode: sourceBinding.providerConfigMode,
2580
+ providerPresetId: sourceBinding.providerPresetId,
2581
+ runtimeHomeDir: sourceBinding.runtimeHomeDir,
2455
2582
  createdAt: sourceBinding.createdAt,
2456
2583
  updatedAt: input.timestamp
2457
2584
  });
@@ -2690,7 +2817,9 @@ export class SessionHistoryService {
2690
2817
  const liveObservation = this.resolveLiveActivityObservation(sessionId);
2691
2818
  const inspection = liveObservation
2692
2819
  ? null
2693
- : inspectSessionActivity(binding.provider, binding.rawStoreRef);
2820
+ : binding.provider === "gemini"
2821
+ ? await this.inspectGeminiHistoryActivity(sessionId, binding)
2822
+ : inspectSessionActivity(binding.provider, binding.rawStoreRef);
2694
2823
  if (inspection) {
2695
2824
  const nowMs = Date.parse(timestamp);
2696
2825
  if (shouldClearStaleRuntimeWithoutInspection(current, inspection, nowMs)) {
@@ -2753,6 +2882,15 @@ export class SessionHistoryService {
2753
2882
  });
2754
2883
  return nextRecord;
2755
2884
  }
2885
+ async inspectGeminiHistoryActivity(sessionId, binding) {
2886
+ try {
2887
+ const page = await this.readPage(sessionId, binding.provider, binding.providerSessionId, binding.rawStoreRef, null, GEMINI_ACTIVITY_INFERENCE_HISTORY_LIMIT, "backward", this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.messageCount ?? null);
2888
+ return inferGeminiInspectionFromHistory(page.messages);
2889
+ }
2890
+ catch {
2891
+ return inspectSessionActivity(binding.provider, binding.rawStoreRef);
2892
+ }
2893
+ }
2756
2894
  async repairCodexDirtyBindingBeforeHistoryRead(sessionId, userId, binding) {
2757
2895
  if (!shouldRepairCodexDirtyBinding(binding)) {
2758
2896
  this.codexDirtyBindingRepairStates.delete(sessionId);
@@ -2829,7 +2967,11 @@ export class SessionHistoryService {
2829
2967
  }
2830
2968
  }
2831
2969
  function isProviderCliBacked(provider) {
2832
- return provider === "claude-code" || provider === "codex" || provider === "gemini" || provider === "kimi";
2970
+ return provider === "claude-code"
2971
+ || provider === "legna-code"
2972
+ || provider === "codex"
2973
+ || provider === "gemini"
2974
+ || provider === "kimi";
2833
2975
  }
2834
2976
  function buildProviderCliAvailabilitySnapshot(commandPaths) {
2835
2977
  return Object.freeze(Object.fromEntries(Object.entries(commandPaths).map(([provider, commandPath]) => [
@@ -2841,6 +2983,8 @@ function buildProviderCliUnavailableMessage(provider) {
2841
2983
  switch (provider) {
2842
2984
  case "claude-code":
2843
2985
  return "未检测到 Claude CLI";
2986
+ case "legna-code":
2987
+ return "未检测到 Legna CLI";
2844
2988
  case "codex":
2845
2989
  return "未检测到 Codex CLI";
2846
2990
  case "gemini":
@@ -2900,6 +3044,46 @@ function hasInspectionEvidence(inspection) {
2900
3044
  || !!inspection.lastEventAt
2901
3045
  || !!inspection.completedAtCandidate;
2902
3046
  }
3047
+ function inferGeminiInspectionFromHistory(messages) {
3048
+ let lastEventAt = null;
3049
+ let lastUserAt = null;
3050
+ let latestNonUserMessage = null;
3051
+ const pendingToolCallIds = new Set();
3052
+ for (const message of messages) {
3053
+ lastEventAt = pickLaterIso(lastEventAt, message.timestamp);
3054
+ if (message.role === "user") {
3055
+ lastUserAt = pickLaterIso(lastUserAt, message.timestamp);
3056
+ continue;
3057
+ }
3058
+ latestNonUserMessage = message;
3059
+ if (message.kind === "tool_call" && message.toolCall?.callId) {
3060
+ pendingToolCallIds.add(message.toolCall.callId);
3061
+ continue;
3062
+ }
3063
+ if (message.kind === "tool_result" && message.toolCall?.callId) {
3064
+ pendingToolCallIds.delete(message.toolCall.callId);
3065
+ }
3066
+ }
3067
+ const latestNonUserAt = latestNonUserMessage?.timestamp ?? null;
3068
+ const hasReplyAfterLatestUser = !!latestNonUserAt && (!lastUserAt || latestNonUserAt.localeCompare(lastUserAt) >= 0);
3069
+ const isTerminalReplyKind = latestNonUserMessage?.kind === "text" || latestNonUserMessage?.kind === "tool_result";
3070
+ const completedAtCandidate = hasReplyAfterLatestUser && isTerminalReplyKind && pendingToolCallIds.size === 0
3071
+ ? latestNonUserAt
3072
+ : null;
3073
+ const hasOpenTurn = !completedAtCandidate
3074
+ && hasReplyAfterLatestUser
3075
+ && (pendingToolCallIds.size > 0
3076
+ || latestNonUserMessage?.kind === "thinking"
3077
+ || latestNonUserMessage?.kind === "tool_call");
3078
+ return {
3079
+ runningState: hasOpenTurn ? "running" : "idle",
3080
+ hasPendingTools: pendingToolCallIds.size > 0,
3081
+ lastEventAt,
3082
+ completedAtCandidate,
3083
+ errorCode: null,
3084
+ errorDetail: null
3085
+ };
3086
+ }
2903
3087
  function applySessionActivityResolution(item, resolution) {
2904
3088
  const rawResolvedRunningState = resolution.runningState === "unknown" && item.runningState === null
2905
3089
  ? null
@@ -3453,6 +3637,40 @@ function shouldTreatMissingSyntheticHistoryAsEmpty(provider, rawStoreRef, error)
3453
3637
  const detail = error instanceof Error ? error.message : String(error);
3454
3638
  return detail.includes("ENOENT");
3455
3639
  }
3640
+ function shouldTreatMissingGeminiRuntimeHistoryStateAsTransient(state, sessionIndex) {
3641
+ if (state.activitySource !== "runtime") {
3642
+ return false;
3643
+ }
3644
+ if (state.runningState === "starting" || state.runningState === "running") {
3645
+ return true;
3646
+ }
3647
+ if ((sessionIndex?.messageCount ?? 0) !== 0) {
3648
+ return false;
3649
+ }
3650
+ return isWithinGeminiRuntimeChatDiscoveryGraceWindow(state.lastEventAt ?? state.updatedAt ?? sessionIndex?.updatedAt ?? sessionIndex?.createdAt ?? null);
3651
+ }
3652
+ function isWithinGeminiRuntimeChatDiscoveryGraceWindow(timestamp) {
3653
+ if (!timestamp) {
3654
+ return false;
3655
+ }
3656
+ const timestampMs = Date.parse(timestamp);
3657
+ if (!Number.isFinite(timestampMs)) {
3658
+ return false;
3659
+ }
3660
+ return Date.now() - timestampMs <= GEMINI_RUNTIME_CHAT_DISCOVERY_GRACE_MS;
3661
+ }
3662
+ function isGeminiChatNotFoundError(error) {
3663
+ if (error instanceof AppError) {
3664
+ return error.errorCode === "GEMINI_CHAT_NOT_FOUND";
3665
+ }
3666
+ return error instanceof Error && error.message === "GEMINI_CHAT_NOT_FOUND";
3667
+ }
3668
+ function clearSuccessfulProviderReadErrorCode(errorCode) {
3669
+ return errorCode === "PROVIDER_READ_FAILED" ? null : errorCode;
3670
+ }
3671
+ function clearSuccessfulProviderReadErrorDetail(errorCode, errorDetail) {
3672
+ return errorCode === "PROVIDER_READ_FAILED" ? null : errorDetail;
3673
+ }
3456
3674
  function shouldShortCircuitMissingSyntheticCodexHistory(provider, rawStoreRef) {
3457
3675
  return provider === "codex" && isSyntheticCodexRawStoreRef(rawStoreRef) && !existsSync(rawStoreRef);
3458
3676
  }
@@ -3599,6 +3817,9 @@ function isSyntheticCodexSessionTitle(title) {
3599
3817
  return (/^rollout-\d{4}-\d{2}-\d{2}t/i.test(title) ||
3600
3818
  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(title));
3601
3819
  }
3820
+ function isSyntheticGeminiSessionTitle(title) {
3821
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(title);
3822
+ }
3602
3823
  function shouldSyncSessionTitleFromProvider(provider, currentTitle) {
3603
3824
  const normalizedTitle = currentTitle?.trim() ?? "";
3604
3825
  if (normalizedTitle.length === 0) {
@@ -3607,6 +3828,9 @@ function shouldSyncSessionTitleFromProvider(provider, currentTitle) {
3607
3828
  if (provider === "codex" && isSyntheticCodexSessionTitle(normalizedTitle)) {
3608
3829
  return true;
3609
3830
  }
3831
+ if (provider === "gemini" && isSyntheticGeminiSessionTitle(normalizedTitle)) {
3832
+ return true;
3833
+ }
3610
3834
  return false;
3611
3835
  }
3612
3836
  function shouldRemoveHiddenClaudeDebugSession(session) {
@@ -3618,6 +3842,7 @@ function shouldRemoveHiddenClaudeDebugSession(session) {
3618
3842
  /\/agent-[^/]+\.jsonl$/i.test(normalizedRawStoreRef));
3619
3843
  }
3620
3844
  const STALE_RUNTIME_WITHOUT_INSPECTION_GRACE_MS = 120_000;
3845
+ const GEMINI_ACTIVITY_INFERENCE_HISTORY_LIMIT = 80;
3621
3846
  function shouldClearStaleRuntimeWithoutInspection(current, inspection, nowMs) {
3622
3847
  if (!current || current.activitySource !== "runtime") {
3623
3848
  return false;
@@ -3795,8 +4020,51 @@ function areEquivalentSessionBindings(current, next) {
3795
4020
  current.provider === next.provider &&
3796
4021
  current.providerSessionId === next.providerSessionId &&
3797
4022
  current.rawStoreRef === next.rawStoreRef &&
4023
+ current.providerConfigMode === next.providerConfigMode &&
4024
+ current.providerPresetId === next.providerPresetId &&
4025
+ current.runtimeHomeDir === next.runtimeHomeDir &&
3798
4026
  current.createdAt === next.createdAt);
3799
4027
  }
4028
+ function resolveRequestedProviderSelection(input) {
4029
+ const existingSelection = input.existingBinding
4030
+ ? {
4031
+ providerConfigMode: input.existingBinding.providerConfigMode,
4032
+ providerPresetId: input.existingBinding.providerPresetId
4033
+ }
4034
+ : null;
4035
+ const normalizedPresetId = input.providerPresetId?.trim() || null;
4036
+ if (input.providerConfigMode === undefined && input.providerPresetId === undefined) {
4037
+ return existingSelection ?? {
4038
+ providerConfigMode: "global-default",
4039
+ providerPresetId: null
4040
+ };
4041
+ }
4042
+ const providerConfigMode = input.providerConfigMode
4043
+ ?? (normalizedPresetId ? "cc-switch-preset" : "global-default");
4044
+ if (providerConfigMode === "global-default") {
4045
+ return {
4046
+ providerConfigMode,
4047
+ providerPresetId: null
4048
+ };
4049
+ }
4050
+ const providerPresetId = normalizedPresetId ?? existingSelection?.providerPresetId ?? null;
4051
+ if (!providerPresetId) {
4052
+ throw new AppError({
4053
+ statusCode: 400,
4054
+ errorCode: "INVALID_INPUT",
4055
+ detail: "使用 cc-switch preset 时必须提供 providerPresetId",
4056
+ field: "providerPresetId"
4057
+ });
4058
+ }
4059
+ return {
4060
+ providerConfigMode,
4061
+ providerPresetId
4062
+ };
4063
+ }
4064
+ function areEquivalentProviderBindingSelection(binding, selection) {
4065
+ return (binding.providerConfigMode === selection.providerConfigMode
4066
+ && binding.providerPresetId === selection.providerPresetId);
4067
+ }
3800
4068
  function areEquivalentSessionIndexRecords(current, next) {
3801
4069
  if (!current) {
3802
4070
  return false;