@jingyi0605/codingns 0.9.7 → 0.9.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/dist/public/assets/{AdaptiveButlerPage-DclGPzEx.js → AdaptiveButlerPage-CqBM1XiC.js} +2 -2
- package/dist/public/assets/{App-CcDXqFl1.css → App-7zrCMhE-.css} +1 -1
- package/dist/public/assets/App-Pe30N_Dy.js +30 -0
- package/dist/public/assets/{BootstrapPage-Bl21SsuW.js → BootstrapPage-D9ai1XhC.js} +1 -1
- package/dist/public/assets/ConversationPage-C8aU99KN.js +9 -0
- package/dist/public/assets/{DesktopDetachPreviewPage-uaOHVsjV.js → DesktopDetachPreviewPage-BJVPVFf2.js} +1 -1
- package/dist/public/assets/{DesktopModal-BxsogpLf.js → DesktopModal-DyC_e0eF.js} +1 -1
- package/dist/public/assets/DesktopWindowPage-BZleww0T.js +2 -0
- package/dist/public/assets/FileContextPanel-BklQRQXj.js +1 -0
- package/dist/public/assets/GitSidebar-D0tb3W87.js +6 -0
- package/dist/public/assets/MobileCreateSessionSheet-BS881Agk.js +1 -0
- package/dist/public/assets/{MobileSheet-C5IVmUsO.js → MobileSheet-C32hwIFd.js} +1 -1
- package/dist/public/assets/PluginAccessOverview-BDiz6fV8.js +1 -0
- package/dist/public/assets/{PluginContainerPage-_2u-9thM.js → PluginContainerPage-BMT6J0YS.js} +1 -1
- package/dist/public/assets/{PluginDetailPage-F9cKjSCp.js → PluginDetailPage-ztNhvCWP.js} +1 -1
- package/dist/public/assets/{PluginsListPage-BOhcua_4.js → PluginsListPage-Cuk2-U6E.js} +1 -1
- package/dist/public/assets/PureConversationPage-ShluI2En.js +1 -0
- package/dist/public/assets/{RelayConnectEntryPage-Ck_uZLzj.js → RelayConnectEntryPage-CMbjlD0k.js} +1 -1
- package/dist/public/assets/{ServerSettingsModal-ICd82a_x.js → ServerSettingsModal-CLSWyHu6.js} +1 -1
- package/dist/public/assets/SessionIndexPage-dqfauVAj.js +1 -0
- package/dist/public/assets/SettingsPage-PUga-wwJ.js +2 -0
- package/dist/public/assets/TerminalManagerPanel-75khWhRz.js +1 -0
- package/dist/public/assets/ToolFilesPage-S3eWxO_D.js +1 -0
- package/dist/public/assets/ToolGitPage-8RAMpope.js +1 -0
- package/dist/public/assets/ToolProcessesPage-TZACKEoH.js +1 -0
- package/dist/public/assets/ToolsHomePage-Whgu6Tyf.js +1 -0
- package/dist/public/assets/WorkbenchLandingPage-CC24vHza.js +1 -0
- package/dist/public/assets/{WorkbenchLayout-BksVkkFF.css → WorkbenchLayout-BZdfVest.css} +32 -1
- package/dist/public/assets/WorkbenchLayout-CoYxMzzR.js +1081 -0
- package/dist/public/assets/{WorkbenchModal-CsZeLArg.js → WorkbenchModal-2PaCY_Gj.js} +1 -1
- package/dist/public/assets/WorkbenchShellRoute-DLVRpGzb.css +1 -0
- package/dist/public/assets/WorkbenchShellRoute-QXztW_Ny.js +1 -0
- package/dist/public/assets/WorkspaceDebugDetailPage-Ce_oMmJ8.js +1 -0
- package/dist/public/assets/WorkspaceDetailPage-aX6w4Q7S.js +1 -0
- package/dist/public/assets/WorkspaceHomePage-DQttgj2w.js +1 -0
- package/dist/public/assets/{client-runtime-manager-BgGugw8T.js → client-runtime-manager-CGVLChqs.js} +1 -1
- package/dist/public/assets/host-alias-CeDJeL0T.js +1 -0
- package/dist/public/assets/index-BehUkul4.css +1 -0
- package/dist/public/assets/index-Bp1FnRo_.js +50 -0
- package/dist/public/assets/{login-direct-candidate-resolver-ty2uOY5y.js → login-direct-candidate-resolver-Bvs5uPix.js} +1 -1
- package/dist/public/assets/peer-host-config-sync-oelf2T0_.js +1 -0
- package/dist/public/assets/{plugin-permission-copy-iK3faI3n.js → plugin-permission-copy-Do028HzJ.js} +1 -1
- package/dist/public/assets/{plugins-api-CmV7aDGA.js → plugins-api-BOugklJ-.js} +1 -1
- package/dist/public/assets/{preferences-service-BoSmT_ny.js → preferences-service-Dys1mbBy.js} +1 -1
- package/dist/public/assets/relay-entry-B6UDc5cD.js +1 -0
- package/dist/public/assets/useRegisteredDebugTemplates-BafVuBvE.js +1 -0
- package/dist/public/assets/workbench-navigation-Bs4OQYD3.js +1 -0
- package/dist/public/index.html +2 -2
- package/dist/server/middlewares/auth-guard.js +1 -0
- package/dist/server/middlewares/auth-guard.js.map +1 -1
- package/dist/server/modules/assistant-capability/assistant-capability-controller.d.ts +3 -0
- package/dist/server/modules/assistant-capability/assistant-capability-controller.js +9 -0
- package/dist/server/modules/assistant-capability/assistant-capability-controller.js.map +1 -1
- package/dist/server/modules/assistant-capability/assistant-capability-service.d.ts +3 -0
- package/dist/server/modules/assistant-capability/assistant-capability-service.js +4 -1
- package/dist/server/modules/assistant-capability/assistant-capability-service.js.map +1 -1
- package/dist/server/modules/peer-host/host-api-proxy-service.d.ts +9 -0
- package/dist/server/modules/peer-host/host-api-proxy-service.js +174 -0
- package/dist/server/modules/peer-host/host-api-proxy-service.js.map +1 -0
- package/dist/server/modules/peer-host/host-handshake-controller.d.ts +7 -0
- package/dist/server/modules/peer-host/host-handshake-controller.js +10 -0
- package/dist/server/modules/peer-host/host-handshake-controller.js.map +1 -0
- package/dist/server/modules/peer-host/host-handshake.d.ts +15 -0
- package/dist/server/modules/peer-host/host-handshake.js +20 -0
- package/dist/server/modules/peer-host/host-handshake.js.map +1 -0
- package/dist/server/modules/peer-host/host-ws-proxy-service.d.ts +14 -0
- package/dist/server/modules/peer-host/host-ws-proxy-service.js +256 -0
- package/dist/server/modules/peer-host/host-ws-proxy-service.js.map +1 -0
- package/dist/server/modules/peer-host/peer-host-controller.d.ts +55 -0
- package/dist/server/modules/peer-host/peer-host-controller.js +62 -0
- package/dist/server/modules/peer-host/peer-host-controller.js.map +1 -0
- package/dist/server/modules/peer-host/peer-host-service.d.ts +77 -0
- package/dist/server/modules/peer-host/peer-host-service.js +529 -0
- package/dist/server/modules/peer-host/peer-host-service.js.map +1 -0
- package/dist/server/modules/sessions/codex-session-title-generator.js +18 -5
- package/dist/server/modules/sessions/codex-session-title-generator.js.map +1 -1
- package/dist/server/modules/sessions/session-controller.d.ts +10 -0
- package/dist/server/modules/sessions/session-controller.js +11 -0
- package/dist/server/modules/sessions/session-controller.js.map +1 -1
- package/dist/server/modules/sessions/session-history-service.d.ts +54 -2
- package/dist/server/modules/sessions/session-history-service.js +407 -63
- package/dist/server/modules/sessions/session-history-service.js.map +1 -1
- package/dist/server/modules/sessions/session-live-runtime-service.d.ts +6 -0
- package/dist/server/modules/sessions/session-live-runtime-service.js +181 -80
- package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
- package/dist/server/modules/sessions/session-title-utils.d.ts +3 -0
- package/dist/server/modules/sessions/session-title-utils.js +25 -0
- package/dist/server/modules/sessions/session-title-utils.js.map +1 -0
- package/dist/server/modules/sessions/workspace-office-mcp-config.d.ts +2 -1
- package/dist/server/modules/sessions/workspace-office-mcp-config.js +44 -3
- package/dist/server/modules/sessions/workspace-office-mcp-config.js.map +1 -1
- package/dist/server/modules/tasks/observability-controller.d.ts +2 -0
- package/dist/server/modules/tasks/observability-controller.js +16 -1
- package/dist/server/modules/tasks/observability-controller.js.map +1 -1
- package/dist/server/modules/tasks/observability-service.d.ts +12 -2
- package/dist/server/modules/tasks/observability-service.js +16 -3
- package/dist/server/modules/tasks/observability-service.js.map +1 -1
- package/dist/server/modules/tasks/task-manager.d.ts +2 -1
- package/dist/server/modules/tasks/task-manager.js +3 -0
- package/dist/server/modules/tasks/task-manager.js.map +1 -1
- package/dist/server/modules/tasks/task-scheduler.d.ts +2 -1
- package/dist/server/modules/tasks/task-scheduler.js +21 -0
- package/dist/server/modules/tasks/task-scheduler.js.map +1 -1
- package/dist/server/modules/tasks/task-types.d.ts +5 -0
- package/dist/server/modules/tasks/task-types.js.map +1 -1
- package/dist/server/modules/workbench/workbench-controller.js +3 -2
- package/dist/server/modules/workbench/workbench-controller.js.map +1 -1
- package/dist/server/modules/workbench/workbench-service.d.ts +2 -0
- package/dist/server/modules/workbench/workbench-service.js +162 -19
- package/dist/server/modules/workbench/workbench-service.js.map +1 -1
- package/dist/server/modules/workspace/affairs-library-controller.d.ts +1 -0
- package/dist/server/modules/workspace/affairs-library-controller.js +3 -0
- package/dist/server/modules/workspace/affairs-library-controller.js.map +1 -1
- package/dist/server/modules/workspace/affairs-library-service.d.ts +5 -0
- package/dist/server/modules/workspace/affairs-library-service.js +103 -4
- package/dist/server/modules/workspace/affairs-library-service.js.map +1 -1
- package/dist/server/modules/workspace/workspace-controller.d.ts +7 -1
- package/dist/server/modules/workspace/workspace-controller.js +11 -1
- package/dist/server/modules/workspace/workspace-controller.js.map +1 -1
- package/dist/server/modules/workspace/workspace-service.d.ts +9 -2
- package/dist/server/modules/workspace/workspace-service.js +45 -10
- package/dist/server/modules/workspace/workspace-service.js.map +1 -1
- package/dist/server/routes/affairs.js +4 -0
- package/dist/server/routes/affairs.js.map +1 -1
- package/dist/server/routes/peer-hosts.d.ts +3 -0
- package/dist/server/routes/peer-hosts.js +18 -0
- package/dist/server/routes/peer-hosts.js.map +1 -0
- package/dist/server/routes/public.d.ts +2 -1
- package/dist/server/routes/public.js +2 -1
- package/dist/server/routes/public.js.map +1 -1
- package/dist/server/routes/sessions.js +2 -0
- package/dist/server/routes/sessions.js.map +1 -1
- package/dist/server/server/create-server.d.ts +4 -0
- package/dist/server/server/create-server.js +31 -3
- package/dist/server/server/create-server.js.map +1 -1
- package/dist/server/shared/http/error-handler.js +12 -0
- package/dist/server/shared/http/error-handler.js.map +1 -1
- package/dist/server/storage/repositories/peer-host-repository.d.ts +44 -0
- package/dist/server/storage/repositories/peer-host-repository.js +271 -0
- package/dist/server/storage/repositories/peer-host-repository.js.map +1 -0
- package/dist/server/storage/repositories/session-discovery-diagnostics-repository.d.ts +10 -0
- package/dist/server/storage/repositories/session-discovery-diagnostics-repository.js +68 -0
- package/dist/server/storage/repositories/session-discovery-diagnostics-repository.js.map +1 -0
- package/dist/server/storage/repositories/session-source-index-repository.d.ts +14 -0
- package/dist/server/storage/repositories/session-source-index-repository.js +164 -0
- package/dist/server/storage/repositories/session-source-index-repository.js.map +1 -0
- package/dist/server/storage/repositories/workspace-navigation-state-repository.js +16 -7
- package/dist/server/storage/repositories/workspace-navigation-state-repository.js.map +1 -1
- package/dist/server/storage/sqlite/client.js +107 -0
- package/dist/server/storage/sqlite/client.js.map +1 -1
- package/dist/server/storage/sqlite/schema.sql +121 -0
- package/dist/server/types/domain.d.ts +88 -0
- package/dist/server/ws/workbench-ws-hub.js +5 -14
- package/dist/server/ws/workbench-ws-hub.js.map +1 -1
- package/dist/server/ws/ws-server.d.ts +2 -1
- package/dist/server/ws/ws-server.js +5 -1
- package/dist/server/ws/ws-server.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/claude-session-store.js +19 -2
- package/node_modules/@codingns/session-sync-core/dist/providers/claude-session-store.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.d.ts +1 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +74 -6
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +11 -4
- package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js +8 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.d.ts +3 -0
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +144 -27
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
- package/package.json +1 -1
- package/dist/public/assets/App-BxX5mm9o.js +0 -30
- package/dist/public/assets/ConversationPage-CmiVCV0q.js +0 -9
- package/dist/public/assets/DesktopWindowPage-Bubfw1nC.js +0 -2
- package/dist/public/assets/FileContextPanel-DrYWcTkp.js +0 -1
- package/dist/public/assets/GitSidebar-CQsfJqun.js +0 -6
- package/dist/public/assets/MobileCreateSessionSheet-CqtmfVNo.js +0 -1
- package/dist/public/assets/MobileTopHeaderFrame-EoBm3-X0.js +0 -1
- package/dist/public/assets/MobileWorkspaceSwitcherHeader-BC8MMQs_.js +0 -1
- package/dist/public/assets/PluginAccessOverview-CrQiQxxZ.js +0 -1
- package/dist/public/assets/SessionIndexPage-Cox6P6dw.js +0 -1
- package/dist/public/assets/SettingsPage-BI5cM3j5.js +0 -2
- package/dist/public/assets/TerminalManagerPanel-DiVhBQhf.js +0 -1
- package/dist/public/assets/TerminalPage-6GBZ9nXN.css +0 -32
- package/dist/public/assets/TerminalPage-DUMUO7Ng.js +0 -55
- package/dist/public/assets/TerminalRuntimeFallbackModal-DGPR_CMh.js +0 -1
- package/dist/public/assets/ToolFilesPage-m88CAp-r.js +0 -1
- package/dist/public/assets/ToolGitPage-DCYpfTsb.js +0 -1
- package/dist/public/assets/ToolProcessesPage-Bk5ulsyq.js +0 -1
- package/dist/public/assets/ToolsHomePage-BrTwfjI7.js +0 -1
- package/dist/public/assets/WorkbenchLandingPage-q4AAmdMV.js +0 -1
- package/dist/public/assets/WorkbenchLayout-DD8b-m2G.js +0 -1027
- package/dist/public/assets/WorkbenchShellRoute-BaiW_vfb.js +0 -1
- package/dist/public/assets/WorkbenchShellRoute-CxKYZ6uF.css +0 -1
- package/dist/public/assets/WorkspaceDebugDetailPage-BcUUDEyw.js +0 -1
- package/dist/public/assets/WorkspaceDetailPage-zEZ1VARA.js +0 -1
- package/dist/public/assets/WorkspaceHomePage-CR0rqS4e.js +0 -1
- package/dist/public/assets/index-CFyk1rgJ.js +0 -50
- package/dist/public/assets/index-CrU73EIV.css +0 -1
- package/dist/public/assets/relay-entry-C8k5qsl5.js +0 -1
- package/dist/public/assets/terminal-runtime-meta-okQIDzfA.js +0 -1
- package/dist/public/assets/useRegisteredDebugTemplates-B_vXUtSe.js +0 -1
- package/dist/public/assets/workbench-navigation-DoDaQR4z.js +0 -1
|
@@ -7,8 +7,11 @@ import { createId } from "../../shared/utils/id.js";
|
|
|
7
7
|
import { logPerformance } from "../../shared/utils/perf-log.js";
|
|
8
8
|
import { isTerminalDebugEnabled, logTerminalDebug, terminalDebugNowMs } from "../../shared/utils/terminal-debug-log.js";
|
|
9
9
|
import { nowIso } from "../../shared/utils/time.js";
|
|
10
|
+
import { SessionDiscoveryDiagnosticsRepository } from "../../storage/repositories/session-discovery-diagnostics-repository.js";
|
|
11
|
+
import { SessionSourceIndexRepository } from "../../storage/repositories/session-source-index-repository.js";
|
|
10
12
|
import { inspectSessionActivity } from "./session-activity-inspector.js";
|
|
11
13
|
import { SessionActivityAuthorityService } from "./session-activity-authority-service.js";
|
|
14
|
+
import { buildSessionTitleFromContent, normalizeRuntimePromptTitle } from "./session-title-utils.js";
|
|
12
15
|
import { mapSessionProviderError } from "./session-provider-error-mapper.js";
|
|
13
16
|
import { SessionForkRepository } from "../../storage/repositories/session-fork-repository.js";
|
|
14
17
|
import { buildParallelGroupColorToken, resolveParallelDisplayParentSessionId } from "../parallel-sessions/parallel-session-group-service.js";
|
|
@@ -71,6 +74,8 @@ export class SessionHistoryService {
|
|
|
71
74
|
capabilityService;
|
|
72
75
|
sessionActivityAuthorityService;
|
|
73
76
|
sessionForkRepository;
|
|
77
|
+
sessionSourceIndexRepository;
|
|
78
|
+
sessionDiscoveryDiagnosticsRepository;
|
|
74
79
|
providerSessionDeleteCli;
|
|
75
80
|
claudeCodeHomeDir;
|
|
76
81
|
codexModelOptionsService;
|
|
@@ -86,11 +91,13 @@ export class SessionHistoryService {
|
|
|
86
91
|
providerControlRepository;
|
|
87
92
|
taskManager;
|
|
88
93
|
workspaceDiscoveryStatuses = new Map();
|
|
94
|
+
sessionSourceIndexRepairScopes = new Map();
|
|
89
95
|
workspaceStateRefreshStatuses = new Map();
|
|
90
96
|
providerCapabilityCache = new Map();
|
|
91
97
|
codexDirtyBindingRepairStates = new Map();
|
|
92
98
|
streamingDeltaSuppressionDebugState = new Map();
|
|
93
99
|
liveActivityObservationResolvers = new Set();
|
|
100
|
+
sessionTitleChangedObservers = new Set();
|
|
94
101
|
sessionDeletedObservers = new Set();
|
|
95
102
|
workspaceSessionRelations = new Map();
|
|
96
103
|
workspaceStateRefreshTaskSequence = 0;
|
|
@@ -106,6 +113,8 @@ export class SessionHistoryService {
|
|
|
106
113
|
this.sessionMessageOriginRepository = sessionMessageOriginRepository;
|
|
107
114
|
this.sessionActivityAuthorityService = sessionActivityAuthorityService;
|
|
108
115
|
this.sessionForkRepository = sessionForkRepository ?? new SessionForkRepository(db);
|
|
116
|
+
this.sessionSourceIndexRepository = new SessionSourceIndexRepository(db);
|
|
117
|
+
this.sessionDiscoveryDiagnosticsRepository = new SessionDiscoveryDiagnosticsRepository(db);
|
|
109
118
|
this.providerSessionDeleteCli =
|
|
110
119
|
adapterOverrides.providerSessionDeleteCli ?? new CodingnsProviderSessionDeleteCli(config);
|
|
111
120
|
this.taskManager = taskManager;
|
|
@@ -183,6 +192,73 @@ export class SessionHistoryService {
|
|
|
183
192
|
observeBackgroundTaskMetrics() {
|
|
184
193
|
return this.taskManager.observe();
|
|
185
194
|
}
|
|
195
|
+
listWorkspaceDiscoveryDiagnostics(workspaceId, userId, limit = 20) {
|
|
196
|
+
this.getDiscoverableWorkspaceForUserOrThrow(workspaceId, userId);
|
|
197
|
+
const normalizedLimit = Number.isFinite(limit) ? Math.max(1, Math.floor(limit)) : 20;
|
|
198
|
+
return this.sessionDiscoveryDiagnosticsRepository.listByWorkspaceId(workspaceId, normalizedLimit);
|
|
199
|
+
}
|
|
200
|
+
getWorkspaceDiscoveryStatusSummary(workspaceId) {
|
|
201
|
+
const status = this.workspaceDiscoveryStatuses.get(workspaceId);
|
|
202
|
+
if (!status) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
phase: status.phase,
|
|
207
|
+
dirtyReasonCount: status.dirtyReasons.size,
|
|
208
|
+
isComplete: status.isComplete,
|
|
209
|
+
lastRequestedAt: status.lastRequestedAt,
|
|
210
|
+
lastStartedAt: status.lastStartedAt,
|
|
211
|
+
lastCompletedAt: status.lastCompletedAt,
|
|
212
|
+
lastFailedAt: status.lastFailedAt,
|
|
213
|
+
nextAllowedAt: status.nextAllowedAt
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
async repairSessionSourceIndex(input) {
|
|
217
|
+
const workspaceId = input.workspaceId.trim();
|
|
218
|
+
const userId = input.userId.trim();
|
|
219
|
+
this.getDiscoverableWorkspaceForUserOrThrow(workspaceId, userId);
|
|
220
|
+
const provider = normalizeOptionalText(input.provider);
|
|
221
|
+
if (provider && !this.providerRegistry.list().some((adapter) => adapter.providerId === provider)) {
|
|
222
|
+
throw new AppError({
|
|
223
|
+
statusCode: 400,
|
|
224
|
+
errorCode: "INVALID_INPUT",
|
|
225
|
+
detail: "provider 不存在或当前不受支持",
|
|
226
|
+
field: "provider"
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
const sourceKeys = normalizeDistinctTexts(input.sourceKeys);
|
|
230
|
+
const rawStoreRefs = normalizeDistinctTexts(input.rawStoreRefs);
|
|
231
|
+
const existingRecords = this.sessionSourceIndexRepository.listByWorkspaceId(workspaceId);
|
|
232
|
+
const matchedRecords = existingRecords.filter((record) => matchesSessionSourceIndexRepairScope(record, provider, sourceKeys, rawStoreRefs));
|
|
233
|
+
const deletedSourceKeys = matchedRecords.map((record) => record.sourceKey);
|
|
234
|
+
const clearedSourceCount = this.sessionSourceIndexRepository.deleteBySourceKeys(deletedSourceKeys);
|
|
235
|
+
this.sessionSourceIndexRepairScopes.set(workspaceId, {
|
|
236
|
+
provider,
|
|
237
|
+
sourceKeys: new Set(sourceKeys),
|
|
238
|
+
rawStoreRefs: new Set(rawStoreRefs)
|
|
239
|
+
});
|
|
240
|
+
const awaitDiscovery = input.awaitDiscovery === true;
|
|
241
|
+
if (awaitDiscovery) {
|
|
242
|
+
await this.discoverWorkspaceSessions(workspaceId, userId, {
|
|
243
|
+
force: true,
|
|
244
|
+
refreshStateMode: "deferred"
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
this.requestWorkspaceDiscovery(workspaceId, userId, {
|
|
249
|
+
force: true,
|
|
250
|
+
refreshStateMode: "deferred"
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
workspaceId,
|
|
255
|
+
provider,
|
|
256
|
+
sourceKeyCount: sourceKeys.length,
|
|
257
|
+
rawStoreRefCount: rawStoreRefs.length,
|
|
258
|
+
clearedSourceCount,
|
|
259
|
+
awaitDiscovery
|
|
260
|
+
};
|
|
261
|
+
}
|
|
186
262
|
registerLiveActivityObservationResolver(resolver) {
|
|
187
263
|
this.liveActivityObservationResolvers.add(resolver);
|
|
188
264
|
let closed = false;
|
|
@@ -204,12 +280,20 @@ export class SessionHistoryService {
|
|
|
204
280
|
}
|
|
205
281
|
};
|
|
206
282
|
}
|
|
283
|
+
registerSessionTitleChangedObserver(observer) {
|
|
284
|
+
this.sessionTitleChangedObservers.add(observer);
|
|
285
|
+
return {
|
|
286
|
+
close: () => {
|
|
287
|
+
this.sessionTitleChangedObservers.delete(observer);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
}
|
|
207
291
|
registerBackgroundTasks() {
|
|
208
292
|
if (!this.taskManager.has(HOST_TASK_TYPES.workspaceDiscovery)) {
|
|
209
293
|
this.taskManager.register({
|
|
210
294
|
taskType: HOST_TASK_TYPES.workspaceDiscovery,
|
|
211
295
|
executionLane: "host_background",
|
|
212
|
-
run: async ({ workspaceId, userId, refreshStateMode }, context) => this.runDiscoverWorkspaceSessions(workspaceId, userId, refreshStateMode, context.signal)
|
|
296
|
+
run: async ({ workspaceId, userId, refreshStateMode }, context) => this.runDiscoverWorkspaceSessions(workspaceId, userId, refreshStateMode, context.signal, context.taskId)
|
|
213
297
|
});
|
|
214
298
|
}
|
|
215
299
|
if (!this.taskManager.has(HOST_TASK_TYPES.workspaceDiscoveryScan)) {
|
|
@@ -238,23 +322,21 @@ export class SessionHistoryService {
|
|
|
238
322
|
this.taskManager.register({
|
|
239
323
|
taskType: HOST_TASK_TYPES.sessionCodexTitleGenerate,
|
|
240
324
|
executionLane: "external_process",
|
|
325
|
+
concurrency: 1,
|
|
241
326
|
timeoutMs: 45_000,
|
|
242
|
-
run: async ({ sessionId }, context) => this.runCodexSessionTitleGeneration(sessionId, context.signal)
|
|
327
|
+
run: async ({ sessionId, firstUserMessage }, context) => this.runCodexSessionTitleGeneration(sessionId, firstUserMessage, context.signal)
|
|
243
328
|
});
|
|
244
329
|
}
|
|
245
330
|
}
|
|
246
331
|
async discoverWorkspaceSessions(workspaceId, userId, options) {
|
|
332
|
+
this.getDiscoverableWorkspaceForUserOrThrow(workspaceId, userId);
|
|
333
|
+
this.markWorkspaceDiscoveryRequested(workspaceId, "session_history.discover_workspace_sessions");
|
|
247
334
|
const maxAgeMs = options?.maxAgeMs ?? 0;
|
|
248
335
|
const force = options?.force ?? false;
|
|
249
336
|
const discoveryStatus = this.workspaceDiscoveryStatuses.get(workspaceId);
|
|
250
|
-
const lastRefreshedAt = discoveryStatus?.refreshedAt ?? 0;
|
|
251
337
|
const now = Date.now();
|
|
252
|
-
const isPartialCoolingDown = discoveryStatus
|
|
253
|
-
|
|
254
|
-
now < discoveryStatus.partialCooldownUntil;
|
|
255
|
-
const isCompleteAndFresh = discoveryStatus?.isComplete === true &&
|
|
256
|
-
maxAgeMs > 0 &&
|
|
257
|
-
now - lastRefreshedAt <= maxAgeMs;
|
|
338
|
+
const isPartialCoolingDown = this.isWorkspaceDiscoveryPartialCoolingDown(discoveryStatus, now);
|
|
339
|
+
const isCompleteAndFresh = this.isWorkspaceDiscoveryCompleteAndFresh(discoveryStatus, maxAgeMs, now);
|
|
258
340
|
if (!force &&
|
|
259
341
|
discoveryStatus &&
|
|
260
342
|
(isPartialCoolingDown || isCompleteAndFresh)) {
|
|
@@ -274,6 +356,13 @@ export class SessionHistoryService {
|
|
|
274
356
|
requestWorkspaceDiscovery(workspaceId, userId, options) {
|
|
275
357
|
const maxAgeMs = options?.maxAgeMs ?? WORKSPACE_DISCOVERY_BACKGROUND_MAX_AGE_MS;
|
|
276
358
|
const force = options?.force ?? false;
|
|
359
|
+
if (!this.isWorkspaceDiscoverableForUser(workspaceId, userId)) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const discoveryStatus = this.markWorkspaceDiscoveryRequested(workspaceId, "session_history.request_workspace_discovery");
|
|
363
|
+
if (!force && discoveryStatus.phase === "running") {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
277
366
|
if (!force && !this.needsWorkspaceDiscovery(workspaceId, maxAgeMs)) {
|
|
278
367
|
return;
|
|
279
368
|
}
|
|
@@ -301,19 +390,27 @@ export class SessionHistoryService {
|
|
|
301
390
|
});
|
|
302
391
|
}
|
|
303
392
|
needsWorkspaceDiscovery(workspaceId, maxAgeMs) {
|
|
304
|
-
if (maxAgeMs <= 0) {
|
|
305
|
-
return true;
|
|
306
|
-
}
|
|
307
393
|
const discoveryStatus = this.workspaceDiscoveryStatuses.get(workspaceId);
|
|
308
394
|
if (!discoveryStatus) {
|
|
309
395
|
return true;
|
|
310
396
|
}
|
|
397
|
+
if (discoveryStatus.phase === "running") {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
if (discoveryStatus.phase === "failed") {
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
if (discoveryStatus.dirtyReasons.size > 0) {
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
311
406
|
if (!discoveryStatus.isComplete &&
|
|
312
|
-
(discoveryStatus.
|
|
313
|
-
|
|
407
|
+
!this.isWorkspaceDiscoveryPartialCoolingDown(discoveryStatus, Date.now())) {
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
if (maxAgeMs <= 0) {
|
|
314
411
|
return true;
|
|
315
412
|
}
|
|
316
|
-
return Date.now()
|
|
413
|
+
return !this.isWorkspaceDiscoveryCompleteAndFresh(discoveryStatus, maxAgeMs, Date.now());
|
|
317
414
|
}
|
|
318
415
|
async readSessionHistory(sessionId, cursor, limit, direction = "forward", userId) {
|
|
319
416
|
const startedAt = Date.now();
|
|
@@ -461,7 +558,6 @@ export class SessionHistoryService {
|
|
|
461
558
|
async syncSessionTitle(sessionId, signal) {
|
|
462
559
|
const binding = this.getBindingOrThrow(sessionId);
|
|
463
560
|
await this.syncSessionTitleFromProvider(sessionId, binding, signal);
|
|
464
|
-
this.requestCodexTitleGenerationIfNeeded(sessionId, "session_history.sync_session_title");
|
|
465
561
|
}
|
|
466
562
|
async syncWorkspaceSessionTitles(workspaceId, userId, concurrency = 1, signal) {
|
|
467
563
|
const sessions = this.sessionIndexRepository.listByWorkspace(workspaceId, userId);
|
|
@@ -1167,7 +1263,6 @@ export class SessionHistoryService {
|
|
|
1167
1263
|
return null;
|
|
1168
1264
|
}
|
|
1169
1265
|
await this.syncSessionTitleFromProvider(sessionId, binding);
|
|
1170
|
-
this.requestCodexTitleGenerationIfNeeded(sessionId, "session_history.read_recent_history");
|
|
1171
1266
|
const snapshot = this.sessionStatusSnapshotRepository.findBySessionId(sessionId);
|
|
1172
1267
|
this.upsertSnapshot(sessionId, {
|
|
1173
1268
|
syncStatus: "idle",
|
|
@@ -1424,10 +1519,14 @@ export class SessionHistoryService {
|
|
|
1424
1519
|
}
|
|
1425
1520
|
}
|
|
1426
1521
|
}
|
|
1427
|
-
async runDiscoverWorkspaceSessions(workspaceId, userId, refreshStateMode = "inline", signal) {
|
|
1522
|
+
async runDiscoverWorkspaceSessions(workspaceId, userId, refreshStateMode = "inline", signal, taskId) {
|
|
1428
1523
|
const startedAt = Date.now();
|
|
1429
1524
|
const debugStartedAtMs = terminalDebugNowMs();
|
|
1430
|
-
const workspace = this.
|
|
1525
|
+
const workspace = this.getDiscoverableWorkspaceForUserOrThrow(workspaceId, userId);
|
|
1526
|
+
const discoveryStatus = this.getOrCreateWorkspaceDiscoveryStatus(workspaceId);
|
|
1527
|
+
discoveryStatus.phase = "running";
|
|
1528
|
+
discoveryStatus.lastStartedAt = startedAt;
|
|
1529
|
+
discoveryStatus.runningTaskId = taskId ?? `workspace.discovery:${workspaceId}:${startedAt}`;
|
|
1431
1530
|
let discoverDurationMs = 0;
|
|
1432
1531
|
let persistDurationMs = 0;
|
|
1433
1532
|
let persistPass1DurationMs = 0;
|
|
@@ -1441,14 +1540,16 @@ export class SessionHistoryService {
|
|
|
1441
1540
|
let listItemsDurationMs = 0;
|
|
1442
1541
|
let refreshStateDurationMs = 0;
|
|
1443
1542
|
const refreshStateCount = 10;
|
|
1543
|
+
const activeRepairScope = this.sessionSourceIndexRepairScopes.get(workspaceId) ?? null;
|
|
1444
1544
|
try {
|
|
1445
1545
|
const discoverStartedAt = Date.now();
|
|
1446
1546
|
const existingWorkspaceSessions = this.sessionIndexRepository.listByWorkspace(workspaceId, userId);
|
|
1547
|
+
const existingWorkspaceSourceIndexes = this.sessionSourceIndexRepository.listByWorkspaceId(workspaceId);
|
|
1447
1548
|
const enabledProviders = this.providerRegistry
|
|
1448
1549
|
.list()
|
|
1449
1550
|
.map((adapter) => adapter.providerId)
|
|
1450
1551
|
.filter((providerId) => this.isProviderEnabled(providerId));
|
|
1451
|
-
const knownSessions = this.buildKnownSessionSummaries(existingWorkspaceSessions.filter((session) => enabledProviders.includes(session.provider)), workspace.path);
|
|
1552
|
+
const knownSessions = this.buildKnownSessionSummaries(existingWorkspaceSessions.filter((session) => enabledProviders.includes(session.provider)), existingWorkspaceSourceIndexes.filter((record) => enabledProviders.includes(record.provider)), workspace.path, activeRepairScope);
|
|
1452
1553
|
const discoveryHandle = this.taskManager.enqueue(HOST_TASK_TYPES.workspaceDiscoveryScan, {
|
|
1453
1554
|
key: workspaceId,
|
|
1454
1555
|
source: "session_history.workspace_discovery.scan",
|
|
@@ -1465,6 +1566,7 @@ export class SessionHistoryService {
|
|
|
1465
1566
|
const sessions = discovery.sessions;
|
|
1466
1567
|
discoverDurationMs = Date.now() - discoverStartedAt;
|
|
1467
1568
|
const timestamp = nowIso();
|
|
1569
|
+
this.persistDiscoveryDiagnostics(workspaceId, "session_history.workspace_discovery.scan", discovery, timestamp);
|
|
1468
1570
|
const discoveredSessionIds = new Map();
|
|
1469
1571
|
const persistedSessions = [];
|
|
1470
1572
|
const claimedPendingSessionIds = new Set();
|
|
@@ -1633,6 +1735,7 @@ export class SessionHistoryService {
|
|
|
1633
1735
|
persistPass2DurationMs = Date.now() - persistPass2StartedAt;
|
|
1634
1736
|
persistPass2BatchCount = persistPass2Stats.batchCount;
|
|
1635
1737
|
persistPass2MaxBatchMs = persistPass2Stats.maxBatchMs;
|
|
1738
|
+
this.persistSessionSourceIndexRecords(workspaceId, workspace.path, sessions, existingWorkspaceSourceIndexes, timestamp);
|
|
1636
1739
|
persistDurationMs = persistPass1DurationMs + relationMapDurationMs + persistPass2DurationMs;
|
|
1637
1740
|
if (discovery.isComplete) {
|
|
1638
1741
|
const cleanupStartedAt = Date.now();
|
|
@@ -1641,19 +1744,24 @@ export class SessionHistoryService {
|
|
|
1641
1744
|
}
|
|
1642
1745
|
this.workspaceSessionRelations.set(workspaceId, relationMap);
|
|
1643
1746
|
this.observeDiscoveredProviderActivity(sessions, discoveredSessionIds, userId, timestamp);
|
|
1644
|
-
for (const persisted of persistedSessions) {
|
|
1645
|
-
this.requestCodexTitleGenerationIfNeeded(persisted.sessionId, "session_history.discover_workspace_sessions.codex_title");
|
|
1646
|
-
}
|
|
1647
1747
|
const listItemsStartedAt = Date.now();
|
|
1648
1748
|
const items = this.sessionIndexRepository.listByWorkspace(workspaceId, userId);
|
|
1649
1749
|
listItemsDurationMs = Date.now() - listItemsStartedAt;
|
|
1650
1750
|
const refreshCandidates = buildSessionStateRefreshCandidates(items, refreshStateCount);
|
|
1651
1751
|
this.workspaceDiscoveryStatuses.set(workspaceId, {
|
|
1752
|
+
...discoveryStatus,
|
|
1753
|
+
phase: discovery.isComplete ? "fresh" : "cooldown",
|
|
1754
|
+
dirtyReasons: new Set(),
|
|
1652
1755
|
refreshedAt: Date.now(),
|
|
1653
1756
|
isComplete: discovery.isComplete,
|
|
1654
1757
|
partialCooldownUntil: discovery.isComplete
|
|
1655
1758
|
? null
|
|
1656
|
-
: Date.now() + WORKSPACE_DISCOVERY_PARTIAL_COOLDOWN_MS
|
|
1759
|
+
: Date.now() + WORKSPACE_DISCOVERY_PARTIAL_COOLDOWN_MS,
|
|
1760
|
+
lastCompletedAt: Date.now(),
|
|
1761
|
+
nextAllowedAt: discovery.isComplete
|
|
1762
|
+
? null
|
|
1763
|
+
: Date.now() + WORKSPACE_DISCOVERY_PARTIAL_COOLDOWN_MS,
|
|
1764
|
+
runningTaskId: null
|
|
1657
1765
|
});
|
|
1658
1766
|
const refreshStateStartedAt = Date.now();
|
|
1659
1767
|
if (refreshStateMode === "inline") {
|
|
@@ -1728,6 +1836,13 @@ export class SessionHistoryService {
|
|
|
1728
1836
|
return nextItems;
|
|
1729
1837
|
}
|
|
1730
1838
|
catch (error) {
|
|
1839
|
+
const failedAt = Date.now();
|
|
1840
|
+
this.workspaceDiscoveryStatuses.set(workspaceId, {
|
|
1841
|
+
...discoveryStatus,
|
|
1842
|
+
phase: "failed",
|
|
1843
|
+
lastFailedAt: failedAt,
|
|
1844
|
+
runningTaskId: null
|
|
1845
|
+
});
|
|
1731
1846
|
logPerformance("workspace.discover_sessions.failed", Date.now() - startedAt, {
|
|
1732
1847
|
workspaceId,
|
|
1733
1848
|
workspacePath: workspace.path,
|
|
@@ -1751,6 +1866,11 @@ export class SessionHistoryService {
|
|
|
1751
1866
|
});
|
|
1752
1867
|
throw error;
|
|
1753
1868
|
}
|
|
1869
|
+
finally {
|
|
1870
|
+
if (activeRepairScope) {
|
|
1871
|
+
this.sessionSourceIndexRepairScopes.delete(workspaceId);
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1754
1874
|
}
|
|
1755
1875
|
async readPage(sessionId, provider, providerSessionId, rawStoreRef, cursor, limit, direction = "forward", knownTotalMessageCount = null) {
|
|
1756
1876
|
if (shouldShortCircuitClaudePendingHistory(provider, providerSessionId, rawStoreRef)) {
|
|
@@ -2221,7 +2341,6 @@ export class SessionHistoryService {
|
|
|
2221
2341
|
return;
|
|
2222
2342
|
}
|
|
2223
2343
|
await this.syncSessionTitleFromProvider(sessionId, binding);
|
|
2224
|
-
this.requestCodexTitleGenerationIfNeeded(sessionId, "session_history.publish_history_envelope");
|
|
2225
2344
|
const snapshot = this.sessionStatusSnapshotRepository.findBySessionId(sessionId);
|
|
2226
2345
|
this.upsertSnapshot(sessionId, {
|
|
2227
2346
|
syncStatus: "idle",
|
|
@@ -2274,15 +2393,20 @@ export class SessionHistoryService {
|
|
|
2274
2393
|
updatedAt: nowIso()
|
|
2275
2394
|
});
|
|
2276
2395
|
}
|
|
2277
|
-
|
|
2278
|
-
|
|
2396
|
+
requestCodexTitleGenerationForNewSession(sessionId, firstUserMessage) {
|
|
2397
|
+
const normalizedFirstUserMessage = firstUserMessage.trim();
|
|
2398
|
+
if (!normalizedFirstUserMessage) {
|
|
2399
|
+
return;
|
|
2400
|
+
}
|
|
2401
|
+
if (!this.shouldRequestCodexTitleGeneration(sessionId, normalizedFirstUserMessage)) {
|
|
2279
2402
|
return;
|
|
2280
2403
|
}
|
|
2281
2404
|
const handle = this.taskManager.enqueue(HOST_TASK_TYPES.sessionCodexTitleGenerate, {
|
|
2282
2405
|
key: sessionId,
|
|
2283
|
-
source,
|
|
2406
|
+
source: "session_history.new_codex_session_title",
|
|
2284
2407
|
input: {
|
|
2285
|
-
sessionId
|
|
2408
|
+
sessionId,
|
|
2409
|
+
firstUserMessage: normalizedFirstUserMessage
|
|
2286
2410
|
}
|
|
2287
2411
|
});
|
|
2288
2412
|
if (handle.deduped) {
|
|
@@ -2298,7 +2422,7 @@ export class SessionHistoryService {
|
|
|
2298
2422
|
});
|
|
2299
2423
|
});
|
|
2300
2424
|
}
|
|
2301
|
-
shouldRequestCodexTitleGeneration(sessionId) {
|
|
2425
|
+
shouldRequestCodexTitleGeneration(sessionId, firstUserMessage) {
|
|
2302
2426
|
const binding = this.sessionBindingRepository.findBySessionId(sessionId);
|
|
2303
2427
|
const index = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
|
|
2304
2428
|
if (!binding || !index || binding.provider !== "codex") {
|
|
@@ -2307,12 +2431,9 @@ export class SessionHistoryService {
|
|
|
2307
2431
|
if (isPendingBindingValue(binding.providerSessionId) || isPendingBindingValue(binding.rawStoreRef)) {
|
|
2308
2432
|
return false;
|
|
2309
2433
|
}
|
|
2310
|
-
|
|
2311
|
-
return false;
|
|
2312
|
-
}
|
|
2313
|
-
return true;
|
|
2434
|
+
return shouldGenerateCodexSessionTitle(index.title, firstUserMessage);
|
|
2314
2435
|
}
|
|
2315
|
-
async runCodexSessionTitleGeneration(sessionId, signal) {
|
|
2436
|
+
async runCodexSessionTitleGeneration(sessionId, firstUserMessage, signal) {
|
|
2316
2437
|
const binding = this.sessionBindingRepository.findBySessionId(sessionId);
|
|
2317
2438
|
const index = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
|
|
2318
2439
|
if (!binding || !index || binding.provider !== "codex") {
|
|
@@ -2321,20 +2442,17 @@ export class SessionHistoryService {
|
|
|
2321
2442
|
if (isPendingBindingValue(binding.providerSessionId) || isPendingBindingValue(binding.rawStoreRef)) {
|
|
2322
2443
|
return { title: null };
|
|
2323
2444
|
}
|
|
2324
|
-
const page = await this.sessionSyncService.readHistory(binding.provider, binding.providerSessionId, binding.rawStoreRef, null, 16, "forward").catch(() => null);
|
|
2325
|
-
if (!page) {
|
|
2326
|
-
return { title: null };
|
|
2327
|
-
}
|
|
2328
|
-
const firstUserMessage = page.messages.find((message) => message.role === "user")?.content ?? null;
|
|
2329
2445
|
if (!shouldGenerateCodexSessionTitle(index.title, firstUserMessage)) {
|
|
2330
2446
|
return { title: null };
|
|
2331
2447
|
}
|
|
2332
2448
|
const title = await this.codexSessionTitleGenerator.generate({
|
|
2333
2449
|
currentTitle: index.title,
|
|
2334
|
-
messages:
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2450
|
+
messages: [
|
|
2451
|
+
{
|
|
2452
|
+
role: "user",
|
|
2453
|
+
content: firstUserMessage
|
|
2454
|
+
}
|
|
2455
|
+
],
|
|
2338
2456
|
signal
|
|
2339
2457
|
});
|
|
2340
2458
|
if (!title || !shouldApplyGeneratedCodexSessionTitle(title, firstUserMessage)) {
|
|
@@ -2348,9 +2466,29 @@ export class SessionHistoryService {
|
|
|
2348
2466
|
title,
|
|
2349
2467
|
updatedAt: nowIso()
|
|
2350
2468
|
});
|
|
2469
|
+
await this.notifySessionTitleChanged({
|
|
2470
|
+
sessionId,
|
|
2471
|
+
userId: binding.userId,
|
|
2472
|
+
workspaceId: binding.workspaceId,
|
|
2473
|
+
title
|
|
2474
|
+
});
|
|
2351
2475
|
}
|
|
2352
2476
|
return { title };
|
|
2353
2477
|
}
|
|
2478
|
+
async notifySessionTitleChanged(input) {
|
|
2479
|
+
const userId = input.userId ?? this.workspaceRepository.findById(input.workspaceId)?.ownerUserId;
|
|
2480
|
+
if (!userId) {
|
|
2481
|
+
return;
|
|
2482
|
+
}
|
|
2483
|
+
await Promise.allSettled(Array.from(this.sessionTitleChangedObservers).map(async (observer) => {
|
|
2484
|
+
await observer({
|
|
2485
|
+
sessionId: input.sessionId,
|
|
2486
|
+
userId,
|
|
2487
|
+
workspaceId: input.workspaceId,
|
|
2488
|
+
title: input.title
|
|
2489
|
+
});
|
|
2490
|
+
}));
|
|
2491
|
+
}
|
|
2354
2492
|
async readFirstUserMessageTitleForSync(binding) {
|
|
2355
2493
|
const pageSize = 20;
|
|
2356
2494
|
const maxPages = 3;
|
|
@@ -2426,6 +2564,65 @@ export class SessionHistoryService {
|
|
|
2426
2564
|
}
|
|
2427
2565
|
return workspace;
|
|
2428
2566
|
}
|
|
2567
|
+
getOrCreateWorkspaceDiscoveryStatus(workspaceId) {
|
|
2568
|
+
const existing = this.workspaceDiscoveryStatuses.get(workspaceId);
|
|
2569
|
+
if (existing) {
|
|
2570
|
+
return existing;
|
|
2571
|
+
}
|
|
2572
|
+
const created = {
|
|
2573
|
+
phase: "fresh",
|
|
2574
|
+
dirtyReasons: new Set(),
|
|
2575
|
+
refreshedAt: 0,
|
|
2576
|
+
isComplete: false,
|
|
2577
|
+
partialCooldownUntil: null,
|
|
2578
|
+
lastRequestedAt: null,
|
|
2579
|
+
lastStartedAt: null,
|
|
2580
|
+
lastCompletedAt: null,
|
|
2581
|
+
lastFailedAt: null,
|
|
2582
|
+
nextAllowedAt: null,
|
|
2583
|
+
runningTaskId: null
|
|
2584
|
+
};
|
|
2585
|
+
this.workspaceDiscoveryStatuses.set(workspaceId, created);
|
|
2586
|
+
return created;
|
|
2587
|
+
}
|
|
2588
|
+
markWorkspaceDiscoveryRequested(workspaceId, reason) {
|
|
2589
|
+
const status = this.getOrCreateWorkspaceDiscoveryStatus(workspaceId);
|
|
2590
|
+
status.lastRequestedAt = Date.now();
|
|
2591
|
+
if (status.phase === "running") {
|
|
2592
|
+
status.dirtyReasons.add(reason);
|
|
2593
|
+
}
|
|
2594
|
+
return status;
|
|
2595
|
+
}
|
|
2596
|
+
isWorkspaceDiscoveryPartialCoolingDown(status, now) {
|
|
2597
|
+
return Boolean(status
|
|
2598
|
+
&& status.isComplete === false
|
|
2599
|
+
&& status.partialCooldownUntil !== null
|
|
2600
|
+
&& now < status.partialCooldownUntil);
|
|
2601
|
+
}
|
|
2602
|
+
isWorkspaceDiscoveryCompleteAndFresh(status, maxAgeMs, now) {
|
|
2603
|
+
if (!status || !status.isComplete || maxAgeMs <= 0) {
|
|
2604
|
+
return false;
|
|
2605
|
+
}
|
|
2606
|
+
return now - status.refreshedAt <= maxAgeMs;
|
|
2607
|
+
}
|
|
2608
|
+
isWorkspaceDiscoverableForUser(workspaceId, userId) {
|
|
2609
|
+
const workspace = this.workspaceRepository.findById(workspaceId);
|
|
2610
|
+
if (!workspace || workspace.removedAt || workspace.ownerUserId !== userId) {
|
|
2611
|
+
return false;
|
|
2612
|
+
}
|
|
2613
|
+
return true;
|
|
2614
|
+
}
|
|
2615
|
+
getDiscoverableWorkspaceForUserOrThrow(workspaceId, userId) {
|
|
2616
|
+
const workspace = this.getWorkspaceForUserOrThrow(workspaceId, userId);
|
|
2617
|
+
if (workspace.removedAt) {
|
|
2618
|
+
throw new AppError({
|
|
2619
|
+
statusCode: 404,
|
|
2620
|
+
errorCode: "WORKSPACE_NOT_FOUND",
|
|
2621
|
+
detail: "工作区不存在"
|
|
2622
|
+
});
|
|
2623
|
+
}
|
|
2624
|
+
return workspace;
|
|
2625
|
+
}
|
|
2429
2626
|
getBindingForUserOrThrow(sessionId, userId) {
|
|
2430
2627
|
const binding = this.sessionBindingRepository.findBySessionIdForUser(sessionId, userId);
|
|
2431
2628
|
if (!binding) {
|
|
@@ -3165,24 +3362,121 @@ export class SessionHistoryService {
|
|
|
3165
3362
|
}
|
|
3166
3363
|
}
|
|
3167
3364
|
}
|
|
3168
|
-
buildKnownSessionSummaries(sessions, workspacePath) {
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
.
|
|
3172
|
-
|
|
3365
|
+
buildKnownSessionSummaries(sessions, sourceIndexes, workspacePath, repairScope = null) {
|
|
3366
|
+
const merged = new Map();
|
|
3367
|
+
for (const session of sessions) {
|
|
3368
|
+
if (this.isPendingSessionAlias(session) || shouldSkipClaudePendingBinding(session)) {
|
|
3369
|
+
continue;
|
|
3370
|
+
}
|
|
3371
|
+
const sourceKey = buildSessionSourceKey(session.provider, session.providerSessionId, session.rawStoreRef);
|
|
3372
|
+
if (shouldExcludeKnownSessionFromRepairScope(repairScope, session.provider, sourceKey, session.rawStoreRef)) {
|
|
3373
|
+
continue;
|
|
3374
|
+
}
|
|
3173
3375
|
const stats = safeStat(session.rawStoreRef);
|
|
3174
|
-
|
|
3376
|
+
const summary = {
|
|
3175
3377
|
provider: session.provider,
|
|
3176
3378
|
providerSessionId: session.providerSessionId,
|
|
3177
3379
|
title: session.title,
|
|
3178
3380
|
workspacePath,
|
|
3179
3381
|
rawStoreRef: session.rawStoreRef,
|
|
3382
|
+
isArchived: session.isArchived,
|
|
3180
3383
|
lastMessageAt: session.lastMessageAt,
|
|
3181
3384
|
messageCount: session.messageCount,
|
|
3182
3385
|
sourceMtimeMs: stats?.mtimeMs,
|
|
3183
3386
|
sourceSizeBytes: stats?.size
|
|
3184
3387
|
};
|
|
3185
|
-
|
|
3388
|
+
merged.set(buildKnownSessionSummaryKey(summary.provider, summary.providerSessionId, summary.rawStoreRef), summary);
|
|
3389
|
+
}
|
|
3390
|
+
for (const record of sourceIndexes) {
|
|
3391
|
+
if (record.deletedAt) {
|
|
3392
|
+
continue;
|
|
3393
|
+
}
|
|
3394
|
+
if (!record.providerSessionId || !record.rawStoreRef) {
|
|
3395
|
+
continue;
|
|
3396
|
+
}
|
|
3397
|
+
if (shouldExcludeKnownSessionFromRepairScope(repairScope, record.provider, record.sourceKey, record.rawStoreRef)) {
|
|
3398
|
+
continue;
|
|
3399
|
+
}
|
|
3400
|
+
const key = buildKnownSessionSummaryKey(record.provider, record.providerSessionId, record.rawStoreRef);
|
|
3401
|
+
if (merged.has(key)) {
|
|
3402
|
+
continue;
|
|
3403
|
+
}
|
|
3404
|
+
merged.set(key, {
|
|
3405
|
+
provider: record.provider,
|
|
3406
|
+
providerSessionId: record.providerSessionId,
|
|
3407
|
+
title: record.title?.trim() || record.providerSessionId,
|
|
3408
|
+
workspacePath: record.workspacePath?.trim() || workspacePath,
|
|
3409
|
+
rawStoreRef: record.rawStoreRef,
|
|
3410
|
+
isArchived: record.isArchivedHint ?? undefined,
|
|
3411
|
+
lastMessageAt: record.lastMessageAt,
|
|
3412
|
+
messageCount: Math.max(0, record.messageCount ?? 0),
|
|
3413
|
+
sourceMtimeMs: record.fingerprintMtimeMs ?? undefined,
|
|
3414
|
+
sourceSizeBytes: record.fingerprintSizeBytes ?? undefined
|
|
3415
|
+
});
|
|
3416
|
+
}
|
|
3417
|
+
return [...merged.values()];
|
|
3418
|
+
}
|
|
3419
|
+
persistSessionSourceIndexRecords(workspaceId, workspacePath, sessions, existingSourceIndexes, timestamp) {
|
|
3420
|
+
const existingByKey = new Map(existingSourceIndexes.map((record) => [record.sourceKey, record]));
|
|
3421
|
+
for (const session of sessions) {
|
|
3422
|
+
const sourceKind = inferSessionSourceKind(session.rawStoreRef);
|
|
3423
|
+
const sourceKey = buildSessionSourceKey(session.provider, session.providerSessionId, session.rawStoreRef);
|
|
3424
|
+
const stats = safeStat(session.rawStoreRef);
|
|
3425
|
+
const existing = existingByKey.get(sourceKey);
|
|
3426
|
+
this.sessionSourceIndexRepository.upsert({
|
|
3427
|
+
sourceKey,
|
|
3428
|
+
provider: session.provider,
|
|
3429
|
+
sourceKind,
|
|
3430
|
+
workspaceId,
|
|
3431
|
+
providerSessionId: session.providerSessionId,
|
|
3432
|
+
rawStoreRef: session.rawStoreRef,
|
|
3433
|
+
workspacePath,
|
|
3434
|
+
fingerprintMtimeMs: stats?.mtimeMs ?? existing?.fingerprintMtimeMs ?? null,
|
|
3435
|
+
fingerprintSizeBytes: stats?.size ?? existing?.fingerprintSizeBytes ?? null,
|
|
3436
|
+
fingerprintInode: existing?.fingerprintInode ?? null,
|
|
3437
|
+
fingerprintVersion: existing?.fingerprintVersion ?? null,
|
|
3438
|
+
title: session.title,
|
|
3439
|
+
messageCount: session.messageCount,
|
|
3440
|
+
lastMessageAt: session.lastMessageAt,
|
|
3441
|
+
isArchivedHint: session.isArchived ?? existing?.isArchivedHint ?? null,
|
|
3442
|
+
lastParsedAt: timestamp,
|
|
3443
|
+
lastVerifiedAt: timestamp,
|
|
3444
|
+
sampleDueAt: existing?.sampleDueAt ?? null,
|
|
3445
|
+
deletedAt: null,
|
|
3446
|
+
createdAt: existing?.createdAt ?? timestamp,
|
|
3447
|
+
updatedAt: timestamp
|
|
3448
|
+
});
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
persistDiscoveryDiagnostics(workspaceId, triggerSource, discovery, timestamp) {
|
|
3452
|
+
try {
|
|
3453
|
+
for (const entry of discovery.providerDiagnostics ?? []) {
|
|
3454
|
+
const record = {
|
|
3455
|
+
id: createId(),
|
|
3456
|
+
workspaceId,
|
|
3457
|
+
triggerSource,
|
|
3458
|
+
provider: entry.provider,
|
|
3459
|
+
isComplete: entry.isComplete,
|
|
3460
|
+
status: entry.status,
|
|
3461
|
+
durationMs: Math.max(0, Math.round(entry.durationMs)),
|
|
3462
|
+
sessionCount: Math.max(0, entry.sessionCount),
|
|
3463
|
+
scannedFiles: Math.max(0, entry.scannedFiles ?? 0),
|
|
3464
|
+
skippedByFingerprint: Math.max(0, entry.skippedByMtimeSize ?? 0),
|
|
3465
|
+
parsedFiles: Math.max(0, entry.parsedFiles ?? 0),
|
|
3466
|
+
bytesRead: Math.max(0, entry.bytesRead ?? 0),
|
|
3467
|
+
createdAt: timestamp
|
|
3468
|
+
};
|
|
3469
|
+
this.sessionDiscoveryDiagnosticsRepository.insert(record);
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
catch (error) {
|
|
3473
|
+
console.warn("[session-discovery-diagnostics-persist-failed]", {
|
|
3474
|
+
workspaceId,
|
|
3475
|
+
triggerSource,
|
|
3476
|
+
providerCount: discovery.providerDiagnostics?.length ?? 0,
|
|
3477
|
+
error
|
|
3478
|
+
});
|
|
3479
|
+
}
|
|
3186
3480
|
}
|
|
3187
3481
|
async refreshSessionState(sessionId, userId) {
|
|
3188
3482
|
const binding = this.getBindingOrThrow(sessionId);
|
|
@@ -3759,6 +4053,67 @@ function pickLaterIso(left, right) {
|
|
|
3759
4053
|
function buildProviderSessionKey(provider, providerSessionId) {
|
|
3760
4054
|
return `${provider}::${providerSessionId}`;
|
|
3761
4055
|
}
|
|
4056
|
+
function normalizeOptionalText(value) {
|
|
4057
|
+
const normalized = value?.trim();
|
|
4058
|
+
return normalized ? normalized : null;
|
|
4059
|
+
}
|
|
4060
|
+
function normalizeDistinctTexts(values) {
|
|
4061
|
+
if (!Array.isArray(values)) {
|
|
4062
|
+
return [];
|
|
4063
|
+
}
|
|
4064
|
+
const unique = new Set();
|
|
4065
|
+
for (const value of values) {
|
|
4066
|
+
const normalized = value?.trim();
|
|
4067
|
+
if (normalized) {
|
|
4068
|
+
unique.add(normalized);
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
return [...unique];
|
|
4072
|
+
}
|
|
4073
|
+
function matchesSessionSourceIndexRepairScope(record, provider, sourceKeys, rawStoreRefs) {
|
|
4074
|
+
if (provider && record.provider !== provider) {
|
|
4075
|
+
return false;
|
|
4076
|
+
}
|
|
4077
|
+
if (sourceKeys.length === 0 && rawStoreRefs.length === 0) {
|
|
4078
|
+
return true;
|
|
4079
|
+
}
|
|
4080
|
+
return sourceKeys.includes(record.sourceKey)
|
|
4081
|
+
|| (record.rawStoreRef ? rawStoreRefs.includes(record.rawStoreRef) : false);
|
|
4082
|
+
}
|
|
4083
|
+
function shouldExcludeKnownSessionFromRepairScope(scope, provider, sourceKey, rawStoreRef) {
|
|
4084
|
+
if (!scope) {
|
|
4085
|
+
return false;
|
|
4086
|
+
}
|
|
4087
|
+
if (scope.provider && scope.provider !== provider) {
|
|
4088
|
+
return false;
|
|
4089
|
+
}
|
|
4090
|
+
if (scope.sourceKeys.size === 0 && scope.rawStoreRefs.size === 0) {
|
|
4091
|
+
return true;
|
|
4092
|
+
}
|
|
4093
|
+
return scope.sourceKeys.has(sourceKey) || scope.rawStoreRefs.has(rawStoreRef);
|
|
4094
|
+
}
|
|
4095
|
+
function buildKnownSessionSummaryKey(provider, providerSessionId, rawStoreRef) {
|
|
4096
|
+
return `${provider}::${providerSessionId}::${rawStoreRef}`;
|
|
4097
|
+
}
|
|
4098
|
+
function buildSessionSourceKey(provider, providerSessionId, rawStoreRef) {
|
|
4099
|
+
const normalizedRawStoreRef = rawStoreRef.trim();
|
|
4100
|
+
if (normalizedRawStoreRef.length > 0) {
|
|
4101
|
+
return `${provider}:raw:${normalizedRawStoreRef}`;
|
|
4102
|
+
}
|
|
4103
|
+
return `${provider}:session:${providerSessionId}`;
|
|
4104
|
+
}
|
|
4105
|
+
function inferSessionSourceKind(rawStoreRef) {
|
|
4106
|
+
if (rawStoreRef.endsWith(".jsonl")) {
|
|
4107
|
+
return "jsonl";
|
|
4108
|
+
}
|
|
4109
|
+
if (rawStoreRef.startsWith("opencode://") || rawStoreRef.startsWith("server://")) {
|
|
4110
|
+
return "server_session";
|
|
4111
|
+
}
|
|
4112
|
+
if (rawStoreRef.endsWith(".sqlite") || rawStoreRef.includes(".sqlite:")) {
|
|
4113
|
+
return "sqlite_row";
|
|
4114
|
+
}
|
|
4115
|
+
return "index_entry";
|
|
4116
|
+
}
|
|
3762
4117
|
function normalizeSessionBindingSnapshot(sessionId, snapshot) {
|
|
3763
4118
|
if (snapshot.provider !== "claude-code" ||
|
|
3764
4119
|
!(isPendingBindingValue(snapshot.providerSessionId) ||
|
|
@@ -4265,7 +4620,7 @@ function shouldMatchSessionBindingByRawStoreRef(provider) {
|
|
|
4265
4620
|
function resolveSessionListTitle(provider, existingTitle, fallbackContent, parentTitle = null) {
|
|
4266
4621
|
const normalizedExistingTitle = existingTitle?.trim() ?? "";
|
|
4267
4622
|
const normalizedParentTitle = parentTitle?.trim() ?? "";
|
|
4268
|
-
const fallbackTitle =
|
|
4623
|
+
const fallbackTitle = buildSessionTitleFromContent(fallbackContent, normalizedExistingTitle || "继续对话");
|
|
4269
4624
|
if (normalizedExistingTitle.length > 0 &&
|
|
4270
4625
|
!isSyntheticCodexSessionTitle(normalizedExistingTitle) &&
|
|
4271
4626
|
(normalizedParentTitle.length === 0 ||
|
|
@@ -4280,17 +4635,6 @@ function resolveSessionListTitle(provider, existingTitle, fallbackContent, paren
|
|
|
4280
4635
|
}
|
|
4281
4636
|
return normalizedExistingTitle || fallbackTitle;
|
|
4282
4637
|
}
|
|
4283
|
-
function buildUserMessageTitle(content, fallbackTitle) {
|
|
4284
|
-
const title = content.trim().replace(/\s+/g, " ");
|
|
4285
|
-
return title.slice(0, 48) || fallbackTitle;
|
|
4286
|
-
}
|
|
4287
|
-
function normalizeRuntimePromptTitle(content) {
|
|
4288
|
-
const normalized = (typeof content === "string" ? content : "").trim().replace(/\s+/g, " ");
|
|
4289
|
-
if (normalized.length === 0) {
|
|
4290
|
-
return null;
|
|
4291
|
-
}
|
|
4292
|
-
return normalized.slice(0, 48);
|
|
4293
|
-
}
|
|
4294
4638
|
function buildRecoveredSessionTitle(provider, providerSessionId) {
|
|
4295
4639
|
if (isPendingBindingValue(providerSessionId)) {
|
|
4296
4640
|
return "新会话";
|