@jingyi0605/codingns 0.1.4 → 0.2.0
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/{TerminalPage-4ulgBhv9.js → TerminalPage-BlbQuWi1.js} +1 -1
- package/dist/public/assets/gemini-D4G1NbrE.png +0 -0
- package/dist/public/assets/index-1VIm8lVL.css +1 -0
- package/dist/public/assets/index-Dti93O2S.js +109 -0
- package/dist/public/assets/kimi-BWNNSh7e.png +0 -0
- package/dist/public/index.html +2 -2
- package/dist/server/config/env.d.ts +7 -0
- package/dist/server/config/env.js +150 -1
- package/dist/server/config/env.js.map +1 -1
- package/dist/server/config/opencode-system-probe-helper-process.d.ts +24 -0
- package/dist/server/config/opencode-system-probe-helper-process.js +70 -5
- package/dist/server/config/opencode-system-probe-helper-process.js.map +1 -1
- package/dist/server/modules/butler/butler-action-context-service.d.ts +30 -0
- package/dist/server/modules/butler/butler-action-context-service.js +108 -0
- package/dist/server/modules/butler/butler-action-context-service.js.map +1 -0
- package/dist/server/modules/butler/butler-auth-service.d.ts +17 -0
- package/dist/server/modules/butler/butler-auth-service.js +91 -0
- package/dist/server/modules/butler/butler-auth-service.js.map +1 -0
- package/dist/server/modules/butler/butler-control-action-service.d.ts +65 -0
- package/dist/server/modules/butler/butler-control-action-service.js +296 -0
- package/dist/server/modules/butler/butler-control-action-service.js.map +1 -0
- package/dist/server/modules/butler/butler-control-session-service.d.ts +55 -0
- package/dist/server/modules/butler/butler-control-session-service.js +367 -0
- package/dist/server/modules/butler/butler-control-session-service.js.map +1 -0
- package/dist/server/modules/butler/butler-controller.d.ts +367 -0
- package/dist/server/modules/butler/butler-controller.js +475 -0
- package/dist/server/modules/butler/butler-controller.js.map +1 -0
- package/dist/server/modules/butler/butler-follow-up-evaluation-instruction-adapter.d.ts +34 -0
- package/dist/server/modules/butler/butler-follow-up-evaluation-instruction-adapter.js +77 -0
- package/dist/server/modules/butler/butler-follow-up-evaluation-instruction-adapter.js.map +1 -0
- package/dist/server/modules/butler/butler-follow-up-scheduler.d.ts +23 -0
- package/dist/server/modules/butler/butler-follow-up-scheduler.js +57 -0
- package/dist/server/modules/butler/butler-follow-up-scheduler.js.map +1 -0
- package/dist/server/modules/butler/butler-follow-up-service.d.ts +86 -0
- package/dist/server/modules/butler/butler-follow-up-service.js +948 -0
- package/dist/server/modules/butler/butler-follow-up-service.js.map +1 -0
- package/dist/server/modules/butler/butler-inbox-service.d.ts +35 -0
- package/dist/server/modules/butler/butler-inbox-service.js +136 -0
- package/dist/server/modules/butler/butler-inbox-service.js.map +1 -0
- package/dist/server/modules/butler/butler-notification-service.d.ts +12 -0
- package/dist/server/modules/butler/butler-notification-service.js +45 -0
- package/dist/server/modules/butler/butler-notification-service.js.map +1 -0
- package/dist/server/modules/butler/butler-profile-service.d.ts +26 -0
- package/dist/server/modules/butler/butler-profile-service.js +529 -0
- package/dist/server/modules/butler/butler-profile-service.js.map +1 -0
- package/dist/server/modules/butler/butler-project-service.d.ts +48 -0
- package/dist/server/modules/butler/butler-project-service.js +253 -0
- package/dist/server/modules/butler/butler-project-service.js.map +1 -0
- package/dist/server/modules/butler/butler-session-service.d.ts +79 -0
- package/dist/server/modules/butler/butler-session-service.js +503 -0
- package/dist/server/modules/butler/butler-session-service.js.map +1 -0
- package/dist/server/modules/butler/butler-session-summary-service.d.ts +55 -0
- package/dist/server/modules/butler/butler-session-summary-service.js +382 -0
- package/dist/server/modules/butler/butler-session-summary-service.js.map +1 -0
- package/dist/server/modules/butler/context-aggregator.d.ts +187 -0
- package/dist/server/modules/butler/context-aggregator.js +807 -0
- package/dist/server/modules/butler/context-aggregator.js.map +1 -0
- package/dist/server/modules/butler/instruction-adapter.d.ts +28 -0
- package/dist/server/modules/butler/instruction-adapter.js +101 -0
- package/dist/server/modules/butler/instruction-adapter.js.map +1 -0
- package/dist/server/modules/butler/patrol-execution-service.d.ts +47 -0
- package/dist/server/modules/butler/patrol-execution-service.js +347 -0
- package/dist/server/modules/butler/patrol-execution-service.js.map +1 -0
- package/dist/server/modules/butler/patrol-plan-service.d.ts +54 -0
- package/dist/server/modules/butler/patrol-plan-service.js +272 -0
- package/dist/server/modules/butler/patrol-plan-service.js.map +1 -0
- package/dist/server/modules/butler/patrol-run-service.d.ts +60 -0
- package/dist/server/modules/butler/patrol-run-service.js +185 -0
- package/dist/server/modules/butler/patrol-run-service.js.map +1 -0
- package/dist/server/modules/butler/patrol-scheduler.d.ts +36 -0
- package/dist/server/modules/butler/patrol-scheduler.js +99 -0
- package/dist/server/modules/butler/patrol-scheduler.js.map +1 -0
- package/dist/server/modules/butler/project-memory-service.d.ts +30 -0
- package/dist/server/modules/butler/project-memory-service.js +103 -0
- package/dist/server/modules/butler/project-memory-service.js.map +1 -0
- package/dist/server/modules/butler/provider-adapter-registry.d.ts +61 -0
- package/dist/server/modules/butler/provider-adapter-registry.js +430 -0
- package/dist/server/modules/butler/provider-adapter-registry.js.map +1 -0
- package/dist/server/modules/butler/session-summary-instruction-adapter.d.ts +28 -0
- package/dist/server/modules/butler/session-summary-instruction-adapter.js +79 -0
- package/dist/server/modules/butler/session-summary-instruction-adapter.js.map +1 -0
- package/dist/server/modules/butler/session-summary-scheduler.d.ts +23 -0
- package/dist/server/modules/butler/session-summary-scheduler.js +57 -0
- package/dist/server/modules/butler/session-summary-scheduler.js.map +1 -0
- package/dist/server/modules/butler/verification-run-service.d.ts +73 -0
- package/dist/server/modules/butler/verification-run-service.js +633 -0
- package/dist/server/modules/butler/verification-run-service.js.map +1 -0
- 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/preferences/profile-service.js +8 -2
- package/dist/server/modules/preferences/profile-service.js.map +1 -1
- package/dist/server/modules/sessions/claude-runtime-helper-process.js +1 -1
- package/dist/server/modules/sessions/claude-runtime-helper-process.js.map +1 -1
- package/dist/server/modules/sessions/codex-app-server-helper-client.d.ts +7 -2
- package/dist/server/modules/sessions/codex-app-server-helper-client.js +113 -2
- 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 +24 -1
- package/dist/server/modules/sessions/session-controller.js +34 -3
- package/dist/server/modules/sessions/session-controller.js.map +1 -1
- package/dist/server/modules/sessions/session-history-service.d.ts +47 -2
- package/dist/server/modules/sessions/session-history-service.js +881 -56
- package/dist/server/modules/sessions/session-history-service.js.map +1 -1
- package/dist/server/modules/sessions/session-live-runtime-service.d.ts +30 -2
- package/dist/server/modules/sessions/session-live-runtime-service.js +584 -159
- package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
- package/dist/server/modules/sessions/session-provider-error-mapper.js +94 -0
- package/dist/server/modules/sessions/session-provider-error-mapper.js.map +1 -1
- package/dist/server/modules/workbench/workbench-service.d.ts +7 -1
- package/dist/server/modules/workbench/workbench-service.js +31 -7
- package/dist/server/modules/workbench/workbench-service.js.map +1 -1
- package/dist/server/routes/butler.d.ts +3 -0
- package/dist/server/routes/butler.js +54 -0
- package/dist/server/routes/butler.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/sessions.js +1 -0
- package/dist/server/routes/sessions.js.map +1 -1
- package/dist/server/server/create-server.d.ts +65 -0
- package/dist/server/server/create-server.js +154 -5
- 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/storage/repositories/butler-control-event-repository.d.ts +8 -0
- package/dist/server/storage/repositories/butler-control-event-repository.js +78 -0
- package/dist/server/storage/repositories/butler-control-event-repository.js.map +1 -0
- package/dist/server/storage/repositories/butler-control-session-repository.d.ts +11 -0
- package/dist/server/storage/repositories/butler-control-session-repository.js +86 -0
- package/dist/server/storage/repositories/butler-control-session-repository.js.map +1 -0
- package/dist/server/storage/repositories/butler-follow-up-task-repository.d.ts +16 -0
- package/dist/server/storage/repositories/butler-follow-up-task-repository.js +252 -0
- package/dist/server/storage/repositories/butler-follow-up-task-repository.js.map +1 -0
- package/dist/server/storage/repositories/butler-inbox-item-repository.d.ts +15 -0
- package/dist/server/storage/repositories/butler-inbox-item-repository.js +111 -0
- package/dist/server/storage/repositories/butler-inbox-item-repository.js.map +1 -0
- package/dist/server/storage/repositories/butler-notification-archive-repository.d.ts +9 -0
- package/dist/server/storage/repositories/butler-notification-archive-repository.js +48 -0
- package/dist/server/storage/repositories/butler-notification-archive-repository.js.map +1 -0
- package/dist/server/storage/repositories/butler-profile-repository.d.ts +9 -0
- package/dist/server/storage/repositories/butler-profile-repository.js +86 -0
- package/dist/server/storage/repositories/butler-profile-repository.js.map +1 -0
- package/dist/server/storage/repositories/butler-project-repository.d.ts +14 -0
- package/dist/server/storage/repositories/butler-project-repository.js +140 -0
- package/dist/server/storage/repositories/butler-project-repository.js.map +1 -0
- package/dist/server/storage/repositories/butler-session-repository.d.ts +11 -0
- package/dist/server/storage/repositories/butler-session-repository.js +106 -0
- package/dist/server/storage/repositories/butler-session-repository.js.map +1 -0
- package/dist/server/storage/repositories/butler-session-summary-state-repository.d.ts +8 -0
- package/dist/server/storage/repositories/butler-session-summary-state-repository.js +62 -0
- package/dist/server/storage/repositories/butler-session-summary-state-repository.js.map +1 -0
- package/dist/server/storage/repositories/patrol-plan-repository.d.ts +27 -0
- package/dist/server/storage/repositories/patrol-plan-repository.js +119 -0
- package/dist/server/storage/repositories/patrol-plan-repository.js.map +1 -0
- package/dist/server/storage/repositories/patrol-run-repository.d.ts +28 -0
- package/dist/server/storage/repositories/patrol-run-repository.js +121 -0
- package/dist/server/storage/repositories/patrol-run-repository.js.map +1 -0
- package/dist/server/storage/repositories/project-memory-repository.d.ts +15 -0
- package/dist/server/storage/repositories/project-memory-repository.js +150 -0
- package/dist/server/storage/repositories/project-memory-repository.js.map +1 -0
- package/dist/server/storage/repositories/session-checkpoint-repository.d.ts +9 -0
- package/dist/server/storage/repositories/session-checkpoint-repository.js +72 -0
- package/dist/server/storage/repositories/session-checkpoint-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/session-message-origin-repository.d.ts +10 -0
- package/dist/server/storage/repositories/session-message-origin-repository.js +93 -0
- package/dist/server/storage/repositories/session-message-origin-repository.js.map +1 -0
- package/dist/server/storage/repositories/verification-run-repository.d.ts +29 -0
- package/dist/server/storage/repositories/verification-run-repository.js +125 -0
- package/dist/server/storage/repositories/verification-run-repository.js.map +1 -0
- package/dist/server/storage/sqlite/client.js +146 -0
- package/dist/server/storage/sqlite/client.js.map +1 -1
- package/dist/server/storage/sqlite/schema.sql +354 -0
- package/dist/server/types/domain.d.ts +286 -2
- package/dist/server/ws/ws-server.d.ts +2 -1
- package/dist/server/ws/ws-server.js +2 -1
- package/dist/server/ws/ws-server.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/index.d.ts +4 -0
- package/node_modules/@codingns/session-sync-core/dist/index.js +4 -0
- package/node_modules/@codingns/session-sync-core/dist/index.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/kimi-message-normalizer.d.ts +18 -0
- package/node_modules/@codingns/session-sync-core/dist/kimi-message-normalizer.js +659 -0
- package/node_modules/@codingns/session-sync-core/dist/kimi-message-normalizer.js.map +1 -0
- package/node_modules/@codingns/session-sync-core/dist/kimi-shared.d.ts +11 -0
- package/node_modules/@codingns/session-sync-core/dist/kimi-shared.js +72 -0
- package/node_modules/@codingns/session-sync-core/dist/kimi-shared.js.map +1 -0
- package/node_modules/@codingns/session-sync-core/dist/patch-builder.d.ts +8 -0
- package/node_modules/@codingns/session-sync-core/dist/patch-builder.js +89 -0
- package/node_modules/@codingns/session-sync-core/dist/patch-builder.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 +228 -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 +499 -3
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/gemini.d.ts +41 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js +1175 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js.map +1 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/kimi.d.ts +29 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js +578 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js.map +1 -0
- 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 +271 -4
- 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 +147 -19
- package/node_modules/@codingns/session-sync-core/dist/providers/utils.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.d.ts +2 -0
- package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js +43 -5
- 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 +12 -0
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +442 -71
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/gemini-runtime.d.ts +21 -0
- package/node_modules/@codingns/session-sync-core/dist/runtime/gemini-runtime.js +537 -0
- package/node_modules/@codingns/session-sync-core/dist/runtime/gemini-runtime.js.map +1 -0
- package/node_modules/@codingns/session-sync-core/dist/runtime/kimi-runtime.d.ts +38 -0
- package/node_modules/@codingns/session-sync-core/dist/runtime/kimi-runtime.js +911 -0
- package/node_modules/@codingns/session-sync-core/dist/runtime/kimi-runtime.js.map +1 -0
- 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/sqlite/node-sqlite.d.ts +6 -0
- package/node_modules/@codingns/session-sync-core/dist/sqlite/node-sqlite.js +9 -0
- package/node_modules/@codingns/session-sync-core/dist/sqlite/node-sqlite.js.map +1 -0
- package/node_modules/@codingns/session-sync-core/dist/types.d.ts +27 -0
- package/node_modules/@codingns/session-sync-core/package.json +8 -0
- package/package.json +1 -1
- package/dist/public/assets/index-C5lu52cQ.css +0 -1
- package/dist/public/assets/index-WpdUo_Vs.js +0 -108
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
-
import { CapabilityService, ClaudeCodeAdapter, CodexAdapter, OpenCodeAdapter, ProviderRegistry, SessionSyncService } from "@codingns/session-sync-core";
|
|
2
|
+
import { CapabilityService, ClaudeCodeAdapter, CodexAdapter, GeminiAdapter, KimiAdapter, OpenCodeAdapter, ProviderRegistry, SessionSyncService } from "@codingns/session-sync-core";
|
|
3
3
|
import { AppError } from "../../shared/errors/app-error.js";
|
|
4
|
+
import { hashContent } from "../../shared/utils/hash.js";
|
|
4
5
|
import { createId } from "../../shared/utils/id.js";
|
|
5
6
|
import { logPerformance } from "../../shared/utils/perf-log.js";
|
|
6
7
|
import { nowIso } from "../../shared/utils/time.js";
|
|
8
|
+
import { isCommandAvailable } from "../../shared/utils/command-availability.js";
|
|
7
9
|
import { inspectSessionActivity } from "./session-activity-inspector.js";
|
|
8
10
|
import { SessionActivityAuthorityService } from "./session-activity-authority-service.js";
|
|
9
11
|
import { mapSessionProviderError } from "./session-provider-error-mapper.js";
|
|
12
|
+
import { SessionForkRepository } from "../../storage/repositories/session-fork-repository.js";
|
|
10
13
|
import { enrichClaudeCapabilities } from "../provider/claude-model-options.js";
|
|
11
14
|
import { CodexModelOptionsService, enrichCodexCapabilities } from "../provider/codex-model-options.js";
|
|
12
15
|
import { OpenCodeModelOptionsService, enrichOpenCodeCapabilities } from "../provider/opencode-model-options.js";
|
|
16
|
+
import { CodexAppServerHelperClient } from "./codex-app-server-helper-client.js";
|
|
17
|
+
const RECONSTRUCTED_FORK_TARGET_PROVIDERS = new Set(["codex", "claude-code", "opencode"]);
|
|
18
|
+
const FORK_RECONSTRUCTION_PAGE_SIZE = 200;
|
|
19
|
+
const MAX_FORK_DEPTH = 4;
|
|
20
|
+
const SESSION_START_DEFERRED_PROVIDERS = new Set([
|
|
21
|
+
"codex",
|
|
22
|
+
"claude-code",
|
|
23
|
+
"opencode",
|
|
24
|
+
"gemini",
|
|
25
|
+
"kimi"
|
|
26
|
+
]);
|
|
27
|
+
const MUTABLE_HISTORY_TAIL_REFRESH_INTERVAL_MS = 1_200;
|
|
13
28
|
export class SessionHistoryService {
|
|
14
29
|
db;
|
|
15
30
|
workspaceRepository;
|
|
@@ -19,18 +34,22 @@ export class SessionHistoryService {
|
|
|
19
34
|
sessionMessageAttachmentService;
|
|
20
35
|
sessionStateRepository;
|
|
21
36
|
sessionStatusSnapshotRepository;
|
|
37
|
+
sessionMessageOriginRepository;
|
|
22
38
|
providerRegistry;
|
|
23
39
|
sessionSyncService;
|
|
24
40
|
capabilityService;
|
|
25
41
|
sessionActivityAuthorityService;
|
|
42
|
+
sessionForkRepository;
|
|
26
43
|
claudeCodeHomeDir;
|
|
27
44
|
codexModelOptionsService;
|
|
28
45
|
openCodeModelOptionsService;
|
|
46
|
+
providerCliCommandPaths;
|
|
47
|
+
providerCliAvailability;
|
|
29
48
|
workspaceDiscoveryStatuses = new Map();
|
|
30
49
|
workspaceDiscoveryInflight = new Map();
|
|
31
50
|
workspaceStateRefreshInflight = new Map();
|
|
32
51
|
workspaceSessionRelations = new Map();
|
|
33
|
-
constructor(db, workspaceRepository, sessionBindingRepository, sessionChangedFileService, sessionIndexRepository, sessionMessageAttachmentService, sessionStateRepository, sessionStatusSnapshotRepository, config, sessionActivityAuthorityService = new SessionActivityAuthorityService()) {
|
|
52
|
+
constructor(db, workspaceRepository, sessionBindingRepository, sessionChangedFileService, sessionIndexRepository, sessionMessageAttachmentService, sessionStateRepository, sessionStatusSnapshotRepository, config, sessionActivityAuthorityService = new SessionActivityAuthorityService(), sessionMessageOriginRepository = null, sessionForkRepository = null, adapterOverrides = {}) {
|
|
34
53
|
this.db = db;
|
|
35
54
|
this.workspaceRepository = workspaceRepository;
|
|
36
55
|
this.sessionBindingRepository = sessionBindingRepository;
|
|
@@ -39,11 +58,33 @@ export class SessionHistoryService {
|
|
|
39
58
|
this.sessionMessageAttachmentService = sessionMessageAttachmentService;
|
|
40
59
|
this.sessionStateRepository = sessionStateRepository;
|
|
41
60
|
this.sessionStatusSnapshotRepository = sessionStatusSnapshotRepository;
|
|
61
|
+
this.sessionMessageOriginRepository = sessionMessageOriginRepository;
|
|
42
62
|
this.sessionActivityAuthorityService = sessionActivityAuthorityService;
|
|
63
|
+
this.sessionForkRepository = sessionForkRepository ?? new SessionForkRepository(db);
|
|
43
64
|
this.claudeCodeHomeDir = config.claudeCodeHomeDir;
|
|
65
|
+
this.providerCliCommandPaths = {
|
|
66
|
+
"claude-code": process.platform === "win32" ? "claude.cmd" : "claude",
|
|
67
|
+
codex: config.codexCliPath,
|
|
68
|
+
gemini: config.geminiCliPath,
|
|
69
|
+
kimi: config.kimiCliPath
|
|
70
|
+
};
|
|
71
|
+
// CLI 是否可用只在 Host 启动时探测一次;后续统一读缓存,更新 CLI 后重启 Host 生效。
|
|
72
|
+
this.providerCliAvailability = buildProviderCliAvailabilitySnapshot(this.providerCliCommandPaths);
|
|
44
73
|
this.providerRegistry = new ProviderRegistry([
|
|
45
74
|
new ClaudeCodeAdapter({ homeDir: config.claudeCodeHomeDir }),
|
|
46
|
-
new CodexAdapter({
|
|
75
|
+
new CodexAdapter({
|
|
76
|
+
homeDir: config.codexHomeDir,
|
|
77
|
+
forkTransportFactory: adapterOverrides.codexForkTransportFactory
|
|
78
|
+
?? createCodexForkTransportFactory(config.codexCliPath, config.codexHomeDir)
|
|
79
|
+
}),
|
|
80
|
+
new GeminiAdapter({
|
|
81
|
+
homeDir: config.geminiHomeDir,
|
|
82
|
+
commandPath: config.geminiCliPath
|
|
83
|
+
}),
|
|
84
|
+
new KimiAdapter({
|
|
85
|
+
homeDir: config.kimiHomeDir,
|
|
86
|
+
defaultModel: config.kimiDefaultModel
|
|
87
|
+
}),
|
|
47
88
|
new OpenCodeAdapter({
|
|
48
89
|
baseUrl: config.opencodeBaseUrl,
|
|
49
90
|
baseUrlResolver: config.opencodeBaseUrlResolver?.resolve.bind(config.opencodeBaseUrlResolver),
|
|
@@ -98,15 +139,16 @@ export class SessionHistoryService {
|
|
|
98
139
|
}
|
|
99
140
|
async readSessionHistory(sessionId, cursor, limit, direction = "forward", userId) {
|
|
100
141
|
const startedAt = Date.now();
|
|
101
|
-
const
|
|
102
|
-
const
|
|
142
|
+
const resolvedSessionId = this.resolveCanonicalSessionId(sessionId, userId);
|
|
143
|
+
const binding = this.getBindingOrThrow(resolvedSessionId);
|
|
144
|
+
const current = this.sessionStatusSnapshotRepository.findBySessionId(resolvedSessionId);
|
|
103
145
|
const safeLimit = clampLimit(limit);
|
|
104
146
|
const knownTotalMessageCount = direction === "backward" && cursor === null
|
|
105
|
-
? this.sessionIndexRepository.findIndexRecordBySessionId(
|
|
147
|
+
? this.sessionIndexRepository.findIndexRecordBySessionId(resolvedSessionId)?.messageCount ?? null
|
|
106
148
|
: null;
|
|
107
149
|
let readDurationMs = 0;
|
|
108
150
|
let refreshStateDurationMs = 0;
|
|
109
|
-
this.upsertSnapshot(
|
|
151
|
+
this.upsertSnapshot(resolvedSessionId, {
|
|
110
152
|
syncStatus: "syncing",
|
|
111
153
|
syncCursor: current?.syncCursor ?? cursor,
|
|
112
154
|
lastSyncAt: current?.lastSyncAt ?? null,
|
|
@@ -116,9 +158,9 @@ export class SessionHistoryService {
|
|
|
116
158
|
});
|
|
117
159
|
try {
|
|
118
160
|
const readStartedAt = Date.now();
|
|
119
|
-
const page = await this.readPage(
|
|
161
|
+
const page = await this.readPage(resolvedSessionId, binding.provider, binding.providerSessionId, binding.rawStoreRef, cursor, safeLimit, direction, knownTotalMessageCount);
|
|
120
162
|
readDurationMs = Date.now() - readStartedAt;
|
|
121
|
-
this.upsertSnapshot(
|
|
163
|
+
this.upsertSnapshot(resolvedSessionId, {
|
|
122
164
|
syncStatus: "idle",
|
|
123
165
|
syncCursor: direction === "backward" && cursor !== null
|
|
124
166
|
? current?.syncCursor ?? page.cursor
|
|
@@ -129,7 +171,8 @@ export class SessionHistoryService {
|
|
|
129
171
|
resumedAt: current?.resumedAt ?? null
|
|
130
172
|
});
|
|
131
173
|
logPerformance("session.read_history", Date.now() - startedAt, {
|
|
132
|
-
sessionId,
|
|
174
|
+
sessionId: resolvedSessionId,
|
|
175
|
+
requestedSessionId: sessionId,
|
|
133
176
|
provider: binding.provider,
|
|
134
177
|
direction,
|
|
135
178
|
limit: safeLimit,
|
|
@@ -145,7 +188,8 @@ export class SessionHistoryService {
|
|
|
145
188
|
}
|
|
146
189
|
catch (error) {
|
|
147
190
|
logPerformance("session.read_history.failed", Date.now() - startedAt, {
|
|
148
|
-
sessionId,
|
|
191
|
+
sessionId: resolvedSessionId,
|
|
192
|
+
requestedSessionId: sessionId,
|
|
149
193
|
provider: binding.provider,
|
|
150
194
|
direction,
|
|
151
195
|
limit: safeLimit,
|
|
@@ -157,10 +201,17 @@ export class SessionHistoryService {
|
|
|
157
201
|
thresholdMs: 0,
|
|
158
202
|
force: true
|
|
159
203
|
});
|
|
160
|
-
this.markSessionError(
|
|
204
|
+
this.markSessionError(resolvedSessionId, "PROVIDER_READ_FAILED", error);
|
|
161
205
|
throw mapSessionProviderError(error);
|
|
162
206
|
}
|
|
163
207
|
}
|
|
208
|
+
resolveMessageOrigin(sessionId, message) {
|
|
209
|
+
return this.resolveMessageOrigins(sessionId, [message])[0] ?? {
|
|
210
|
+
...message,
|
|
211
|
+
origin: null,
|
|
212
|
+
originRef: null
|
|
213
|
+
};
|
|
214
|
+
}
|
|
164
215
|
async findLatestUserMessage(sessionId, content, maxAttempts = 12, minTimestamp = null) {
|
|
165
216
|
const binding = this.getBindingOrThrow(sessionId);
|
|
166
217
|
const acceptedContents = new Set((Array.isArray(content) ? content : [content]).filter((value) => value.trim().length > 0));
|
|
@@ -171,7 +222,7 @@ export class SessionHistoryService {
|
|
|
171
222
|
.reverse()
|
|
172
223
|
.find((message) => message.role === "user" &&
|
|
173
224
|
acceptedContents.has(message.content) &&
|
|
174
|
-
|
|
225
|
+
isAcceptedUserMessageTimestamp(binding.provider, message.timestamp, minTimestamp));
|
|
175
226
|
if (matched) {
|
|
176
227
|
return matched;
|
|
177
228
|
}
|
|
@@ -209,11 +260,13 @@ export class SessionHistoryService {
|
|
|
209
260
|
return this.sessionChangedFileService.listBySessionId(sessionId);
|
|
210
261
|
}
|
|
211
262
|
listWorkspaceSessions(workspaceId, userId) {
|
|
212
|
-
return this.enrichSessionItems(workspaceId, this.sessionIndexRepository
|
|
263
|
+
return this.enrichSessionItems(workspaceId, this.sessionIndexRepository
|
|
264
|
+
.listByWorkspace(workspaceId, userId)
|
|
265
|
+
.filter((item) => !this.isPendingSessionAlias(item)));
|
|
213
266
|
}
|
|
214
267
|
getProviderCapabilitiesSnapshot(provider) {
|
|
215
268
|
try {
|
|
216
|
-
return this.capabilityService.getProviderCapabilities(provider);
|
|
269
|
+
return this.applyProviderCliAvailability(this.capabilityService.getProviderCapabilities(provider));
|
|
217
270
|
}
|
|
218
271
|
catch (error) {
|
|
219
272
|
throw mapSessionProviderError(error);
|
|
@@ -222,7 +275,7 @@ export class SessionHistoryService {
|
|
|
222
275
|
async getProviderCapabilities(provider, workspaceId) {
|
|
223
276
|
try {
|
|
224
277
|
const workspacePath = workspaceId ? this.getWorkspaceOrThrow(workspaceId).path : null;
|
|
225
|
-
return await this.enrichProviderCapabilities(this.capabilityService.getProviderCapabilities(provider), workspacePath);
|
|
278
|
+
return await this.enrichProviderCapabilities(this.applyProviderCliAvailability(this.capabilityService.getProviderCapabilities(provider)), workspacePath);
|
|
226
279
|
}
|
|
227
280
|
catch (error) {
|
|
228
281
|
throw mapSessionProviderError(error);
|
|
@@ -233,7 +286,7 @@ export class SessionHistoryService {
|
|
|
233
286
|
const workspace = this.getWorkspaceOrThrow(binding.workspaceId);
|
|
234
287
|
return this.capabilityService
|
|
235
288
|
.getSessionCapabilities(binding.provider, binding.providerSessionId)
|
|
236
|
-
.then((capabilities) => this.enrichProviderCapabilities(capabilities, workspace.path))
|
|
289
|
+
.then((capabilities) => this.enrichProviderCapabilities(this.applyProviderCliAvailability(capabilities), workspace.path))
|
|
237
290
|
.catch((error) => {
|
|
238
291
|
throw mapSessionProviderError(error);
|
|
239
292
|
});
|
|
@@ -246,6 +299,41 @@ export class SessionHistoryService {
|
|
|
246
299
|
const codexEnriched = await enrichCodexCapabilities(claudeEnriched, this.codexModelOptionsService);
|
|
247
300
|
return enrichOpenCodeCapabilities(codexEnriched, this.openCodeModelOptionsService, workspacePath);
|
|
248
301
|
}
|
|
302
|
+
applyProviderCliAvailability(capabilities) {
|
|
303
|
+
if (!isProviderCliBacked(capabilities.provider)) {
|
|
304
|
+
return capabilities;
|
|
305
|
+
}
|
|
306
|
+
if (this.providerCliAvailability[capabilities.provider]) {
|
|
307
|
+
return capabilities;
|
|
308
|
+
}
|
|
309
|
+
const limitation = buildProviderCliUnavailableMessage(capabilities.provider);
|
|
310
|
+
const limitations = capabilities.limitations.includes(limitation)
|
|
311
|
+
? capabilities.limitations
|
|
312
|
+
: [limitation, ...capabilities.limitations];
|
|
313
|
+
return {
|
|
314
|
+
...capabilities,
|
|
315
|
+
canStartSession: false,
|
|
316
|
+
canResumeSession: false,
|
|
317
|
+
canSendMessage: false,
|
|
318
|
+
supportsSubagents: false,
|
|
319
|
+
supportsInterrupt: false,
|
|
320
|
+
supportsSessionFork: false,
|
|
321
|
+
supportsNativeAgents: false,
|
|
322
|
+
limitations
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
assertProviderCapabilityEnabled(provider, capability, fallbackDetail) {
|
|
326
|
+
const capabilities = this.getProviderCapabilitiesSnapshot(provider);
|
|
327
|
+
if (capabilities[capability]) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
throw new AppError({
|
|
331
|
+
statusCode: 409,
|
|
332
|
+
errorCode: "PROVIDER_UNAVAILABLE",
|
|
333
|
+
detail: capabilities.limitations[0] ?? fallbackDetail,
|
|
334
|
+
field: "provider"
|
|
335
|
+
});
|
|
336
|
+
}
|
|
249
337
|
async getSessionContextUsage(sessionId) {
|
|
250
338
|
const binding = this.getBindingOrThrow(sessionId);
|
|
251
339
|
try {
|
|
@@ -257,6 +345,7 @@ export class SessionHistoryService {
|
|
|
257
345
|
}
|
|
258
346
|
async resumeSession(sessionId) {
|
|
259
347
|
const binding = this.getBindingOrThrow(sessionId);
|
|
348
|
+
this.assertProviderCapabilityEnabled(binding.provider, "canResumeSession", "当前 provider 不支持继续会话");
|
|
260
349
|
try {
|
|
261
350
|
const result = await this.sessionSyncService.resumeSession(binding.provider, binding.providerSessionId, binding.rawStoreRef);
|
|
262
351
|
this.upsertSnapshot(sessionId, {
|
|
@@ -280,8 +369,7 @@ export class SessionHistoryService {
|
|
|
280
369
|
}
|
|
281
370
|
}
|
|
282
371
|
async startSession(input) {
|
|
283
|
-
|
|
284
|
-
if (input.provider === "codex" || input.provider === "claude-code" || input.provider === "opencode") {
|
|
372
|
+
if (SESSION_START_DEFERRED_PROVIDERS.has(input.provider)) {
|
|
285
373
|
throw new AppError({
|
|
286
374
|
statusCode: 409,
|
|
287
375
|
errorCode: "SESSION_START_DEFERRED",
|
|
@@ -289,6 +377,11 @@ export class SessionHistoryService {
|
|
|
289
377
|
field: "provider"
|
|
290
378
|
});
|
|
291
379
|
}
|
|
380
|
+
return this.startSessionDirect(input);
|
|
381
|
+
}
|
|
382
|
+
async startSessionDirect(input) {
|
|
383
|
+
const workspace = this.getWorkspaceOrThrow(input.workspaceId);
|
|
384
|
+
this.assertProviderCapabilityEnabled(input.provider, "canStartSession", "当前 provider 不支持创建会话");
|
|
292
385
|
try {
|
|
293
386
|
const result = await this.sessionSyncService.startSession(input.provider, workspace.path, {
|
|
294
387
|
initialPrompt: input.initialPrompt
|
|
@@ -309,7 +402,10 @@ export class SessionHistoryService {
|
|
|
309
402
|
sessionId,
|
|
310
403
|
workspaceId: workspace.id,
|
|
311
404
|
provider: result.session.provider,
|
|
312
|
-
parentSessionId: result.session.parentProviderSessionId ?? null,
|
|
405
|
+
parentSessionId: input.parentSessionId ?? result.session.parentProviderSessionId ?? null,
|
|
406
|
+
sessionKind: input.sessionKind ?? "default",
|
|
407
|
+
annotationSourceMessageId: input.annotationSourceMessageId ?? null,
|
|
408
|
+
annotationSourceText: input.annotationSourceText ?? null,
|
|
313
409
|
isSubagent: result.session.isSubagent ?? false,
|
|
314
410
|
subagentLabel: result.session.subagentLabel ?? null,
|
|
315
411
|
title: result.session.title,
|
|
@@ -348,6 +444,236 @@ export class SessionHistoryService {
|
|
|
348
444
|
throw mapSessionProviderError(error);
|
|
349
445
|
}
|
|
350
446
|
}
|
|
447
|
+
async forkSession(input) {
|
|
448
|
+
const binding = this.getBindingOrThrow(input.sessionId);
|
|
449
|
+
const workspace = this.getWorkspaceOrThrow(binding.workspaceId);
|
|
450
|
+
const targetProvider = input.targetProvider?.trim() || binding.provider;
|
|
451
|
+
this.assertProviderCapabilityEnabled(targetProvider, "canStartSession", "当前 provider 不支持 fork 创建会话");
|
|
452
|
+
const sourceMessageId = input.sourceType === "message"
|
|
453
|
+
? input.sourceMessageId?.trim() || null
|
|
454
|
+
: null;
|
|
455
|
+
if (input.sourceType === "message" && !sourceMessageId) {
|
|
456
|
+
throw new AppError({
|
|
457
|
+
statusCode: 400,
|
|
458
|
+
errorCode: "INVALID_INPUT",
|
|
459
|
+
detail: "按消息派生会话时必须提供 sourceMessageId",
|
|
460
|
+
field: "sourceMessageId"
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
this.assertForkDepthWithinLimit(input.sessionId);
|
|
464
|
+
if (targetProvider !== binding.provider) {
|
|
465
|
+
return this.forkSessionAcrossProviders({
|
|
466
|
+
...input,
|
|
467
|
+
targetProvider
|
|
468
|
+
}, binding, sourceMessageId);
|
|
469
|
+
}
|
|
470
|
+
try {
|
|
471
|
+
const result = await this.sessionSyncService.forkSession(binding.provider, binding.providerSessionId, workspace.path, {
|
|
472
|
+
rawStoreRef: binding.rawStoreRef,
|
|
473
|
+
sourceType: input.sourceType,
|
|
474
|
+
sourceMessageId,
|
|
475
|
+
strategy: input.strategy ?? "auto"
|
|
476
|
+
});
|
|
477
|
+
const sessionId = createId();
|
|
478
|
+
const timestamp = nowIso();
|
|
479
|
+
this.db.transaction(() => {
|
|
480
|
+
this.sessionBindingRepository.upsert({
|
|
481
|
+
sessionId,
|
|
482
|
+
workspaceId: workspace.id,
|
|
483
|
+
provider: result.session.provider,
|
|
484
|
+
providerSessionId: result.session.providerSessionId,
|
|
485
|
+
rawStoreRef: result.session.rawStoreRef,
|
|
486
|
+
createdAt: timestamp,
|
|
487
|
+
updatedAt: timestamp
|
|
488
|
+
});
|
|
489
|
+
this.sessionIndexRepository.upsert({
|
|
490
|
+
sessionId,
|
|
491
|
+
workspaceId: workspace.id,
|
|
492
|
+
provider: result.session.provider,
|
|
493
|
+
parentSessionId: input.sessionId,
|
|
494
|
+
sessionKind: input.sessionKind ?? "default",
|
|
495
|
+
annotationSourceMessageId: input.annotationSourceMessageId ?? null,
|
|
496
|
+
annotationSourceText: input.annotationSourceText ?? null,
|
|
497
|
+
isSubagent: result.session.isSubagent ?? false,
|
|
498
|
+
subagentLabel: result.session.subagentLabel ?? null,
|
|
499
|
+
title: result.session.title,
|
|
500
|
+
messageCount: result.session.messageCount,
|
|
501
|
+
isArchived: result.session.isArchived ?? false,
|
|
502
|
+
lastMessageAt: result.session.lastMessageAt,
|
|
503
|
+
createdAt: timestamp,
|
|
504
|
+
updatedAt: timestamp
|
|
505
|
+
});
|
|
506
|
+
this.sessionForkRepository.upsert({
|
|
507
|
+
sessionId,
|
|
508
|
+
parentSessionId: input.sessionId,
|
|
509
|
+
provider: result.session.provider,
|
|
510
|
+
forkSourceType: result.forkSourceType,
|
|
511
|
+
forkSourceSessionId: input.sessionId,
|
|
512
|
+
forkSourceMessageId: sourceMessageId,
|
|
513
|
+
inheritedPrefixMessageCount: result.inheritedPrefixMessageCount,
|
|
514
|
+
providerParentSessionId: binding.providerSessionId,
|
|
515
|
+
providerSourceMessageId: result.providerSourceMessageId ?? null,
|
|
516
|
+
forkMethod: result.forkMethod,
|
|
517
|
+
createdAt: timestamp
|
|
518
|
+
});
|
|
519
|
+
this.sessionStatusSnapshotRepository.upsert({
|
|
520
|
+
sessionId,
|
|
521
|
+
syncStatus: "idle",
|
|
522
|
+
syncCursor: null,
|
|
523
|
+
lastSyncAt: timestamp,
|
|
524
|
+
lastErrorCode: null,
|
|
525
|
+
lastErrorDetail: null,
|
|
526
|
+
resumedAt: null,
|
|
527
|
+
updatedAt: timestamp
|
|
528
|
+
});
|
|
529
|
+
this.sessionStateRepository.upsert({
|
|
530
|
+
sessionId,
|
|
531
|
+
userId: input.userId,
|
|
532
|
+
runningState: "idle",
|
|
533
|
+
activitySource: "none",
|
|
534
|
+
favorite: false,
|
|
535
|
+
lastEventAt: result.session.lastMessageAt,
|
|
536
|
+
completedAt: null,
|
|
537
|
+
lastSeenAt: null,
|
|
538
|
+
updatedAt: timestamp
|
|
539
|
+
});
|
|
540
|
+
})();
|
|
541
|
+
const forkedSession = this.getSessionListItemOrThrow(sessionId, input.userId);
|
|
542
|
+
const relationMap = this.workspaceSessionRelations.get(workspace.id)
|
|
543
|
+
?? new Map();
|
|
544
|
+
relationMap.set(sessionId, {
|
|
545
|
+
parentSessionId: input.sessionId,
|
|
546
|
+
sessionKind: forkedSession.sessionKind ?? input.sessionKind ?? "default",
|
|
547
|
+
annotationSourceMessageId: forkedSession.annotationSourceMessageId ?? input.annotationSourceMessageId ?? null,
|
|
548
|
+
annotationSourceText: forkedSession.annotationSourceText ?? input.annotationSourceText ?? null,
|
|
549
|
+
isSubagent: forkedSession.isSubagent ?? false,
|
|
550
|
+
subagentLabel: forkedSession.subagentLabel ?? null
|
|
551
|
+
});
|
|
552
|
+
this.workspaceSessionRelations.set(workspace.id, relationMap);
|
|
553
|
+
return this.getSessionListItemOrThrow(sessionId, input.userId);
|
|
554
|
+
}
|
|
555
|
+
catch (error) {
|
|
556
|
+
throw mapSessionProviderError(error);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
async forkSessionAcrossProviders(input, sourceBinding, sourceMessageId) {
|
|
560
|
+
if (!RECONSTRUCTED_FORK_TARGET_PROVIDERS.has(input.targetProvider)) {
|
|
561
|
+
throw mapSessionProviderError(new Error("FORK_TARGET_PROVIDER_NOT_SUPPORTED"));
|
|
562
|
+
}
|
|
563
|
+
const sourceIndex = this.sessionIndexRepository.findIndexRecordBySessionId(input.sessionId);
|
|
564
|
+
const inheritedMessages = await this.readForkSourceMessages(input.sessionId, sourceBinding, input.sourceType, sourceMessageId);
|
|
565
|
+
const reconstructedMessages = inheritedMessages.filter((message) => (message.role === "user" || message.role === "assistant")
|
|
566
|
+
&& message.kind === "text"
|
|
567
|
+
&& message.content.trim().length > 0);
|
|
568
|
+
const inheritedPrompt = buildReconstructedForkPrompt({
|
|
569
|
+
sourceProvider: sourceBinding.provider,
|
|
570
|
+
targetProvider: input.targetProvider,
|
|
571
|
+
sourceType: input.sourceType,
|
|
572
|
+
sourceTitle: sourceIndex?.title?.trim() || null,
|
|
573
|
+
messages: reconstructedMessages
|
|
574
|
+
});
|
|
575
|
+
const startedSession = await this.startSessionDirect({
|
|
576
|
+
workspaceId: sourceBinding.workspaceId,
|
|
577
|
+
userId: input.userId,
|
|
578
|
+
provider: input.targetProvider,
|
|
579
|
+
initialPrompt: inheritedPrompt,
|
|
580
|
+
parentSessionId: input.sessionId,
|
|
581
|
+
sessionKind: input.sessionKind ?? "default",
|
|
582
|
+
annotationSourceMessageId: input.annotationSourceMessageId ?? null,
|
|
583
|
+
annotationSourceText: input.annotationSourceText ?? null
|
|
584
|
+
});
|
|
585
|
+
const timestamp = nowIso();
|
|
586
|
+
const currentIndex = this.sessionIndexRepository.findIndexRecordBySessionId(startedSession.sessionId);
|
|
587
|
+
this.db.transaction(() => {
|
|
588
|
+
if (currentIndex) {
|
|
589
|
+
this.sessionIndexRepository.upsert({
|
|
590
|
+
...currentIndex,
|
|
591
|
+
parentSessionId: input.sessionId,
|
|
592
|
+
sessionKind: input.sessionKind ?? currentIndex.sessionKind ?? "default",
|
|
593
|
+
annotationSourceMessageId: input.annotationSourceMessageId ?? currentIndex.annotationSourceMessageId ?? null,
|
|
594
|
+
annotationSourceText: input.annotationSourceText ?? currentIndex.annotationSourceText ?? null,
|
|
595
|
+
updatedAt: timestamp
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
this.sessionForkRepository.upsert({
|
|
599
|
+
sessionId: startedSession.sessionId,
|
|
600
|
+
parentSessionId: input.sessionId,
|
|
601
|
+
provider: input.targetProvider,
|
|
602
|
+
forkSourceType: input.sourceType,
|
|
603
|
+
forkSourceSessionId: input.sessionId,
|
|
604
|
+
forkSourceMessageId: sourceMessageId,
|
|
605
|
+
inheritedPrefixMessageCount: reconstructedMessages.length,
|
|
606
|
+
providerParentSessionId: sourceBinding.providerSessionId,
|
|
607
|
+
providerSourceMessageId: null,
|
|
608
|
+
forkMethod: input.sourceType === "session"
|
|
609
|
+
? "reconstructed_session_fork"
|
|
610
|
+
: "reconstructed_message_fork",
|
|
611
|
+
createdAt: timestamp
|
|
612
|
+
});
|
|
613
|
+
})();
|
|
614
|
+
const relationMap = this.workspaceSessionRelations.get(sourceBinding.workspaceId)
|
|
615
|
+
?? new Map();
|
|
616
|
+
relationMap.set(startedSession.sessionId, {
|
|
617
|
+
parentSessionId: input.sessionId,
|
|
618
|
+
sessionKind: startedSession.sessionKind ?? input.sessionKind ?? "default",
|
|
619
|
+
annotationSourceMessageId: startedSession.annotationSourceMessageId ?? input.annotationSourceMessageId ?? null,
|
|
620
|
+
annotationSourceText: startedSession.annotationSourceText ?? input.annotationSourceText ?? null,
|
|
621
|
+
isSubagent: startedSession.isSubagent ?? false,
|
|
622
|
+
subagentLabel: startedSession.subagentLabel ?? null
|
|
623
|
+
});
|
|
624
|
+
this.workspaceSessionRelations.set(sourceBinding.workspaceId, relationMap);
|
|
625
|
+
return this.getSessionListItemOrThrow(startedSession.sessionId, input.userId);
|
|
626
|
+
}
|
|
627
|
+
async readForkSourceMessages(sessionId, binding, sourceType, sourceMessageId) {
|
|
628
|
+
const messages = [];
|
|
629
|
+
let cursor = null;
|
|
630
|
+
while (true) {
|
|
631
|
+
const page = await this.readPage(sessionId, binding.provider, binding.providerSessionId, binding.rawStoreRef, cursor, FORK_RECONSTRUCTION_PAGE_SIZE, "forward");
|
|
632
|
+
messages.push(...page.messages);
|
|
633
|
+
if (!page.nextCursor) {
|
|
634
|
+
break;
|
|
635
|
+
}
|
|
636
|
+
cursor = page.nextCursor;
|
|
637
|
+
}
|
|
638
|
+
if (sourceType === "session") {
|
|
639
|
+
return messages;
|
|
640
|
+
}
|
|
641
|
+
const targetIndex = messages.findIndex((message) => message.messageId === sourceMessageId);
|
|
642
|
+
if (targetIndex < 0) {
|
|
643
|
+
throw mapSessionProviderError(new Error("FORK_SOURCE_MESSAGE_NOT_FOUND"));
|
|
644
|
+
}
|
|
645
|
+
return messages.slice(0, targetIndex + 1);
|
|
646
|
+
}
|
|
647
|
+
assertForkDepthWithinLimit(parentSessionId) {
|
|
648
|
+
const nextDepth = this.getSessionForkDepth(parentSessionId) + 1;
|
|
649
|
+
if (nextDepth > MAX_FORK_DEPTH) {
|
|
650
|
+
throw new AppError({
|
|
651
|
+
statusCode: 409,
|
|
652
|
+
errorCode: "FORK_DEPTH_LIMIT_EXCEEDED",
|
|
653
|
+
detail: `fork 会话层级最多支持 ${MAX_FORK_DEPTH} 级`
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
getSessionForkDepth(sessionId) {
|
|
658
|
+
let depth = 1;
|
|
659
|
+
let currentSessionId = sessionId;
|
|
660
|
+
const visitedSessionIds = new Set();
|
|
661
|
+
while (currentSessionId) {
|
|
662
|
+
if (visitedSessionIds.has(currentSessionId)) {
|
|
663
|
+
return depth;
|
|
664
|
+
}
|
|
665
|
+
visitedSessionIds.add(currentSessionId);
|
|
666
|
+
const parentSessionId = this.sessionForkRepository.findBySessionId(currentSessionId)?.parentSessionId
|
|
667
|
+
?? this.sessionIndexRepository.findIndexRecordBySessionId(currentSessionId)?.parentSessionId
|
|
668
|
+
?? null;
|
|
669
|
+
if (!parentSessionId) {
|
|
670
|
+
return depth;
|
|
671
|
+
}
|
|
672
|
+
depth += 1;
|
|
673
|
+
currentSessionId = parentSessionId;
|
|
674
|
+
}
|
|
675
|
+
return depth;
|
|
676
|
+
}
|
|
351
677
|
async sendMessage(sessionId, content, clientRequestId, permissionMode = null) {
|
|
352
678
|
const binding = this.getBindingOrThrow(sessionId);
|
|
353
679
|
const result = await this.sessionSyncService
|
|
@@ -357,14 +683,21 @@ export class SessionHistoryService {
|
|
|
357
683
|
throw mapSessionProviderError(error);
|
|
358
684
|
});
|
|
359
685
|
const existing = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
|
|
686
|
+
const sessionFork = this.sessionForkRepository.findBySessionId(sessionId);
|
|
687
|
+
const parentTitle = sessionFork?.parentSessionId
|
|
688
|
+
? this.sessionIndexRepository.findIndexRecordBySessionId(sessionFork.parentSessionId)?.title ?? null
|
|
689
|
+
: null;
|
|
360
690
|
this.sessionIndexRepository.upsert({
|
|
361
691
|
sessionId,
|
|
362
692
|
workspaceId: binding.workspaceId,
|
|
363
693
|
provider: binding.provider,
|
|
364
694
|
parentSessionId: existing?.parentSessionId ?? null,
|
|
695
|
+
sessionKind: existing?.sessionKind ?? "default",
|
|
696
|
+
annotationSourceMessageId: existing?.annotationSourceMessageId ?? null,
|
|
697
|
+
annotationSourceText: existing?.annotationSourceText ?? null,
|
|
365
698
|
isSubagent: existing?.isSubagent ?? false,
|
|
366
699
|
subagentLabel: existing?.subagentLabel ?? null,
|
|
367
|
-
title: existing?.title ?? result.message.content
|
|
700
|
+
title: resolveSessionListTitle(binding.provider, existing?.title ?? null, result.message.content, parentTitle),
|
|
368
701
|
messageCount: (existing?.messageCount ?? 0) + 1,
|
|
369
702
|
isArchived: existing?.isArchived ?? false,
|
|
370
703
|
lastMessageAt: result.message.timestamp,
|
|
@@ -385,7 +718,7 @@ export class SessionHistoryService {
|
|
|
385
718
|
};
|
|
386
719
|
}
|
|
387
720
|
async subscribeSession(sessionId, cursor, limit, onEnvelope) {
|
|
388
|
-
const
|
|
721
|
+
const deliveredMessages = createDeliveredHistoryMessageState();
|
|
389
722
|
const safeLimit = clampLimit(limit);
|
|
390
723
|
let currentCursor = cursor;
|
|
391
724
|
const current = this.sessionStatusSnapshotRepository.findBySessionId(sessionId);
|
|
@@ -401,10 +734,10 @@ export class SessionHistoryService {
|
|
|
401
734
|
});
|
|
402
735
|
try {
|
|
403
736
|
if (currentCursor === null) {
|
|
404
|
-
currentCursor = await this.pullRecentSessionHistory(sessionId, safeLimit,
|
|
737
|
+
currentCursor = await this.pullRecentSessionHistory(sessionId, safeLimit, deliveredMessages, onEnvelope, "session.backfill");
|
|
405
738
|
}
|
|
406
739
|
else {
|
|
407
|
-
await this.pullSessionHistory(sessionId, currentCursor, safeLimit,
|
|
740
|
+
await this.pullSessionHistory(sessionId, currentCursor, safeLimit, deliveredMessages, onEnvelope, "session.backfill").then((nextCursor) => {
|
|
408
741
|
currentCursor = nextCursor;
|
|
409
742
|
});
|
|
410
743
|
}
|
|
@@ -418,7 +751,7 @@ export class SessionHistoryService {
|
|
|
418
751
|
return;
|
|
419
752
|
}
|
|
420
753
|
polling = true;
|
|
421
|
-
void this.pullSessionHistory(sessionId, currentCursor, safeLimit,
|
|
754
|
+
void this.pullSessionHistory(sessionId, currentCursor, safeLimit, deliveredMessages, onEnvelope, "session.delta", () => closed)
|
|
422
755
|
.then((nextCursor) => {
|
|
423
756
|
currentCursor = nextCursor;
|
|
424
757
|
})
|
|
@@ -462,6 +795,25 @@ export class SessionHistoryService {
|
|
|
462
795
|
messages: page.messages
|
|
463
796
|
};
|
|
464
797
|
}
|
|
798
|
+
async readAllTextHistoryMessages(sessionId, limit = FORK_RECONSTRUCTION_PAGE_SIZE) {
|
|
799
|
+
const binding = this.getBindingOrThrow(sessionId);
|
|
800
|
+
const messages = [];
|
|
801
|
+
let cursor = null;
|
|
802
|
+
let remaining = Math.max(limit, 0);
|
|
803
|
+
while (remaining > 0) {
|
|
804
|
+
const pageSize = Math.min(remaining, FORK_RECONSTRUCTION_PAGE_SIZE);
|
|
805
|
+
const page = await this.readPage(sessionId, binding.provider, binding.providerSessionId, binding.rawStoreRef, cursor, pageSize, "forward");
|
|
806
|
+
messages.push(...page.messages.filter((message) => (message.role === "user" || message.role === "assistant")
|
|
807
|
+
&& message.kind === "text"
|
|
808
|
+
&& message.content.trim().length > 0));
|
|
809
|
+
if (!page.nextCursor || page.messages.length === 0) {
|
|
810
|
+
break;
|
|
811
|
+
}
|
|
812
|
+
cursor = page.nextCursor;
|
|
813
|
+
remaining -= page.messages.length;
|
|
814
|
+
}
|
|
815
|
+
return messages;
|
|
816
|
+
}
|
|
465
817
|
async markSessionSeen(sessionId, userId) {
|
|
466
818
|
const existing = this.sessionStateRepository.findBySessionAndUser(sessionId, userId) ??
|
|
467
819
|
(await this.refreshSessionState(sessionId, userId));
|
|
@@ -525,6 +877,9 @@ export class SessionHistoryService {
|
|
|
525
877
|
workspaceId: existing.workspaceId,
|
|
526
878
|
provider: existing.provider,
|
|
527
879
|
parentSessionId: existing.parentSessionId ?? null,
|
|
880
|
+
sessionKind: existing.sessionKind ?? "default",
|
|
881
|
+
annotationSourceMessageId: existing.annotationSourceMessageId ?? null,
|
|
882
|
+
annotationSourceText: existing.annotationSourceText ?? null,
|
|
528
883
|
isSubagent: existing.isSubagent ?? false,
|
|
529
884
|
subagentLabel: existing.subagentLabel ?? null,
|
|
530
885
|
title: existing.title,
|
|
@@ -567,7 +922,7 @@ export class SessionHistoryService {
|
|
|
567
922
|
detail: "session 不存在"
|
|
568
923
|
});
|
|
569
924
|
}
|
|
570
|
-
return binding;
|
|
925
|
+
return this.resolvePendingSessionAliasBinding(binding) ?? binding;
|
|
571
926
|
}
|
|
572
927
|
persistSessionBinding(sessionId, workspaceId, snapshot) {
|
|
573
928
|
if (!snapshot.providerSessionId || !snapshot.rawStoreRef) {
|
|
@@ -643,7 +998,8 @@ export class SessionHistoryService {
|
|
|
643
998
|
continue;
|
|
644
999
|
}
|
|
645
1000
|
const pendingDuplicate = exactExisting
|
|
646
|
-
?? findClaudePendingDiscoveryDuplicate(session, existingWorkspaceSessions, claimedPendingSessionIds)
|
|
1001
|
+
?? findClaudePendingDiscoveryDuplicate(session, existingWorkspaceSessions, claimedPendingSessionIds)
|
|
1002
|
+
?? findKimiRuntimeDiscoveryDuplicate(session, existingWorkspaceSessions, claimedPendingSessionIds);
|
|
647
1003
|
const existing = exactExisting ?? (pendingDuplicate
|
|
648
1004
|
? this.sessionBindingRepository.findBySessionId(pendingDuplicate.sessionId)
|
|
649
1005
|
: null);
|
|
@@ -667,11 +1023,24 @@ export class SessionHistoryService {
|
|
|
667
1023
|
createdAt,
|
|
668
1024
|
updatedAt: timestamp
|
|
669
1025
|
});
|
|
1026
|
+
const preservedParentSessionId = existingIndex?.parentSessionId
|
|
1027
|
+
?? this.sessionForkRepository.findBySessionId(sessionId)?.parentSessionId
|
|
1028
|
+
?? null;
|
|
1029
|
+
const preservedParentTitle = preservedParentSessionId
|
|
1030
|
+
? this.sessionIndexRepository.findIndexRecordBySessionId(preservedParentSessionId)?.title ?? null
|
|
1031
|
+
: null;
|
|
1032
|
+
const preservedTitle = resolvePersistedSessionTitle(session.provider, session.title, existingIndex?.title ?? null, preservedParentTitle);
|
|
670
1033
|
this.sessionIndexRepository.upsert({
|
|
671
1034
|
sessionId,
|
|
672
1035
|
workspaceId: workspace.id,
|
|
673
1036
|
provider: session.provider,
|
|
674
|
-
|
|
1037
|
+
parentSessionId: preservedParentSessionId,
|
|
1038
|
+
sessionKind: existingIndex?.sessionKind ?? "default",
|
|
1039
|
+
annotationSourceMessageId: existingIndex?.annotationSourceMessageId ?? null,
|
|
1040
|
+
annotationSourceText: existingIndex?.annotationSourceText ?? null,
|
|
1041
|
+
isSubagent: existingIndex?.isSubagent ?? false,
|
|
1042
|
+
subagentLabel: existingIndex?.subagentLabel ?? null,
|
|
1043
|
+
title: preservedTitle,
|
|
675
1044
|
messageCount: session.messageCount,
|
|
676
1045
|
isArchived: resolveDiscoveredArchiveState(existingIndex?.isArchived ?? false, session.isArchived),
|
|
677
1046
|
lastMessageAt: session.lastMessageAt,
|
|
@@ -699,14 +1068,34 @@ export class SessionHistoryService {
|
|
|
699
1068
|
const relationMap = this.buildWorkspaceSessionRelationMap(sessions, discoveredSessionIds);
|
|
700
1069
|
for (const persistedSession of persistedSessions) {
|
|
701
1070
|
const relation = relationMap.get(persistedSession.sessionId);
|
|
1071
|
+
const resolvedParentSessionId = relation?.parentSessionId
|
|
1072
|
+
?? persistedSession.existingIndex?.parentSessionId
|
|
1073
|
+
?? this.sessionForkRepository.findBySessionId(persistedSession.sessionId)?.parentSessionId
|
|
1074
|
+
?? null;
|
|
1075
|
+
const resolvedParentTitle = resolvedParentSessionId
|
|
1076
|
+
? this.sessionIndexRepository.findIndexRecordBySessionId(resolvedParentSessionId)?.title ?? null
|
|
1077
|
+
: null;
|
|
702
1078
|
this.sessionIndexRepository.upsert({
|
|
703
1079
|
sessionId: persistedSession.sessionId,
|
|
704
1080
|
workspaceId: workspace.id,
|
|
705
1081
|
provider: persistedSession.session.provider,
|
|
706
|
-
parentSessionId:
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
1082
|
+
parentSessionId: resolvedParentSessionId,
|
|
1083
|
+
sessionKind: relation?.sessionKind
|
|
1084
|
+
?? persistedSession.existingIndex?.sessionKind
|
|
1085
|
+
?? "default",
|
|
1086
|
+
annotationSourceMessageId: relation?.annotationSourceMessageId
|
|
1087
|
+
?? persistedSession.existingIndex?.annotationSourceMessageId
|
|
1088
|
+
?? null,
|
|
1089
|
+
annotationSourceText: relation?.annotationSourceText
|
|
1090
|
+
?? persistedSession.existingIndex?.annotationSourceText
|
|
1091
|
+
?? null,
|
|
1092
|
+
isSubagent: relation?.isSubagent
|
|
1093
|
+
?? persistedSession.existingIndex?.isSubagent
|
|
1094
|
+
?? false,
|
|
1095
|
+
subagentLabel: relation?.subagentLabel
|
|
1096
|
+
?? persistedSession.existingIndex?.subagentLabel
|
|
1097
|
+
?? null,
|
|
1098
|
+
title: resolvePersistedSessionTitle(persistedSession.session.provider, persistedSession.session.title, persistedSession.existingIndex?.title ?? null, resolvedParentTitle),
|
|
710
1099
|
messageCount: persistedSession.session.messageCount,
|
|
711
1100
|
isArchived: resolveDiscoveredArchiveState(persistedSession.existingIndex?.isArchived ?? false, persistedSession.session.isArchived),
|
|
712
1101
|
lastMessageAt: persistedSession.session.lastMessageAt,
|
|
@@ -723,16 +1112,16 @@ export class SessionHistoryService {
|
|
|
723
1112
|
}
|
|
724
1113
|
this.workspaceSessionRelations.set(workspaceId, this.buildWorkspaceSessionRelationMap(sessions, discoveredSessionIds));
|
|
725
1114
|
const items = this.sessionIndexRepository.listByWorkspace(workspaceId, userId);
|
|
726
|
-
const
|
|
1115
|
+
const refreshCandidates = buildSessionStateRefreshCandidates(items, refreshStateCount);
|
|
727
1116
|
this.workspaceDiscoveryStatuses.set(workspaceId, {
|
|
728
1117
|
refreshedAt: Date.now(),
|
|
729
1118
|
isComplete: discovery.isComplete
|
|
730
1119
|
});
|
|
731
1120
|
if (refreshStateMode === "inline") {
|
|
732
|
-
await this.refreshRecentSessionStates(
|
|
1121
|
+
await this.refreshRecentSessionStates(refreshCandidates, userId);
|
|
733
1122
|
}
|
|
734
1123
|
else {
|
|
735
|
-
this.scheduleWorkspaceStateRefresh(workspaceId, userId,
|
|
1124
|
+
this.scheduleWorkspaceStateRefresh(workspaceId, userId, refreshCandidates);
|
|
736
1125
|
}
|
|
737
1126
|
const nextItems = this.listWorkspaceSessions(workspaceId, userId);
|
|
738
1127
|
logPerformance("workspace.discover_sessions", Date.now() - startedAt, {
|
|
@@ -742,7 +1131,8 @@ export class SessionHistoryService {
|
|
|
742
1131
|
discoveredSessions: sessions.length,
|
|
743
1132
|
returnedSessions: nextItems.length,
|
|
744
1133
|
discoveryComplete: discovery.isComplete,
|
|
745
|
-
|
|
1134
|
+
providerDiagnostics: (discovery.providerDiagnostics ?? []).map((entry) => `${entry.provider}:${entry.status}:${Math.round(entry.durationMs)}ms`),
|
|
1135
|
+
refreshedStates: refreshCandidates.length,
|
|
746
1136
|
discoverMs: discoverDurationMs,
|
|
747
1137
|
persistMs: persistDurationMs,
|
|
748
1138
|
refreshStateDeferred: refreshStateMode !== "inline"
|
|
@@ -788,7 +1178,8 @@ export class SessionHistoryService {
|
|
|
788
1178
|
: this.sessionSyncService.readHistory(provider, providerSessionId, rawStoreRef, cursor, limit, direction);
|
|
789
1179
|
return historyTask
|
|
790
1180
|
.then((page) => {
|
|
791
|
-
const
|
|
1181
|
+
const messagesWithAttachments = this.sessionMessageAttachmentService.enrichMessages(sessionId, page.messages);
|
|
1182
|
+
const messages = this.enrichMessagesWithOrigin(sessionId, messagesWithAttachments);
|
|
792
1183
|
this.persistSessionChangedFiles(sessionId, messages);
|
|
793
1184
|
return {
|
|
794
1185
|
...page,
|
|
@@ -807,6 +1198,64 @@ export class SessionHistoryService {
|
|
|
807
1198
|
throw mapSessionProviderError(error);
|
|
808
1199
|
});
|
|
809
1200
|
}
|
|
1201
|
+
enrichMessagesWithOrigin(sessionId, messages) {
|
|
1202
|
+
return this.resolveMessageOrigins(sessionId, messages);
|
|
1203
|
+
}
|
|
1204
|
+
resolveMessageOrigins(sessionId, messages) {
|
|
1205
|
+
const originRepository = this.sessionMessageOriginRepository;
|
|
1206
|
+
if (!originRepository || messages.length === 0) {
|
|
1207
|
+
return messages.map((message) => ({
|
|
1208
|
+
...message,
|
|
1209
|
+
origin: null,
|
|
1210
|
+
originRef: null
|
|
1211
|
+
}));
|
|
1212
|
+
}
|
|
1213
|
+
const messageIds = [...new Set(messages.map((message) => message.messageId).filter(Boolean))];
|
|
1214
|
+
const originRows = originRepository.listBySessionAndMessageIds(sessionId, messageIds);
|
|
1215
|
+
const originByMessageId = new Map(originRows
|
|
1216
|
+
.filter((row) => row.messageId)
|
|
1217
|
+
.map((row) => [row.messageId, row]));
|
|
1218
|
+
const unresolvedRows = originRepository.listUnresolvedBySessionAndContents(sessionId, [...new Set(messages.map((message) => message.content).filter((content) => content.trim().length > 0))]);
|
|
1219
|
+
const unresolvedByContent = new Map();
|
|
1220
|
+
for (const row of unresolvedRows) {
|
|
1221
|
+
const current = unresolvedByContent.get(row.content) ?? [];
|
|
1222
|
+
current.push(row);
|
|
1223
|
+
unresolvedByContent.set(row.content, current);
|
|
1224
|
+
}
|
|
1225
|
+
return messages.map((message) => {
|
|
1226
|
+
const resolved = originByMessageId.get(message.messageId) ?? null;
|
|
1227
|
+
if (resolved) {
|
|
1228
|
+
return {
|
|
1229
|
+
...message,
|
|
1230
|
+
origin: resolved.origin,
|
|
1231
|
+
originRef: resolved.originRef
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
if (message.role !== "user") {
|
|
1235
|
+
return {
|
|
1236
|
+
...message,
|
|
1237
|
+
origin: null,
|
|
1238
|
+
originRef: null
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
const candidates = unresolvedByContent.get(message.content) ?? [];
|
|
1242
|
+
const matched = candidates.find((row) => isMessageAtOrAfter(message.timestamp, row.createdAt)) ?? null;
|
|
1243
|
+
if (!matched) {
|
|
1244
|
+
return {
|
|
1245
|
+
...message,
|
|
1246
|
+
origin: null,
|
|
1247
|
+
originRef: null
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
originRepository.resolveMessageId(sessionId, matched.clientRequestId, message.messageId, message.timestamp);
|
|
1251
|
+
unresolvedByContent.set(message.content, candidates.filter((candidate) => candidate.clientRequestId !== matched.clientRequestId));
|
|
1252
|
+
return {
|
|
1253
|
+
...message,
|
|
1254
|
+
origin: matched.origin,
|
|
1255
|
+
originRef: matched.originRef
|
|
1256
|
+
};
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
810
1259
|
buildWorkspaceSessionRelationMap(sessions, discoveredSessionIds) {
|
|
811
1260
|
const relationMap = new Map();
|
|
812
1261
|
for (const session of sessions) {
|
|
@@ -818,11 +1267,17 @@ export class SessionHistoryService {
|
|
|
818
1267
|
? discoveredSessionIds.get(buildProviderSessionKey(session.provider, session.parentProviderSessionId)) ??
|
|
819
1268
|
this.sessionBindingRepository.findByProviderSession(session.provider, session.parentProviderSessionId)?.sessionId ??
|
|
820
1269
|
null
|
|
821
|
-
:
|
|
1270
|
+
: this.resolvePersistedParentSessionId(sessionId);
|
|
822
1271
|
relationMap.set(sessionId, {
|
|
823
1272
|
parentSessionId,
|
|
824
|
-
|
|
825
|
-
|
|
1273
|
+
sessionKind: this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.sessionKind ?? "default",
|
|
1274
|
+
annotationSourceMessageId: this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.annotationSourceMessageId ?? null,
|
|
1275
|
+
annotationSourceText: this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.annotationSourceText ?? null,
|
|
1276
|
+
isSubagent: session.isSubagent === true
|
|
1277
|
+
|| this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.isSubagent === true,
|
|
1278
|
+
subagentLabel: session.subagentLabel?.trim()
|
|
1279
|
+
|| this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.subagentLabel
|
|
1280
|
+
|| null
|
|
826
1281
|
});
|
|
827
1282
|
}
|
|
828
1283
|
return relationMap;
|
|
@@ -840,6 +1295,9 @@ export class SessionHistoryService {
|
|
|
840
1295
|
return this.enrichSessionItem({
|
|
841
1296
|
...item,
|
|
842
1297
|
parentSessionId: relation.parentSessionId,
|
|
1298
|
+
sessionKind: relation.sessionKind,
|
|
1299
|
+
annotationSourceMessageId: relation.annotationSourceMessageId,
|
|
1300
|
+
annotationSourceText: relation.annotationSourceText,
|
|
843
1301
|
isSubagent: relation.isSubagent,
|
|
844
1302
|
subagentLabel: relation.subagentLabel
|
|
845
1303
|
});
|
|
@@ -851,24 +1309,36 @@ export class SessionHistoryService {
|
|
|
851
1309
|
? {
|
|
852
1310
|
...item,
|
|
853
1311
|
parentSessionId: relation.parentSessionId,
|
|
1312
|
+
sessionKind: relation.sessionKind,
|
|
1313
|
+
annotationSourceMessageId: relation.annotationSourceMessageId,
|
|
1314
|
+
annotationSourceText: relation.annotationSourceText,
|
|
854
1315
|
isSubagent: relation.isSubagent,
|
|
855
1316
|
subagentLabel: relation.subagentLabel
|
|
856
1317
|
}
|
|
857
1318
|
: {
|
|
858
1319
|
...item,
|
|
859
1320
|
parentSessionId: item.parentSessionId ?? null,
|
|
1321
|
+
sessionKind: item.sessionKind ?? "default",
|
|
1322
|
+
annotationSourceMessageId: item.annotationSourceMessageId ?? null,
|
|
1323
|
+
annotationSourceText: item.annotationSourceText ?? null,
|
|
860
1324
|
isSubagent: item.isSubagent ?? false,
|
|
861
1325
|
subagentLabel: item.subagentLabel ?? null
|
|
862
1326
|
};
|
|
863
1327
|
const resolution = this.sessionActivityAuthorityService.resolvePersistedSession(nextItem);
|
|
864
1328
|
return applySessionActivityResolution(nextItem, resolution);
|
|
865
1329
|
}
|
|
866
|
-
async pullSessionHistory(sessionId, cursor, limit,
|
|
1330
|
+
async pullSessionHistory(sessionId, cursor, limit, deliveredMessages, onEnvelope, envelopeType, isClosed = () => false) {
|
|
867
1331
|
let currentCursor = cursor;
|
|
868
1332
|
while (!isClosed()) {
|
|
869
1333
|
const binding = this.getBindingOrThrow(sessionId);
|
|
870
1334
|
const page = await this.readPage(sessionId, binding.provider, binding.providerSessionId, binding.rawStoreRef, currentCursor, limit);
|
|
871
|
-
await this.publishHistoryEnvelope(sessionId, binding, page,
|
|
1335
|
+
await this.publishHistoryEnvelope(sessionId, binding, page, deliveredMessages, onEnvelope, envelopeType);
|
|
1336
|
+
if (envelopeType === "session.delta" &&
|
|
1337
|
+
shouldRefreshMutableHistoryTail(binding.provider, page, currentCursor, deliveredMessages)) {
|
|
1338
|
+
const tailPage = await this.readPage(sessionId, binding.provider, binding.providerSessionId, binding.rawStoreRef, null, Math.max(limit, 20), "backward");
|
|
1339
|
+
deliveredMessages.lastMutableTailRefreshAtMs = Date.now();
|
|
1340
|
+
await this.publishHistoryEnvelope(sessionId, binding, tailPage, deliveredMessages, onEnvelope, envelopeType);
|
|
1341
|
+
}
|
|
872
1342
|
currentCursor = page.cursor;
|
|
873
1343
|
if (!page.nextCursor) {
|
|
874
1344
|
return currentCursor;
|
|
@@ -876,19 +1346,21 @@ export class SessionHistoryService {
|
|
|
876
1346
|
}
|
|
877
1347
|
return currentCursor;
|
|
878
1348
|
}
|
|
879
|
-
async pullRecentSessionHistory(sessionId, limit,
|
|
1349
|
+
async pullRecentSessionHistory(sessionId, limit, deliveredMessages, onEnvelope, envelopeType) {
|
|
880
1350
|
const binding = this.getBindingOrThrow(sessionId);
|
|
881
1351
|
const knownTotalMessageCount = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.messageCount ?? null;
|
|
882
1352
|
const page = await this.readPage(sessionId, binding.provider, binding.providerSessionId, binding.rawStoreRef, null, limit, "backward", knownTotalMessageCount);
|
|
883
|
-
await this.publishHistoryEnvelope(sessionId, binding, page,
|
|
1353
|
+
await this.publishHistoryEnvelope(sessionId, binding, page, deliveredMessages, onEnvelope, envelopeType);
|
|
884
1354
|
return page.cursor;
|
|
885
1355
|
}
|
|
886
|
-
async publishHistoryEnvelope(sessionId, binding, page,
|
|
1356
|
+
async publishHistoryEnvelope(sessionId, binding, page, deliveredMessages, onEnvelope, envelopeType) {
|
|
887
1357
|
const messages = page.messages.filter((message) => {
|
|
888
|
-
|
|
1358
|
+
const nextSignature = buildDeliveredHistoryMessageSignature(message);
|
|
1359
|
+
const previousSignature = deliveredMessages.signaturesByMessageId.get(message.messageId);
|
|
1360
|
+
if (previousSignature === nextSignature) {
|
|
889
1361
|
return false;
|
|
890
1362
|
}
|
|
891
|
-
|
|
1363
|
+
rememberDeliveredHistoryMessage(deliveredMessages, message.messageId, nextSignature);
|
|
892
1364
|
return true;
|
|
893
1365
|
});
|
|
894
1366
|
if (messages.length === 0) {
|
|
@@ -920,15 +1392,21 @@ export class SessionHistoryService {
|
|
|
920
1392
|
return;
|
|
921
1393
|
}
|
|
922
1394
|
const nextTitle = (await this.sessionSyncService.readSessionTitle(binding.provider, binding.providerSessionId, binding.rawStoreRef)).trim();
|
|
923
|
-
|
|
1395
|
+
const resolvedTitle = resolvePersistedSessionTitle(binding.provider, nextTitle, currentIndex.title);
|
|
1396
|
+
if (resolvedTitle.length === 0 || resolvedTitle === currentIndex.title) {
|
|
924
1397
|
return;
|
|
925
1398
|
}
|
|
926
1399
|
this.sessionIndexRepository.upsert({
|
|
927
1400
|
...currentIndex,
|
|
928
|
-
title:
|
|
1401
|
+
title: resolvedTitle,
|
|
929
1402
|
updatedAt: nowIso()
|
|
930
1403
|
});
|
|
931
1404
|
}
|
|
1405
|
+
resolvePersistedParentSessionId(sessionId) {
|
|
1406
|
+
return (this.sessionForkRepository.findBySessionId(sessionId)?.parentSessionId
|
|
1407
|
+
?? this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.parentSessionId
|
|
1408
|
+
?? null);
|
|
1409
|
+
}
|
|
932
1410
|
async ensureSessionChangedFilesIndexed(sessionId) {
|
|
933
1411
|
if (this.sessionChangedFileService.hasIndexedSession(sessionId)) {
|
|
934
1412
|
return;
|
|
@@ -980,7 +1458,53 @@ export class SessionHistoryService {
|
|
|
980
1458
|
detail: "session 索引缺失"
|
|
981
1459
|
});
|
|
982
1460
|
}
|
|
983
|
-
|
|
1461
|
+
const aliasTargetSessionId = this.findPendingSessionAliasTargetSessionId(item);
|
|
1462
|
+
if (!aliasTargetSessionId) {
|
|
1463
|
+
return item;
|
|
1464
|
+
}
|
|
1465
|
+
return this.sessionIndexRepository.findBySessionId(aliasTargetSessionId, userId) ?? item;
|
|
1466
|
+
}
|
|
1467
|
+
resolveCanonicalSessionId(sessionId, userId) {
|
|
1468
|
+
if (userId) {
|
|
1469
|
+
const item = this.sessionIndexRepository.findBySessionId(sessionId, userId);
|
|
1470
|
+
const aliasTargetSessionId = this.findPendingSessionAliasTargetSessionId(item);
|
|
1471
|
+
if (aliasTargetSessionId) {
|
|
1472
|
+
return aliasTargetSessionId;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
const binding = this.sessionBindingRepository.findBySessionId(sessionId);
|
|
1476
|
+
return this.findPendingSessionAliasTargetSessionId(binding) ?? sessionId;
|
|
1477
|
+
}
|
|
1478
|
+
isPendingSessionAlias(item) {
|
|
1479
|
+
return Boolean(this.findPendingSessionAliasTargetSessionId(item));
|
|
1480
|
+
}
|
|
1481
|
+
resolvePendingSessionAliasBinding(binding) {
|
|
1482
|
+
const aliasTargetSessionId = this.findPendingSessionAliasTargetSessionId(binding);
|
|
1483
|
+
if (!aliasTargetSessionId) {
|
|
1484
|
+
return null;
|
|
1485
|
+
}
|
|
1486
|
+
return this.sessionBindingRepository.findBySessionId(aliasTargetSessionId);
|
|
1487
|
+
}
|
|
1488
|
+
findPendingSessionAliasTargetSessionId(descriptor) {
|
|
1489
|
+
if (!descriptor || descriptor.provider !== "gemini") {
|
|
1490
|
+
return null;
|
|
1491
|
+
}
|
|
1492
|
+
const aliasTargetSessionId = extractPendingBindingTargetSessionId(descriptor.providerSessionId)
|
|
1493
|
+
?? extractPendingBindingTargetSessionId(descriptor.rawStoreRef);
|
|
1494
|
+
if (!aliasTargetSessionId || aliasTargetSessionId === descriptor.sessionId) {
|
|
1495
|
+
return null;
|
|
1496
|
+
}
|
|
1497
|
+
const targetBinding = this.sessionBindingRepository.findBySessionId(aliasTargetSessionId);
|
|
1498
|
+
if (!targetBinding) {
|
|
1499
|
+
return null;
|
|
1500
|
+
}
|
|
1501
|
+
if (targetBinding.workspaceId !== descriptor.workspaceId
|
|
1502
|
+
|| targetBinding.provider !== descriptor.provider
|
|
1503
|
+
|| isPendingBindingValue(targetBinding.providerSessionId)
|
|
1504
|
+
|| isPendingBindingValue(targetBinding.rawStoreRef)) {
|
|
1505
|
+
return null;
|
|
1506
|
+
}
|
|
1507
|
+
return aliasTargetSessionId;
|
|
984
1508
|
}
|
|
985
1509
|
async refreshRecentSessionStates(sessions, userId) {
|
|
986
1510
|
for (let index = 0; index < sessions.length; index += 1) {
|
|
@@ -1146,6 +1670,9 @@ export class SessionHistoryService {
|
|
|
1146
1670
|
this.db
|
|
1147
1671
|
.prepare("DELETE FROM session_status_snapshots WHERE session_id = ?")
|
|
1148
1672
|
.run(input.sourceSessionId);
|
|
1673
|
+
this.db
|
|
1674
|
+
.prepare("DELETE FROM session_forks WHERE session_id = ?")
|
|
1675
|
+
.run(input.sourceSessionId);
|
|
1149
1676
|
this.db
|
|
1150
1677
|
.prepare("DELETE FROM session_indices WHERE session_id = ?")
|
|
1151
1678
|
.run(input.sourceSessionId);
|
|
@@ -1217,11 +1744,25 @@ export class SessionHistoryService {
|
|
|
1217
1744
|
relationMap.delete(sourceSessionId);
|
|
1218
1745
|
relationMap.set(targetSessionId, {
|
|
1219
1746
|
parentSessionId: targetRelation?.parentSessionId ?? sourceRelation?.parentSessionId ?? fallbackParentSessionId,
|
|
1747
|
+
sessionKind: targetRelation?.sessionKind
|
|
1748
|
+
?? sourceRelation?.sessionKind
|
|
1749
|
+
?? targetIndex?.sessionKind
|
|
1750
|
+
?? sourceIndex?.sessionKind
|
|
1751
|
+
?? "default",
|
|
1752
|
+
annotationSourceMessageId: targetRelation?.annotationSourceMessageId
|
|
1753
|
+
?? sourceRelation?.annotationSourceMessageId
|
|
1754
|
+
?? targetIndex?.annotationSourceMessageId
|
|
1755
|
+
?? sourceIndex?.annotationSourceMessageId
|
|
1756
|
+
?? null,
|
|
1757
|
+
annotationSourceText: targetRelation?.annotationSourceText
|
|
1758
|
+
?? sourceRelation?.annotationSourceText
|
|
1759
|
+
?? targetIndex?.annotationSourceText
|
|
1760
|
+
?? sourceIndex?.annotationSourceText
|
|
1761
|
+
?? null,
|
|
1220
1762
|
isSubagent: Boolean(targetRelation?.isSubagent
|
|
1221
1763
|
|| sourceRelation?.isSubagent
|
|
1222
1764
|
|| targetIndex?.isSubagent
|
|
1223
|
-
|| sourceIndex?.isSubagent
|
|
1224
|
-
|| fallbackParentSessionId),
|
|
1765
|
+
|| sourceIndex?.isSubagent),
|
|
1225
1766
|
subagentLabel: targetRelation?.subagentLabel
|
|
1226
1767
|
?? sourceRelation?.subagentLabel
|
|
1227
1768
|
?? targetIndex?.subagentLabel
|
|
@@ -1251,6 +1792,9 @@ export class SessionHistoryService {
|
|
|
1251
1792
|
this.db
|
|
1252
1793
|
.prepare("DELETE FROM session_status_snapshots WHERE session_id = ?")
|
|
1253
1794
|
.run(sessionId);
|
|
1795
|
+
this.db
|
|
1796
|
+
.prepare("DELETE FROM session_forks WHERE session_id = ?")
|
|
1797
|
+
.run(sessionId);
|
|
1254
1798
|
this.db
|
|
1255
1799
|
.prepare("DELETE FROM session_indices WHERE session_id = ?")
|
|
1256
1800
|
.run(sessionId);
|
|
@@ -1281,17 +1825,24 @@ export class SessionHistoryService {
|
|
|
1281
1825
|
const current = this.sessionStateRepository.findBySessionAndUser(sessionId, userId);
|
|
1282
1826
|
const inspection = inspectSessionActivity(binding.provider, binding.rawStoreRef);
|
|
1283
1827
|
const timestamp = nowIso();
|
|
1828
|
+
const nowMs = Date.parse(timestamp);
|
|
1829
|
+
if (shouldClearStaleRuntimeWithoutInspection(current, inspection, nowMs)) {
|
|
1830
|
+
this.sessionActivityAuthorityService.clearSession(sessionId);
|
|
1831
|
+
}
|
|
1284
1832
|
if (shouldPreserveRuntimeTerminalState(current, inspection)) {
|
|
1285
1833
|
return current;
|
|
1286
1834
|
}
|
|
1287
1835
|
const resolution = this.sessionActivityAuthorityService.observe(buildInspectionActivityObservation(sessionId, inspection, timestamp));
|
|
1836
|
+
const resolvedLastEventAt = hasInspectionEvidence(inspection)
|
|
1837
|
+
? resolution.lastObservedAt ?? inspection.lastEventAt ?? current?.lastEventAt ?? null
|
|
1838
|
+
: current?.lastEventAt ?? null;
|
|
1288
1839
|
const nextRecord = {
|
|
1289
1840
|
sessionId,
|
|
1290
1841
|
userId,
|
|
1291
1842
|
runningState: mapResolvedRunningStateToStored(resolution.runningState, current),
|
|
1292
1843
|
activitySource: mapResolutionSourceToLegacyActivitySource(resolution.activityResolutionSource, inspection),
|
|
1293
1844
|
favorite: current?.favorite ?? false,
|
|
1294
|
-
lastEventAt:
|
|
1845
|
+
lastEventAt: resolvedLastEventAt,
|
|
1295
1846
|
completedAt: isTerminalResolvedRunningState(resolution.runningState)
|
|
1296
1847
|
? resolution.terminalAt ?? inspection.completedAtCandidate ?? current?.completedAt ?? null
|
|
1297
1848
|
: null,
|
|
@@ -1351,11 +1902,52 @@ export class SessionHistoryService {
|
|
|
1351
1902
|
});
|
|
1352
1903
|
}
|
|
1353
1904
|
}
|
|
1905
|
+
function isProviderCliBacked(provider) {
|
|
1906
|
+
return provider === "claude-code" || provider === "codex" || provider === "gemini" || provider === "kimi";
|
|
1907
|
+
}
|
|
1908
|
+
function buildProviderCliAvailabilitySnapshot(commandPaths) {
|
|
1909
|
+
return Object.freeze(Object.fromEntries(Object.entries(commandPaths).map(([provider, commandPath]) => [
|
|
1910
|
+
provider,
|
|
1911
|
+
isCommandAvailable(commandPath)
|
|
1912
|
+
])));
|
|
1913
|
+
}
|
|
1914
|
+
function buildProviderCliUnavailableMessage(provider) {
|
|
1915
|
+
switch (provider) {
|
|
1916
|
+
case "claude-code":
|
|
1917
|
+
return "未检测到 Claude CLI";
|
|
1918
|
+
case "codex":
|
|
1919
|
+
return "未检测到 Codex CLI";
|
|
1920
|
+
case "gemini":
|
|
1921
|
+
return "未检测到 Gemini CLI";
|
|
1922
|
+
case "kimi":
|
|
1923
|
+
return "未检测到 Kimi CLI";
|
|
1924
|
+
default:
|
|
1925
|
+
return "未检测到对应 CLI";
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
function createCodexForkTransportFactory(commandPath, homeDir) {
|
|
1929
|
+
return () => {
|
|
1930
|
+
const client = new CodexAppServerHelperClient(commandPath, { homeDir });
|
|
1931
|
+
const transport = client.createForkTransport();
|
|
1932
|
+
return {
|
|
1933
|
+
...transport,
|
|
1934
|
+
close() {
|
|
1935
|
+
transport.close();
|
|
1936
|
+
client.dispose();
|
|
1937
|
+
}
|
|
1938
|
+
};
|
|
1939
|
+
};
|
|
1940
|
+
}
|
|
1354
1941
|
function buildInspectionActivityObservation(sessionId, inspection, observedAt) {
|
|
1942
|
+
const resolvedRunningState = inspection.runningState === "failed"
|
|
1943
|
+
? "failed"
|
|
1944
|
+
: inspection.completedAtCandidate
|
|
1945
|
+
? "completed"
|
|
1946
|
+
: inspection.runningState;
|
|
1355
1947
|
return {
|
|
1356
1948
|
sessionId,
|
|
1357
1949
|
runId: null,
|
|
1358
|
-
runningState:
|
|
1950
|
+
runningState: resolvedRunningState,
|
|
1359
1951
|
source: hasInspectionEvidence(inspection) ? "inferred_log" : "unknown",
|
|
1360
1952
|
confidence: "weak",
|
|
1361
1953
|
detail: inspection.errorDetail,
|
|
@@ -1412,6 +2004,20 @@ function clampLimit(limit) {
|
|
|
1412
2004
|
}
|
|
1413
2005
|
return Math.max(1, Math.min(Math.trunc(limit), 100));
|
|
1414
2006
|
}
|
|
2007
|
+
function buildSessionStateRefreshCandidates(items, recentCount) {
|
|
2008
|
+
const recentItems = items.slice(0, recentCount);
|
|
2009
|
+
const activeResidues = items.filter((item) => isSessionStateRefreshCandidate(item));
|
|
2010
|
+
const deduped = new Map();
|
|
2011
|
+
for (const item of [...recentItems, ...activeResidues]) {
|
|
2012
|
+
deduped.set(item.sessionId, item);
|
|
2013
|
+
}
|
|
2014
|
+
return Array.from(deduped.values());
|
|
2015
|
+
}
|
|
2016
|
+
function isSessionStateRefreshCandidate(item) {
|
|
2017
|
+
return item.activityState === "running"
|
|
2018
|
+
|| item.runningState === "starting"
|
|
2019
|
+
|| item.runningState === "running";
|
|
2020
|
+
}
|
|
1415
2021
|
function mapSessionStateRecordRow(row) {
|
|
1416
2022
|
return {
|
|
1417
2023
|
sessionId: row.session_id,
|
|
@@ -1477,6 +2083,9 @@ function mergeSessionIndexRecord(input) {
|
|
|
1477
2083
|
workspaceId: input.workspaceId,
|
|
1478
2084
|
provider: (input.target?.provider ?? input.source?.provider ?? input.provider),
|
|
1479
2085
|
parentSessionId: input.target?.parentSessionId ?? input.source?.parentSessionId ?? null,
|
|
2086
|
+
sessionKind: input.target?.sessionKind ?? input.source?.sessionKind ?? "default",
|
|
2087
|
+
annotationSourceMessageId: input.target?.annotationSourceMessageId ?? input.source?.annotationSourceMessageId ?? null,
|
|
2088
|
+
annotationSourceText: input.target?.annotationSourceText ?? input.source?.annotationSourceText ?? null,
|
|
1480
2089
|
isSubagent: Boolean(input.target?.isSubagent || input.source?.isSubagent),
|
|
1481
2090
|
subagentLabel: input.target?.subagentLabel ?? input.source?.subagentLabel ?? null,
|
|
1482
2091
|
title: pickPreferredSessionTitle(input.target?.title ?? null, input.source?.title ?? null),
|
|
@@ -1585,12 +2194,20 @@ function isPendingBindingValue(value) {
|
|
|
1585
2194
|
function buildPendingBindingValue(provider, sessionId) {
|
|
1586
2195
|
return `pending://${provider}/${sessionId}`;
|
|
1587
2196
|
}
|
|
2197
|
+
function extractPendingBindingTargetSessionId(value) {
|
|
2198
|
+
if (!isPendingBindingValue(value)) {
|
|
2199
|
+
return null;
|
|
2200
|
+
}
|
|
2201
|
+
const normalizedValue = value.trim();
|
|
2202
|
+
const targetSessionId = normalizedValue.slice(normalizedValue.indexOf("/", "pending://".length) + 1).trim();
|
|
2203
|
+
return targetSessionId || null;
|
|
2204
|
+
}
|
|
1588
2205
|
function isClaudePendingRuntimeRawStoreRef(rawStoreRef) {
|
|
1589
2206
|
const normalizedRawStoreRef = rawStoreRef.replaceAll("\\", "/").toLowerCase();
|
|
1590
2207
|
return normalizedRawStoreRef.includes("/.pending-");
|
|
1591
2208
|
}
|
|
1592
2209
|
function shouldShortCircuitClaudePendingHistory(provider, providerSessionId, rawStoreRef) {
|
|
1593
|
-
if (provider !== "claude-code") {
|
|
2210
|
+
if (provider !== "claude-code" && provider !== "gemini") {
|
|
1594
2211
|
return false;
|
|
1595
2212
|
}
|
|
1596
2213
|
return isPendingBindingValue(providerSessionId) || isPendingBindingValue(rawStoreRef);
|
|
@@ -1639,6 +2256,66 @@ function findClaudePendingDiscoveryDuplicate(session, existingSessions, claimedS
|
|
|
1639
2256
|
});
|
|
1640
2257
|
return activePendingCandidates.length === 1 ? activePendingCandidates[0] : null;
|
|
1641
2258
|
}
|
|
2259
|
+
function findKimiRuntimeDiscoveryDuplicate(session, existingSessions, claimedSessionIds) {
|
|
2260
|
+
if (session.provider !== "kimi" || isPendingBindingValue(session.providerSessionId)) {
|
|
2261
|
+
return null;
|
|
2262
|
+
}
|
|
2263
|
+
const candidates = existingSessions.filter((item) => {
|
|
2264
|
+
if (claimedSessionIds.has(item.sessionId)) {
|
|
2265
|
+
return false;
|
|
2266
|
+
}
|
|
2267
|
+
if (item.provider !== "kimi" || !shouldRecoverKimiRuntimeBinding(item)) {
|
|
2268
|
+
return false;
|
|
2269
|
+
}
|
|
2270
|
+
return isCloseKimiSessionTimestamp(item.lastMessageAt ?? item.createdAt, session.lastMessageAt);
|
|
2271
|
+
});
|
|
2272
|
+
if (candidates.length === 1) {
|
|
2273
|
+
return candidates[0];
|
|
2274
|
+
}
|
|
2275
|
+
const comparableTitle = normalizeKimiComparableTitle(session.title);
|
|
2276
|
+
if (!comparableTitle) {
|
|
2277
|
+
return null;
|
|
2278
|
+
}
|
|
2279
|
+
const titleMatchedCandidates = candidates.filter((item) => normalizeKimiComparableTitle(item.title) === comparableTitle);
|
|
2280
|
+
return titleMatchedCandidates.length === 1
|
|
2281
|
+
? titleMatchedCandidates[0]
|
|
2282
|
+
: null;
|
|
2283
|
+
}
|
|
2284
|
+
function shouldRecoverKimiRuntimeBinding(item) {
|
|
2285
|
+
if (isPendingBindingValue(item.providerSessionId)) {
|
|
2286
|
+
return true;
|
|
2287
|
+
}
|
|
2288
|
+
if (item.messageCount !== 0 || item.activitySource !== "runtime") {
|
|
2289
|
+
return false;
|
|
2290
|
+
}
|
|
2291
|
+
if (item.runningState === "starting") {
|
|
2292
|
+
return true;
|
|
2293
|
+
}
|
|
2294
|
+
if (item.lastErrorCode === "PROVIDER_READ_FAILED") {
|
|
2295
|
+
return true;
|
|
2296
|
+
}
|
|
2297
|
+
return (item.lastErrorDetail ?? "").includes("provider 会话不存在");
|
|
2298
|
+
}
|
|
2299
|
+
function normalizeKimiComparableTitle(title) {
|
|
2300
|
+
const normalized = title.trim().replace(/\s+/g, " ");
|
|
2301
|
+
if (!normalized) {
|
|
2302
|
+
return null;
|
|
2303
|
+
}
|
|
2304
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(normalized)
|
|
2305
|
+
? null
|
|
2306
|
+
: normalized;
|
|
2307
|
+
}
|
|
2308
|
+
function isCloseKimiSessionTimestamp(left, right) {
|
|
2309
|
+
if (!left || !right) {
|
|
2310
|
+
return false;
|
|
2311
|
+
}
|
|
2312
|
+
const leftAt = Date.parse(left);
|
|
2313
|
+
const rightAt = Date.parse(right);
|
|
2314
|
+
if (!Number.isFinite(leftAt) || !Number.isFinite(rightAt)) {
|
|
2315
|
+
return false;
|
|
2316
|
+
}
|
|
2317
|
+
return Math.abs(leftAt - rightAt) <= 2 * 60 * 1_000;
|
|
2318
|
+
}
|
|
1642
2319
|
function normalizeClaudeComparableTitle(title) {
|
|
1643
2320
|
return title?.trim().replace(/\s+/g, " ").toLowerCase() ?? "";
|
|
1644
2321
|
}
|
|
@@ -1670,6 +2347,54 @@ function isMessageAtOrAfter(timestamp, minTimestamp) {
|
|
|
1670
2347
|
}
|
|
1671
2348
|
return messageAt >= minAt;
|
|
1672
2349
|
}
|
|
2350
|
+
function isAcceptedUserMessageTimestamp(provider, timestamp, minTimestamp) {
|
|
2351
|
+
if (provider === "kimi"
|
|
2352
|
+
&& isSyntheticKimiHistoryTimestamp(timestamp)) {
|
|
2353
|
+
return true;
|
|
2354
|
+
}
|
|
2355
|
+
return isMessageAtOrAfter(timestamp, minTimestamp);
|
|
2356
|
+
}
|
|
2357
|
+
function isSyntheticKimiHistoryTimestamp(timestamp) {
|
|
2358
|
+
return timestamp.startsWith("2020-01-01T00:");
|
|
2359
|
+
}
|
|
2360
|
+
function createDeliveredHistoryMessageState() {
|
|
2361
|
+
return {
|
|
2362
|
+
signaturesByMessageId: new Map(),
|
|
2363
|
+
lastMutableTailRefreshAtMs: 0
|
|
2364
|
+
};
|
|
2365
|
+
}
|
|
2366
|
+
function shouldRefreshMutableHistoryTail(provider, page, cursor, deliveredMessages) {
|
|
2367
|
+
if (provider !== "kimi" || cursor === null || page.messages.length > 0) {
|
|
2368
|
+
return false;
|
|
2369
|
+
}
|
|
2370
|
+
return Date.now() - deliveredMessages.lastMutableTailRefreshAtMs >= MUTABLE_HISTORY_TAIL_REFRESH_INTERVAL_MS;
|
|
2371
|
+
}
|
|
2372
|
+
function buildDeliveredHistoryMessageSignature(message) {
|
|
2373
|
+
return hashContent(JSON.stringify({
|
|
2374
|
+
provider: message.provider,
|
|
2375
|
+
providerSessionId: message.providerSessionId,
|
|
2376
|
+
role: message.role,
|
|
2377
|
+
kind: message.kind,
|
|
2378
|
+
content: message.content,
|
|
2379
|
+
toolCall: message.toolCall,
|
|
2380
|
+
attachments: message.attachments ?? [],
|
|
2381
|
+
timestamp: message.timestamp,
|
|
2382
|
+
rawRef: message.rawRef
|
|
2383
|
+
}));
|
|
2384
|
+
}
|
|
2385
|
+
function rememberDeliveredHistoryMessage(state, messageId, signature) {
|
|
2386
|
+
if (state.signaturesByMessageId.has(messageId)) {
|
|
2387
|
+
state.signaturesByMessageId.delete(messageId);
|
|
2388
|
+
}
|
|
2389
|
+
state.signaturesByMessageId.set(messageId, signature);
|
|
2390
|
+
while (state.signaturesByMessageId.size > 2_048) {
|
|
2391
|
+
const oldestMessageId = state.signaturesByMessageId.keys().next().value;
|
|
2392
|
+
if (typeof oldestMessageId !== "string") {
|
|
2393
|
+
break;
|
|
2394
|
+
}
|
|
2395
|
+
state.signaturesByMessageId.delete(oldestMessageId);
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
1673
2398
|
function delay(ms) {
|
|
1674
2399
|
return new Promise((resolve) => {
|
|
1675
2400
|
setTimeout(resolve, ms);
|
|
@@ -1730,6 +2455,56 @@ function isLegacyCodingNsRolloutSession(providerSessionId, rawStoreRef) {
|
|
|
1730
2455
|
function shouldRemoveMissingSyntheticCodexSession(rawStoreRef) {
|
|
1731
2456
|
return isSyntheticCodexRawStoreRef(rawStoreRef) && !existsSync(rawStoreRef);
|
|
1732
2457
|
}
|
|
2458
|
+
function resolveSessionListTitle(provider, existingTitle, fallbackContent, parentTitle = null) {
|
|
2459
|
+
const normalizedExistingTitle = existingTitle?.trim() ?? "";
|
|
2460
|
+
const normalizedParentTitle = parentTitle?.trim() ?? "";
|
|
2461
|
+
const fallbackTitle = buildUserMessageTitle(fallbackContent, normalizedExistingTitle || "继续对话");
|
|
2462
|
+
if (normalizedExistingTitle.length > 0 &&
|
|
2463
|
+
!isSyntheticCodexSessionTitle(normalizedExistingTitle) &&
|
|
2464
|
+
(normalizedParentTitle.length === 0 ||
|
|
2465
|
+
normalizedExistingTitle !== normalizedParentTitle)) {
|
|
2466
|
+
return normalizedExistingTitle;
|
|
2467
|
+
}
|
|
2468
|
+
if (normalizedParentTitle.length > 0 && normalizedExistingTitle === normalizedParentTitle) {
|
|
2469
|
+
return fallbackTitle;
|
|
2470
|
+
}
|
|
2471
|
+
if (provider === "codex") {
|
|
2472
|
+
return fallbackTitle;
|
|
2473
|
+
}
|
|
2474
|
+
return normalizedExistingTitle || fallbackTitle;
|
|
2475
|
+
}
|
|
2476
|
+
function buildUserMessageTitle(content, fallbackTitle) {
|
|
2477
|
+
const title = content.trim().replace(/\s+/g, " ");
|
|
2478
|
+
return title.slice(0, 48) || fallbackTitle;
|
|
2479
|
+
}
|
|
2480
|
+
function resolvePersistedSessionTitle(provider, discoveredTitle, existingTitle, parentTitle = null) {
|
|
2481
|
+
const nextTitle = discoveredTitle.trim();
|
|
2482
|
+
const currentTitle = existingTitle?.trim() ?? "";
|
|
2483
|
+
const normalizedParentTitle = parentTitle?.trim() ?? "";
|
|
2484
|
+
if (!currentTitle) {
|
|
2485
|
+
if (provider === "codex" && isSyntheticCodexSessionTitle(nextTitle)) {
|
|
2486
|
+
return currentTitle;
|
|
2487
|
+
}
|
|
2488
|
+
if (normalizedParentTitle.length > 0 && nextTitle === normalizedParentTitle) {
|
|
2489
|
+
return currentTitle;
|
|
2490
|
+
}
|
|
2491
|
+
return nextTitle;
|
|
2492
|
+
}
|
|
2493
|
+
if (nextTitle.length === 0) {
|
|
2494
|
+
return currentTitle;
|
|
2495
|
+
}
|
|
2496
|
+
if (provider === "codex" && isSyntheticCodexSessionTitle(nextTitle)) {
|
|
2497
|
+
return currentTitle;
|
|
2498
|
+
}
|
|
2499
|
+
if (normalizedParentTitle.length > 0 && nextTitle === normalizedParentTitle && currentTitle !== normalizedParentTitle) {
|
|
2500
|
+
return currentTitle;
|
|
2501
|
+
}
|
|
2502
|
+
return nextTitle;
|
|
2503
|
+
}
|
|
2504
|
+
function isSyntheticCodexSessionTitle(title) {
|
|
2505
|
+
return (/^rollout-\d{4}-\d{2}-\d{2}t/i.test(title) ||
|
|
2506
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(title));
|
|
2507
|
+
}
|
|
1733
2508
|
function shouldRemoveHiddenClaudeDebugSession(session) {
|
|
1734
2509
|
const normalizedRawStoreRef = session.rawStoreRef.replaceAll("\\", "/");
|
|
1735
2510
|
if (normalizedRawStoreRef.includes("/subagents/")) {
|
|
@@ -1738,11 +2513,34 @@ function shouldRemoveHiddenClaudeDebugSession(session) {
|
|
|
1738
2513
|
return (/^agent-[^/]+$/i.test(session.providerSessionId) &&
|
|
1739
2514
|
/\/agent-[^/]+\.jsonl$/i.test(normalizedRawStoreRef));
|
|
1740
2515
|
}
|
|
2516
|
+
const STALE_RUNTIME_WITHOUT_INSPECTION_GRACE_MS = 120_000;
|
|
2517
|
+
function shouldClearStaleRuntimeWithoutInspection(current, inspection, nowMs) {
|
|
2518
|
+
if (!current || current.activitySource !== "runtime") {
|
|
2519
|
+
return false;
|
|
2520
|
+
}
|
|
2521
|
+
if (current.runningState !== "starting" && current.runningState !== "running") {
|
|
2522
|
+
return false;
|
|
2523
|
+
}
|
|
2524
|
+
if (inspection.lastEventAt || inspection.completedAtCandidate || inspection.errorCode) {
|
|
2525
|
+
return false;
|
|
2526
|
+
}
|
|
2527
|
+
if (!current.lastEventAt) {
|
|
2528
|
+
return true;
|
|
2529
|
+
}
|
|
2530
|
+
const lastEventAtMs = Date.parse(current.lastEventAt);
|
|
2531
|
+
if (!Number.isFinite(lastEventAtMs)) {
|
|
2532
|
+
return true;
|
|
2533
|
+
}
|
|
2534
|
+
return nowMs - lastEventAtMs > STALE_RUNTIME_WITHOUT_INSPECTION_GRACE_MS;
|
|
2535
|
+
}
|
|
1741
2536
|
function shouldPreserveRuntimeTerminalState(current, inspection) {
|
|
1742
2537
|
if (!current || current.activitySource !== "runtime") {
|
|
1743
2538
|
return false;
|
|
1744
2539
|
}
|
|
1745
|
-
if (!inspection.lastEventAt
|
|
2540
|
+
if (!inspection.lastEventAt) {
|
|
2541
|
+
return !shouldClearStaleRuntimeWithoutInspection(current, inspection, Date.now());
|
|
2542
|
+
}
|
|
2543
|
+
if (!current.lastEventAt) {
|
|
1746
2544
|
return true;
|
|
1747
2545
|
}
|
|
1748
2546
|
if (isTerminalRunningState(current.runningState)) {
|
|
@@ -1795,4 +2593,31 @@ function resolveActivityState(runningState, completedAt, lastSeenAt) {
|
|
|
1795
2593
|
}
|
|
1796
2594
|
return "idle";
|
|
1797
2595
|
}
|
|
2596
|
+
function buildReconstructedForkPrompt(input) {
|
|
2597
|
+
const lines = [
|
|
2598
|
+
input.sourceTitle
|
|
2599
|
+
? `源会话:${input.sourceTitle}`
|
|
2600
|
+
: "源会话:未命名会话",
|
|
2601
|
+
`源 provider:${input.sourceProvider}`,
|
|
2602
|
+
`目标 provider:${input.targetProvider}`,
|
|
2603
|
+
input.sourceType === "message"
|
|
2604
|
+
? "分叉方式:从指定消息点重建后续上下文"
|
|
2605
|
+
: "分叉方式:从整条会话重建上下文",
|
|
2606
|
+
"",
|
|
2607
|
+
"下面是需要继承到新会话里的历史文本。",
|
|
2608
|
+
"请把这些内容当作已经发生过的上下文事实,不要逐条复述,也不要把它们当成新的用户问题重新回答。",
|
|
2609
|
+
"后续我会在这条新分支里继续追加新的指令。",
|
|
2610
|
+
""
|
|
2611
|
+
];
|
|
2612
|
+
if (input.messages.length === 0) {
|
|
2613
|
+
lines.push("当前没有可继承的历史文本。");
|
|
2614
|
+
return lines.join("\n");
|
|
2615
|
+
}
|
|
2616
|
+
for (const message of input.messages) {
|
|
2617
|
+
lines.push(message.role === "user" ? "[用户]" : "[助手]");
|
|
2618
|
+
lines.push(message.content.trim());
|
|
2619
|
+
lines.push("");
|
|
2620
|
+
}
|
|
2621
|
+
return lines.join("\n").trim();
|
|
2622
|
+
}
|
|
1798
2623
|
//# sourceMappingURL=session-history-service.js.map
|