@jingyi0605/codingns 0.3.0 → 0.3.6
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 +13 -0
- package/bin/codingns.mjs +880 -9
- package/dist/public/assets/{TerminalPage-Dfw1QUqW.js → TerminalPage-D00S4KM6.js} +19 -19
- package/dist/public/assets/index-BlOinYqR.js +122 -0
- package/dist/public/assets/index-Dg_7g6lA.css +1 -0
- package/dist/public/index.html +2 -2
- package/dist/server/config/env.d.ts +2 -0
- package/dist/server/config/env.js +35 -0
- package/dist/server/config/env.js.map +1 -1
- package/dist/server/config/opencode-base-url-resolver.d.ts +7 -0
- package/dist/server/config/opencode-base-url-resolver.js +48 -11
- package/dist/server/config/opencode-base-url-resolver.js.map +1 -1
- package/dist/server/config/opencode-system-probe-helper-client.d.ts +4 -0
- package/dist/server/config/opencode-system-probe-helper-client.js +29 -0
- package/dist/server/config/opencode-system-probe-helper-client.js.map +1 -1
- package/dist/server/config/opencode-system-probe-helper-process.d.ts +12 -0
- package/dist/server/config/opencode-system-probe-helper-process.js +34 -7
- package/dist/server/config/opencode-system-probe-helper-process.js.map +1 -1
- package/dist/server/modules/assistant-capability/assistant-capability-controller.d.ts +144 -0
- package/dist/server/modules/assistant-capability/assistant-capability-controller.js +242 -1
- package/dist/server/modules/assistant-capability/assistant-capability-controller.js.map +1 -1
- package/dist/server/modules/assistant-capability/assistant-capability-service.d.ts +133 -2
- package/dist/server/modules/assistant-capability/assistant-capability-service.js +395 -2
- package/dist/server/modules/assistant-capability/assistant-capability-service.js.map +1 -1
- package/dist/server/modules/auth/auth-service.d.ts +18 -1
- package/dist/server/modules/auth/auth-service.js +168 -7
- package/dist/server/modules/auth/auth-service.js.map +1 -1
- package/dist/server/modules/butler/butler-codex-model-policy.d.ts +1 -0
- package/dist/server/modules/butler/butler-codex-model-policy.js +36 -0
- package/dist/server/modules/butler/butler-codex-model-policy.js.map +1 -0
- package/dist/server/modules/butler/butler-control-session-service.d.ts +13 -1
- package/dist/server/modules/butler/butler-control-session-service.js +55 -231
- package/dist/server/modules/butler/butler-control-session-service.js.map +1 -1
- package/dist/server/modules/butler/butler-controller.d.ts +17 -0
- package/dist/server/modules/butler/butler-controller.js +20 -1
- package/dist/server/modules/butler/butler-controller.js.map +1 -1
- package/dist/server/modules/butler/butler-follow-up-service.js +7 -3
- package/dist/server/modules/butler/butler-follow-up-service.js.map +1 -1
- package/dist/server/modules/butler/butler-inbox-analysis-service.d.ts +36 -0
- package/dist/server/modules/butler/butler-inbox-analysis-service.js +375 -0
- package/dist/server/modules/butler/butler-inbox-analysis-service.js.map +1 -0
- package/dist/server/modules/butler/butler-inbox-instruction-adapter.d.ts +23 -0
- package/dist/server/modules/butler/butler-inbox-instruction-adapter.js +97 -0
- package/dist/server/modules/butler/butler-inbox-instruction-adapter.js.map +1 -0
- package/dist/server/modules/butler/butler-inbox-service.d.ts +39 -2
- package/dist/server/modules/butler/butler-inbox-service.js +392 -2
- package/dist/server/modules/butler/butler-inbox-service.js.map +1 -1
- package/dist/server/modules/butler/butler-session-service.d.ts +8 -0
- package/dist/server/modules/butler/butler-session-service.js +205 -53
- package/dist/server/modules/butler/butler-session-service.js.map +1 -1
- package/dist/server/modules/butler/butler-session-summary-service.d.ts +1 -0
- package/dist/server/modules/butler/butler-session-summary-service.js +48 -23
- package/dist/server/modules/butler/butler-session-summary-service.js.map +1 -1
- package/dist/server/modules/butler/butler-workspace-context.d.ts +13 -0
- package/dist/server/modules/butler/butler-workspace-context.js +234 -0
- package/dist/server/modules/butler/butler-workspace-context.js.map +1 -0
- package/dist/server/modules/client/client-controller.d.ts +6 -0
- package/dist/server/modules/client/client-controller.js +30 -8
- package/dist/server/modules/client/client-controller.js.map +1 -1
- package/dist/server/modules/client/client-service.d.ts +22 -10
- package/dist/server/modules/client/client-service.js +77 -100
- package/dist/server/modules/client/client-service.js.map +1 -1
- package/dist/server/modules/client/npm-global-package-service.d.ts +21 -0
- package/dist/server/modules/client/npm-global-package-service.js +210 -0
- package/dist/server/modules/client/npm-global-package-service.js.map +1 -0
- package/dist/server/modules/client/service-update-task-service.d.ts +15 -0
- package/dist/server/modules/client/service-update-task-service.js +147 -0
- package/dist/server/modules/client/service-update-task-service.js.map +1 -0
- package/dist/server/modules/client/service-update-types.d.ts +30 -0
- package/dist/server/modules/client/service-update-types.js +2 -0
- package/dist/server/modules/client/service-update-types.js.map +1 -0
- package/dist/server/modules/debug-target/debug-target-controller.d.ts +13 -0
- package/dist/server/modules/debug-target/debug-target-controller.js +77 -2
- package/dist/server/modules/debug-target/debug-target-controller.js.map +1 -1
- package/dist/server/modules/debug-target/debug-target-service.d.ts +11 -2
- package/dist/server/modules/debug-target/debug-target-service.js +138 -3
- package/dist/server/modules/debug-target/debug-target-service.js.map +1 -1
- package/dist/server/modules/git/git-command-helper-client.d.ts +2 -0
- package/dist/server/modules/git/git-command-helper-client.js +52 -3
- package/dist/server/modules/git/git-command-helper-client.js.map +1 -1
- package/dist/server/modules/git/git-command-helper-process.js +62 -9
- package/dist/server/modules/git/git-command-helper-process.js.map +1 -1
- package/dist/server/modules/git/git-command-runner.d.ts +1 -0
- package/dist/server/modules/git/git-command-runner.js +25 -0
- package/dist/server/modules/git/git-command-runner.js.map +1 -1
- package/dist/server/modules/git/git-controller.js +8 -7
- package/dist/server/modules/git/git-controller.js.map +1 -1
- package/dist/server/modules/git/git-read-service.d.ts +7 -7
- package/dist/server/modules/git/git-read-service.js +41 -24
- package/dist/server/modules/git/git-read-service.js.map +1 -1
- package/dist/server/modules/model-switch/cc-switch-adapter.d.ts +36 -0
- package/dist/server/modules/model-switch/cc-switch-adapter.js +321 -0
- package/dist/server/modules/model-switch/cc-switch-adapter.js.map +1 -0
- package/dist/server/modules/model-switch/model-switch-controller.d.ts +11 -0
- package/dist/server/modules/model-switch/model-switch-controller.js +30 -0
- package/dist/server/modules/model-switch/model-switch-controller.js.map +1 -0
- package/dist/server/modules/model-switch/model-switch-service.d.ts +16 -0
- package/dist/server/modules/model-switch/model-switch-service.js +29 -0
- package/dist/server/modules/model-switch/model-switch-service.js.map +1 -0
- package/dist/server/modules/preferences/profile-service.d.ts +1 -0
- package/dist/server/modules/preferences/profile-service.js +9 -0
- package/dist/server/modules/preferences/profile-service.js.map +1 -1
- package/dist/server/modules/provider/codex-model-options.js +2 -3
- package/dist/server/modules/provider/codex-model-options.js.map +1 -1
- package/dist/server/modules/provider/opencode-model-options.js +2 -3
- package/dist/server/modules/provider/opencode-model-options.js.map +1 -1
- package/dist/server/modules/provider/provider-discovery-helper-client.d.ts +9 -4
- package/dist/server/modules/provider/provider-discovery-helper-client.js +87 -11
- package/dist/server/modules/provider/provider-discovery-helper-client.js.map +1 -1
- package/dist/server/modules/provider/provider-discovery-helper-process.js +52 -47
- package/dist/server/modules/provider/provider-discovery-helper-process.js.map +1 -1
- package/dist/server/modules/provider/provider-discovery-runtime.d.ts +4 -0
- package/dist/server/modules/provider/provider-discovery-runtime.js +65 -0
- package/dist/server/modules/provider/provider-discovery-runtime.js.map +1 -0
- package/dist/server/modules/sessions/codex-app-server-helper-process.js +3 -0
- package/dist/server/modules/sessions/codex-app-server-helper-process.js.map +1 -1
- package/dist/server/modules/sessions/session-activity-authority-service.d.ts +3 -1
- package/dist/server/modules/sessions/session-activity-authority-service.js +3 -0
- package/dist/server/modules/sessions/session-activity-authority-service.js.map +1 -1
- package/dist/server/modules/sessions/session-activity-inspector.d.ts +1 -1
- package/dist/server/modules/sessions/session-activity-inspector.js +43 -16
- package/dist/server/modules/sessions/session-activity-inspector.js.map +1 -1
- package/dist/server/modules/sessions/session-controller.d.ts +3 -3
- package/dist/server/modules/sessions/session-controller.js +3 -3
- package/dist/server/modules/sessions/session-controller.js.map +1 -1
- package/dist/server/modules/sessions/session-history-service.d.ts +8 -2
- package/dist/server/modules/sessions/session-history-service.js +473 -65
- package/dist/server/modules/sessions/session-history-service.js.map +1 -1
- package/dist/server/modules/sessions/session-live-runtime-service.d.ts +9 -3
- package/dist/server/modules/sessions/session-live-runtime-service.js +114 -34
- package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
- package/dist/server/modules/sessions/session-message-attachment-service.d.ts +8 -8
- package/dist/server/modules/sessions/session-message-attachment-service.js +25 -34
- package/dist/server/modules/sessions/session-message-attachment-service.js.map +1 -1
- package/dist/server/modules/sessions/session-provider-error-mapper.js +7 -0
- package/dist/server/modules/sessions/session-provider-error-mapper.js.map +1 -1
- package/dist/server/modules/skills/builtin-skill-service.d.ts +12 -0
- package/dist/server/modules/skills/builtin-skill-service.js +49 -0
- package/dist/server/modules/skills/builtin-skill-service.js.map +1 -0
- package/dist/server/modules/skills/builtin-skills/codingns-assistant/SKILL.md +75 -0
- package/dist/server/modules/skills/builtin-skills/codingns-assistant/agents/openai.yaml +4 -0
- package/dist/server/modules/skills/builtin-skills/codingns-assistant/references/cli-workflow.md +130 -0
- package/dist/server/modules/skills/skill-manager-service.d.ts +7 -0
- package/dist/server/modules/skills/skill-manager-service.js +98 -0
- package/dist/server/modules/skills/skill-manager-service.js.map +1 -1
- package/dist/server/modules/tailscale/tailscale-helper-client.d.ts +1 -0
- package/dist/server/modules/tailscale/tailscale-helper-client.js +12 -0
- package/dist/server/modules/tailscale/tailscale-helper-client.js.map +1 -1
- package/dist/server/modules/tailscale/tailscale-manager.js +5 -1
- package/dist/server/modules/tailscale/tailscale-manager.js.map +1 -1
- package/dist/server/modules/tasks/task-helper-client.d.ts +5 -0
- package/dist/server/modules/tasks/task-helper-client.js +45 -0
- package/dist/server/modules/tasks/task-helper-client.js.map +1 -1
- package/dist/server/modules/tasks/task-helper-process-handlers.d.ts +10 -3
- package/dist/server/modules/tasks/task-helper-process-handlers.js +7 -5
- package/dist/server/modules/tasks/task-helper-process-handlers.js.map +1 -1
- package/dist/server/modules/tasks/task-helper-process.js +11 -1
- package/dist/server/modules/tasks/task-helper-process.js.map +1 -1
- package/dist/server/modules/tasks/task-lane-executors.js +5 -2
- package/dist/server/modules/tasks/task-lane-executors.js.map +1 -1
- package/dist/server/modules/tasks/task-types.d.ts +3 -0
- package/dist/server/modules/tasks/task-types.js +4 -1
- package/dist/server/modules/tasks/task-types.js.map +1 -1
- package/dist/server/modules/terminal/command-template-service.d.ts +2 -2
- package/dist/server/modules/terminal/command-template-service.js +14 -5
- package/dist/server/modules/terminal/command-template-service.js.map +1 -1
- package/dist/server/modules/terminal/runtime/terminal-log-writer-client.js +1 -1
- package/dist/server/modules/terminal/runtime/terminal-log-writer-client.js.map +1 -1
- package/dist/server/modules/terminal/runtime/terminal-log-writer-process.js +160 -11
- package/dist/server/modules/terminal/runtime/terminal-log-writer-process.js.map +1 -1
- package/dist/server/modules/terminal/template-port-runtime.d.ts +13 -2
- package/dist/server/modules/terminal/template-port-runtime.js +266 -44
- package/dist/server/modules/terminal/template-port-runtime.js.map +1 -1
- package/dist/server/modules/terminal/terminal-service.d.ts +4 -0
- package/dist/server/modules/terminal/terminal-service.js +65 -4
- package/dist/server/modules/terminal/terminal-service.js.map +1 -1
- package/dist/server/modules/workbench/workbench-service.js +3 -3
- package/dist/server/modules/workbench/workbench-service.js.map +1 -1
- package/dist/server/modules/workbench/workspace-panel-snapshot-service.d.ts +1 -0
- package/dist/server/modules/workbench/workspace-panel-snapshot-service.js +118 -39
- package/dist/server/modules/workbench/workspace-panel-snapshot-service.js.map +1 -1
- package/dist/server/modules/workspace/workspace-code-composition.d.ts +1 -0
- package/dist/server/modules/workspace/workspace-code-composition.js +277 -2
- package/dist/server/modules/workspace/workspace-code-composition.js.map +1 -1
- package/dist/server/modules/workspace/workspace-service.js +54 -17
- package/dist/server/modules/workspace/workspace-service.js.map +1 -1
- package/dist/server/modules/worktree/worktree-cleanup-service.d.ts +1 -1
- package/dist/server/modules/worktree/worktree-cleanup-service.js +22 -17
- package/dist/server/modules/worktree/worktree-cleanup-service.js.map +1 -1
- package/dist/server/modules/worktree/worktree-controller.js +6 -5
- package/dist/server/modules/worktree/worktree-controller.js.map +1 -1
- package/dist/server/modules/worktree/worktree-manager.d.ts +1 -1
- package/dist/server/modules/worktree/worktree-manager.js +26 -19
- package/dist/server/modules/worktree/worktree-manager.js.map +1 -1
- package/dist/server/modules/worktree/worktree-merge-service.d.ts +2 -2
- package/dist/server/modules/worktree/worktree-merge-service.js +34 -27
- package/dist/server/modules/worktree/worktree-merge-service.js.map +1 -1
- package/dist/server/modules/worktree/worktree-sync-service.d.ts +1 -1
- package/dist/server/modules/worktree/worktree-sync-service.js +5 -3
- package/dist/server/modules/worktree/worktree-sync-service.js.map +1 -1
- package/dist/server/routes/assistant.js +24 -0
- package/dist/server/routes/assistant.js.map +1 -1
- package/dist/server/routes/butler.js +4 -0
- package/dist/server/routes/butler.js.map +1 -1
- package/dist/server/routes/client.js +2 -0
- package/dist/server/routes/client.js.map +1 -1
- package/dist/server/routes/sessions.js +1 -1
- package/dist/server/routes/sessions.js.map +1 -1
- package/dist/server/routes/system.d.ts +2 -1
- package/dist/server/routes/system.js +3 -1
- package/dist/server/routes/system.js.map +1 -1
- package/dist/server/server/create-server.d.ts +4 -0
- package/dist/server/server/create-server.js +67 -8
- package/dist/server/server/create-server.js.map +1 -1
- package/dist/server/shared/errors/app-error.d.ts +2 -0
- package/dist/server/shared/errors/app-error.js +2 -0
- package/dist/server/shared/errors/app-error.js.map +1 -1
- package/dist/server/shared/http/error-handler.d.ts +2 -1
- package/dist/server/shared/http/error-handler.js +3 -2
- package/dist/server/shared/http/error-handler.js.map +1 -1
- package/dist/server/shared/http/request-abort.d.ts +2 -0
- package/dist/server/shared/http/request-abort.js +38 -0
- package/dist/server/shared/http/request-abort.js.map +1 -0
- package/dist/server/storage/repositories/auth-login-attempt-repository.d.ts +9 -0
- package/dist/server/storage/repositories/auth-login-attempt-repository.js +59 -0
- package/dist/server/storage/repositories/auth-login-attempt-repository.js.map +1 -0
- package/dist/server/storage/repositories/butler-control-session-repository.d.ts +3 -0
- package/dist/server/storage/repositories/butler-control-session-repository.js +80 -4
- package/dist/server/storage/repositories/butler-control-session-repository.js.map +1 -1
- package/dist/server/storage/repositories/butler-inbox-item-repository.js +54 -3
- package/dist/server/storage/repositories/butler-inbox-item-repository.js.map +1 -1
- package/dist/server/storage/repositories/terminal-instance-repository.js +1 -1
- package/dist/server/storage/repositories/terminal-instance-repository.js.map +1 -1
- package/dist/server/storage/repositories/user-preference-profile-repository.js +6 -3
- package/dist/server/storage/repositories/user-preference-profile-repository.js.map +1 -1
- package/dist/server/storage/sqlite/client.js +127 -0
- package/dist/server/storage/sqlite/client.js.map +1 -1
- package/dist/server/storage/sqlite/schema.sql +19 -1
- package/dist/server/types/domain.d.ts +37 -1
- package/dist/server/ws/workbench-ws-hub.d.ts +1 -0
- package/dist/server/ws/workbench-ws-hub.js +25 -3
- package/dist/server/ws/workbench-ws-hub.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/patch-builder.d.ts +23 -0
- package/node_modules/@codingns/session-sync-core/dist/patch-builder.js +162 -0
- package/node_modules/@codingns/session-sync-core/dist/patch-builder.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.d.ts +1 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +89 -33
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js +18 -2
- package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.d.ts +3 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +238 -53
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/provider-runtime-service.js +1 -0
- package/node_modules/@codingns/session-sync-core/dist/runtime/provider-runtime-service.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/types.d.ts +6 -2
- package/node_modules/@codingns/session-sync-core/dist/types.d.ts +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.mjs +33 -0
- package/dist/public/assets/index-DR2rPNi7.css +0 -1
- package/dist/public/assets/index-DTOruahn.js +0 -114
|
@@ -14,13 +14,15 @@ import { SessionForkRepository } from "../../storage/repositories/session-fork-r
|
|
|
14
14
|
import { enrichClaudeCapabilities } from "../provider/claude-model-options.js";
|
|
15
15
|
import { CodexModelOptionsService, createFallbackCodexModelOptions, enrichCodexCapabilities } from "../provider/codex-model-options.js";
|
|
16
16
|
import { OpenCodeModelOptionsService, createFallbackOpenCodeModelOptions, enrichOpenCodeCapabilities } from "../provider/opencode-model-options.js";
|
|
17
|
-
import {
|
|
17
|
+
import { getSharedProviderDiscoveryHelperClient } from "../provider/provider-discovery-helper-client.js";
|
|
18
|
+
import { discoverWorkspaceSessionsInRuntime } from "../provider/provider-discovery-runtime.js";
|
|
18
19
|
import { createTaskManager } from "../tasks/task-manager.js";
|
|
19
20
|
import { HOST_TASK_TYPES } from "../tasks/task-types.js";
|
|
20
21
|
import { CodexAppServerHelperClient } from "./codex-app-server-helper-client.js";
|
|
21
22
|
const RECONSTRUCTED_FORK_TARGET_PROVIDERS = new Set(["codex", "claude-code", "opencode"]);
|
|
22
23
|
const FORK_RECONSTRUCTION_PAGE_SIZE = 200;
|
|
23
24
|
const MAX_FORK_DEPTH = 4;
|
|
25
|
+
const SYNTHETIC_CODEX_SESSION_CLEANUP_GRACE_MS = 120_000;
|
|
24
26
|
const SESSION_START_DEFERRED_PROVIDERS = new Set([
|
|
25
27
|
"codex",
|
|
26
28
|
"claude-code",
|
|
@@ -32,6 +34,7 @@ const MUTABLE_HISTORY_TAIL_REFRESH_INTERVAL_MS = 1_200;
|
|
|
32
34
|
const WORKSPACE_DISCOVERY_BACKGROUND_MAX_AGE_MS = 15_000;
|
|
33
35
|
const PROVIDER_CAPABILITY_CACHE_MAX_AGE_MS = 5_000;
|
|
34
36
|
const WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE = 25;
|
|
37
|
+
const SESSION_TRANSACTION_HOTSPOT_THRESHOLD_MS = 150;
|
|
35
38
|
export class SessionHistoryService {
|
|
36
39
|
db;
|
|
37
40
|
workspaceRepository;
|
|
@@ -52,7 +55,7 @@ export class SessionHistoryService {
|
|
|
52
55
|
openCodeModelOptionsService;
|
|
53
56
|
providerCliCommandPaths;
|
|
54
57
|
providerCliAvailability;
|
|
55
|
-
providerDiscoveryHelperClient =
|
|
58
|
+
providerDiscoveryHelperClient = getSharedProviderDiscoveryHelperClient();
|
|
56
59
|
providerSessionDiscoveryConfig;
|
|
57
60
|
taskManager;
|
|
58
61
|
workspaceDiscoveryStatuses = new Map();
|
|
@@ -148,8 +151,16 @@ export class SessionHistoryService {
|
|
|
148
151
|
if (!this.taskManager.has(HOST_TASK_TYPES.workspaceDiscovery)) {
|
|
149
152
|
this.taskManager.register({
|
|
150
153
|
taskType: HOST_TASK_TYPES.workspaceDiscovery,
|
|
154
|
+
executionLane: "host_background",
|
|
155
|
+
run: async ({ workspaceId, userId, refreshStateMode }, context) => this.runDiscoverWorkspaceSessions(workspaceId, userId, refreshStateMode, context.signal)
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (!this.taskManager.has(HOST_TASK_TYPES.workspaceDiscoveryScan)) {
|
|
159
|
+
this.taskManager.register({
|
|
160
|
+
taskType: HOST_TASK_TYPES.workspaceDiscoveryScan,
|
|
151
161
|
executionLane: "helper_process",
|
|
152
|
-
|
|
162
|
+
helperProcessHandler: "session.workspace_discovery",
|
|
163
|
+
run: async ({ config, workspacePath, knownSessions }, context) => await discoverWorkspaceSessionsInRuntime(config, workspacePath, knownSessions, context.signal)
|
|
153
164
|
});
|
|
154
165
|
}
|
|
155
166
|
if (!this.taskManager.has(HOST_TASK_TYPES.providerCapabilityRefresh)) {
|
|
@@ -233,7 +244,10 @@ export class SessionHistoryService {
|
|
|
233
244
|
async readSessionHistory(sessionId, cursor, limit, direction = "forward", userId) {
|
|
234
245
|
const startedAt = Date.now();
|
|
235
246
|
const resolvedSessionId = this.resolveCanonicalSessionId(sessionId, userId);
|
|
236
|
-
|
|
247
|
+
let binding = this.getBindingOrThrow(resolvedSessionId);
|
|
248
|
+
if (userId) {
|
|
249
|
+
binding = await this.repairCodexDirtyBindingBeforeHistoryRead(resolvedSessionId, userId, binding);
|
|
250
|
+
}
|
|
237
251
|
const current = this.sessionStatusSnapshotRepository.findBySessionId(resolvedSessionId);
|
|
238
252
|
const safeLimit = clampLimit(limit);
|
|
239
253
|
const knownTotalMessageCount = direction === "backward" && cursor === null
|
|
@@ -335,17 +349,17 @@ export class SessionHistoryService {
|
|
|
335
349
|
await this.refreshSessionState(sessionId, userId);
|
|
336
350
|
return this.enrichSessionItem(this.getSessionListItemOrThrow(sessionId, userId));
|
|
337
351
|
}
|
|
338
|
-
async syncSessionTitle(sessionId) {
|
|
352
|
+
async syncSessionTitle(sessionId, signal) {
|
|
339
353
|
const binding = this.getBindingOrThrow(sessionId);
|
|
340
|
-
await this.syncSessionTitleFromProvider(sessionId, binding);
|
|
354
|
+
await this.syncSessionTitleFromProvider(sessionId, binding, signal);
|
|
341
355
|
}
|
|
342
|
-
async syncWorkspaceSessionTitles(workspaceId, userId, concurrency = 1) {
|
|
356
|
+
async syncWorkspaceSessionTitles(workspaceId, userId, concurrency = 1, signal) {
|
|
343
357
|
const sessions = this.sessionIndexRepository.listByWorkspace(workspaceId, userId);
|
|
344
358
|
await runWithConcurrency(sessions, concurrency, async (session) => {
|
|
345
|
-
await this.syncSessionTitle(session.sessionId).catch(() => {
|
|
359
|
+
await this.syncSessionTitle(session.sessionId, signal).catch(() => {
|
|
346
360
|
return;
|
|
347
361
|
});
|
|
348
|
-
});
|
|
362
|
+
}, signal);
|
|
349
363
|
}
|
|
350
364
|
async listSessionChangedFiles(sessionId, userId) {
|
|
351
365
|
this.getSession(sessionId, userId);
|
|
@@ -1090,41 +1104,54 @@ export class SessionHistoryService {
|
|
|
1090
1104
|
providerSessionId: snapshot.providerSessionId,
|
|
1091
1105
|
rawStoreRef: snapshot.rawStoreRef
|
|
1092
1106
|
});
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
+
// discovery 和 runtime 回填会并发命中这里;如果在事务外先看重复,再事务内写入,
|
|
1108
|
+
// 中间就会留下一个竞态窗口,最后直接撞 UNIQUE(provider, provider_session_id)。
|
|
1109
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
1110
|
+
try {
|
|
1111
|
+
this.db.transaction(() => {
|
|
1112
|
+
const currentBinding = this.sessionBindingRepository.findBySessionId(sessionId);
|
|
1113
|
+
const timestamp = nowIso();
|
|
1114
|
+
const duplicateBinding = this.findSameWorkspaceBindingDuplicate(sessionId, workspaceId, resolvedSnapshot);
|
|
1115
|
+
if (duplicateBinding) {
|
|
1116
|
+
// 运行时链路显式指定了当前 sessionId,就应该由当前会话接管同工作区里的重复底层会话。
|
|
1117
|
+
// 否则后续事件重放或后台发现补录都会持续撞 UNIQUE(provider, provider_session_id)。
|
|
1118
|
+
this.mergeSessionIntoTarget({
|
|
1119
|
+
workspaceId,
|
|
1120
|
+
targetSessionId: sessionId,
|
|
1121
|
+
sourceSessionId: duplicateBinding.sessionId,
|
|
1122
|
+
provider: resolvedSnapshot.provider,
|
|
1123
|
+
timestamp
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
const currentIndex = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
|
|
1127
|
+
this.sessionBindingRepository.upsert({
|
|
1128
|
+
sessionId,
|
|
1129
|
+
workspaceId,
|
|
1130
|
+
provider: resolvedSnapshot.provider,
|
|
1131
|
+
providerSessionId: resolvedSnapshot.providerSessionId,
|
|
1132
|
+
rawStoreRef: resolvedSnapshot.rawStoreRef,
|
|
1133
|
+
createdAt: pickEarlierIso(currentBinding?.createdAt ?? null, duplicateBinding?.createdAt ?? null)
|
|
1134
|
+
?? timestamp,
|
|
1135
|
+
updatedAt: timestamp
|
|
1136
|
+
});
|
|
1137
|
+
if (currentIndex) {
|
|
1138
|
+
this.sessionIndexRepository.upsert({
|
|
1139
|
+
...currentIndex,
|
|
1140
|
+
updatedAt: timestamp
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
})();
|
|
1144
|
+
return;
|
|
1107
1145
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
providerSessionId: resolvedSnapshot.providerSessionId,
|
|
1114
|
-
rawStoreRef: resolvedSnapshot.rawStoreRef,
|
|
1115
|
-
createdAt: pickEarlierIso(currentBinding?.createdAt ?? null, duplicateBinding?.createdAt ?? null)
|
|
1116
|
-
?? timestamp,
|
|
1117
|
-
updatedAt: timestamp
|
|
1118
|
-
});
|
|
1119
|
-
if (currentIndex) {
|
|
1120
|
-
this.sessionIndexRepository.upsert({
|
|
1121
|
-
...currentIndex,
|
|
1122
|
-
updatedAt: timestamp
|
|
1123
|
-
});
|
|
1146
|
+
catch (error) {
|
|
1147
|
+
if (attempt === 0 && isSessionBindingProviderUniqueConflict(error)) {
|
|
1148
|
+
continue;
|
|
1149
|
+
}
|
|
1150
|
+
throw error;
|
|
1124
1151
|
}
|
|
1125
|
-
}
|
|
1152
|
+
}
|
|
1126
1153
|
}
|
|
1127
|
-
async runDiscoverWorkspaceSessions(workspaceId, userId, refreshStateMode = "inline") {
|
|
1154
|
+
async runDiscoverWorkspaceSessions(workspaceId, userId, refreshStateMode = "inline", signal) {
|
|
1128
1155
|
const startedAt = Date.now();
|
|
1129
1156
|
const debugStartedAtMs = terminalDebugNowMs();
|
|
1130
1157
|
const workspace = this.getWorkspaceOrThrow(workspaceId);
|
|
@@ -1145,13 +1172,16 @@ export class SessionHistoryService {
|
|
|
1145
1172
|
const discoverStartedAt = Date.now();
|
|
1146
1173
|
const existingWorkspaceSessions = this.sessionIndexRepository.listByWorkspace(workspaceId, userId);
|
|
1147
1174
|
const knownSessions = this.buildKnownSessionSummaries(existingWorkspaceSessions, workspace.path);
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1175
|
+
const discoveryHandle = this.taskManager.enqueue(HOST_TASK_TYPES.workspaceDiscoveryScan, {
|
|
1176
|
+
key: workspaceId,
|
|
1177
|
+
source: "session_history.workspace_discovery.scan",
|
|
1178
|
+
input: {
|
|
1179
|
+
config: this.providerSessionDiscoveryConfig,
|
|
1180
|
+
workspacePath: workspace.path,
|
|
1181
|
+
knownSessions
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
const discovery = await awaitTaskHandleWithSignal(discoveryHandle, signal).catch((error) => {
|
|
1155
1185
|
throw mapSessionProviderError(error);
|
|
1156
1186
|
});
|
|
1157
1187
|
const sessions = discovery.sessions;
|
|
@@ -1162,7 +1192,9 @@ export class SessionHistoryService {
|
|
|
1162
1192
|
const claimedPendingSessionIds = new Set();
|
|
1163
1193
|
const persistPass1Transaction = this.db.transaction((batch) => {
|
|
1164
1194
|
for (const session of batch) {
|
|
1165
|
-
const exactExisting = this.sessionBindingRepository.findByProviderSession(session.provider, session.providerSessionId) ??
|
|
1195
|
+
const exactExisting = this.sessionBindingRepository.findByProviderSession(session.provider, session.providerSessionId) ?? (shouldMatchSessionBindingByRawStoreRef(session.provider)
|
|
1196
|
+
? this.sessionBindingRepository.findByRawStoreRef(session.provider, session.rawStoreRef)
|
|
1197
|
+
: null);
|
|
1166
1198
|
if (exactExisting && exactExisting.workspaceId !== workspaceId) {
|
|
1167
1199
|
continue;
|
|
1168
1200
|
}
|
|
@@ -1236,7 +1268,15 @@ export class SessionHistoryService {
|
|
|
1236
1268
|
}
|
|
1237
1269
|
});
|
|
1238
1270
|
const persistPass1StartedAt = Date.now();
|
|
1239
|
-
const persistPass1Stats = await runBatchedTransactions(sessions, WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE, persistPass1Transaction
|
|
1271
|
+
const persistPass1Stats = await runBatchedTransactions(sessions, WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE, persistPass1Transaction, {
|
|
1272
|
+
scope: "workspace.discover_sessions.persist_pass1.batch",
|
|
1273
|
+
thresholdMs: SESSION_TRANSACTION_HOTSPOT_THRESHOLD_MS,
|
|
1274
|
+
detail: {
|
|
1275
|
+
workspaceId,
|
|
1276
|
+
workspacePath: workspace.path,
|
|
1277
|
+
phase: "pass1"
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1240
1280
|
persistPass1DurationMs = Date.now() - persistPass1StartedAt;
|
|
1241
1281
|
persistPass1BatchCount = persistPass1Stats.batchCount;
|
|
1242
1282
|
persistPass1MaxBatchMs = persistPass1Stats.maxBatchMs;
|
|
@@ -1283,7 +1323,15 @@ export class SessionHistoryService {
|
|
|
1283
1323
|
}
|
|
1284
1324
|
});
|
|
1285
1325
|
const persistPass2StartedAt = Date.now();
|
|
1286
|
-
const persistPass2Stats = await runBatchedTransactions(persistedSessions, WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE, persistPass2Transaction
|
|
1326
|
+
const persistPass2Stats = await runBatchedTransactions(persistedSessions, WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE, persistPass2Transaction, {
|
|
1327
|
+
scope: "workspace.discover_sessions.persist_pass2.batch",
|
|
1328
|
+
thresholdMs: SESSION_TRANSACTION_HOTSPOT_THRESHOLD_MS,
|
|
1329
|
+
detail: {
|
|
1330
|
+
workspaceId,
|
|
1331
|
+
workspacePath: workspace.path,
|
|
1332
|
+
phase: "pass2"
|
|
1333
|
+
}
|
|
1334
|
+
});
|
|
1287
1335
|
persistPass2DurationMs = Date.now() - persistPass2StartedAt;
|
|
1288
1336
|
persistPass2BatchCount = persistPass2Stats.batchCount;
|
|
1289
1337
|
persistPass2MaxBatchMs = persistPass2Stats.maxBatchMs;
|
|
@@ -1405,12 +1453,13 @@ export class SessionHistoryService {
|
|
|
1405
1453
|
? this.sessionSyncService.readRecentHistory(provider, providerSessionId, rawStoreRef, knownTotalMessageCount, limit)
|
|
1406
1454
|
: this.sessionSyncService.readHistory(provider, providerSessionId, rawStoreRef, cursor, limit, direction);
|
|
1407
1455
|
return historyTask
|
|
1408
|
-
.then((page) => {
|
|
1409
|
-
const
|
|
1456
|
+
.then(async (page) => {
|
|
1457
|
+
const sanitizedPage = await this.sanitizeForkHistoryPage(sessionId, page, cursor, direction);
|
|
1458
|
+
const messagesWithAttachments = this.sessionMessageAttachmentService.enrichMessages(sessionId, sanitizedPage.messages);
|
|
1410
1459
|
const messages = this.enrichMessagesWithOrigin(sessionId, messagesWithAttachments);
|
|
1411
1460
|
this.persistSessionChangedFiles(sessionId, messages);
|
|
1412
1461
|
return {
|
|
1413
|
-
...
|
|
1462
|
+
...sanitizedPage,
|
|
1414
1463
|
messages
|
|
1415
1464
|
};
|
|
1416
1465
|
})
|
|
@@ -1429,6 +1478,56 @@ export class SessionHistoryService {
|
|
|
1429
1478
|
enrichMessagesWithOrigin(sessionId, messages) {
|
|
1430
1479
|
return this.resolveMessageOrigins(sessionId, messages);
|
|
1431
1480
|
}
|
|
1481
|
+
async sanitizeForkHistoryPage(sessionId, page, cursor, direction) {
|
|
1482
|
+
if (direction !== "forward" || cursor !== null || page.messages.length === 0) {
|
|
1483
|
+
return page;
|
|
1484
|
+
}
|
|
1485
|
+
const forkRecord = this.sessionForkRepository.findBySessionId(sessionId);
|
|
1486
|
+
if (!forkRecord
|
|
1487
|
+
|| forkRecord.forkSourceType !== "message"
|
|
1488
|
+
|| !forkRecord.forkSourceMessageId) {
|
|
1489
|
+
return page;
|
|
1490
|
+
}
|
|
1491
|
+
const childSession = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
|
|
1492
|
+
const childCreatedAt = childSession?.createdAt?.trim() || null;
|
|
1493
|
+
if (!childCreatedAt) {
|
|
1494
|
+
return page;
|
|
1495
|
+
}
|
|
1496
|
+
const parentBinding = this.getBindingOrThrow(forkRecord.forkSourceSessionId);
|
|
1497
|
+
const inheritedMessages = await this.readForkSourceMessages(forkRecord.forkSourceSessionId, parentBinding, "message", forkRecord.forkSourceMessageId, null);
|
|
1498
|
+
const expectedInheritedCount = inheritedMessages.length;
|
|
1499
|
+
if (expectedInheritedCount <= 0) {
|
|
1500
|
+
return page;
|
|
1501
|
+
}
|
|
1502
|
+
const parentMessages = await this.readForkSourceMessages(forkRecord.forkSourceSessionId, parentBinding, "session", null, null);
|
|
1503
|
+
let leakedInheritedCount = countCommonHistoryPrefixLength(page.messages.slice(expectedInheritedCount), parentMessages.slice(expectedInheritedCount));
|
|
1504
|
+
if (leakedInheritedCount <= 0) {
|
|
1505
|
+
for (let index = expectedInheritedCount; index < page.messages.length; index += 1) {
|
|
1506
|
+
const message = page.messages[index];
|
|
1507
|
+
if (!message || message.timestamp > childCreatedAt) {
|
|
1508
|
+
break;
|
|
1509
|
+
}
|
|
1510
|
+
leakedInheritedCount += 1;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
if (forkRecord.inheritedPrefixMessageCount !== expectedInheritedCount) {
|
|
1514
|
+
this.sessionForkRepository.upsert({
|
|
1515
|
+
...forkRecord,
|
|
1516
|
+
inheritedPrefixMessageCount: expectedInheritedCount
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
if (leakedInheritedCount <= 0) {
|
|
1520
|
+
return page;
|
|
1521
|
+
}
|
|
1522
|
+
return {
|
|
1523
|
+
...page,
|
|
1524
|
+
messages: [
|
|
1525
|
+
...page.messages.slice(0, expectedInheritedCount),
|
|
1526
|
+
...page.messages.slice(expectedInheritedCount + leakedInheritedCount)
|
|
1527
|
+
],
|
|
1528
|
+
total: Math.max(0, page.total - leakedInheritedCount)
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1432
1531
|
resolveMessageOrigins(sessionId, messages) {
|
|
1433
1532
|
const originRepository = this.sessionMessageOriginRepository;
|
|
1434
1533
|
if (!originRepository || messages.length === 0) {
|
|
@@ -1611,7 +1710,7 @@ export class SessionHistoryService {
|
|
|
1611
1710
|
messages
|
|
1612
1711
|
});
|
|
1613
1712
|
}
|
|
1614
|
-
async syncSessionTitleFromProvider(sessionId, binding) {
|
|
1713
|
+
async syncSessionTitleFromProvider(sessionId, binding, signal) {
|
|
1615
1714
|
const currentIndex = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
|
|
1616
1715
|
if (!currentIndex) {
|
|
1617
1716
|
return;
|
|
@@ -1624,7 +1723,7 @@ export class SessionHistoryService {
|
|
|
1624
1723
|
provider: binding.provider,
|
|
1625
1724
|
providerSessionId: binding.providerSessionId,
|
|
1626
1725
|
rawStoreRef: binding.rawStoreRef
|
|
1627
|
-
})).trim();
|
|
1726
|
+
}, signal)).trim();
|
|
1628
1727
|
const resolvedTitle = resolvePersistedSessionTitle(binding.provider, nextTitle, currentIndex.title);
|
|
1629
1728
|
if (resolvedTitle.length === 0 || resolvedTitle === currentIndex.title) {
|
|
1630
1729
|
return;
|
|
@@ -1683,7 +1782,13 @@ export class SessionHistoryService {
|
|
|
1683
1782
|
return workspace;
|
|
1684
1783
|
}
|
|
1685
1784
|
getSessionListItemOrThrow(sessionId, userId) {
|
|
1686
|
-
const
|
|
1785
|
+
const canonicalSessionId = this.resolveCanonicalSessionId(sessionId, userId);
|
|
1786
|
+
const item = this.findSessionListItem(canonicalSessionId, sessionId, userId)
|
|
1787
|
+
?? this.repairMissingSessionListItem(canonicalSessionId, userId)
|
|
1788
|
+
?? (canonicalSessionId === sessionId
|
|
1789
|
+
? null
|
|
1790
|
+
: this.repairMissingSessionListItem(sessionId, userId))
|
|
1791
|
+
?? this.findSessionListItem(canonicalSessionId, sessionId, userId);
|
|
1687
1792
|
if (!item) {
|
|
1688
1793
|
throw new AppError({
|
|
1689
1794
|
statusCode: 500,
|
|
@@ -1697,6 +1802,75 @@ export class SessionHistoryService {
|
|
|
1697
1802
|
}
|
|
1698
1803
|
return this.sessionIndexRepository.findBySessionId(aliasTargetSessionId, userId) ?? item;
|
|
1699
1804
|
}
|
|
1805
|
+
findSessionListItem(canonicalSessionId, sessionId, userId) {
|
|
1806
|
+
return (this.sessionIndexRepository.findBySessionId(canonicalSessionId, userId)
|
|
1807
|
+
?? (canonicalSessionId === sessionId
|
|
1808
|
+
? null
|
|
1809
|
+
: this.sessionIndexRepository.findBySessionId(sessionId, userId)));
|
|
1810
|
+
}
|
|
1811
|
+
repairMissingSessionListItem(sessionId, userId) {
|
|
1812
|
+
const binding = this.sessionBindingRepository.findBySessionId(sessionId);
|
|
1813
|
+
if (!binding) {
|
|
1814
|
+
return null;
|
|
1815
|
+
}
|
|
1816
|
+
const existingIndex = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
|
|
1817
|
+
const existingSnapshot = this.sessionStatusSnapshotRepository.findBySessionId(sessionId);
|
|
1818
|
+
const existingState = this.sessionStateRepository.findBySessionAndUser(sessionId, userId);
|
|
1819
|
+
const timestamp = nowIso();
|
|
1820
|
+
const fallbackLastMessageAt = existingIndex?.lastMessageAt
|
|
1821
|
+
?? existingState?.lastEventAt
|
|
1822
|
+
?? existingSnapshot?.lastSyncAt
|
|
1823
|
+
?? null;
|
|
1824
|
+
const fallbackCreatedAt = pickEarlierIso(binding.createdAt, existingIndex?.createdAt ?? null)
|
|
1825
|
+
?? timestamp;
|
|
1826
|
+
this.db.transaction(() => {
|
|
1827
|
+
this.sessionIndexRepository.upsert({
|
|
1828
|
+
sessionId,
|
|
1829
|
+
workspaceId: binding.workspaceId,
|
|
1830
|
+
provider: binding.provider,
|
|
1831
|
+
parentSessionId: existingIndex?.parentSessionId ?? this.sessionForkRepository.findBySessionId(sessionId)?.parentSessionId ?? null,
|
|
1832
|
+
sessionKind: existingIndex?.sessionKind ?? "default",
|
|
1833
|
+
annotationSourceMessageId: existingIndex?.annotationSourceMessageId ?? null,
|
|
1834
|
+
annotationSourceText: existingIndex?.annotationSourceText ?? null,
|
|
1835
|
+
isSubagent: existingIndex?.isSubagent ?? false,
|
|
1836
|
+
subagentLabel: existingIndex?.subagentLabel ?? null,
|
|
1837
|
+
title: existingIndex?.title?.trim()
|
|
1838
|
+
|| buildRecoveredSessionTitle(binding.provider, binding.providerSessionId),
|
|
1839
|
+
messageCount: existingIndex?.messageCount ?? 0,
|
|
1840
|
+
isArchived: existingIndex?.isArchived ?? false,
|
|
1841
|
+
lastMessageAt: fallbackLastMessageAt,
|
|
1842
|
+
createdAt: fallbackCreatedAt,
|
|
1843
|
+
updatedAt: timestamp
|
|
1844
|
+
});
|
|
1845
|
+
if (!existingSnapshot) {
|
|
1846
|
+
this.sessionStatusSnapshotRepository.upsert({
|
|
1847
|
+
sessionId,
|
|
1848
|
+
syncStatus: "idle",
|
|
1849
|
+
syncCursor: null,
|
|
1850
|
+
lastSyncAt: fallbackLastMessageAt,
|
|
1851
|
+
lastErrorCode: null,
|
|
1852
|
+
lastErrorDetail: null,
|
|
1853
|
+
resumedAt: null,
|
|
1854
|
+
updatedAt: timestamp
|
|
1855
|
+
});
|
|
1856
|
+
}
|
|
1857
|
+
if (!existingState) {
|
|
1858
|
+
this.sessionStateRepository.upsert({
|
|
1859
|
+
sessionId,
|
|
1860
|
+
userId,
|
|
1861
|
+
runningState: inferRecoveredSessionRunningState(binding),
|
|
1862
|
+
activitySource: inferRecoveredSessionActivitySource(binding),
|
|
1863
|
+
favorite: false,
|
|
1864
|
+
lastEventAt: shouldRecoverSessionAsActive(binding) ? (binding.updatedAt || timestamp) : fallbackLastMessageAt,
|
|
1865
|
+
completedAt: null,
|
|
1866
|
+
lastSeenAt: null,
|
|
1867
|
+
updatedAt: timestamp
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1870
|
+
})();
|
|
1871
|
+
console.warn(`[session-history] repaired missing session index for ${sessionId} (${binding.provider})`);
|
|
1872
|
+
return this.sessionIndexRepository.findBySessionId(sessionId, userId);
|
|
1873
|
+
}
|
|
1700
1874
|
resolveCanonicalSessionId(sessionId, userId) {
|
|
1701
1875
|
if (userId) {
|
|
1702
1876
|
const item = this.sessionIndexRepository.findBySessionId(sessionId, userId);
|
|
@@ -1784,6 +1958,7 @@ export class SessionHistoryService {
|
|
|
1784
1958
|
async cleanupStaleHiddenSessions(workspaceId, userId, sessions) {
|
|
1785
1959
|
const discoveredProviderSessionIds = new Set(sessions.map((session) => buildProviderSessionKey(session.provider, session.providerSessionId)));
|
|
1786
1960
|
const discoveredRawStoreRefs = new Set(sessions.map((session) => session.rawStoreRef));
|
|
1961
|
+
const nowMs = Date.now();
|
|
1787
1962
|
const staleHiddenSessions = this.sessionIndexRepository
|
|
1788
1963
|
.listByWorkspace(workspaceId, userId)
|
|
1789
1964
|
.filter((session) => {
|
|
@@ -1795,10 +1970,13 @@ export class SessionHistoryService {
|
|
|
1795
1970
|
}
|
|
1796
1971
|
return ((session.provider === "codex" &&
|
|
1797
1972
|
(isLegacyCodingNsRolloutSession(session.providerSessionId, session.rawStoreRef) ||
|
|
1798
|
-
shouldRemoveMissingSyntheticCodexSession(session.rawStoreRef)
|
|
1973
|
+
(shouldRemoveMissingSyntheticCodexSession(session.rawStoreRef) &&
|
|
1974
|
+
!this.shouldPreserveSyntheticCodexSession(session, nowMs)))) ||
|
|
1799
1975
|
(session.provider === "claude-code" && shouldRemoveHiddenClaudeDebugSession(session)));
|
|
1800
1976
|
});
|
|
1801
|
-
|
|
1977
|
+
const managedButlerSessionIds = this.listManagedButlerSessionIds(staleHiddenSessions.map((session) => session.sessionId));
|
|
1978
|
+
const deletableSessions = staleHiddenSessions.filter((session) => !managedButlerSessionIds.has(session.sessionId));
|
|
1979
|
+
if (deletableSessions.length === 0) {
|
|
1802
1980
|
return;
|
|
1803
1981
|
}
|
|
1804
1982
|
const deleteTransaction = this.db.transaction((ids) => {
|
|
@@ -1806,13 +1984,61 @@ export class SessionHistoryService {
|
|
|
1806
1984
|
this.deleteSessionById(sessionId);
|
|
1807
1985
|
}
|
|
1808
1986
|
});
|
|
1809
|
-
await runBatchedTransactions(
|
|
1987
|
+
await runBatchedTransactions(deletableSessions.map((session) => session.sessionId), WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE, deleteTransaction, {
|
|
1988
|
+
scope: "workspace.discover_sessions.cleanup_hidden.batch",
|
|
1989
|
+
thresholdMs: SESSION_TRANSACTION_HOTSPOT_THRESHOLD_MS,
|
|
1990
|
+
detail: {
|
|
1991
|
+
workspaceId,
|
|
1992
|
+
userId,
|
|
1993
|
+
phase: "cleanup_hidden"
|
|
1994
|
+
}
|
|
1995
|
+
});
|
|
1996
|
+
}
|
|
1997
|
+
listManagedButlerSessionIds(sessionIds) {
|
|
1998
|
+
if (sessionIds.length === 0) {
|
|
1999
|
+
return new Set();
|
|
2000
|
+
}
|
|
2001
|
+
const managedSessionIds = new Set();
|
|
2002
|
+
for (let index = 0; index < sessionIds.length; index += WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE) {
|
|
2003
|
+
const batch = sessionIds.slice(index, index + WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE);
|
|
2004
|
+
const placeholders = batch.map(() => "?").join(", ");
|
|
2005
|
+
const rows = this.db
|
|
2006
|
+
.prepare(`SELECT session_id
|
|
2007
|
+
FROM butler_sessions
|
|
2008
|
+
WHERE session_id IN (${placeholders})`)
|
|
2009
|
+
.all(...batch);
|
|
2010
|
+
for (const row of rows) {
|
|
2011
|
+
managedSessionIds.add(row.session_id);
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
return managedSessionIds;
|
|
2015
|
+
}
|
|
2016
|
+
shouldPreserveSyntheticCodexSession(session, nowMs) {
|
|
2017
|
+
if (session.activitySource === "runtime"
|
|
2018
|
+
|| session.runningState === "starting"
|
|
2019
|
+
|| session.runningState === "running") {
|
|
2020
|
+
return true;
|
|
2021
|
+
}
|
|
2022
|
+
const hasActiveRuntimeState = this.listSessionStatesBySessionId(session.sessionId).some((state) => state.activitySource === "runtime"
|
|
2023
|
+
|| state.runningState === "starting"
|
|
2024
|
+
|| state.runningState === "running");
|
|
2025
|
+
if (hasActiveRuntimeState) {
|
|
2026
|
+
return true;
|
|
2027
|
+
}
|
|
2028
|
+
const latestTouchedAt = pickLaterIso(session.updatedAt, session.createdAt) ?? session.updatedAt;
|
|
2029
|
+
const latestTouchedAtMs = Date.parse(latestTouchedAt);
|
|
2030
|
+
if (!Number.isFinite(latestTouchedAtMs)) {
|
|
2031
|
+
return false;
|
|
2032
|
+
}
|
|
2033
|
+
return nowMs - latestTouchedAtMs <= SYNTHETIC_CODEX_SESSION_CLEANUP_GRACE_MS;
|
|
1810
2034
|
}
|
|
1811
2035
|
findSameWorkspaceBindingDuplicate(sessionId, workspaceId, snapshot) {
|
|
1812
2036
|
if (isPendingBindingValue(snapshot.providerSessionId)) {
|
|
1813
2037
|
return null;
|
|
1814
2038
|
}
|
|
1815
|
-
const existing = this.sessionBindingRepository.findByProviderSession(snapshot.provider, snapshot.providerSessionId) ??
|
|
2039
|
+
const existing = this.sessionBindingRepository.findByProviderSession(snapshot.provider, snapshot.providerSessionId) ?? (shouldMatchSessionBindingByRawStoreRef(snapshot.provider)
|
|
2040
|
+
? this.sessionBindingRepository.findByRawStoreRef(snapshot.provider, snapshot.rawStoreRef)
|
|
2041
|
+
: null);
|
|
1816
2042
|
if (!existing || existing.sessionId === sessionId) {
|
|
1817
2043
|
return null;
|
|
1818
2044
|
}
|
|
@@ -1825,11 +2051,22 @@ export class SessionHistoryService {
|
|
|
1825
2051
|
if (input.targetSessionId === input.sourceSessionId) {
|
|
1826
2052
|
return;
|
|
1827
2053
|
}
|
|
1828
|
-
const targetBinding = this.sessionBindingRepository.findBySessionId(input.targetSessionId);
|
|
1829
2054
|
const sourceBinding = this.sessionBindingRepository.findBySessionId(input.sourceSessionId);
|
|
1830
|
-
if (!
|
|
2055
|
+
if (!sourceBinding) {
|
|
1831
2056
|
return;
|
|
1832
2057
|
}
|
|
2058
|
+
const targetBinding = this.sessionBindingRepository.findBySessionId(input.targetSessionId);
|
|
2059
|
+
if (!targetBinding) {
|
|
2060
|
+
this.sessionBindingRepository.upsert({
|
|
2061
|
+
sessionId: input.targetSessionId,
|
|
2062
|
+
workspaceId: input.workspaceId,
|
|
2063
|
+
provider: input.provider,
|
|
2064
|
+
providerSessionId: buildPendingBindingValue(input.provider, input.targetSessionId),
|
|
2065
|
+
rawStoreRef: buildPendingBindingValue(input.provider, input.targetSessionId),
|
|
2066
|
+
createdAt: sourceBinding.createdAt,
|
|
2067
|
+
updatedAt: input.timestamp
|
|
2068
|
+
});
|
|
2069
|
+
}
|
|
1833
2070
|
const targetIndex = this.sessionIndexRepository.findIndexRecordBySessionId(input.targetSessionId);
|
|
1834
2071
|
const sourceIndex = this.sessionIndexRepository.findIndexRecordBySessionId(input.sourceSessionId);
|
|
1835
2072
|
const targetSnapshot = this.sessionStatusSnapshotRepository.findBySessionId(input.targetSessionId);
|
|
@@ -2128,6 +2365,18 @@ export class SessionHistoryService {
|
|
|
2128
2365
|
});
|
|
2129
2366
|
return nextRecord;
|
|
2130
2367
|
}
|
|
2368
|
+
async repairCodexDirtyBindingBeforeHistoryRead(sessionId, userId, binding) {
|
|
2369
|
+
if (!shouldRepairCodexDirtyBinding(binding)) {
|
|
2370
|
+
return binding;
|
|
2371
|
+
}
|
|
2372
|
+
await this.discoverWorkspaceSessions(binding.workspaceId, userId, {
|
|
2373
|
+
force: true,
|
|
2374
|
+
refreshStateMode: "deferred"
|
|
2375
|
+
}).catch(() => {
|
|
2376
|
+
return [];
|
|
2377
|
+
});
|
|
2378
|
+
return this.getBindingOrThrow(sessionId);
|
|
2379
|
+
}
|
|
2131
2380
|
resolveLiveActivityObservation(sessionId) {
|
|
2132
2381
|
for (const resolver of this.liveActivityObservationResolvers) {
|
|
2133
2382
|
const observation = resolver(sessionId);
|
|
@@ -2215,6 +2464,7 @@ function buildInspectionActivityObservation(sessionId, inspection, observedAt) {
|
|
|
2215
2464
|
source: hasInspectionEvidence(inspection) ? "inferred_log" : "unknown",
|
|
2216
2465
|
confidence: "weak",
|
|
2217
2466
|
detail: inspection.errorDetail,
|
|
2467
|
+
interruptSource: null,
|
|
2218
2468
|
errorCode: inspection.errorCode,
|
|
2219
2469
|
observedAt: inspection.completedAtCandidate ?? inspection.lastEventAt ?? observedAt
|
|
2220
2470
|
};
|
|
@@ -2448,6 +2698,35 @@ function normalizeSessionBindingSnapshot(sessionId, snapshot) {
|
|
|
2448
2698
|
rawStoreRef: buildPendingBindingValue("claude-code", sessionId)
|
|
2449
2699
|
};
|
|
2450
2700
|
}
|
|
2701
|
+
function countCommonHistoryPrefixLength(left, right) {
|
|
2702
|
+
const maxLength = Math.min(left.length, right.length);
|
|
2703
|
+
let count = 0;
|
|
2704
|
+
for (; count < maxLength; count += 1) {
|
|
2705
|
+
if (!areHistoryMessagesEquivalent(left[count], right[count])) {
|
|
2706
|
+
break;
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
return count;
|
|
2710
|
+
}
|
|
2711
|
+
function areHistoryMessagesEquivalent(left, right) {
|
|
2712
|
+
if (!left || !right) {
|
|
2713
|
+
return false;
|
|
2714
|
+
}
|
|
2715
|
+
if (left.messageId && right.messageId) {
|
|
2716
|
+
if (left.messageId === right.messageId) {
|
|
2717
|
+
return true;
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
if (left.rawRef && right.rawRef) {
|
|
2721
|
+
if (left.rawRef === right.rawRef) {
|
|
2722
|
+
return true;
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
return left.role === right.role
|
|
2726
|
+
&& left.kind === right.kind
|
|
2727
|
+
&& left.content === right.content
|
|
2728
|
+
&& left.timestamp === right.timestamp;
|
|
2729
|
+
}
|
|
2451
2730
|
function shouldSkipClaudePendingBinding(binding) {
|
|
2452
2731
|
if (binding.provider !== "claude-code") {
|
|
2453
2732
|
return false;
|
|
@@ -2460,9 +2739,24 @@ function shouldSkipClaudePendingBinding(binding) {
|
|
|
2460
2739
|
function isPendingBindingValue(value) {
|
|
2461
2740
|
return value.trim().toLowerCase().startsWith("pending://");
|
|
2462
2741
|
}
|
|
2742
|
+
function isSessionBindingProviderUniqueConflict(error) {
|
|
2743
|
+
if (!(error instanceof Error)) {
|
|
2744
|
+
return false;
|
|
2745
|
+
}
|
|
2746
|
+
return error.message.includes("UNIQUE constraint failed: session_bindings.provider, session_bindings.provider_session_id");
|
|
2747
|
+
}
|
|
2463
2748
|
function buildPendingBindingValue(provider, sessionId) {
|
|
2464
2749
|
return `pending://${provider}/${sessionId}`;
|
|
2465
2750
|
}
|
|
2751
|
+
function shouldRecoverSessionAsActive(binding) {
|
|
2752
|
+
return isPendingBindingValue(binding.providerSessionId) || isPendingBindingValue(binding.rawStoreRef);
|
|
2753
|
+
}
|
|
2754
|
+
function inferRecoveredSessionRunningState(binding) {
|
|
2755
|
+
return shouldRecoverSessionAsActive(binding) ? "starting" : "idle";
|
|
2756
|
+
}
|
|
2757
|
+
function inferRecoveredSessionActivitySource(binding) {
|
|
2758
|
+
return shouldRecoverSessionAsActive(binding) ? "runtime" : "none";
|
|
2759
|
+
}
|
|
2466
2760
|
function buildAliasBindingValue(provider, targetSessionId, sourceSessionId) {
|
|
2467
2761
|
return `alias://${provider}/${targetSessionId}/${sourceSessionId}`;
|
|
2468
2762
|
}
|
|
@@ -2746,6 +3040,51 @@ function isLegacyCodingNsRolloutSession(providerSessionId, rawStoreRef) {
|
|
|
2746
3040
|
function shouldRemoveMissingSyntheticCodexSession(rawStoreRef) {
|
|
2747
3041
|
return isSyntheticCodexRawStoreRef(rawStoreRef) && !existsSync(rawStoreRef);
|
|
2748
3042
|
}
|
|
3043
|
+
function shouldRepairCodexDirtyBinding(binding) {
|
|
3044
|
+
if (binding.provider !== "codex") {
|
|
3045
|
+
return false;
|
|
3046
|
+
}
|
|
3047
|
+
if (isSyntheticCodexRawStoreRef(binding.rawStoreRef)) {
|
|
3048
|
+
return false;
|
|
3049
|
+
}
|
|
3050
|
+
const expectedThreadId = binding.providerSessionId.trim();
|
|
3051
|
+
if (!expectedThreadId) {
|
|
3052
|
+
return false;
|
|
3053
|
+
}
|
|
3054
|
+
const boundThreadId = readCodexThreadIdFromRawStore(binding.rawStoreRef);
|
|
3055
|
+
if (boundThreadId) {
|
|
3056
|
+
return boundThreadId !== expectedThreadId;
|
|
3057
|
+
}
|
|
3058
|
+
return !existsSync(binding.rawStoreRef);
|
|
3059
|
+
}
|
|
3060
|
+
function readCodexThreadIdFromRawStore(filePath) {
|
|
3061
|
+
if (!existsSync(filePath)) {
|
|
3062
|
+
return null;
|
|
3063
|
+
}
|
|
3064
|
+
try {
|
|
3065
|
+
const firstLine = readFileSync(filePath, "utf8")
|
|
3066
|
+
.split(/\r?\n/, 1)
|
|
3067
|
+
.at(0)
|
|
3068
|
+
?.trim();
|
|
3069
|
+
if (!firstLine) {
|
|
3070
|
+
return null;
|
|
3071
|
+
}
|
|
3072
|
+
const record = JSON.parse(firstLine);
|
|
3073
|
+
if (record.type !== "session_meta") {
|
|
3074
|
+
return null;
|
|
3075
|
+
}
|
|
3076
|
+
const threadId = typeof record.payload?.id === "string"
|
|
3077
|
+
? record.payload.id.trim()
|
|
3078
|
+
: "";
|
|
3079
|
+
return threadId.length > 0 ? threadId : null;
|
|
3080
|
+
}
|
|
3081
|
+
catch {
|
|
3082
|
+
return null;
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
function shouldMatchSessionBindingByRawStoreRef(provider) {
|
|
3086
|
+
return provider !== "codex";
|
|
3087
|
+
}
|
|
2749
3088
|
function resolveSessionListTitle(provider, existingTitle, fallbackContent, parentTitle = null) {
|
|
2750
3089
|
const normalizedExistingTitle = existingTitle?.trim() ?? "";
|
|
2751
3090
|
const normalizedParentTitle = parentTitle?.trim() ?? "";
|
|
@@ -2768,6 +3107,24 @@ function buildUserMessageTitle(content, fallbackTitle) {
|
|
|
2768
3107
|
const title = content.trim().replace(/\s+/g, " ");
|
|
2769
3108
|
return title.slice(0, 48) || fallbackTitle;
|
|
2770
3109
|
}
|
|
3110
|
+
function buildRecoveredSessionTitle(provider, providerSessionId) {
|
|
3111
|
+
if (isPendingBindingValue(providerSessionId)) {
|
|
3112
|
+
return "新会话";
|
|
3113
|
+
}
|
|
3114
|
+
const normalizedProvider = provider.trim().toLowerCase();
|
|
3115
|
+
const providerLabel = normalizedProvider === "claude-code"
|
|
3116
|
+
? "Claude"
|
|
3117
|
+
: normalizedProvider === "codex"
|
|
3118
|
+
? "Codex"
|
|
3119
|
+
: normalizedProvider === "gemini"
|
|
3120
|
+
? "Gemini"
|
|
3121
|
+
: normalizedProvider === "kimi"
|
|
3122
|
+
? "Kimi"
|
|
3123
|
+
: normalizedProvider === "opencode"
|
|
3124
|
+
? "OpenCode"
|
|
3125
|
+
: provider;
|
|
3126
|
+
return `${providerLabel} 会话 ${providerSessionId.slice(0, 8)}`;
|
|
3127
|
+
}
|
|
2771
3128
|
function resolvePersistedSessionTitle(provider, discoveredTitle, existingTitle, parentTitle = null) {
|
|
2772
3129
|
const nextTitle = discoveredTitle.trim();
|
|
2773
3130
|
const currentTitle = existingTitle?.trim() ?? "";
|
|
@@ -2914,13 +3271,14 @@ function buildReconstructedForkPrompt(input) {
|
|
|
2914
3271
|
function buildProviderCapabilityCacheKey(provider, workspacePath) {
|
|
2915
3272
|
return `${provider}::${workspacePath ?? ""}`;
|
|
2916
3273
|
}
|
|
2917
|
-
async function runWithConcurrency(items, concurrency, worker) {
|
|
3274
|
+
async function runWithConcurrency(items, concurrency, worker, signal) {
|
|
2918
3275
|
const normalizedConcurrency = Math.max(1, Math.floor(concurrency) || 1);
|
|
2919
3276
|
const queue = [...items];
|
|
2920
3277
|
const runners = Array.from({
|
|
2921
3278
|
length: Math.min(normalizedConcurrency, queue.length || 1)
|
|
2922
3279
|
}, async () => {
|
|
2923
3280
|
while (queue.length > 0) {
|
|
3281
|
+
throwIfAborted(signal);
|
|
2924
3282
|
const current = queue.shift();
|
|
2925
3283
|
if (current === undefined) {
|
|
2926
3284
|
return;
|
|
@@ -2930,7 +3288,44 @@ async function runWithConcurrency(items, concurrency, worker) {
|
|
|
2930
3288
|
});
|
|
2931
3289
|
await Promise.all(runners);
|
|
2932
3290
|
}
|
|
2933
|
-
|
|
3291
|
+
function throwIfAborted(signal) {
|
|
3292
|
+
if (signal?.aborted) {
|
|
3293
|
+
throw signal.reason ?? new Error("任务已取消");
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
async function awaitTaskHandleWithSignal(handle, signal) {
|
|
3297
|
+
if (!signal) {
|
|
3298
|
+
return await handle.promise;
|
|
3299
|
+
}
|
|
3300
|
+
if (signal.aborted) {
|
|
3301
|
+
handle.cancel(getAbortMessage(signal.reason));
|
|
3302
|
+
throw signal.reason ?? new Error("任务已取消");
|
|
3303
|
+
}
|
|
3304
|
+
return await new Promise((resolve, reject) => {
|
|
3305
|
+
const onAbort = () => {
|
|
3306
|
+
handle.cancel(getAbortMessage(signal.reason));
|
|
3307
|
+
reject(signal.reason ?? new Error("任务已取消"));
|
|
3308
|
+
};
|
|
3309
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
3310
|
+
handle.promise.then((value) => {
|
|
3311
|
+
signal.removeEventListener("abort", onAbort);
|
|
3312
|
+
resolve(value);
|
|
3313
|
+
}, (error) => {
|
|
3314
|
+
signal.removeEventListener("abort", onAbort);
|
|
3315
|
+
reject(error);
|
|
3316
|
+
});
|
|
3317
|
+
});
|
|
3318
|
+
}
|
|
3319
|
+
function getAbortMessage(reason) {
|
|
3320
|
+
if (reason instanceof Error && reason.message.trim().length > 0) {
|
|
3321
|
+
return reason.message;
|
|
3322
|
+
}
|
|
3323
|
+
if (typeof reason === "string" && reason.trim().length > 0) {
|
|
3324
|
+
return reason;
|
|
3325
|
+
}
|
|
3326
|
+
return "任务已取消";
|
|
3327
|
+
}
|
|
3328
|
+
async function runBatchedTransactions(items, batchSize, transaction, logOptions) {
|
|
2934
3329
|
const normalizedBatchSize = Math.max(1, Math.floor(batchSize) || 1);
|
|
2935
3330
|
let batchCount = 0;
|
|
2936
3331
|
let maxBatchMs = 0;
|
|
@@ -2939,6 +3334,19 @@ async function runBatchedTransactions(items, batchSize, transaction) {
|
|
|
2939
3334
|
const batchStartedAt = Date.now();
|
|
2940
3335
|
transaction(batch);
|
|
2941
3336
|
const batchDurationMs = Date.now() - batchStartedAt;
|
|
3337
|
+
const nextBatchIndex = batchCount + 1;
|
|
3338
|
+
if (logOptions) {
|
|
3339
|
+
logPerformance(logOptions.scope, batchDurationMs, {
|
|
3340
|
+
...logOptions.detail,
|
|
3341
|
+
batchIndex: nextBatchIndex,
|
|
3342
|
+
batchSize: batch.length,
|
|
3343
|
+
batchStartIndex: index,
|
|
3344
|
+
totalItems: items.length,
|
|
3345
|
+
configuredBatchSize: normalizedBatchSize
|
|
3346
|
+
}, {
|
|
3347
|
+
thresholdMs: logOptions.thresholdMs ?? SESSION_TRANSACTION_HOTSPOT_THRESHOLD_MS
|
|
3348
|
+
});
|
|
3349
|
+
}
|
|
2942
3350
|
batchCount += 1;
|
|
2943
3351
|
maxBatchMs = Math.max(maxBatchMs, batchDurationMs);
|
|
2944
3352
|
if (index + normalizedBatchSize < items.length) {
|