@jingyi0605/codingns 0.1.5 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -0
- package/bin/codingns.mjs +640 -53
- package/dist/public/assets/{TerminalPage-4p6EBqrR.js → TerminalPage-BkjqU9NG.js} +19 -19
- package/dist/public/assets/index-C6U8-9jg.css +1 -0
- package/dist/public/assets/index-CKSumuV2.js +109 -0
- package/dist/public/index.html +2 -2
- package/dist/server/config/env.d.ts +2 -0
- package/dist/server/config/env.js +8 -1
- package/dist/server/config/env.js.map +1 -1
- package/dist/server/modules/assistant-capability/assistant-capability-controller.d.ts +89 -0
- package/dist/server/modules/assistant-capability/assistant-capability-controller.js +138 -0
- package/dist/server/modules/assistant-capability/assistant-capability-controller.js.map +1 -0
- package/dist/server/modules/assistant-capability/assistant-capability-service.d.ts +115 -0
- package/dist/server/modules/assistant-capability/assistant-capability-service.js +241 -0
- package/dist/server/modules/assistant-capability/assistant-capability-service.js.map +1 -0
- package/dist/server/modules/butler/butler-control-session-service.js +69 -30
- package/dist/server/modules/butler/butler-control-session-service.js.map +1 -1
- package/dist/server/modules/butler/butler-follow-up-scheduler.d.ts +9 -0
- package/dist/server/modules/butler/butler-follow-up-scheduler.js +47 -11
- package/dist/server/modules/butler/butler-follow-up-scheduler.js.map +1 -1
- package/dist/server/modules/butler/butler-follow-up-service.d.ts +7 -1
- package/dist/server/modules/butler/butler-follow-up-service.js +10 -0
- package/dist/server/modules/butler/butler-follow-up-service.js.map +1 -1
- package/dist/server/modules/butler/butler-session-service.d.ts +2 -1
- package/dist/server/modules/butler/butler-session-service.js +10 -1
- package/dist/server/modules/butler/butler-session-service.js.map +1 -1
- package/dist/server/modules/butler/butler-session-summary-service.d.ts +8 -1
- package/dist/server/modules/butler/butler-session-summary-service.js +34 -7
- package/dist/server/modules/butler/butler-session-summary-service.js.map +1 -1
- package/dist/server/modules/butler/context-aggregator.js +44 -13
- package/dist/server/modules/butler/context-aggregator.js.map +1 -1
- package/dist/server/modules/butler/patrol-scheduler.d.ts +9 -0
- package/dist/server/modules/butler/patrol-scheduler.js +63 -9
- package/dist/server/modules/butler/patrol-scheduler.js.map +1 -1
- package/dist/server/modules/butler/session-summary-scheduler.d.ts +9 -0
- package/dist/server/modules/butler/session-summary-scheduler.js +47 -11
- package/dist/server/modules/butler/session-summary-scheduler.js.map +1 -1
- package/dist/server/modules/debug-target/debug-runtime-reconciliation-scheduler.d.ts +38 -0
- package/dist/server/modules/debug-target/debug-runtime-reconciliation-scheduler.js +99 -0
- package/dist/server/modules/debug-target/debug-runtime-reconciliation-scheduler.js.map +1 -0
- package/dist/server/modules/debug-target/debug-target-controller.d.ts +70 -0
- package/dist/server/modules/debug-target/debug-target-controller.js +113 -0
- package/dist/server/modules/debug-target/debug-target-controller.js.map +1 -0
- package/dist/server/modules/debug-target/debug-target-service.d.ts +102 -0
- package/dist/server/modules/debug-target/debug-target-service.js +1484 -0
- package/dist/server/modules/debug-target/debug-target-service.js.map +1 -0
- package/dist/server/modules/debug-target/framework-compatibility-matrix.d.ts +4 -0
- package/dist/server/modules/debug-target/framework-compatibility-matrix.js +45 -0
- package/dist/server/modules/debug-target/framework-compatibility-matrix.js.map +1 -0
- package/dist/server/modules/debug-target/launch-adapter-registry.d.ts +25 -0
- package/dist/server/modules/debug-target/launch-adapter-registry.js +445 -0
- package/dist/server/modules/debug-target/launch-adapter-registry.js.map +1 -0
- package/dist/server/modules/file/file-content-service.d.ts +2 -1
- package/dist/server/modules/file/file-content-service.js +53 -0
- package/dist/server/modules/file/file-content-service.js.map +1 -1
- package/dist/server/modules/file/file-controller.d.ts +12 -1
- package/dist/server/modules/file/file-controller.js +72 -1
- package/dist/server/modules/file/file-controller.js.map +1 -1
- package/dist/server/modules/file/file-preview-link-service.d.ts +22 -0
- package/dist/server/modules/file/file-preview-link-service.js +160 -0
- package/dist/server/modules/file/file-preview-link-service.js.map +1 -0
- package/dist/server/modules/git/commit-orchestrator.d.ts +4 -1
- package/dist/server/modules/git/commit-orchestrator.js +18 -1
- package/dist/server/modules/git/commit-orchestrator.js.map +1 -1
- package/dist/server/modules/git/git-auth.d.ts +25 -0
- package/dist/server/modules/git/git-auth.js +88 -0
- package/dist/server/modules/git/git-auth.js.map +1 -0
- package/dist/server/modules/git/git-controller.d.ts +6 -0
- package/dist/server/modules/git/git-controller.js +5 -1
- package/dist/server/modules/git/git-controller.js.map +1 -1
- package/dist/server/modules/git/git-read-service.d.ts +2 -1
- package/dist/server/modules/git/git-read-service.js +19 -2
- package/dist/server/modules/git/git-read-service.js.map +1 -1
- package/dist/server/modules/git/git-remote-credential-service.d.ts +9 -0
- package/dist/server/modules/git/git-remote-credential-service.js +76 -0
- package/dist/server/modules/git/git-remote-credential-service.js.map +1 -0
- package/dist/server/modules/git/git-write-service.d.ts +5 -2
- package/dist/server/modules/git/git-write-service.js +33 -17
- package/dist/server/modules/git/git-write-service.js.map +1 -1
- package/dist/server/modules/git/types.d.ts +3 -0
- package/dist/server/modules/git/workspace-repo-guard.js +3 -2
- package/dist/server/modules/git/workspace-repo-guard.js.map +1 -1
- package/dist/server/modules/provider/codex-model-options.d.ts +3 -1
- package/dist/server/modules/provider/codex-model-options.js +4 -1
- package/dist/server/modules/provider/codex-model-options.js.map +1 -1
- package/dist/server/modules/provider/opencode-model-options.d.ts +3 -1
- package/dist/server/modules/provider/opencode-model-options.js +5 -1
- package/dist/server/modules/provider/opencode-model-options.js.map +1 -1
- package/dist/server/modules/provider/provider-discovery-helper-client.d.ts +24 -0
- package/dist/server/modules/provider/provider-discovery-helper-client.js +14 -0
- package/dist/server/modules/provider/provider-discovery-helper-client.js.map +1 -1
- package/dist/server/modules/provider/provider-discovery-helper-process.js +54 -0
- package/dist/server/modules/provider/provider-discovery-helper-process.js.map +1 -1
- package/dist/server/modules/sessions/codex-app-server-helper-client.d.ts +2 -1
- package/dist/server/modules/sessions/codex-app-server-helper-client.js +103 -0
- package/dist/server/modules/sessions/codex-app-server-helper-client.js.map +1 -1
- package/dist/server/modules/sessions/codex-app-server-helper-process.js +106 -1
- package/dist/server/modules/sessions/codex-app-server-helper-process.js.map +1 -1
- package/dist/server/modules/sessions/session-controller.d.ts +26 -0
- package/dist/server/modules/sessions/session-controller.js +39 -1
- package/dist/server/modules/sessions/session-controller.js.map +1 -1
- package/dist/server/modules/sessions/session-history-service.d.ts +51 -5
- package/dist/server/modules/sessions/session-history-service.js +906 -59
- package/dist/server/modules/sessions/session-history-service.js.map +1 -1
- package/dist/server/modules/sessions/session-live-runtime-service.d.ts +5 -0
- package/dist/server/modules/sessions/session-live-runtime-service.js +59 -2
- package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
- package/dist/server/modules/sessions/session-provider-error-mapper.js +66 -0
- package/dist/server/modules/sessions/session-provider-error-mapper.js.map +1 -1
- package/dist/server/modules/tasks/event-loop-monitor.d.ts +21 -0
- package/dist/server/modules/tasks/event-loop-monitor.js +64 -0
- package/dist/server/modules/tasks/event-loop-monitor.js.map +1 -0
- package/dist/server/modules/tasks/observability-controller.d.ts +30 -0
- package/dist/server/modules/tasks/observability-controller.js +44 -0
- package/dist/server/modules/tasks/observability-controller.js.map +1 -0
- package/dist/server/modules/tasks/observability-service.d.ts +32 -0
- package/dist/server/modules/tasks/observability-service.js +104 -0
- package/dist/server/modules/tasks/observability-service.js.map +1 -0
- package/dist/server/modules/tasks/scheduler-metrics.d.ts +41 -0
- package/dist/server/modules/tasks/scheduler-metrics.js +92 -0
- package/dist/server/modules/tasks/scheduler-metrics.js.map +1 -0
- package/dist/server/modules/tasks/task-activity-log.d.ts +39 -0
- package/dist/server/modules/tasks/task-activity-log.js +43 -0
- package/dist/server/modules/tasks/task-activity-log.js.map +1 -0
- package/dist/server/modules/tasks/task-helper-client.d.ts +11 -0
- package/dist/server/modules/tasks/task-helper-client.js +132 -0
- package/dist/server/modules/tasks/task-helper-client.js.map +1 -0
- package/dist/server/modules/tasks/task-helper-process-handlers.d.ts +16 -0
- package/dist/server/modules/tasks/task-helper-process-handlers.js +14 -0
- package/dist/server/modules/tasks/task-helper-process-handlers.js.map +1 -0
- package/dist/server/modules/tasks/task-helper-process.d.ts +1 -0
- package/dist/server/modules/tasks/task-helper-process.js +49 -0
- package/dist/server/modules/tasks/task-helper-process.js.map +1 -0
- package/dist/server/modules/tasks/task-lane-executors.d.ts +2 -0
- package/dist/server/modules/tasks/task-lane-executors.js +15 -0
- package/dist/server/modules/tasks/task-lane-executors.js.map +1 -0
- package/dist/server/modules/tasks/task-manager.d.ts +15 -0
- package/dist/server/modules/tasks/task-manager.js +36 -0
- package/dist/server/modules/tasks/task-manager.js.map +1 -0
- package/dist/server/modules/tasks/task-metrics.d.ts +9 -0
- package/dist/server/modules/tasks/task-metrics.js +81 -0
- package/dist/server/modules/tasks/task-metrics.js.map +1 -0
- package/dist/server/modules/tasks/task-registry.d.ts +7 -0
- package/dist/server/modules/tasks/task-registry.js +21 -0
- package/dist/server/modules/tasks/task-registry.js.map +1 -0
- package/dist/server/modules/tasks/task-scheduler.d.ts +31 -0
- package/dist/server/modules/tasks/task-scheduler.js +473 -0
- package/dist/server/modules/tasks/task-scheduler.js.map +1 -0
- package/dist/server/modules/tasks/task-types.d.ts +106 -0
- package/dist/server/modules/tasks/task-types.js +23 -0
- package/dist/server/modules/tasks/task-types.js.map +1 -0
- package/dist/server/modules/terminal/command-template-service.d.ts +4 -0
- package/dist/server/modules/terminal/command-template-service.js +5 -3
- package/dist/server/modules/terminal/command-template-service.js.map +1 -1
- package/dist/server/modules/terminal/runtime/terminal-log-spooler.d.ts +7 -3
- package/dist/server/modules/terminal/runtime/terminal-log-spooler.js +95 -15
- package/dist/server/modules/terminal/runtime/terminal-log-spooler.js.map +1 -1
- package/dist/server/modules/terminal/runtime/terminal-log-writer-client.d.ts +21 -0
- package/dist/server/modules/terminal/runtime/terminal-log-writer-client.js +144 -0
- package/dist/server/modules/terminal/runtime/terminal-log-writer-client.js.map +1 -0
- package/dist/server/modules/terminal/runtime/terminal-log-writer-process.d.ts +1 -0
- package/dist/server/modules/terminal/runtime/terminal-log-writer-process.js +187 -0
- package/dist/server/modules/terminal/runtime/terminal-log-writer-process.js.map +1 -0
- package/dist/server/modules/terminal/terminal-service.d.ts +12 -0
- package/dist/server/modules/terminal/terminal-service.js +34 -17
- package/dist/server/modules/terminal/terminal-service.js.map +1 -1
- package/dist/server/modules/workbench/workbench-service.d.ts +23 -2
- package/dist/server/modules/workbench/workbench-service.js +126 -15
- package/dist/server/modules/workbench/workbench-service.js.map +1 -1
- package/dist/server/modules/workbench/workspace-panel-snapshot-service.d.ts +5 -1
- package/dist/server/modules/workbench/workspace-panel-snapshot-service.js +88 -19
- package/dist/server/modules/workbench/workspace-panel-snapshot-service.js.map +1 -1
- package/dist/server/modules/workspace/workspace-code-composition.d.ts +2 -0
- package/dist/server/modules/workspace/workspace-code-composition.js +154 -0
- package/dist/server/modules/workspace/workspace-code-composition.js.map +1 -0
- package/dist/server/modules/workspace/workspace-controller.d.ts +14 -0
- package/dist/server/modules/workspace/workspace-controller.js +19 -0
- package/dist/server/modules/workspace/workspace-controller.js.map +1 -1
- package/dist/server/modules/workspace/workspace-service.d.ts +21 -14
- package/dist/server/modules/workspace/workspace-service.js +183 -234
- package/dist/server/modules/workspace/workspace-service.js.map +1 -1
- package/dist/server/modules/worktree/worktree-cleanup-service.d.ts +35 -0
- package/dist/server/modules/worktree/worktree-cleanup-service.js +210 -0
- package/dist/server/modules/worktree/worktree-cleanup-service.js.map +1 -0
- package/dist/server/modules/worktree/worktree-controller.d.ts +44 -0
- package/dist/server/modules/worktree/worktree-controller.js +40 -0
- package/dist/server/modules/worktree/worktree-controller.js.map +1 -0
- package/dist/server/modules/worktree/worktree-manager.d.ts +34 -0
- package/dist/server/modules/worktree/worktree-manager.js +292 -0
- package/dist/server/modules/worktree/worktree-manager.js.map +1 -0
- package/dist/server/modules/worktree/worktree-merge-service.d.ts +52 -0
- package/dist/server/modules/worktree/worktree-merge-service.js +293 -0
- package/dist/server/modules/worktree/worktree-merge-service.js.map +1 -0
- package/dist/server/modules/worktree/worktree-sync-service.d.ts +23 -0
- package/dist/server/modules/worktree/worktree-sync-service.js +166 -0
- package/dist/server/modules/worktree/worktree-sync-service.js.map +1 -0
- package/dist/server/routes/assistant.d.ts +3 -0
- package/dist/server/routes/assistant.js +15 -0
- package/dist/server/routes/assistant.js.map +1 -0
- package/dist/server/routes/debug-targets.d.ts +3 -0
- package/dist/server/routes/debug-targets.js +15 -0
- package/dist/server/routes/debug-targets.js.map +1 -0
- package/dist/server/routes/files.js +2 -0
- package/dist/server/routes/files.js.map +1 -1
- package/dist/server/routes/git.js +1 -0
- package/dist/server/routes/git.js.map +1 -1
- package/dist/server/routes/observability.d.ts +3 -0
- package/dist/server/routes/observability.js +7 -0
- package/dist/server/routes/observability.js.map +1 -0
- package/dist/server/routes/sessions.js +1 -0
- package/dist/server/routes/sessions.js.map +1 -1
- package/dist/server/routes/workspaces.js +2 -0
- package/dist/server/routes/workspaces.js.map +1 -1
- package/dist/server/routes/worktrees.d.ts +3 -0
- package/dist/server/routes/worktrees.js +8 -0
- package/dist/server/routes/worktrees.js.map +1 -0
- package/dist/server/server/create-server.d.ts +38 -0
- package/dist/server/server/create-server.js +106 -11
- package/dist/server/server/create-server.js.map +1 -1
- package/dist/server/shared/utils/command-availability.d.ts +1 -0
- package/dist/server/shared/utils/command-availability.js +83 -0
- package/dist/server/shared/utils/command-availability.js.map +1 -0
- package/dist/server/shared/utils/secret-box.d.ts +2 -0
- package/dist/server/shared/utils/secret-box.js +29 -0
- package/dist/server/shared/utils/secret-box.js.map +1 -0
- package/dist/server/shared/utils/terminal-debug-log.js +5 -3
- package/dist/server/shared/utils/terminal-debug-log.js.map +1 -1
- package/dist/server/storage/repositories/ai-fallback-edit-repository.d.ts +11 -0
- package/dist/server/storage/repositories/ai-fallback-edit-repository.js +118 -0
- package/dist/server/storage/repositories/ai-fallback-edit-repository.js.map +1 -0
- package/dist/server/storage/repositories/debug-runtime-session-repository.d.ts +11 -0
- package/dist/server/storage/repositories/debug-runtime-session-repository.js +100 -0
- package/dist/server/storage/repositories/debug-runtime-session-repository.js.map +1 -0
- package/dist/server/storage/repositories/debug-service-repository.d.ts +9 -0
- package/dist/server/storage/repositories/debug-service-repository.js +99 -0
- package/dist/server/storage/repositories/debug-service-repository.js.map +1 -0
- package/dist/server/storage/repositories/debug-target-repository.d.ts +11 -0
- package/dist/server/storage/repositories/debug-target-repository.js +100 -0
- package/dist/server/storage/repositories/debug-target-repository.js.map +1 -0
- package/dist/server/storage/repositories/framework-analysis-result-repository.d.ts +9 -0
- package/dist/server/storage/repositories/framework-analysis-result-repository.js +98 -0
- package/dist/server/storage/repositories/framework-analysis-result-repository.js.map +1 -0
- package/dist/server/storage/repositories/git-remote-credential-repository.d.ts +9 -0
- package/dist/server/storage/repositories/git-remote-credential-repository.js +51 -0
- package/dist/server/storage/repositories/git-remote-credential-repository.js.map +1 -0
- package/dist/server/storage/repositories/port-lease-repository.d.ts +12 -0
- package/dist/server/storage/repositories/port-lease-repository.js +124 -0
- package/dist/server/storage/repositories/port-lease-repository.js.map +1 -0
- package/dist/server/storage/repositories/runtime-binding-repository.d.ts +10 -0
- package/dist/server/storage/repositories/runtime-binding-repository.js +89 -0
- package/dist/server/storage/repositories/runtime-binding-repository.js.map +1 -0
- package/dist/server/storage/repositories/session-fork-repository.d.ts +8 -0
- package/dist/server/storage/repositories/session-fork-repository.js +69 -0
- package/dist/server/storage/repositories/session-fork-repository.js.map +1 -0
- package/dist/server/storage/repositories/session-index-repository.js +40 -2
- package/dist/server/storage/repositories/session-index-repository.js.map +1 -1
- package/dist/server/storage/repositories/terminal-command-template-repository.js +77 -4
- package/dist/server/storage/repositories/terminal-command-template-repository.js.map +1 -1
- package/dist/server/storage/repositories/terminal-instance-repository.js +89 -7
- package/dist/server/storage/repositories/terminal-instance-repository.js.map +1 -1
- package/dist/server/storage/repositories/workspace-navigation-state-repository.d.ts +9 -0
- package/dist/server/storage/repositories/workspace-navigation-state-repository.js +49 -0
- package/dist/server/storage/repositories/workspace-navigation-state-repository.js.map +1 -0
- package/dist/server/storage/repositories/workspace-repository.d.ts +7 -1
- package/dist/server/storage/repositories/workspace-repository.js +32 -8
- package/dist/server/storage/repositories/workspace-repository.js.map +1 -1
- package/dist/server/storage/repositories/workspace-worktree-repository.d.ts +13 -0
- package/dist/server/storage/repositories/workspace-worktree-repository.js +158 -0
- package/dist/server/storage/repositories/workspace-worktree-repository.js.map +1 -0
- package/dist/server/storage/sqlite/client.js +408 -0
- package/dist/server/storage/sqlite/client.js.map +1 -1
- package/dist/server/storage/sqlite/schema.sql +268 -0
- package/dist/server/types/domain.d.ts +256 -1
- package/dist/server/ws/workbench-ws-hub.js +33 -9
- package/dist/server/ws/workbench-ws-hub.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.d.ts +6 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js +240 -7
- package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.d.ts +26 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +507 -2
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js +94 -5
- package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/opencode.d.ts +5 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +293 -3
- package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/utils.d.ts +1 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/utils.js +117 -17
- package/node_modules/@codingns/session-sync-core/dist/providers/utils.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.d.ts +10 -0
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +128 -8
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/services.d.ts +2 -1
- package/node_modules/@codingns/session-sync-core/dist/services.js +55 -8
- package/node_modules/@codingns/session-sync-core/dist/services.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/types.d.ts +33 -0
- package/package.json +1 -1
- package/dist/public/assets/index-CxeghocY.css +0 -1
- package/dist/public/assets/index-DXusStl0.js +0 -108
|
@@ -4,13 +4,23 @@ 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";
|
|
6
6
|
import { logPerformance } from "../../shared/utils/perf-log.js";
|
|
7
|
+
import { isTerminalDebugEnabled, logTerminalDebug, terminalDebugNowMs } from "../../shared/utils/terminal-debug-log.js";
|
|
7
8
|
import { nowIso } from "../../shared/utils/time.js";
|
|
9
|
+
import { isCommandAvailable } from "../../shared/utils/command-availability.js";
|
|
8
10
|
import { inspectSessionActivity } from "./session-activity-inspector.js";
|
|
9
11
|
import { SessionActivityAuthorityService } from "./session-activity-authority-service.js";
|
|
10
12
|
import { mapSessionProviderError } from "./session-provider-error-mapper.js";
|
|
13
|
+
import { SessionForkRepository } from "../../storage/repositories/session-fork-repository.js";
|
|
11
14
|
import { enrichClaudeCapabilities } from "../provider/claude-model-options.js";
|
|
12
|
-
import { CodexModelOptionsService, enrichCodexCapabilities } from "../provider/codex-model-options.js";
|
|
13
|
-
import { OpenCodeModelOptionsService, enrichOpenCodeCapabilities } from "../provider/opencode-model-options.js";
|
|
15
|
+
import { CodexModelOptionsService, createFallbackCodexModelOptions, enrichCodexCapabilities } from "../provider/codex-model-options.js";
|
|
16
|
+
import { OpenCodeModelOptionsService, createFallbackOpenCodeModelOptions, enrichOpenCodeCapabilities } from "../provider/opencode-model-options.js";
|
|
17
|
+
import { ProviderDiscoveryHelperClient } from "../provider/provider-discovery-helper-client.js";
|
|
18
|
+
import { createTaskManager } from "../tasks/task-manager.js";
|
|
19
|
+
import { HOST_TASK_TYPES } from "../tasks/task-types.js";
|
|
20
|
+
import { CodexAppServerHelperClient } from "./codex-app-server-helper-client.js";
|
|
21
|
+
const RECONSTRUCTED_FORK_TARGET_PROVIDERS = new Set(["codex", "claude-code", "opencode"]);
|
|
22
|
+
const FORK_RECONSTRUCTION_PAGE_SIZE = 200;
|
|
23
|
+
const MAX_FORK_DEPTH = 4;
|
|
14
24
|
const SESSION_START_DEFERRED_PROVIDERS = new Set([
|
|
15
25
|
"codex",
|
|
16
26
|
"claude-code",
|
|
@@ -19,6 +29,9 @@ const SESSION_START_DEFERRED_PROVIDERS = new Set([
|
|
|
19
29
|
"kimi"
|
|
20
30
|
]);
|
|
21
31
|
const MUTABLE_HISTORY_TAIL_REFRESH_INTERVAL_MS = 1_200;
|
|
32
|
+
const WORKSPACE_DISCOVERY_BACKGROUND_MAX_AGE_MS = 15_000;
|
|
33
|
+
const PROVIDER_CAPABILITY_CACHE_MAX_AGE_MS = 5_000;
|
|
34
|
+
const WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE = 25;
|
|
22
35
|
export class SessionHistoryService {
|
|
23
36
|
db;
|
|
24
37
|
workspaceRepository;
|
|
@@ -33,14 +46,20 @@ export class SessionHistoryService {
|
|
|
33
46
|
sessionSyncService;
|
|
34
47
|
capabilityService;
|
|
35
48
|
sessionActivityAuthorityService;
|
|
49
|
+
sessionForkRepository;
|
|
36
50
|
claudeCodeHomeDir;
|
|
37
51
|
codexModelOptionsService;
|
|
38
52
|
openCodeModelOptionsService;
|
|
53
|
+
providerCliCommandPaths;
|
|
54
|
+
providerCliAvailability;
|
|
55
|
+
providerDiscoveryHelperClient = new ProviderDiscoveryHelperClient();
|
|
56
|
+
providerSessionDiscoveryConfig;
|
|
57
|
+
taskManager;
|
|
39
58
|
workspaceDiscoveryStatuses = new Map();
|
|
40
|
-
workspaceDiscoveryInflight = new Map();
|
|
41
59
|
workspaceStateRefreshInflight = new Map();
|
|
60
|
+
providerCapabilityCache = new Map();
|
|
42
61
|
workspaceSessionRelations = new Map();
|
|
43
|
-
constructor(db, workspaceRepository, sessionBindingRepository, sessionChangedFileService, sessionIndexRepository, sessionMessageAttachmentService, sessionStateRepository, sessionStatusSnapshotRepository, config, sessionActivityAuthorityService = new SessionActivityAuthorityService(), sessionMessageOriginRepository = null) {
|
|
62
|
+
constructor(db, workspaceRepository, sessionBindingRepository, sessionChangedFileService, sessionIndexRepository, sessionMessageAttachmentService, sessionStateRepository, sessionStatusSnapshotRepository, config, sessionActivityAuthorityService = new SessionActivityAuthorityService(), sessionMessageOriginRepository = null, sessionForkRepository = null, adapterOverrides = {}, taskManager = createTaskManager()) {
|
|
44
63
|
this.db = db;
|
|
45
64
|
this.workspaceRepository = workspaceRepository;
|
|
46
65
|
this.sessionBindingRepository = sessionBindingRepository;
|
|
@@ -51,10 +70,36 @@ export class SessionHistoryService {
|
|
|
51
70
|
this.sessionStatusSnapshotRepository = sessionStatusSnapshotRepository;
|
|
52
71
|
this.sessionMessageOriginRepository = sessionMessageOriginRepository;
|
|
53
72
|
this.sessionActivityAuthorityService = sessionActivityAuthorityService;
|
|
73
|
+
this.sessionForkRepository = sessionForkRepository ?? new SessionForkRepository(db);
|
|
74
|
+
this.taskManager = taskManager;
|
|
54
75
|
this.claudeCodeHomeDir = config.claudeCodeHomeDir;
|
|
76
|
+
this.providerCliCommandPaths = {
|
|
77
|
+
"claude-code": process.platform === "win32" ? "claude.cmd" : "claude",
|
|
78
|
+
codex: config.codexCliPath,
|
|
79
|
+
gemini: config.geminiCliPath,
|
|
80
|
+
kimi: config.kimiCliPath
|
|
81
|
+
};
|
|
82
|
+
// CLI 是否可用只在 Host 启动时探测一次;后续统一读缓存,更新 CLI 后重启 Host 生效。
|
|
83
|
+
this.providerCliAvailability = buildProviderCliAvailabilitySnapshot(this.providerCliCommandPaths);
|
|
84
|
+
this.providerSessionDiscoveryConfig = {
|
|
85
|
+
claudeCodeHomeDir: config.claudeCodeHomeDir,
|
|
86
|
+
codexCliPath: config.codexCliPath,
|
|
87
|
+
codexHomeDir: config.codexHomeDir,
|
|
88
|
+
geminiCliPath: config.geminiCliPath,
|
|
89
|
+
geminiHomeDir: config.geminiHomeDir,
|
|
90
|
+
kimiDefaultModel: config.kimiDefaultModel,
|
|
91
|
+
kimiHomeDir: config.kimiHomeDir,
|
|
92
|
+
opencodeBaseUrl: config.opencodeBaseUrl,
|
|
93
|
+
opencodeDataDir: config.opencodeDataDir,
|
|
94
|
+
opencodeDbPath: config.opencodeDbPath
|
|
95
|
+
};
|
|
55
96
|
this.providerRegistry = new ProviderRegistry([
|
|
56
97
|
new ClaudeCodeAdapter({ homeDir: config.claudeCodeHomeDir }),
|
|
57
|
-
new CodexAdapter({
|
|
98
|
+
new CodexAdapter({
|
|
99
|
+
homeDir: config.codexHomeDir,
|
|
100
|
+
forkTransportFactory: adapterOverrides.codexForkTransportFactory
|
|
101
|
+
?? createCodexForkTransportFactory(config.codexCliPath, config.codexHomeDir)
|
|
102
|
+
}),
|
|
58
103
|
new GeminiAdapter({
|
|
59
104
|
homeDir: config.geminiHomeDir,
|
|
60
105
|
commandPath: config.geminiCliPath
|
|
@@ -80,6 +125,32 @@ export class SessionHistoryService {
|
|
|
80
125
|
baseUrlResolver: config.opencodeBaseUrlResolver?.resolve.bind(config.opencodeBaseUrlResolver),
|
|
81
126
|
commandPath: config.opencodeCliPath
|
|
82
127
|
});
|
|
128
|
+
this.registerBackgroundTasks();
|
|
129
|
+
}
|
|
130
|
+
observeBackgroundTaskMetrics() {
|
|
131
|
+
return this.taskManager.observe();
|
|
132
|
+
}
|
|
133
|
+
registerBackgroundTasks() {
|
|
134
|
+
if (!this.taskManager.has(HOST_TASK_TYPES.workspaceDiscovery)) {
|
|
135
|
+
this.taskManager.register({
|
|
136
|
+
taskType: HOST_TASK_TYPES.workspaceDiscovery,
|
|
137
|
+
executionLane: "helper_process",
|
|
138
|
+
run: async ({ workspaceId, userId, refreshStateMode }) => this.runDiscoverWorkspaceSessions(workspaceId, userId, refreshStateMode)
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (!this.taskManager.has(HOST_TASK_TYPES.providerCapabilityRefresh)) {
|
|
142
|
+
this.taskManager.register({
|
|
143
|
+
taskType: HOST_TASK_TYPES.providerCapabilityRefresh,
|
|
144
|
+
executionLane: "external_process",
|
|
145
|
+
run: async ({ capabilities, workspacePath }) => {
|
|
146
|
+
const value = await this.enrichProviderCapabilities(capabilities, workspacePath);
|
|
147
|
+
this.providerCapabilityCache.set(buildProviderCapabilityCacheKey(capabilities.provider, workspacePath), {
|
|
148
|
+
refreshedAt: Date.now(),
|
|
149
|
+
value
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
83
154
|
}
|
|
84
155
|
async discoverWorkspaceSessions(workspaceId, userId, options) {
|
|
85
156
|
const maxAgeMs = options?.maxAgeMs ?? 0;
|
|
@@ -90,17 +161,47 @@ export class SessionHistoryService {
|
|
|
90
161
|
discoveryStatus?.isComplete === true &&
|
|
91
162
|
maxAgeMs > 0 &&
|
|
92
163
|
Date.now() - lastRefreshedAt <= maxAgeMs) {
|
|
164
|
+
this.taskManager.recordCacheHit(HOST_TASK_TYPES.workspaceDiscovery, workspaceId);
|
|
93
165
|
return this.listWorkspaceSessions(workspaceId, userId);
|
|
94
166
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
167
|
+
return this.taskManager.enqueue(HOST_TASK_TYPES.workspaceDiscovery, {
|
|
168
|
+
key: workspaceId,
|
|
169
|
+
source: "session_history.discover_workspace_sessions",
|
|
170
|
+
input: {
|
|
171
|
+
workspaceId,
|
|
172
|
+
userId,
|
|
173
|
+
refreshStateMode: options?.refreshStateMode ?? "inline"
|
|
174
|
+
}
|
|
175
|
+
}).promise;
|
|
176
|
+
}
|
|
177
|
+
requestWorkspaceDiscovery(workspaceId, userId, options) {
|
|
178
|
+
const maxAgeMs = options?.maxAgeMs ?? WORKSPACE_DISCOVERY_BACKGROUND_MAX_AGE_MS;
|
|
179
|
+
const force = options?.force ?? false;
|
|
180
|
+
if (!force && !this.needsWorkspaceDiscovery(workspaceId, maxAgeMs)) {
|
|
181
|
+
return;
|
|
98
182
|
}
|
|
99
|
-
const task = this.
|
|
100
|
-
|
|
183
|
+
const task = this.taskManager.enqueue(HOST_TASK_TYPES.workspaceDiscovery, {
|
|
184
|
+
key: workspaceId,
|
|
185
|
+
source: "session_history.request_workspace_discovery",
|
|
186
|
+
input: {
|
|
187
|
+
workspaceId,
|
|
188
|
+
userId,
|
|
189
|
+
refreshStateMode: options?.refreshStateMode ?? "deferred"
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
if (task.deduped) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
void task.promise.catch((error) => {
|
|
196
|
+
logPerformance("workspace.discover_sessions.background_failed", 0, {
|
|
197
|
+
workspaceId,
|
|
198
|
+
error: error instanceof Error ? error.message : "unknown"
|
|
199
|
+
}, {
|
|
200
|
+
thresholdMs: 0,
|
|
201
|
+
force: true
|
|
202
|
+
});
|
|
203
|
+
return this.listWorkspaceSessions(workspaceId, userId);
|
|
101
204
|
});
|
|
102
|
-
this.workspaceDiscoveryInflight.set(workspaceId, task);
|
|
103
|
-
return task;
|
|
104
205
|
}
|
|
105
206
|
needsWorkspaceDiscovery(workspaceId, maxAgeMs) {
|
|
106
207
|
if (maxAgeMs <= 0) {
|
|
@@ -224,13 +325,13 @@ export class SessionHistoryService {
|
|
|
224
325
|
const binding = this.getBindingOrThrow(sessionId);
|
|
225
326
|
await this.syncSessionTitleFromProvider(sessionId, binding);
|
|
226
327
|
}
|
|
227
|
-
async syncWorkspaceSessionTitles(workspaceId, userId) {
|
|
328
|
+
async syncWorkspaceSessionTitles(workspaceId, userId, concurrency = 1) {
|
|
228
329
|
const sessions = this.sessionIndexRepository.listByWorkspace(workspaceId, userId);
|
|
229
|
-
|
|
330
|
+
await runWithConcurrency(sessions, concurrency, async (session) => {
|
|
230
331
|
await this.syncSessionTitle(session.sessionId).catch(() => {
|
|
231
332
|
return;
|
|
232
333
|
});
|
|
233
|
-
}
|
|
334
|
+
});
|
|
234
335
|
}
|
|
235
336
|
async listSessionChangedFiles(sessionId, userId) {
|
|
236
337
|
this.getSession(sessionId, userId);
|
|
@@ -244,7 +345,7 @@ export class SessionHistoryService {
|
|
|
244
345
|
}
|
|
245
346
|
getProviderCapabilitiesSnapshot(provider) {
|
|
246
347
|
try {
|
|
247
|
-
return this.capabilityService.getProviderCapabilities(provider);
|
|
348
|
+
return this.resolveProviderCapabilitiesImmediate(this.applyProviderCliAvailability(this.capabilityService.getProviderCapabilities(provider)), null);
|
|
248
349
|
}
|
|
249
350
|
catch (error) {
|
|
250
351
|
throw mapSessionProviderError(error);
|
|
@@ -253,7 +354,9 @@ export class SessionHistoryService {
|
|
|
253
354
|
async getProviderCapabilities(provider, workspaceId) {
|
|
254
355
|
try {
|
|
255
356
|
const workspacePath = workspaceId ? this.getWorkspaceOrThrow(workspaceId).path : null;
|
|
256
|
-
|
|
357
|
+
const baseCapabilities = this.applyProviderCliAvailability(this.capabilityService.getProviderCapabilities(provider));
|
|
358
|
+
this.scheduleProviderCapabilityRefresh(baseCapabilities, workspacePath);
|
|
359
|
+
return this.resolveProviderCapabilitiesImmediate(baseCapabilities, workspacePath);
|
|
257
360
|
}
|
|
258
361
|
catch (error) {
|
|
259
362
|
throw mapSessionProviderError(error);
|
|
@@ -262,9 +365,14 @@ export class SessionHistoryService {
|
|
|
262
365
|
async getSessionCapabilities(sessionId) {
|
|
263
366
|
const binding = this.getBindingOrThrow(sessionId);
|
|
264
367
|
const workspace = this.getWorkspaceOrThrow(binding.workspaceId);
|
|
368
|
+
const workspacePath = workspace.path;
|
|
265
369
|
return this.capabilityService
|
|
266
370
|
.getSessionCapabilities(binding.provider, binding.providerSessionId)
|
|
267
|
-
.then((capabilities) =>
|
|
371
|
+
.then((capabilities) => {
|
|
372
|
+
const normalizedCapabilities = this.applyProviderCliAvailability(capabilities);
|
|
373
|
+
this.scheduleProviderCapabilityRefresh(normalizedCapabilities, workspacePath);
|
|
374
|
+
return this.resolveProviderCapabilitiesImmediate(normalizedCapabilities, workspacePath);
|
|
375
|
+
})
|
|
268
376
|
.catch((error) => {
|
|
269
377
|
throw mapSessionProviderError(error);
|
|
270
378
|
});
|
|
@@ -277,6 +385,83 @@ export class SessionHistoryService {
|
|
|
277
385
|
const codexEnriched = await enrichCodexCapabilities(claudeEnriched, this.codexModelOptionsService);
|
|
278
386
|
return enrichOpenCodeCapabilities(codexEnriched, this.openCodeModelOptionsService, workspacePath);
|
|
279
387
|
}
|
|
388
|
+
resolveProviderCapabilitiesImmediate(capabilities, workspacePath) {
|
|
389
|
+
const cacheKey = buildProviderCapabilityCacheKey(capabilities.provider, workspacePath);
|
|
390
|
+
const cached = this.providerCapabilityCache.get(cacheKey);
|
|
391
|
+
if (cached) {
|
|
392
|
+
this.taskManager.recordCacheHit(HOST_TASK_TYPES.providerCapabilityRefresh, cacheKey);
|
|
393
|
+
return cached.value;
|
|
394
|
+
}
|
|
395
|
+
const claudeEnriched = enrichClaudeCapabilities(capabilities, {
|
|
396
|
+
claudeHomeDir: this.claudeCodeHomeDir,
|
|
397
|
+
workspacePath
|
|
398
|
+
});
|
|
399
|
+
return applyImmediateModelOptionFallbacks(claudeEnriched, this.codexModelOptionsService.peekSnapshot(), this.openCodeModelOptionsService.peekSnapshot(workspacePath));
|
|
400
|
+
}
|
|
401
|
+
scheduleProviderCapabilityRefresh(capabilities, workspacePath) {
|
|
402
|
+
const cacheKey = buildProviderCapabilityCacheKey(capabilities.provider, workspacePath);
|
|
403
|
+
const cached = this.providerCapabilityCache.get(cacheKey);
|
|
404
|
+
if (cached &&
|
|
405
|
+
Date.now() - cached.refreshedAt <= PROVIDER_CAPABILITY_CACHE_MAX_AGE_MS) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const task = this.taskManager.enqueue(HOST_TASK_TYPES.providerCapabilityRefresh, {
|
|
409
|
+
key: cacheKey,
|
|
410
|
+
source: "session_history.provider_capability_refresh",
|
|
411
|
+
input: {
|
|
412
|
+
capabilities,
|
|
413
|
+
workspacePath
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
if (task.deduped) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
void task.promise.catch((error) => {
|
|
420
|
+
logPerformance("provider.capabilities.background_failed", 0, {
|
|
421
|
+
provider: capabilities.provider,
|
|
422
|
+
workspacePath,
|
|
423
|
+
error: error instanceof Error ? error.message : "unknown"
|
|
424
|
+
}, {
|
|
425
|
+
thresholdMs: 0,
|
|
426
|
+
force: true
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
applyProviderCliAvailability(capabilities) {
|
|
431
|
+
if (!isProviderCliBacked(capabilities.provider)) {
|
|
432
|
+
return capabilities;
|
|
433
|
+
}
|
|
434
|
+
if (this.providerCliAvailability[capabilities.provider]) {
|
|
435
|
+
return capabilities;
|
|
436
|
+
}
|
|
437
|
+
const limitation = buildProviderCliUnavailableMessage(capabilities.provider);
|
|
438
|
+
const limitations = capabilities.limitations.includes(limitation)
|
|
439
|
+
? capabilities.limitations
|
|
440
|
+
: [limitation, ...capabilities.limitations];
|
|
441
|
+
return {
|
|
442
|
+
...capabilities,
|
|
443
|
+
canStartSession: false,
|
|
444
|
+
canResumeSession: false,
|
|
445
|
+
canSendMessage: false,
|
|
446
|
+
supportsSubagents: false,
|
|
447
|
+
supportsInterrupt: false,
|
|
448
|
+
supportsSessionFork: false,
|
|
449
|
+
supportsNativeAgents: false,
|
|
450
|
+
limitations
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
assertProviderCapabilityEnabled(provider, capability, fallbackDetail) {
|
|
454
|
+
const capabilities = this.getProviderCapabilitiesSnapshot(provider);
|
|
455
|
+
if (capabilities[capability]) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
throw new AppError({
|
|
459
|
+
statusCode: 409,
|
|
460
|
+
errorCode: "PROVIDER_UNAVAILABLE",
|
|
461
|
+
detail: capabilities.limitations[0] ?? fallbackDetail,
|
|
462
|
+
field: "provider"
|
|
463
|
+
});
|
|
464
|
+
}
|
|
280
465
|
async getSessionContextUsage(sessionId) {
|
|
281
466
|
const binding = this.getBindingOrThrow(sessionId);
|
|
282
467
|
try {
|
|
@@ -288,6 +473,7 @@ export class SessionHistoryService {
|
|
|
288
473
|
}
|
|
289
474
|
async resumeSession(sessionId) {
|
|
290
475
|
const binding = this.getBindingOrThrow(sessionId);
|
|
476
|
+
this.assertProviderCapabilityEnabled(binding.provider, "canResumeSession", "当前 provider 不支持继续会话");
|
|
291
477
|
try {
|
|
292
478
|
const result = await this.sessionSyncService.resumeSession(binding.provider, binding.providerSessionId, binding.rawStoreRef);
|
|
293
479
|
this.upsertSnapshot(sessionId, {
|
|
@@ -311,7 +497,6 @@ export class SessionHistoryService {
|
|
|
311
497
|
}
|
|
312
498
|
}
|
|
313
499
|
async startSession(input) {
|
|
314
|
-
const workspace = this.getWorkspaceOrThrow(input.workspaceId);
|
|
315
500
|
if (SESSION_START_DEFERRED_PROVIDERS.has(input.provider)) {
|
|
316
501
|
throw new AppError({
|
|
317
502
|
statusCode: 409,
|
|
@@ -320,6 +505,11 @@ export class SessionHistoryService {
|
|
|
320
505
|
field: "provider"
|
|
321
506
|
});
|
|
322
507
|
}
|
|
508
|
+
return this.startSessionDirect(input);
|
|
509
|
+
}
|
|
510
|
+
async startSessionDirect(input) {
|
|
511
|
+
const workspace = this.getWorkspaceOrThrow(input.workspaceId);
|
|
512
|
+
this.assertProviderCapabilityEnabled(input.provider, "canStartSession", "当前 provider 不支持创建会话");
|
|
323
513
|
try {
|
|
324
514
|
const result = await this.sessionSyncService.startSession(input.provider, workspace.path, {
|
|
325
515
|
initialPrompt: input.initialPrompt
|
|
@@ -340,7 +530,10 @@ export class SessionHistoryService {
|
|
|
340
530
|
sessionId,
|
|
341
531
|
workspaceId: workspace.id,
|
|
342
532
|
provider: result.session.provider,
|
|
343
|
-
parentSessionId: result.session.parentProviderSessionId ?? null,
|
|
533
|
+
parentSessionId: input.parentSessionId ?? result.session.parentProviderSessionId ?? null,
|
|
534
|
+
sessionKind: input.sessionKind ?? "default",
|
|
535
|
+
annotationSourceMessageId: input.annotationSourceMessageId ?? null,
|
|
536
|
+
annotationSourceText: input.annotationSourceText ?? null,
|
|
344
537
|
isSubagent: result.session.isSubagent ?? false,
|
|
345
538
|
subagentLabel: result.session.subagentLabel ?? null,
|
|
346
539
|
title: result.session.title,
|
|
@@ -379,6 +572,251 @@ export class SessionHistoryService {
|
|
|
379
572
|
throw mapSessionProviderError(error);
|
|
380
573
|
}
|
|
381
574
|
}
|
|
575
|
+
async forkSession(input) {
|
|
576
|
+
const binding = this.getBindingOrThrow(input.sessionId);
|
|
577
|
+
const workspace = this.getWorkspaceOrThrow(binding.workspaceId);
|
|
578
|
+
const targetProvider = input.targetProvider?.trim() || binding.provider;
|
|
579
|
+
this.assertProviderCapabilityEnabled(targetProvider, "canStartSession", "当前 provider 不支持 fork 创建会话");
|
|
580
|
+
const sourceMessageId = input.sourceType === "message"
|
|
581
|
+
? input.sourceMessageId?.trim() || null
|
|
582
|
+
: null;
|
|
583
|
+
if (input.sourceType === "message" && !sourceMessageId) {
|
|
584
|
+
throw new AppError({
|
|
585
|
+
statusCode: 400,
|
|
586
|
+
errorCode: "INVALID_INPUT",
|
|
587
|
+
detail: "按消息派生会话时必须提供 sourceMessageId",
|
|
588
|
+
field: "sourceMessageId"
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
this.assertForkDepthWithinLimit(input.sessionId);
|
|
592
|
+
if (targetProvider !== binding.provider) {
|
|
593
|
+
return this.forkSessionAcrossProviders({
|
|
594
|
+
...input,
|
|
595
|
+
targetProvider
|
|
596
|
+
}, binding, sourceMessageId);
|
|
597
|
+
}
|
|
598
|
+
try {
|
|
599
|
+
const result = await this.sessionSyncService.forkSession(binding.provider, binding.providerSessionId, workspace.path, {
|
|
600
|
+
rawStoreRef: binding.rawStoreRef,
|
|
601
|
+
sourceType: input.sourceType,
|
|
602
|
+
sourceMessageId,
|
|
603
|
+
sourceMessageSnapshot: input.sourceMessageSnapshot ?? null,
|
|
604
|
+
strategy: input.strategy ?? "auto"
|
|
605
|
+
});
|
|
606
|
+
const sessionId = createId();
|
|
607
|
+
const timestamp = nowIso();
|
|
608
|
+
this.db.transaction(() => {
|
|
609
|
+
this.sessionBindingRepository.upsert({
|
|
610
|
+
sessionId,
|
|
611
|
+
workspaceId: workspace.id,
|
|
612
|
+
provider: result.session.provider,
|
|
613
|
+
providerSessionId: result.session.providerSessionId,
|
|
614
|
+
rawStoreRef: result.session.rawStoreRef,
|
|
615
|
+
createdAt: timestamp,
|
|
616
|
+
updatedAt: timestamp
|
|
617
|
+
});
|
|
618
|
+
this.sessionIndexRepository.upsert({
|
|
619
|
+
sessionId,
|
|
620
|
+
workspaceId: workspace.id,
|
|
621
|
+
provider: result.session.provider,
|
|
622
|
+
parentSessionId: input.sessionId,
|
|
623
|
+
sessionKind: input.sessionKind ?? "default",
|
|
624
|
+
annotationSourceMessageId: input.annotationSourceMessageId ?? null,
|
|
625
|
+
annotationSourceText: input.annotationSourceText ?? null,
|
|
626
|
+
isSubagent: result.session.isSubagent ?? false,
|
|
627
|
+
subagentLabel: result.session.subagentLabel ?? null,
|
|
628
|
+
title: result.session.title,
|
|
629
|
+
messageCount: result.session.messageCount,
|
|
630
|
+
isArchived: result.session.isArchived ?? false,
|
|
631
|
+
lastMessageAt: result.session.lastMessageAt,
|
|
632
|
+
createdAt: timestamp,
|
|
633
|
+
updatedAt: timestamp
|
|
634
|
+
});
|
|
635
|
+
this.sessionForkRepository.upsert({
|
|
636
|
+
sessionId,
|
|
637
|
+
parentSessionId: input.sessionId,
|
|
638
|
+
provider: result.session.provider,
|
|
639
|
+
forkSourceType: result.forkSourceType,
|
|
640
|
+
forkSourceSessionId: input.sessionId,
|
|
641
|
+
forkSourceMessageId: sourceMessageId,
|
|
642
|
+
inheritedPrefixMessageCount: result.inheritedPrefixMessageCount,
|
|
643
|
+
providerParentSessionId: binding.providerSessionId,
|
|
644
|
+
providerSourceMessageId: result.providerSourceMessageId ?? null,
|
|
645
|
+
forkMethod: result.forkMethod,
|
|
646
|
+
createdAt: timestamp
|
|
647
|
+
});
|
|
648
|
+
this.sessionStatusSnapshotRepository.upsert({
|
|
649
|
+
sessionId,
|
|
650
|
+
syncStatus: "idle",
|
|
651
|
+
syncCursor: null,
|
|
652
|
+
lastSyncAt: timestamp,
|
|
653
|
+
lastErrorCode: null,
|
|
654
|
+
lastErrorDetail: null,
|
|
655
|
+
resumedAt: null,
|
|
656
|
+
updatedAt: timestamp
|
|
657
|
+
});
|
|
658
|
+
this.sessionStateRepository.upsert({
|
|
659
|
+
sessionId,
|
|
660
|
+
userId: input.userId,
|
|
661
|
+
runningState: "idle",
|
|
662
|
+
activitySource: "none",
|
|
663
|
+
favorite: false,
|
|
664
|
+
lastEventAt: result.session.lastMessageAt,
|
|
665
|
+
completedAt: null,
|
|
666
|
+
lastSeenAt: null,
|
|
667
|
+
updatedAt: timestamp
|
|
668
|
+
});
|
|
669
|
+
})();
|
|
670
|
+
const forkedSession = this.getSessionListItemOrThrow(sessionId, input.userId);
|
|
671
|
+
const relationMap = this.workspaceSessionRelations.get(workspace.id)
|
|
672
|
+
?? new Map();
|
|
673
|
+
relationMap.set(sessionId, {
|
|
674
|
+
parentSessionId: input.sessionId,
|
|
675
|
+
sessionKind: forkedSession.sessionKind ?? input.sessionKind ?? "default",
|
|
676
|
+
annotationSourceMessageId: forkedSession.annotationSourceMessageId ?? input.annotationSourceMessageId ?? null,
|
|
677
|
+
annotationSourceText: forkedSession.annotationSourceText ?? input.annotationSourceText ?? null,
|
|
678
|
+
isSubagent: forkedSession.isSubagent ?? false,
|
|
679
|
+
subagentLabel: forkedSession.subagentLabel ?? null
|
|
680
|
+
});
|
|
681
|
+
this.workspaceSessionRelations.set(workspace.id, relationMap);
|
|
682
|
+
return this.getSessionListItemOrThrow(sessionId, input.userId);
|
|
683
|
+
}
|
|
684
|
+
catch (error) {
|
|
685
|
+
throw mapSessionProviderError(error);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
async forkSessionAcrossProviders(input, sourceBinding, sourceMessageId) {
|
|
689
|
+
if (!RECONSTRUCTED_FORK_TARGET_PROVIDERS.has(input.targetProvider)) {
|
|
690
|
+
throw mapSessionProviderError(new Error("FORK_TARGET_PROVIDER_NOT_SUPPORTED"));
|
|
691
|
+
}
|
|
692
|
+
const sourceIndex = this.sessionIndexRepository.findIndexRecordBySessionId(input.sessionId);
|
|
693
|
+
const inheritedMessages = await this.readForkSourceMessages(input.sessionId, sourceBinding, input.sourceType, sourceMessageId, input.sourceMessageSnapshot ?? null);
|
|
694
|
+
const reconstructedMessages = inheritedMessages.filter((message) => (message.role === "user" || message.role === "assistant")
|
|
695
|
+
&& message.kind === "text"
|
|
696
|
+
&& message.content.trim().length > 0);
|
|
697
|
+
const inheritedPrompt = buildReconstructedForkPrompt({
|
|
698
|
+
sourceProvider: sourceBinding.provider,
|
|
699
|
+
targetProvider: input.targetProvider,
|
|
700
|
+
sourceType: input.sourceType,
|
|
701
|
+
sourceTitle: sourceIndex?.title?.trim() || null,
|
|
702
|
+
messages: reconstructedMessages
|
|
703
|
+
});
|
|
704
|
+
const startedSession = await this.startSessionDirect({
|
|
705
|
+
workspaceId: sourceBinding.workspaceId,
|
|
706
|
+
userId: input.userId,
|
|
707
|
+
provider: input.targetProvider,
|
|
708
|
+
initialPrompt: inheritedPrompt,
|
|
709
|
+
parentSessionId: input.sessionId,
|
|
710
|
+
sessionKind: input.sessionKind ?? "default",
|
|
711
|
+
annotationSourceMessageId: input.annotationSourceMessageId ?? null,
|
|
712
|
+
annotationSourceText: input.annotationSourceText ?? null
|
|
713
|
+
});
|
|
714
|
+
const timestamp = nowIso();
|
|
715
|
+
const currentIndex = this.sessionIndexRepository.findIndexRecordBySessionId(startedSession.sessionId);
|
|
716
|
+
this.db.transaction(() => {
|
|
717
|
+
if (currentIndex) {
|
|
718
|
+
this.sessionIndexRepository.upsert({
|
|
719
|
+
...currentIndex,
|
|
720
|
+
parentSessionId: input.sessionId,
|
|
721
|
+
sessionKind: input.sessionKind ?? currentIndex.sessionKind ?? "default",
|
|
722
|
+
annotationSourceMessageId: input.annotationSourceMessageId ?? currentIndex.annotationSourceMessageId ?? null,
|
|
723
|
+
annotationSourceText: input.annotationSourceText ?? currentIndex.annotationSourceText ?? null,
|
|
724
|
+
updatedAt: timestamp
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
this.sessionForkRepository.upsert({
|
|
728
|
+
sessionId: startedSession.sessionId,
|
|
729
|
+
parentSessionId: input.sessionId,
|
|
730
|
+
provider: input.targetProvider,
|
|
731
|
+
forkSourceType: input.sourceType,
|
|
732
|
+
forkSourceSessionId: input.sessionId,
|
|
733
|
+
forkSourceMessageId: sourceMessageId,
|
|
734
|
+
inheritedPrefixMessageCount: reconstructedMessages.length,
|
|
735
|
+
providerParentSessionId: sourceBinding.providerSessionId,
|
|
736
|
+
providerSourceMessageId: null,
|
|
737
|
+
forkMethod: input.sourceType === "session"
|
|
738
|
+
? "reconstructed_session_fork"
|
|
739
|
+
: "reconstructed_message_fork",
|
|
740
|
+
createdAt: timestamp
|
|
741
|
+
});
|
|
742
|
+
})();
|
|
743
|
+
const relationMap = this.workspaceSessionRelations.get(sourceBinding.workspaceId)
|
|
744
|
+
?? new Map();
|
|
745
|
+
relationMap.set(startedSession.sessionId, {
|
|
746
|
+
parentSessionId: input.sessionId,
|
|
747
|
+
sessionKind: startedSession.sessionKind ?? input.sessionKind ?? "default",
|
|
748
|
+
annotationSourceMessageId: startedSession.annotationSourceMessageId ?? input.annotationSourceMessageId ?? null,
|
|
749
|
+
annotationSourceText: startedSession.annotationSourceText ?? input.annotationSourceText ?? null,
|
|
750
|
+
isSubagent: startedSession.isSubagent ?? false,
|
|
751
|
+
subagentLabel: startedSession.subagentLabel ?? null
|
|
752
|
+
});
|
|
753
|
+
this.workspaceSessionRelations.set(sourceBinding.workspaceId, relationMap);
|
|
754
|
+
return this.getSessionListItemOrThrow(startedSession.sessionId, input.userId);
|
|
755
|
+
}
|
|
756
|
+
async readForkSourceMessages(sessionId, binding, sourceType, sourceMessageId, sourceMessageSnapshot = null) {
|
|
757
|
+
const messages = [];
|
|
758
|
+
let cursor = null;
|
|
759
|
+
while (true) {
|
|
760
|
+
const page = await this.readPage(sessionId, binding.provider, binding.providerSessionId, binding.rawStoreRef, cursor, FORK_RECONSTRUCTION_PAGE_SIZE, "forward");
|
|
761
|
+
messages.push(...page.messages);
|
|
762
|
+
if (!page.nextCursor) {
|
|
763
|
+
break;
|
|
764
|
+
}
|
|
765
|
+
cursor = page.nextCursor;
|
|
766
|
+
}
|
|
767
|
+
if (sourceType === "session") {
|
|
768
|
+
return messages;
|
|
769
|
+
}
|
|
770
|
+
const targetIndex = messages.findIndex((message) => message.messageId === sourceMessageId);
|
|
771
|
+
if (targetIndex < 0) {
|
|
772
|
+
throw mapSessionProviderError(new Error("FORK_SOURCE_MESSAGE_NOT_FOUND"));
|
|
773
|
+
}
|
|
774
|
+
const inheritedMessages = messages.slice(0, targetIndex + 1);
|
|
775
|
+
if (!sourceMessageSnapshot) {
|
|
776
|
+
return inheritedMessages;
|
|
777
|
+
}
|
|
778
|
+
const targetMessage = inheritedMessages[targetIndex];
|
|
779
|
+
if (!targetMessage) {
|
|
780
|
+
return inheritedMessages;
|
|
781
|
+
}
|
|
782
|
+
inheritedMessages[targetIndex] = {
|
|
783
|
+
...targetMessage,
|
|
784
|
+
role: sourceMessageSnapshot.role,
|
|
785
|
+
kind: sourceMessageSnapshot.kind,
|
|
786
|
+
content: sourceMessageSnapshot.content
|
|
787
|
+
};
|
|
788
|
+
return inheritedMessages;
|
|
789
|
+
}
|
|
790
|
+
assertForkDepthWithinLimit(parentSessionId) {
|
|
791
|
+
const nextDepth = this.getSessionForkDepth(parentSessionId) + 1;
|
|
792
|
+
if (nextDepth > MAX_FORK_DEPTH) {
|
|
793
|
+
throw new AppError({
|
|
794
|
+
statusCode: 409,
|
|
795
|
+
errorCode: "FORK_DEPTH_LIMIT_EXCEEDED",
|
|
796
|
+
detail: `fork 会话层级最多支持 ${MAX_FORK_DEPTH} 级`
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
getSessionForkDepth(sessionId) {
|
|
801
|
+
let depth = 1;
|
|
802
|
+
let currentSessionId = sessionId;
|
|
803
|
+
const visitedSessionIds = new Set();
|
|
804
|
+
while (currentSessionId) {
|
|
805
|
+
if (visitedSessionIds.has(currentSessionId)) {
|
|
806
|
+
return depth;
|
|
807
|
+
}
|
|
808
|
+
visitedSessionIds.add(currentSessionId);
|
|
809
|
+
const parentSessionId = this.sessionForkRepository.findBySessionId(currentSessionId)?.parentSessionId
|
|
810
|
+
?? this.sessionIndexRepository.findIndexRecordBySessionId(currentSessionId)?.parentSessionId
|
|
811
|
+
?? null;
|
|
812
|
+
if (!parentSessionId) {
|
|
813
|
+
return depth;
|
|
814
|
+
}
|
|
815
|
+
depth += 1;
|
|
816
|
+
currentSessionId = parentSessionId;
|
|
817
|
+
}
|
|
818
|
+
return depth;
|
|
819
|
+
}
|
|
382
820
|
async sendMessage(sessionId, content, clientRequestId, permissionMode = null) {
|
|
383
821
|
const binding = this.getBindingOrThrow(sessionId);
|
|
384
822
|
const result = await this.sessionSyncService
|
|
@@ -388,14 +826,21 @@ export class SessionHistoryService {
|
|
|
388
826
|
throw mapSessionProviderError(error);
|
|
389
827
|
});
|
|
390
828
|
const existing = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
|
|
829
|
+
const sessionFork = this.sessionForkRepository.findBySessionId(sessionId);
|
|
830
|
+
const parentTitle = sessionFork?.parentSessionId
|
|
831
|
+
? this.sessionIndexRepository.findIndexRecordBySessionId(sessionFork.parentSessionId)?.title ?? null
|
|
832
|
+
: null;
|
|
391
833
|
this.sessionIndexRepository.upsert({
|
|
392
834
|
sessionId,
|
|
393
835
|
workspaceId: binding.workspaceId,
|
|
394
836
|
provider: binding.provider,
|
|
395
837
|
parentSessionId: existing?.parentSessionId ?? null,
|
|
838
|
+
sessionKind: existing?.sessionKind ?? "default",
|
|
839
|
+
annotationSourceMessageId: existing?.annotationSourceMessageId ?? null,
|
|
840
|
+
annotationSourceText: existing?.annotationSourceText ?? null,
|
|
396
841
|
isSubagent: existing?.isSubagent ?? false,
|
|
397
842
|
subagentLabel: existing?.subagentLabel ?? null,
|
|
398
|
-
title: existing?.title ?? result.message.content
|
|
843
|
+
title: resolveSessionListTitle(binding.provider, existing?.title ?? null, result.message.content, parentTitle),
|
|
399
844
|
messageCount: (existing?.messageCount ?? 0) + 1,
|
|
400
845
|
isArchived: existing?.isArchived ?? false,
|
|
401
846
|
lastMessageAt: result.message.timestamp,
|
|
@@ -493,6 +938,25 @@ export class SessionHistoryService {
|
|
|
493
938
|
messages: page.messages
|
|
494
939
|
};
|
|
495
940
|
}
|
|
941
|
+
async readAllTextHistoryMessages(sessionId, limit = FORK_RECONSTRUCTION_PAGE_SIZE) {
|
|
942
|
+
const binding = this.getBindingOrThrow(sessionId);
|
|
943
|
+
const messages = [];
|
|
944
|
+
let cursor = null;
|
|
945
|
+
let remaining = Math.max(limit, 0);
|
|
946
|
+
while (remaining > 0) {
|
|
947
|
+
const pageSize = Math.min(remaining, FORK_RECONSTRUCTION_PAGE_SIZE);
|
|
948
|
+
const page = await this.readPage(sessionId, binding.provider, binding.providerSessionId, binding.rawStoreRef, cursor, pageSize, "forward");
|
|
949
|
+
messages.push(...page.messages.filter((message) => (message.role === "user" || message.role === "assistant")
|
|
950
|
+
&& message.kind === "text"
|
|
951
|
+
&& message.content.trim().length > 0));
|
|
952
|
+
if (!page.nextCursor || page.messages.length === 0) {
|
|
953
|
+
break;
|
|
954
|
+
}
|
|
955
|
+
cursor = page.nextCursor;
|
|
956
|
+
remaining -= page.messages.length;
|
|
957
|
+
}
|
|
958
|
+
return messages;
|
|
959
|
+
}
|
|
496
960
|
async markSessionSeen(sessionId, userId) {
|
|
497
961
|
const existing = this.sessionStateRepository.findBySessionAndUser(sessionId, userId) ??
|
|
498
962
|
(await this.refreshSessionState(sessionId, userId));
|
|
@@ -556,6 +1020,9 @@ export class SessionHistoryService {
|
|
|
556
1020
|
workspaceId: existing.workspaceId,
|
|
557
1021
|
provider: existing.provider,
|
|
558
1022
|
parentSessionId: existing.parentSessionId ?? null,
|
|
1023
|
+
sessionKind: existing.sessionKind ?? "default",
|
|
1024
|
+
annotationSourceMessageId: existing.annotationSourceMessageId ?? null,
|
|
1025
|
+
annotationSourceText: existing.annotationSourceText ?? null,
|
|
559
1026
|
isSubagent: existing.isSubagent ?? false,
|
|
560
1027
|
subagentLabel: existing.subagentLabel ?? null,
|
|
561
1028
|
title: existing.title,
|
|
@@ -645,16 +1112,29 @@ export class SessionHistoryService {
|
|
|
645
1112
|
}
|
|
646
1113
|
async runDiscoverWorkspaceSessions(workspaceId, userId, refreshStateMode = "inline") {
|
|
647
1114
|
const startedAt = Date.now();
|
|
1115
|
+
const debugStartedAtMs = terminalDebugNowMs();
|
|
648
1116
|
const workspace = this.getWorkspaceOrThrow(workspaceId);
|
|
649
1117
|
let discoverDurationMs = 0;
|
|
650
1118
|
let persistDurationMs = 0;
|
|
1119
|
+
let persistPass1DurationMs = 0;
|
|
1120
|
+
let persistPass1BatchCount = 0;
|
|
1121
|
+
let persistPass1MaxBatchMs = 0;
|
|
1122
|
+
let relationMapDurationMs = 0;
|
|
1123
|
+
let persistPass2DurationMs = 0;
|
|
1124
|
+
let persistPass2BatchCount = 0;
|
|
1125
|
+
let persistPass2MaxBatchMs = 0;
|
|
1126
|
+
let cleanupDurationMs = 0;
|
|
1127
|
+
let listItemsDurationMs = 0;
|
|
1128
|
+
let refreshStateDurationMs = 0;
|
|
651
1129
|
const refreshStateCount = 10;
|
|
652
1130
|
try {
|
|
653
1131
|
const discoverStartedAt = Date.now();
|
|
654
1132
|
const existingWorkspaceSessions = this.sessionIndexRepository.listByWorkspace(workspaceId, userId);
|
|
655
1133
|
const knownSessions = this.buildKnownSessionSummaries(existingWorkspaceSessions, workspace.path);
|
|
656
|
-
const discovery = await this.
|
|
657
|
-
.discoverWorkspaceSessions(
|
|
1134
|
+
const discovery = await this.providerDiscoveryHelperClient
|
|
1135
|
+
.discoverWorkspaceSessions({
|
|
1136
|
+
config: this.providerSessionDiscoveryConfig,
|
|
1137
|
+
workspacePath: workspace.path,
|
|
658
1138
|
knownSessions
|
|
659
1139
|
})
|
|
660
1140
|
.catch((error) => {
|
|
@@ -666,10 +1146,9 @@ export class SessionHistoryService {
|
|
|
666
1146
|
const discoveredSessionIds = new Map();
|
|
667
1147
|
const persistedSessions = [];
|
|
668
1148
|
const claimedPendingSessionIds = new Set();
|
|
669
|
-
const
|
|
670
|
-
for (const session of
|
|
1149
|
+
const persistPass1Transaction = this.db.transaction((batch) => {
|
|
1150
|
+
for (const session of batch) {
|
|
671
1151
|
const exactExisting = this.sessionBindingRepository.findByProviderSession(session.provider, session.providerSessionId) ?? this.sessionBindingRepository.findByRawStoreRef(session.provider, session.rawStoreRef);
|
|
672
|
-
// discover 只能补全当前工作区,不能把别的工作区已有会话偷过来重绑。
|
|
673
1152
|
if (exactExisting && exactExisting.workspaceId !== workspaceId) {
|
|
674
1153
|
continue;
|
|
675
1154
|
}
|
|
@@ -699,11 +1178,24 @@ export class SessionHistoryService {
|
|
|
699
1178
|
createdAt,
|
|
700
1179
|
updatedAt: timestamp
|
|
701
1180
|
});
|
|
1181
|
+
const preservedParentSessionId = existingIndex?.parentSessionId
|
|
1182
|
+
?? this.sessionForkRepository.findBySessionId(sessionId)?.parentSessionId
|
|
1183
|
+
?? null;
|
|
1184
|
+
const preservedParentTitle = preservedParentSessionId
|
|
1185
|
+
? this.sessionIndexRepository.findIndexRecordBySessionId(preservedParentSessionId)?.title ?? null
|
|
1186
|
+
: null;
|
|
1187
|
+
const preservedTitle = resolvePersistedSessionTitle(session.provider, session.title, existingIndex?.title ?? null, preservedParentTitle);
|
|
702
1188
|
this.sessionIndexRepository.upsert({
|
|
703
1189
|
sessionId,
|
|
704
1190
|
workspaceId: workspace.id,
|
|
705
1191
|
provider: session.provider,
|
|
706
|
-
|
|
1192
|
+
parentSessionId: preservedParentSessionId,
|
|
1193
|
+
sessionKind: existingIndex?.sessionKind ?? "default",
|
|
1194
|
+
annotationSourceMessageId: existingIndex?.annotationSourceMessageId ?? null,
|
|
1195
|
+
annotationSourceText: existingIndex?.annotationSourceText ?? null,
|
|
1196
|
+
isSubagent: existingIndex?.isSubagent ?? false,
|
|
1197
|
+
subagentLabel: existingIndex?.subagentLabel ?? null,
|
|
1198
|
+
title: preservedTitle,
|
|
707
1199
|
messageCount: session.messageCount,
|
|
708
1200
|
isArchived: resolveDiscoveredArchiveState(existingIndex?.isArchived ?? false, session.isArchived),
|
|
709
1201
|
lastMessageAt: session.lastMessageAt,
|
|
@@ -728,17 +1220,46 @@ export class SessionHistoryService {
|
|
|
728
1220
|
existingIndex
|
|
729
1221
|
});
|
|
730
1222
|
}
|
|
731
|
-
|
|
732
|
-
|
|
1223
|
+
});
|
|
1224
|
+
const persistPass1StartedAt = Date.now();
|
|
1225
|
+
const persistPass1Stats = await runBatchedTransactions(sessions, WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE, persistPass1Transaction);
|
|
1226
|
+
persistPass1DurationMs = Date.now() - persistPass1StartedAt;
|
|
1227
|
+
persistPass1BatchCount = persistPass1Stats.batchCount;
|
|
1228
|
+
persistPass1MaxBatchMs = persistPass1Stats.maxBatchMs;
|
|
1229
|
+
const relationMapStartedAt = Date.now();
|
|
1230
|
+
const relationMap = this.buildWorkspaceSessionRelationMap(sessions, discoveredSessionIds);
|
|
1231
|
+
relationMapDurationMs = Date.now() - relationMapStartedAt;
|
|
1232
|
+
const persistPass2Transaction = this.db.transaction((batch) => {
|
|
1233
|
+
for (const persistedSession of batch) {
|
|
733
1234
|
const relation = relationMap.get(persistedSession.sessionId);
|
|
1235
|
+
const resolvedParentSessionId = relation?.parentSessionId
|
|
1236
|
+
?? persistedSession.existingIndex?.parentSessionId
|
|
1237
|
+
?? this.sessionForkRepository.findBySessionId(persistedSession.sessionId)?.parentSessionId
|
|
1238
|
+
?? null;
|
|
1239
|
+
const resolvedParentTitle = resolvedParentSessionId
|
|
1240
|
+
? this.sessionIndexRepository.findIndexRecordBySessionId(resolvedParentSessionId)?.title ?? null
|
|
1241
|
+
: null;
|
|
734
1242
|
this.sessionIndexRepository.upsert({
|
|
735
1243
|
sessionId: persistedSession.sessionId,
|
|
736
1244
|
workspaceId: workspace.id,
|
|
737
1245
|
provider: persistedSession.session.provider,
|
|
738
|
-
parentSessionId:
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
1246
|
+
parentSessionId: resolvedParentSessionId,
|
|
1247
|
+
sessionKind: relation?.sessionKind
|
|
1248
|
+
?? persistedSession.existingIndex?.sessionKind
|
|
1249
|
+
?? "default",
|
|
1250
|
+
annotationSourceMessageId: relation?.annotationSourceMessageId
|
|
1251
|
+
?? persistedSession.existingIndex?.annotationSourceMessageId
|
|
1252
|
+
?? null,
|
|
1253
|
+
annotationSourceText: relation?.annotationSourceText
|
|
1254
|
+
?? persistedSession.existingIndex?.annotationSourceText
|
|
1255
|
+
?? null,
|
|
1256
|
+
isSubagent: relation?.isSubagent
|
|
1257
|
+
?? persistedSession.existingIndex?.isSubagent
|
|
1258
|
+
?? false,
|
|
1259
|
+
subagentLabel: relation?.subagentLabel
|
|
1260
|
+
?? persistedSession.existingIndex?.subagentLabel
|
|
1261
|
+
?? null,
|
|
1262
|
+
title: resolvePersistedSessionTitle(persistedSession.session.provider, persistedSession.session.title, persistedSession.existingIndex?.title ?? null, resolvedParentTitle),
|
|
742
1263
|
messageCount: persistedSession.session.messageCount,
|
|
743
1264
|
isArchived: resolveDiscoveredArchiveState(persistedSession.existingIndex?.isArchived ?? false, persistedSession.session.isArchived),
|
|
744
1265
|
lastMessageAt: persistedSession.session.lastMessageAt,
|
|
@@ -747,26 +1268,56 @@ export class SessionHistoryService {
|
|
|
747
1268
|
});
|
|
748
1269
|
}
|
|
749
1270
|
});
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
|
|
1271
|
+
const persistPass2StartedAt = Date.now();
|
|
1272
|
+
const persistPass2Stats = await runBatchedTransactions(persistedSessions, WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE, persistPass2Transaction);
|
|
1273
|
+
persistPass2DurationMs = Date.now() - persistPass2StartedAt;
|
|
1274
|
+
persistPass2BatchCount = persistPass2Stats.batchCount;
|
|
1275
|
+
persistPass2MaxBatchMs = persistPass2Stats.maxBatchMs;
|
|
1276
|
+
persistDurationMs = persistPass1DurationMs + relationMapDurationMs + persistPass2DurationMs;
|
|
753
1277
|
if (discovery.isComplete) {
|
|
754
|
-
|
|
1278
|
+
const cleanupStartedAt = Date.now();
|
|
1279
|
+
await this.cleanupStaleHiddenSessions(workspaceId, userId, sessions);
|
|
1280
|
+
cleanupDurationMs = Date.now() - cleanupStartedAt;
|
|
755
1281
|
}
|
|
756
|
-
this.workspaceSessionRelations.set(workspaceId,
|
|
1282
|
+
this.workspaceSessionRelations.set(workspaceId, relationMap);
|
|
1283
|
+
const listItemsStartedAt = Date.now();
|
|
757
1284
|
const items = this.sessionIndexRepository.listByWorkspace(workspaceId, userId);
|
|
758
|
-
|
|
1285
|
+
listItemsDurationMs = Date.now() - listItemsStartedAt;
|
|
1286
|
+
const refreshCandidates = buildSessionStateRefreshCandidates(items, refreshStateCount);
|
|
759
1287
|
this.workspaceDiscoveryStatuses.set(workspaceId, {
|
|
760
1288
|
refreshedAt: Date.now(),
|
|
761
1289
|
isComplete: discovery.isComplete
|
|
762
1290
|
});
|
|
1291
|
+
const refreshStateStartedAt = Date.now();
|
|
763
1292
|
if (refreshStateMode === "inline") {
|
|
764
|
-
await this.refreshRecentSessionStates(
|
|
1293
|
+
await this.refreshRecentSessionStates(refreshCandidates, userId);
|
|
765
1294
|
}
|
|
766
1295
|
else {
|
|
767
|
-
this.scheduleWorkspaceStateRefresh(workspaceId, userId,
|
|
1296
|
+
this.scheduleWorkspaceStateRefresh(workspaceId, userId, refreshCandidates);
|
|
768
1297
|
}
|
|
1298
|
+
refreshStateDurationMs = Date.now() - refreshStateStartedAt;
|
|
769
1299
|
const nextItems = this.listWorkspaceSessions(workspaceId, userId);
|
|
1300
|
+
if (isTerminalDebugEnabled()) {
|
|
1301
|
+
logTerminalDebug("workspace.discovery.completed", {
|
|
1302
|
+
workspaceId,
|
|
1303
|
+
sessionCount: sessions.length,
|
|
1304
|
+
returnedSessionCount: nextItems.length,
|
|
1305
|
+
discoverMs: discoverDurationMs,
|
|
1306
|
+
persistMs: persistDurationMs,
|
|
1307
|
+
persistPass1Ms: persistPass1DurationMs,
|
|
1308
|
+
persistPass1BatchCount,
|
|
1309
|
+
persistPass1MaxBatchMs,
|
|
1310
|
+
relationMapMs: relationMapDurationMs,
|
|
1311
|
+
persistPass2Ms: persistPass2DurationMs,
|
|
1312
|
+
persistPass2BatchCount,
|
|
1313
|
+
persistPass2MaxBatchMs,
|
|
1314
|
+
cleanupMs: cleanupDurationMs,
|
|
1315
|
+
listItemsMs: listItemsDurationMs,
|
|
1316
|
+
refreshStateMs: refreshStateDurationMs,
|
|
1317
|
+
refreshStateDeferred: refreshStateMode !== "inline",
|
|
1318
|
+
durationMs: terminalDebugNowMs() - debugStartedAtMs
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
770
1321
|
logPerformance("workspace.discover_sessions", Date.now() - startedAt, {
|
|
771
1322
|
workspaceId,
|
|
772
1323
|
workspacePath: workspace.path,
|
|
@@ -774,9 +1325,20 @@ export class SessionHistoryService {
|
|
|
774
1325
|
discoveredSessions: sessions.length,
|
|
775
1326
|
returnedSessions: nextItems.length,
|
|
776
1327
|
discoveryComplete: discovery.isComplete,
|
|
777
|
-
|
|
1328
|
+
providerDiagnostics: (discovery.providerDiagnostics ?? []).map((entry) => `${entry.provider}:${entry.status}:${Math.round(entry.durationMs)}ms`),
|
|
1329
|
+
refreshedStates: refreshCandidates.length,
|
|
778
1330
|
discoverMs: discoverDurationMs,
|
|
779
1331
|
persistMs: persistDurationMs,
|
|
1332
|
+
persistPass1Ms: persistPass1DurationMs,
|
|
1333
|
+
persistPass1BatchCount,
|
|
1334
|
+
persistPass1MaxBatchMs,
|
|
1335
|
+
relationMapMs: relationMapDurationMs,
|
|
1336
|
+
persistPass2Ms: persistPass2DurationMs,
|
|
1337
|
+
persistPass2BatchCount,
|
|
1338
|
+
persistPass2MaxBatchMs,
|
|
1339
|
+
cleanupMs: cleanupDurationMs,
|
|
1340
|
+
listItemsMs: listItemsDurationMs,
|
|
1341
|
+
refreshStateMs: refreshStateDurationMs,
|
|
780
1342
|
refreshStateDeferred: refreshStateMode !== "inline"
|
|
781
1343
|
}, {
|
|
782
1344
|
thresholdMs: 500
|
|
@@ -789,6 +1351,16 @@ export class SessionHistoryService {
|
|
|
789
1351
|
workspacePath: workspace.path,
|
|
790
1352
|
discoverMs: discoverDurationMs,
|
|
791
1353
|
persistMs: persistDurationMs,
|
|
1354
|
+
persistPass1Ms: persistPass1DurationMs,
|
|
1355
|
+
persistPass1BatchCount,
|
|
1356
|
+
persistPass1MaxBatchMs,
|
|
1357
|
+
relationMapMs: relationMapDurationMs,
|
|
1358
|
+
persistPass2Ms: persistPass2DurationMs,
|
|
1359
|
+
persistPass2BatchCount,
|
|
1360
|
+
persistPass2MaxBatchMs,
|
|
1361
|
+
cleanupMs: cleanupDurationMs,
|
|
1362
|
+
listItemsMs: listItemsDurationMs,
|
|
1363
|
+
refreshStateMs: refreshStateDurationMs,
|
|
792
1364
|
refreshStateDeferred: refreshStateMode !== "inline",
|
|
793
1365
|
error: error instanceof Error ? error.message : "unknown"
|
|
794
1366
|
}, {
|
|
@@ -909,11 +1481,17 @@ export class SessionHistoryService {
|
|
|
909
1481
|
? discoveredSessionIds.get(buildProviderSessionKey(session.provider, session.parentProviderSessionId)) ??
|
|
910
1482
|
this.sessionBindingRepository.findByProviderSession(session.provider, session.parentProviderSessionId)?.sessionId ??
|
|
911
1483
|
null
|
|
912
|
-
:
|
|
1484
|
+
: this.resolvePersistedParentSessionId(sessionId);
|
|
913
1485
|
relationMap.set(sessionId, {
|
|
914
1486
|
parentSessionId,
|
|
915
|
-
|
|
916
|
-
|
|
1487
|
+
sessionKind: this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.sessionKind ?? "default",
|
|
1488
|
+
annotationSourceMessageId: this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.annotationSourceMessageId ?? null,
|
|
1489
|
+
annotationSourceText: this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.annotationSourceText ?? null,
|
|
1490
|
+
isSubagent: session.isSubagent === true
|
|
1491
|
+
|| this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.isSubagent === true,
|
|
1492
|
+
subagentLabel: session.subagentLabel?.trim()
|
|
1493
|
+
|| this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.subagentLabel
|
|
1494
|
+
|| null
|
|
917
1495
|
});
|
|
918
1496
|
}
|
|
919
1497
|
return relationMap;
|
|
@@ -931,6 +1509,9 @@ export class SessionHistoryService {
|
|
|
931
1509
|
return this.enrichSessionItem({
|
|
932
1510
|
...item,
|
|
933
1511
|
parentSessionId: relation.parentSessionId,
|
|
1512
|
+
sessionKind: relation.sessionKind,
|
|
1513
|
+
annotationSourceMessageId: relation.annotationSourceMessageId,
|
|
1514
|
+
annotationSourceText: relation.annotationSourceText,
|
|
934
1515
|
isSubagent: relation.isSubagent,
|
|
935
1516
|
subagentLabel: relation.subagentLabel
|
|
936
1517
|
});
|
|
@@ -942,12 +1523,18 @@ export class SessionHistoryService {
|
|
|
942
1523
|
? {
|
|
943
1524
|
...item,
|
|
944
1525
|
parentSessionId: relation.parentSessionId,
|
|
1526
|
+
sessionKind: relation.sessionKind,
|
|
1527
|
+
annotationSourceMessageId: relation.annotationSourceMessageId,
|
|
1528
|
+
annotationSourceText: relation.annotationSourceText,
|
|
945
1529
|
isSubagent: relation.isSubagent,
|
|
946
1530
|
subagentLabel: relation.subagentLabel
|
|
947
1531
|
}
|
|
948
1532
|
: {
|
|
949
1533
|
...item,
|
|
950
1534
|
parentSessionId: item.parentSessionId ?? null,
|
|
1535
|
+
sessionKind: item.sessionKind ?? "default",
|
|
1536
|
+
annotationSourceMessageId: item.annotationSourceMessageId ?? null,
|
|
1537
|
+
annotationSourceText: item.annotationSourceText ?? null,
|
|
951
1538
|
isSubagent: item.isSubagent ?? false,
|
|
952
1539
|
subagentLabel: item.subagentLabel ?? null
|
|
953
1540
|
};
|
|
@@ -1018,16 +1605,27 @@ export class SessionHistoryService {
|
|
|
1018
1605
|
if (shouldSkipClaudePendingBinding(binding)) {
|
|
1019
1606
|
return;
|
|
1020
1607
|
}
|
|
1021
|
-
const nextTitle = (await this.
|
|
1022
|
-
|
|
1608
|
+
const nextTitle = (await this.providerDiscoveryHelperClient.readSessionTitle({
|
|
1609
|
+
config: this.providerSessionDiscoveryConfig,
|
|
1610
|
+
provider: binding.provider,
|
|
1611
|
+
providerSessionId: binding.providerSessionId,
|
|
1612
|
+
rawStoreRef: binding.rawStoreRef
|
|
1613
|
+
})).trim();
|
|
1614
|
+
const resolvedTitle = resolvePersistedSessionTitle(binding.provider, nextTitle, currentIndex.title);
|
|
1615
|
+
if (resolvedTitle.length === 0 || resolvedTitle === currentIndex.title) {
|
|
1023
1616
|
return;
|
|
1024
1617
|
}
|
|
1025
1618
|
this.sessionIndexRepository.upsert({
|
|
1026
1619
|
...currentIndex,
|
|
1027
|
-
title:
|
|
1620
|
+
title: resolvedTitle,
|
|
1028
1621
|
updatedAt: nowIso()
|
|
1029
1622
|
});
|
|
1030
1623
|
}
|
|
1624
|
+
resolvePersistedParentSessionId(sessionId) {
|
|
1625
|
+
return (this.sessionForkRepository.findBySessionId(sessionId)?.parentSessionId
|
|
1626
|
+
?? this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.parentSessionId
|
|
1627
|
+
?? null);
|
|
1628
|
+
}
|
|
1031
1629
|
async ensureSessionChangedFilesIndexed(sessionId) {
|
|
1032
1630
|
if (this.sessionChangedFileService.hasIndexedSession(sessionId)) {
|
|
1033
1631
|
return;
|
|
@@ -1169,7 +1767,7 @@ export class SessionHistoryService {
|
|
|
1169
1767
|
});
|
|
1170
1768
|
this.workspaceStateRefreshInflight.set(inflightKey, task);
|
|
1171
1769
|
}
|
|
1172
|
-
cleanupStaleHiddenSessions(workspaceId, userId, sessions) {
|
|
1770
|
+
async cleanupStaleHiddenSessions(workspaceId, userId, sessions) {
|
|
1173
1771
|
const discoveredProviderSessionIds = new Set(sessions.map((session) => buildProviderSessionKey(session.provider, session.providerSessionId)));
|
|
1174
1772
|
const discoveredRawStoreRefs = new Set(sessions.map((session) => session.rawStoreRef));
|
|
1175
1773
|
const staleHiddenSessions = this.sessionIndexRepository
|
|
@@ -1189,15 +1787,12 @@ export class SessionHistoryService {
|
|
|
1189
1787
|
if (staleHiddenSessions.length === 0) {
|
|
1190
1788
|
return;
|
|
1191
1789
|
}
|
|
1192
|
-
this.
|
|
1193
|
-
}
|
|
1194
|
-
deleteSessionsByIds(sessionIds) {
|
|
1195
|
-
const remove = this.db.transaction((ids) => {
|
|
1790
|
+
const deleteTransaction = this.db.transaction((ids) => {
|
|
1196
1791
|
for (const sessionId of ids) {
|
|
1197
1792
|
this.deleteSessionById(sessionId);
|
|
1198
1793
|
}
|
|
1199
1794
|
});
|
|
1200
|
-
|
|
1795
|
+
await runBatchedTransactions(staleHiddenSessions.map((session) => session.sessionId), WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE, deleteTransaction);
|
|
1201
1796
|
}
|
|
1202
1797
|
findPendingBindingDuplicate(sessionId, workspaceId, currentBinding, snapshot) {
|
|
1203
1798
|
if (!currentBinding || !isPendingBindingValue(currentBinding.providerSessionId)) {
|
|
@@ -1291,6 +1886,9 @@ export class SessionHistoryService {
|
|
|
1291
1886
|
this.db
|
|
1292
1887
|
.prepare("DELETE FROM session_status_snapshots WHERE session_id = ?")
|
|
1293
1888
|
.run(input.sourceSessionId);
|
|
1889
|
+
this.db
|
|
1890
|
+
.prepare("DELETE FROM session_forks WHERE session_id = ?")
|
|
1891
|
+
.run(input.sourceSessionId);
|
|
1294
1892
|
this.db
|
|
1295
1893
|
.prepare("DELETE FROM session_indices WHERE session_id = ?")
|
|
1296
1894
|
.run(input.sourceSessionId);
|
|
@@ -1362,11 +1960,25 @@ export class SessionHistoryService {
|
|
|
1362
1960
|
relationMap.delete(sourceSessionId);
|
|
1363
1961
|
relationMap.set(targetSessionId, {
|
|
1364
1962
|
parentSessionId: targetRelation?.parentSessionId ?? sourceRelation?.parentSessionId ?? fallbackParentSessionId,
|
|
1963
|
+
sessionKind: targetRelation?.sessionKind
|
|
1964
|
+
?? sourceRelation?.sessionKind
|
|
1965
|
+
?? targetIndex?.sessionKind
|
|
1966
|
+
?? sourceIndex?.sessionKind
|
|
1967
|
+
?? "default",
|
|
1968
|
+
annotationSourceMessageId: targetRelation?.annotationSourceMessageId
|
|
1969
|
+
?? sourceRelation?.annotationSourceMessageId
|
|
1970
|
+
?? targetIndex?.annotationSourceMessageId
|
|
1971
|
+
?? sourceIndex?.annotationSourceMessageId
|
|
1972
|
+
?? null,
|
|
1973
|
+
annotationSourceText: targetRelation?.annotationSourceText
|
|
1974
|
+
?? sourceRelation?.annotationSourceText
|
|
1975
|
+
?? targetIndex?.annotationSourceText
|
|
1976
|
+
?? sourceIndex?.annotationSourceText
|
|
1977
|
+
?? null,
|
|
1365
1978
|
isSubagent: Boolean(targetRelation?.isSubagent
|
|
1366
1979
|
|| sourceRelation?.isSubagent
|
|
1367
1980
|
|| targetIndex?.isSubagent
|
|
1368
|
-
|| sourceIndex?.isSubagent
|
|
1369
|
-
|| fallbackParentSessionId),
|
|
1981
|
+
|| sourceIndex?.isSubagent),
|
|
1370
1982
|
subagentLabel: targetRelation?.subagentLabel
|
|
1371
1983
|
?? sourceRelation?.subagentLabel
|
|
1372
1984
|
?? targetIndex?.subagentLabel
|
|
@@ -1396,6 +2008,9 @@ export class SessionHistoryService {
|
|
|
1396
2008
|
this.db
|
|
1397
2009
|
.prepare("DELETE FROM session_status_snapshots WHERE session_id = ?")
|
|
1398
2010
|
.run(sessionId);
|
|
2011
|
+
this.db
|
|
2012
|
+
.prepare("DELETE FROM session_forks WHERE session_id = ?")
|
|
2013
|
+
.run(sessionId);
|
|
1399
2014
|
this.db
|
|
1400
2015
|
.prepare("DELETE FROM session_indices WHERE session_id = ?")
|
|
1401
2016
|
.run(sessionId);
|
|
@@ -1426,17 +2041,24 @@ export class SessionHistoryService {
|
|
|
1426
2041
|
const current = this.sessionStateRepository.findBySessionAndUser(sessionId, userId);
|
|
1427
2042
|
const inspection = inspectSessionActivity(binding.provider, binding.rawStoreRef);
|
|
1428
2043
|
const timestamp = nowIso();
|
|
2044
|
+
const nowMs = Date.parse(timestamp);
|
|
2045
|
+
if (shouldClearStaleRuntimeWithoutInspection(current, inspection, nowMs)) {
|
|
2046
|
+
this.sessionActivityAuthorityService.clearSession(sessionId);
|
|
2047
|
+
}
|
|
1429
2048
|
if (shouldPreserveRuntimeTerminalState(current, inspection)) {
|
|
1430
2049
|
return current;
|
|
1431
2050
|
}
|
|
1432
2051
|
const resolution = this.sessionActivityAuthorityService.observe(buildInspectionActivityObservation(sessionId, inspection, timestamp));
|
|
2052
|
+
const resolvedLastEventAt = hasInspectionEvidence(inspection)
|
|
2053
|
+
? resolution.lastObservedAt ?? inspection.lastEventAt ?? current?.lastEventAt ?? null
|
|
2054
|
+
: current?.lastEventAt ?? null;
|
|
1433
2055
|
const nextRecord = {
|
|
1434
2056
|
sessionId,
|
|
1435
2057
|
userId,
|
|
1436
2058
|
runningState: mapResolvedRunningStateToStored(resolution.runningState, current),
|
|
1437
2059
|
activitySource: mapResolutionSourceToLegacyActivitySource(resolution.activityResolutionSource, inspection),
|
|
1438
2060
|
favorite: current?.favorite ?? false,
|
|
1439
|
-
lastEventAt:
|
|
2061
|
+
lastEventAt: resolvedLastEventAt,
|
|
1440
2062
|
completedAt: isTerminalResolvedRunningState(resolution.runningState)
|
|
1441
2063
|
? resolution.terminalAt ?? inspection.completedAtCandidate ?? current?.completedAt ?? null
|
|
1442
2064
|
: null,
|
|
@@ -1496,11 +2118,52 @@ export class SessionHistoryService {
|
|
|
1496
2118
|
});
|
|
1497
2119
|
}
|
|
1498
2120
|
}
|
|
2121
|
+
function isProviderCliBacked(provider) {
|
|
2122
|
+
return provider === "claude-code" || provider === "codex" || provider === "gemini" || provider === "kimi";
|
|
2123
|
+
}
|
|
2124
|
+
function buildProviderCliAvailabilitySnapshot(commandPaths) {
|
|
2125
|
+
return Object.freeze(Object.fromEntries(Object.entries(commandPaths).map(([provider, commandPath]) => [
|
|
2126
|
+
provider,
|
|
2127
|
+
isCommandAvailable(commandPath)
|
|
2128
|
+
])));
|
|
2129
|
+
}
|
|
2130
|
+
function buildProviderCliUnavailableMessage(provider) {
|
|
2131
|
+
switch (provider) {
|
|
2132
|
+
case "claude-code":
|
|
2133
|
+
return "未检测到 Claude CLI";
|
|
2134
|
+
case "codex":
|
|
2135
|
+
return "未检测到 Codex CLI";
|
|
2136
|
+
case "gemini":
|
|
2137
|
+
return "未检测到 Gemini CLI";
|
|
2138
|
+
case "kimi":
|
|
2139
|
+
return "未检测到 Kimi CLI";
|
|
2140
|
+
default:
|
|
2141
|
+
return "未检测到对应 CLI";
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
function createCodexForkTransportFactory(commandPath, homeDir) {
|
|
2145
|
+
return () => {
|
|
2146
|
+
const client = new CodexAppServerHelperClient(commandPath, { homeDir });
|
|
2147
|
+
const transport = client.createForkTransport();
|
|
2148
|
+
return {
|
|
2149
|
+
...transport,
|
|
2150
|
+
close() {
|
|
2151
|
+
transport.close();
|
|
2152
|
+
client.dispose();
|
|
2153
|
+
}
|
|
2154
|
+
};
|
|
2155
|
+
};
|
|
2156
|
+
}
|
|
1499
2157
|
function buildInspectionActivityObservation(sessionId, inspection, observedAt) {
|
|
2158
|
+
const resolvedRunningState = inspection.runningState === "failed"
|
|
2159
|
+
? "failed"
|
|
2160
|
+
: inspection.completedAtCandidate
|
|
2161
|
+
? "completed"
|
|
2162
|
+
: inspection.runningState;
|
|
1500
2163
|
return {
|
|
1501
2164
|
sessionId,
|
|
1502
2165
|
runId: null,
|
|
1503
|
-
runningState:
|
|
2166
|
+
runningState: resolvedRunningState,
|
|
1504
2167
|
source: hasInspectionEvidence(inspection) ? "inferred_log" : "unknown",
|
|
1505
2168
|
confidence: "weak",
|
|
1506
2169
|
detail: inspection.errorDetail,
|
|
@@ -1557,6 +2220,20 @@ function clampLimit(limit) {
|
|
|
1557
2220
|
}
|
|
1558
2221
|
return Math.max(1, Math.min(Math.trunc(limit), 100));
|
|
1559
2222
|
}
|
|
2223
|
+
function buildSessionStateRefreshCandidates(items, recentCount) {
|
|
2224
|
+
const recentItems = items.slice(0, recentCount);
|
|
2225
|
+
const activeResidues = items.filter((item) => isSessionStateRefreshCandidate(item));
|
|
2226
|
+
const deduped = new Map();
|
|
2227
|
+
for (const item of [...recentItems, ...activeResidues]) {
|
|
2228
|
+
deduped.set(item.sessionId, item);
|
|
2229
|
+
}
|
|
2230
|
+
return Array.from(deduped.values());
|
|
2231
|
+
}
|
|
2232
|
+
function isSessionStateRefreshCandidate(item) {
|
|
2233
|
+
return item.activityState === "running"
|
|
2234
|
+
|| item.runningState === "starting"
|
|
2235
|
+
|| item.runningState === "running";
|
|
2236
|
+
}
|
|
1560
2237
|
function mapSessionStateRecordRow(row) {
|
|
1561
2238
|
return {
|
|
1562
2239
|
sessionId: row.session_id,
|
|
@@ -1622,6 +2299,9 @@ function mergeSessionIndexRecord(input) {
|
|
|
1622
2299
|
workspaceId: input.workspaceId,
|
|
1623
2300
|
provider: (input.target?.provider ?? input.source?.provider ?? input.provider),
|
|
1624
2301
|
parentSessionId: input.target?.parentSessionId ?? input.source?.parentSessionId ?? null,
|
|
2302
|
+
sessionKind: input.target?.sessionKind ?? input.source?.sessionKind ?? "default",
|
|
2303
|
+
annotationSourceMessageId: input.target?.annotationSourceMessageId ?? input.source?.annotationSourceMessageId ?? null,
|
|
2304
|
+
annotationSourceText: input.target?.annotationSourceText ?? input.source?.annotationSourceText ?? null,
|
|
1625
2305
|
isSubagent: Boolean(input.target?.isSubagent || input.source?.isSubagent),
|
|
1626
2306
|
subagentLabel: input.target?.subagentLabel ?? input.source?.subagentLabel ?? null,
|
|
1627
2307
|
title: pickPreferredSessionTitle(input.target?.title ?? null, input.source?.title ?? null),
|
|
@@ -1991,6 +2671,56 @@ function isLegacyCodingNsRolloutSession(providerSessionId, rawStoreRef) {
|
|
|
1991
2671
|
function shouldRemoveMissingSyntheticCodexSession(rawStoreRef) {
|
|
1992
2672
|
return isSyntheticCodexRawStoreRef(rawStoreRef) && !existsSync(rawStoreRef);
|
|
1993
2673
|
}
|
|
2674
|
+
function resolveSessionListTitle(provider, existingTitle, fallbackContent, parentTitle = null) {
|
|
2675
|
+
const normalizedExistingTitle = existingTitle?.trim() ?? "";
|
|
2676
|
+
const normalizedParentTitle = parentTitle?.trim() ?? "";
|
|
2677
|
+
const fallbackTitle = buildUserMessageTitle(fallbackContent, normalizedExistingTitle || "继续对话");
|
|
2678
|
+
if (normalizedExistingTitle.length > 0 &&
|
|
2679
|
+
!isSyntheticCodexSessionTitle(normalizedExistingTitle) &&
|
|
2680
|
+
(normalizedParentTitle.length === 0 ||
|
|
2681
|
+
normalizedExistingTitle !== normalizedParentTitle)) {
|
|
2682
|
+
return normalizedExistingTitle;
|
|
2683
|
+
}
|
|
2684
|
+
if (normalizedParentTitle.length > 0 && normalizedExistingTitle === normalizedParentTitle) {
|
|
2685
|
+
return fallbackTitle;
|
|
2686
|
+
}
|
|
2687
|
+
if (provider === "codex") {
|
|
2688
|
+
return fallbackTitle;
|
|
2689
|
+
}
|
|
2690
|
+
return normalizedExistingTitle || fallbackTitle;
|
|
2691
|
+
}
|
|
2692
|
+
function buildUserMessageTitle(content, fallbackTitle) {
|
|
2693
|
+
const title = content.trim().replace(/\s+/g, " ");
|
|
2694
|
+
return title.slice(0, 48) || fallbackTitle;
|
|
2695
|
+
}
|
|
2696
|
+
function resolvePersistedSessionTitle(provider, discoveredTitle, existingTitle, parentTitle = null) {
|
|
2697
|
+
const nextTitle = discoveredTitle.trim();
|
|
2698
|
+
const currentTitle = existingTitle?.trim() ?? "";
|
|
2699
|
+
const normalizedParentTitle = parentTitle?.trim() ?? "";
|
|
2700
|
+
if (!currentTitle) {
|
|
2701
|
+
if (provider === "codex" && isSyntheticCodexSessionTitle(nextTitle)) {
|
|
2702
|
+
return currentTitle;
|
|
2703
|
+
}
|
|
2704
|
+
if (normalizedParentTitle.length > 0 && nextTitle === normalizedParentTitle) {
|
|
2705
|
+
return currentTitle;
|
|
2706
|
+
}
|
|
2707
|
+
return nextTitle;
|
|
2708
|
+
}
|
|
2709
|
+
if (nextTitle.length === 0) {
|
|
2710
|
+
return currentTitle;
|
|
2711
|
+
}
|
|
2712
|
+
if (provider === "codex" && isSyntheticCodexSessionTitle(nextTitle)) {
|
|
2713
|
+
return currentTitle;
|
|
2714
|
+
}
|
|
2715
|
+
if (normalizedParentTitle.length > 0 && nextTitle === normalizedParentTitle && currentTitle !== normalizedParentTitle) {
|
|
2716
|
+
return currentTitle;
|
|
2717
|
+
}
|
|
2718
|
+
return nextTitle;
|
|
2719
|
+
}
|
|
2720
|
+
function isSyntheticCodexSessionTitle(title) {
|
|
2721
|
+
return (/^rollout-\d{4}-\d{2}-\d{2}t/i.test(title) ||
|
|
2722
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(title));
|
|
2723
|
+
}
|
|
1994
2724
|
function shouldRemoveHiddenClaudeDebugSession(session) {
|
|
1995
2725
|
const normalizedRawStoreRef = session.rawStoreRef.replaceAll("\\", "/");
|
|
1996
2726
|
if (normalizedRawStoreRef.includes("/subagents/")) {
|
|
@@ -1999,11 +2729,34 @@ function shouldRemoveHiddenClaudeDebugSession(session) {
|
|
|
1999
2729
|
return (/^agent-[^/]+$/i.test(session.providerSessionId) &&
|
|
2000
2730
|
/\/agent-[^/]+\.jsonl$/i.test(normalizedRawStoreRef));
|
|
2001
2731
|
}
|
|
2732
|
+
const STALE_RUNTIME_WITHOUT_INSPECTION_GRACE_MS = 120_000;
|
|
2733
|
+
function shouldClearStaleRuntimeWithoutInspection(current, inspection, nowMs) {
|
|
2734
|
+
if (!current || current.activitySource !== "runtime") {
|
|
2735
|
+
return false;
|
|
2736
|
+
}
|
|
2737
|
+
if (current.runningState !== "starting" && current.runningState !== "running") {
|
|
2738
|
+
return false;
|
|
2739
|
+
}
|
|
2740
|
+
if (inspection.lastEventAt || inspection.completedAtCandidate || inspection.errorCode) {
|
|
2741
|
+
return false;
|
|
2742
|
+
}
|
|
2743
|
+
if (!current.lastEventAt) {
|
|
2744
|
+
return true;
|
|
2745
|
+
}
|
|
2746
|
+
const lastEventAtMs = Date.parse(current.lastEventAt);
|
|
2747
|
+
if (!Number.isFinite(lastEventAtMs)) {
|
|
2748
|
+
return true;
|
|
2749
|
+
}
|
|
2750
|
+
return nowMs - lastEventAtMs > STALE_RUNTIME_WITHOUT_INSPECTION_GRACE_MS;
|
|
2751
|
+
}
|
|
2002
2752
|
function shouldPreserveRuntimeTerminalState(current, inspection) {
|
|
2003
2753
|
if (!current || current.activitySource !== "runtime") {
|
|
2004
2754
|
return false;
|
|
2005
2755
|
}
|
|
2006
|
-
if (!inspection.lastEventAt
|
|
2756
|
+
if (!inspection.lastEventAt) {
|
|
2757
|
+
return !shouldClearStaleRuntimeWithoutInspection(current, inspection, Date.now());
|
|
2758
|
+
}
|
|
2759
|
+
if (!current.lastEventAt) {
|
|
2007
2760
|
return true;
|
|
2008
2761
|
}
|
|
2009
2762
|
if (isTerminalRunningState(current.runningState)) {
|
|
@@ -2056,4 +2809,98 @@ function resolveActivityState(runningState, completedAt, lastSeenAt) {
|
|
|
2056
2809
|
}
|
|
2057
2810
|
return "idle";
|
|
2058
2811
|
}
|
|
2812
|
+
function buildReconstructedForkPrompt(input) {
|
|
2813
|
+
const lines = [
|
|
2814
|
+
input.sourceTitle
|
|
2815
|
+
? `源会话:${input.sourceTitle}`
|
|
2816
|
+
: "源会话:未命名会话",
|
|
2817
|
+
`源 provider:${input.sourceProvider}`,
|
|
2818
|
+
`目标 provider:${input.targetProvider}`,
|
|
2819
|
+
input.sourceType === "message"
|
|
2820
|
+
? "分叉方式:从指定消息点重建后续上下文"
|
|
2821
|
+
: "分叉方式:从整条会话重建上下文",
|
|
2822
|
+
"",
|
|
2823
|
+
"下面是需要继承到新会话里的历史文本。",
|
|
2824
|
+
"请把这些内容当作已经发生过的上下文事实,不要逐条复述,也不要把它们当成新的用户问题重新回答。",
|
|
2825
|
+
"后续我会在这条新分支里继续追加新的指令。",
|
|
2826
|
+
""
|
|
2827
|
+
];
|
|
2828
|
+
if (input.messages.length === 0) {
|
|
2829
|
+
lines.push("当前没有可继承的历史文本。");
|
|
2830
|
+
return lines.join("\n");
|
|
2831
|
+
}
|
|
2832
|
+
for (const message of input.messages) {
|
|
2833
|
+
lines.push(message.role === "user" ? "[用户]" : "[助手]");
|
|
2834
|
+
lines.push(message.content.trim());
|
|
2835
|
+
lines.push("");
|
|
2836
|
+
}
|
|
2837
|
+
return lines.join("\n").trim();
|
|
2838
|
+
}
|
|
2839
|
+
function buildProviderCapabilityCacheKey(provider, workspacePath) {
|
|
2840
|
+
return `${provider}::${workspacePath ?? ""}`;
|
|
2841
|
+
}
|
|
2842
|
+
async function runWithConcurrency(items, concurrency, worker) {
|
|
2843
|
+
const normalizedConcurrency = Math.max(1, Math.floor(concurrency) || 1);
|
|
2844
|
+
const queue = [...items];
|
|
2845
|
+
const runners = Array.from({
|
|
2846
|
+
length: Math.min(normalizedConcurrency, queue.length || 1)
|
|
2847
|
+
}, async () => {
|
|
2848
|
+
while (queue.length > 0) {
|
|
2849
|
+
const current = queue.shift();
|
|
2850
|
+
if (current === undefined) {
|
|
2851
|
+
return;
|
|
2852
|
+
}
|
|
2853
|
+
await worker(current);
|
|
2854
|
+
}
|
|
2855
|
+
});
|
|
2856
|
+
await Promise.all(runners);
|
|
2857
|
+
}
|
|
2858
|
+
async function runBatchedTransactions(items, batchSize, transaction) {
|
|
2859
|
+
const normalizedBatchSize = Math.max(1, Math.floor(batchSize) || 1);
|
|
2860
|
+
let batchCount = 0;
|
|
2861
|
+
let maxBatchMs = 0;
|
|
2862
|
+
for (let index = 0; index < items.length; index += normalizedBatchSize) {
|
|
2863
|
+
const batch = items.slice(index, index + normalizedBatchSize);
|
|
2864
|
+
const batchStartedAt = Date.now();
|
|
2865
|
+
transaction(batch);
|
|
2866
|
+
const batchDurationMs = Date.now() - batchStartedAt;
|
|
2867
|
+
batchCount += 1;
|
|
2868
|
+
maxBatchMs = Math.max(maxBatchMs, batchDurationMs);
|
|
2869
|
+
if (index + normalizedBatchSize < items.length) {
|
|
2870
|
+
await delay(0);
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
return {
|
|
2874
|
+
batchCount,
|
|
2875
|
+
maxBatchMs
|
|
2876
|
+
};
|
|
2877
|
+
}
|
|
2878
|
+
function applyImmediateModelOptionFallbacks(capabilities, codexSnapshot, openCodeSnapshot) {
|
|
2879
|
+
if (capabilities.provider === "codex") {
|
|
2880
|
+
return {
|
|
2881
|
+
...capabilities,
|
|
2882
|
+
modelOptions: codexSnapshot?.modelOptions ?? createFallbackCodexModelOptions(null),
|
|
2883
|
+
defaultReasoningLevel: codexSnapshot?.defaultReasoningLevel ?? null,
|
|
2884
|
+
limitations: codexSnapshot
|
|
2885
|
+
? capabilities.limitations
|
|
2886
|
+
: Array.from(new Set([
|
|
2887
|
+
...capabilities.limitations,
|
|
2888
|
+
"当前暂时使用缓存或兜底模型列表,后台会继续刷新 Codex 能力。"
|
|
2889
|
+
]))
|
|
2890
|
+
};
|
|
2891
|
+
}
|
|
2892
|
+
if (capabilities.provider === "opencode") {
|
|
2893
|
+
return {
|
|
2894
|
+
...capabilities,
|
|
2895
|
+
modelOptions: openCodeSnapshot?.modelOptions ?? createFallbackOpenCodeModelOptions(null),
|
|
2896
|
+
limitations: openCodeSnapshot
|
|
2897
|
+
? capabilities.limitations
|
|
2898
|
+
: Array.from(new Set([
|
|
2899
|
+
...capabilities.limitations,
|
|
2900
|
+
"当前暂时使用缓存或兜底模型列表,后台会继续刷新 OpenCode 能力。"
|
|
2901
|
+
]))
|
|
2902
|
+
};
|
|
2903
|
+
}
|
|
2904
|
+
return capabilities;
|
|
2905
|
+
}
|
|
2059
2906
|
//# sourceMappingURL=session-history-service.js.map
|