@jingyi0605/codingns 0.3.5 → 0.4.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/README.md +16 -0
- package/bin/codingns.mjs +1369 -10
- package/dist/public/assets/{TerminalPage-CgrfstRm.js → TerminalPage-6jHZV9Mh.js} +17 -17
- package/dist/public/assets/index-CSVhg7I8.js +123 -0
- package/dist/public/assets/index-Ce1VX19m.css +1 -0
- package/dist/public/index.html +2 -2
- 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 +317 -0
- package/dist/server/modules/assistant-capability/assistant-capability-controller.js +549 -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 +330 -2
- package/dist/server/modules/assistant-capability/assistant-capability-service.js +958 -3
- package/dist/server/modules/assistant-capability/assistant-capability-service.js.map +1 -1
- package/dist/server/modules/butler/assistant-automation-service.d.ts +110 -0
- package/dist/server/modules/butler/assistant-automation-service.js +786 -0
- package/dist/server/modules/butler/assistant-automation-service.js.map +1 -0
- package/dist/server/modules/butler/assistant-automation-trigger.d.ts +94 -0
- package/dist/server/modules/butler/assistant-automation-trigger.js +400 -0
- package/dist/server/modules/butler/assistant-automation-trigger.js.map +1 -0
- package/dist/server/modules/butler/assistant-sandbox-service.d.ts +55 -0
- package/dist/server/modules/butler/assistant-sandbox-service.js +266 -0
- package/dist/server/modules/butler/assistant-sandbox-service.js.map +1 -0
- package/dist/server/modules/butler/butler-action-context-service.d.ts +4 -1
- package/dist/server/modules/butler/butler-action-context-service.js +8 -2
- package/dist/server/modules/butler/butler-action-context-service.js.map +1 -1
- package/dist/server/modules/butler/butler-control-session-service.d.ts +8 -1
- package/dist/server/modules/butler/butler-control-session-service.js +154 -40
- package/dist/server/modules/butler/butler-control-session-service.js.map +1 -1
- package/dist/server/modules/butler/butler-control-timer-scheduler.d.ts +32 -0
- package/dist/server/modules/butler/butler-control-timer-scheduler.js +93 -0
- package/dist/server/modules/butler/butler-control-timer-scheduler.js.map +1 -0
- package/dist/server/modules/butler/butler-control-timer-service.d.ts +42 -0
- package/dist/server/modules/butler/butler-control-timer-service.js +132 -0
- package/dist/server/modules/butler/butler-control-timer-service.js.map +1 -0
- package/dist/server/modules/butler/butler-controller.d.ts +42 -2
- package/dist/server/modules/butler/butler-controller.js +79 -12
- package/dist/server/modules/butler/butler-controller.js.map +1 -1
- package/dist/server/modules/butler/butler-follow-up-service.d.ts +9 -1
- package/dist/server/modules/butler/butler-follow-up-service.js +273 -181
- package/dist/server/modules/butler/butler-follow-up-service.js.map +1 -1
- package/dist/server/modules/butler/butler-inbox-analysis-service.d.ts +4 -1
- package/dist/server/modules/butler/butler-inbox-analysis-service.js +18 -4
- package/dist/server/modules/butler/butler-inbox-analysis-service.js.map +1 -1
- package/dist/server/modules/butler/butler-inbox-instruction-adapter.js +7 -6
- package/dist/server/modules/butler/butler-inbox-instruction-adapter.js.map +1 -1
- package/dist/server/modules/butler/butler-profile-service.js +2 -5
- package/dist/server/modules/butler/butler-profile-service.js.map +1 -1
- package/dist/server/modules/butler/butler-project-service.d.ts +3 -1
- package/dist/server/modules/butler/butler-project-service.js +7 -1
- package/dist/server/modules/butler/butler-project-service.js.map +1 -1
- package/dist/server/modules/butler/butler-session-service.d.ts +3 -1
- package/dist/server/modules/butler/butler-session-service.js +12 -1
- package/dist/server/modules/butler/butler-session-service.js.map +1 -1
- package/dist/server/modules/butler/butler-session-summary-service.js +2 -1
- package/dist/server/modules/butler/butler-session-summary-service.js.map +1 -1
- package/dist/server/modules/butler/butler-workspace-context.d.ts +3 -0
- package/dist/server/modules/butler/butler-workspace-context.js +182 -51
- package/dist/server/modules/butler/butler-workspace-context.js.map +1 -1
- package/dist/server/modules/butler/patrol-execution-service.js +2 -1
- package/dist/server/modules/butler/patrol-execution-service.js.map +1 -1
- package/dist/server/modules/butler/provider-adapter-registry.d.ts +3 -0
- package/dist/server/modules/butler/provider-adapter-registry.js +18 -1
- package/dist/server/modules/butler/provider-adapter-registry.js.map +1 -1
- package/dist/server/modules/butler/verification-run-service.d.ts +9 -2
- package/dist/server/modules/butler/verification-run-service.js +188 -34
- package/dist/server/modules/butler/verification-run-service.js.map +1 -1
- 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 +17 -3
- package/dist/server/modules/debug-target/debug-target-service.js +696 -98
- package/dist/server/modules/debug-target/debug-target-service.js.map +1 -1
- package/dist/server/modules/git/git-command-helper-client.d.ts +3 -0
- package/dist/server/modules/git/git-command-helper-client.js +71 -29
- 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 +44 -1
- 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.js +6 -2
- package/dist/server/modules/model-switch/cc-switch-adapter.js.map +1 -1
- package/dist/server/modules/preferences/profile-service.d.ts +3 -1
- package/dist/server/modules/preferences/profile-service.js +74 -3
- 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 +14 -7
- package/dist/server/modules/provider/provider-discovery-helper-client.js +208 -46
- package/dist/server/modules/provider/provider-discovery-helper-client.js.map +1 -1
- package/dist/server/modules/provider/provider-discovery-helper-process.js +96 -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 +145 -0
- package/dist/server/modules/provider/provider-discovery-runtime.js.map +1 -0
- package/dist/server/modules/sessions/claude-runtime-helper-client.js +23 -1
- package/dist/server/modules/sessions/claude-runtime-helper-client.js.map +1 -1
- package/dist/server/modules/sessions/session-history-service.d.ts +12 -3
- package/dist/server/modules/sessions/session-history-service.js +465 -67
- package/dist/server/modules/sessions/session-history-service.js.map +1 -1
- package/dist/server/modules/sessions/session-live-runtime-service.d.ts +8 -0
- package/dist/server/modules/sessions/session-live-runtime-service.js +164 -34
- package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
- package/dist/server/modules/sessions/session-message-origin-utils.d.ts +12 -0
- package/dist/server/modules/sessions/session-message-origin-utils.js +45 -0
- package/dist/server/modules/sessions/session-message-origin-utils.js.map +1 -0
- package/dist/server/modules/sessions/session-permission-request-service.js +167 -0
- package/dist/server/modules/sessions/session-permission-request-service.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 +82 -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 +136 -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/tasks/task-helper-client.d.ts +10 -2
- package/dist/server/modules/tasks/task-helper-client.js +152 -27
- 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 +104 -3
- package/dist/server/modules/tasks/task-helper-process.js.map +1 -1
- package/dist/server/modules/tasks/task-lane-executors.js +2 -2
- package/dist/server/modules/tasks/task-lane-executors.js.map +1 -1
- package/dist/server/modules/tasks/task-types.d.ts +4 -0
- package/dist/server/modules/tasks/task-types.js +5 -1
- package/dist/server/modules/tasks/task-types.js.map +1 -1
- package/dist/server/modules/terminal/command-template-service.d.ts +11 -2
- package/dist/server/modules/terminal/command-template-service.js +91 -9
- 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 +1 -1
- package/dist/server/modules/terminal/template-port-runtime.js +87 -37
- package/dist/server/modules/terminal/template-port-runtime.js.map +1 -1
- package/dist/server/modules/terminal/terminal-controller.d.ts +3 -0
- package/dist/server/modules/terminal/terminal-controller.js +41 -0
- package/dist/server/modules/terminal/terminal-controller.js.map +1 -1
- package/dist/server/modules/terminal/terminal-service.d.ts +4 -0
- package/dist/server/modules/terminal/terminal-service.js +35 -1
- package/dist/server/modules/terminal/terminal-service.js.map +1 -1
- package/dist/server/modules/workbench/workbench-service.d.ts +3 -0
- package/dist/server/modules/workbench/workbench-service.js +7 -6
- package/dist/server/modules/workbench/workbench-service.js.map +1 -1
- package/dist/server/modules/workbench/workspace-file-watcher.d.ts +14 -6
- package/dist/server/modules/workbench/workspace-file-watcher.js +267 -57
- package/dist/server/modules/workbench/workspace-file-watcher.js.map +1 -1
- package/dist/server/modules/workbench/workspace-panel-snapshot-service.d.ts +3 -0
- package/dist/server/modules/workbench/workspace-panel-snapshot-service.js +149 -41
- 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 +183 -1
- 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 +10 -2
- package/dist/server/modules/worktree/worktree-manager.js +35 -20
- 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 +43 -0
- package/dist/server/routes/assistant.js.map +1 -1
- package/dist/server/routes/butler.js +5 -0
- package/dist/server/routes/butler.js.map +1 -1
- package/dist/server/server/create-server.d.ts +8 -0
- package/dist/server/server/create-server.js +51 -13
- package/dist/server/server/create-server.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/assistant-automation-run-repository.d.ts +12 -0
- package/dist/server/storage/repositories/assistant-automation-run-repository.js +139 -0
- package/dist/server/storage/repositories/assistant-automation-run-repository.js.map +1 -0
- package/dist/server/storage/repositories/assistant-automation-task-repository.d.ts +15 -0
- package/dist/server/storage/repositories/assistant-automation-task-repository.js +173 -0
- package/dist/server/storage/repositories/assistant-automation-task-repository.js.map +1 -0
- package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.d.ts +17 -0
- package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.js +164 -0
- package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.js.map +1 -0
- package/dist/server/storage/repositories/butler-control-session-repository.js +27 -3
- package/dist/server/storage/repositories/butler-control-session-repository.js.map +1 -1
- package/dist/server/storage/repositories/butler-control-timer-repository.d.ts +15 -0
- package/dist/server/storage/repositories/butler-control-timer-repository.js +157 -0
- package/dist/server/storage/repositories/butler-control-timer-repository.js.map +1 -0
- 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 +239 -2
- package/dist/server/storage/sqlite/client.js.map +1 -1
- package/dist/server/storage/sqlite/schema.sql +107 -1
- package/dist/server/types/domain.d.ts +89 -2
- package/dist/server/ws/workbench-ws-hub.d.ts +14 -7
- package/dist/server/ws/workbench-ws-hub.js +316 -158
- package/dist/server/ws/workbench-ws-hub.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.d.ts +4 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js +111 -3
- 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 +6 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +306 -31
- 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 +5 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js +187 -26
- package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/kimi.d.ts +4 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js +98 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/opencode.d.ts +2 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +71 -8
- 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 +4 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/utils.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js +44 -0
- package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +9 -3
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/types.d.ts +1 -0
- package/node_modules/@codingns/session-sync-core/dist/services.js +17 -8
- package/node_modules/@codingns/session-sync-core/dist/services.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/types.d.ts +4 -0
- package/package.json +1 -1
- package/scripts/postinstall.mjs +33 -0
- package/dist/public/assets/index-Cek6u0b9.css +0 -1
- package/dist/public/assets/index-THHY79si.js +0 -122
|
@@ -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",
|
|
@@ -30,8 +32,13 @@ const SESSION_START_DEFERRED_PROVIDERS = new Set([
|
|
|
30
32
|
]);
|
|
31
33
|
const MUTABLE_HISTORY_TAIL_REFRESH_INTERVAL_MS = 1_200;
|
|
32
34
|
const WORKSPACE_DISCOVERY_BACKGROUND_MAX_AGE_MS = 15_000;
|
|
35
|
+
const WORKSPACE_DISCOVERY_SCAN_CONCURRENCY = 2;
|
|
33
36
|
const PROVIDER_CAPABILITY_CACHE_MAX_AGE_MS = 5_000;
|
|
34
37
|
const WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE = 25;
|
|
38
|
+
const SESSION_TRANSACTION_HOTSPOT_THRESHOLD_MS = 150;
|
|
39
|
+
const WORKSPACE_STATE_REFRESH_COOLDOWN_MS = 1_500;
|
|
40
|
+
const SQLITE_BUSY_RETRY_LIMIT = 3;
|
|
41
|
+
const SQLITE_BUSY_RETRY_DELAY_MS = 100;
|
|
35
42
|
export class SessionHistoryService {
|
|
36
43
|
db;
|
|
37
44
|
workspaceRepository;
|
|
@@ -52,14 +59,15 @@ export class SessionHistoryService {
|
|
|
52
59
|
openCodeModelOptionsService;
|
|
53
60
|
providerCliCommandPaths;
|
|
54
61
|
providerCliAvailability;
|
|
55
|
-
providerDiscoveryHelperClient =
|
|
62
|
+
providerDiscoveryHelperClient = getSharedProviderDiscoveryHelperClient();
|
|
56
63
|
providerSessionDiscoveryConfig;
|
|
57
64
|
taskManager;
|
|
58
65
|
workspaceDiscoveryStatuses = new Map();
|
|
59
|
-
|
|
66
|
+
workspaceStateRefreshStatuses = new Map();
|
|
60
67
|
providerCapabilityCache = new Map();
|
|
61
68
|
liveActivityObservationResolvers = new Set();
|
|
62
69
|
workspaceSessionRelations = new Map();
|
|
70
|
+
workspaceStateRefreshTaskSequence = 0;
|
|
63
71
|
constructor(db, workspaceRepository, sessionBindingRepository, sessionChangedFileService, sessionIndexRepository, sessionMessageAttachmentService, sessionStateRepository, sessionStatusSnapshotRepository, config, sessionActivityAuthorityService = new SessionActivityAuthorityService(), sessionMessageOriginRepository = null, sessionForkRepository = null, adapterOverrides = {}, taskManager = createTaskManager()) {
|
|
64
72
|
this.db = db;
|
|
65
73
|
this.workspaceRepository = workspaceRepository;
|
|
@@ -148,8 +156,17 @@ export class SessionHistoryService {
|
|
|
148
156
|
if (!this.taskManager.has(HOST_TASK_TYPES.workspaceDiscovery)) {
|
|
149
157
|
this.taskManager.register({
|
|
150
158
|
taskType: HOST_TASK_TYPES.workspaceDiscovery,
|
|
159
|
+
executionLane: "host_background",
|
|
160
|
+
run: async ({ workspaceId, userId, refreshStateMode }, context) => this.runDiscoverWorkspaceSessions(workspaceId, userId, refreshStateMode, context.signal)
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
if (!this.taskManager.has(HOST_TASK_TYPES.workspaceDiscoveryScan)) {
|
|
164
|
+
this.taskManager.register({
|
|
165
|
+
taskType: HOST_TASK_TYPES.workspaceDiscoveryScan,
|
|
151
166
|
executionLane: "helper_process",
|
|
152
|
-
|
|
167
|
+
concurrency: WORKSPACE_DISCOVERY_SCAN_CONCURRENCY,
|
|
168
|
+
helperProcessHandler: "session.workspace_discovery",
|
|
169
|
+
run: async ({ config, workspacePath, knownSessions }, context) => await discoverWorkspaceSessionsInRuntime(config, workspacePath, knownSessions, context.signal)
|
|
153
170
|
});
|
|
154
171
|
}
|
|
155
172
|
if (!this.taskManager.has(HOST_TASK_TYPES.providerCapabilityRefresh)) {
|
|
@@ -233,7 +250,10 @@ export class SessionHistoryService {
|
|
|
233
250
|
async readSessionHistory(sessionId, cursor, limit, direction = "forward", userId) {
|
|
234
251
|
const startedAt = Date.now();
|
|
235
252
|
const resolvedSessionId = this.resolveCanonicalSessionId(sessionId, userId);
|
|
236
|
-
|
|
253
|
+
let binding = this.getBindingOrThrow(resolvedSessionId);
|
|
254
|
+
if (userId) {
|
|
255
|
+
binding = await this.repairCodexDirtyBindingBeforeHistoryRead(resolvedSessionId, userId, binding);
|
|
256
|
+
}
|
|
237
257
|
const current = this.sessionStatusSnapshotRepository.findBySessionId(resolvedSessionId);
|
|
238
258
|
const safeLimit = clampLimit(limit);
|
|
239
259
|
const knownTotalMessageCount = direction === "backward" && cursor === null
|
|
@@ -305,6 +325,12 @@ export class SessionHistoryService {
|
|
|
305
325
|
originRef: null
|
|
306
326
|
};
|
|
307
327
|
}
|
|
328
|
+
resolveMessageOriginByClientRequestId(sessionId, clientRequestId, messageId, updatedAt) {
|
|
329
|
+
if (!this.sessionMessageOriginRepository || !clientRequestId || !messageId) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
this.sessionMessageOriginRepository.resolveMessageId(sessionId, clientRequestId, messageId, updatedAt);
|
|
333
|
+
}
|
|
308
334
|
async findLatestUserMessage(sessionId, content, maxAttempts = 12, minTimestamp = null) {
|
|
309
335
|
const binding = this.getBindingOrThrow(sessionId);
|
|
310
336
|
const acceptedContents = new Set((Array.isArray(content) ? content : [content]).filter((value) => value.trim().length > 0));
|
|
@@ -335,17 +361,17 @@ export class SessionHistoryService {
|
|
|
335
361
|
await this.refreshSessionState(sessionId, userId);
|
|
336
362
|
return this.enrichSessionItem(this.getSessionListItemOrThrow(sessionId, userId));
|
|
337
363
|
}
|
|
338
|
-
async syncSessionTitle(sessionId) {
|
|
364
|
+
async syncSessionTitle(sessionId, signal) {
|
|
339
365
|
const binding = this.getBindingOrThrow(sessionId);
|
|
340
|
-
await this.syncSessionTitleFromProvider(sessionId, binding);
|
|
366
|
+
await this.syncSessionTitleFromProvider(sessionId, binding, signal);
|
|
341
367
|
}
|
|
342
|
-
async syncWorkspaceSessionTitles(workspaceId, userId, concurrency = 1) {
|
|
368
|
+
async syncWorkspaceSessionTitles(workspaceId, userId, concurrency = 1, signal) {
|
|
343
369
|
const sessions = this.sessionIndexRepository.listByWorkspace(workspaceId, userId);
|
|
344
370
|
await runWithConcurrency(sessions, concurrency, async (session) => {
|
|
345
|
-
await this.syncSessionTitle(session.sessionId).catch(() => {
|
|
371
|
+
await this.syncSessionTitle(session.sessionId, signal).catch(() => {
|
|
346
372
|
return;
|
|
347
373
|
});
|
|
348
|
-
});
|
|
374
|
+
}, signal);
|
|
349
375
|
}
|
|
350
376
|
async listSessionChangedFiles(sessionId, userId) {
|
|
351
377
|
this.getSession(sessionId, userId);
|
|
@@ -1137,7 +1163,7 @@ export class SessionHistoryService {
|
|
|
1137
1163
|
}
|
|
1138
1164
|
}
|
|
1139
1165
|
}
|
|
1140
|
-
async runDiscoverWorkspaceSessions(workspaceId, userId, refreshStateMode = "inline") {
|
|
1166
|
+
async runDiscoverWorkspaceSessions(workspaceId, userId, refreshStateMode = "inline", signal) {
|
|
1141
1167
|
const startedAt = Date.now();
|
|
1142
1168
|
const debugStartedAtMs = terminalDebugNowMs();
|
|
1143
1169
|
const workspace = this.getWorkspaceOrThrow(workspaceId);
|
|
@@ -1158,13 +1184,16 @@ export class SessionHistoryService {
|
|
|
1158
1184
|
const discoverStartedAt = Date.now();
|
|
1159
1185
|
const existingWorkspaceSessions = this.sessionIndexRepository.listByWorkspace(workspaceId, userId);
|
|
1160
1186
|
const knownSessions = this.buildKnownSessionSummaries(existingWorkspaceSessions, workspace.path);
|
|
1161
|
-
const
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1187
|
+
const discoveryHandle = this.taskManager.enqueue(HOST_TASK_TYPES.workspaceDiscoveryScan, {
|
|
1188
|
+
key: workspaceId,
|
|
1189
|
+
source: "session_history.workspace_discovery.scan",
|
|
1190
|
+
input: {
|
|
1191
|
+
config: this.providerSessionDiscoveryConfig,
|
|
1192
|
+
workspacePath: workspace.path,
|
|
1193
|
+
knownSessions
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1196
|
+
const discovery = await awaitTaskHandleWithSignal(discoveryHandle, signal).catch((error) => {
|
|
1168
1197
|
throw mapSessionProviderError(error);
|
|
1169
1198
|
});
|
|
1170
1199
|
const sessions = discovery.sessions;
|
|
@@ -1198,7 +1227,7 @@ export class SessionHistoryService {
|
|
|
1198
1227
|
const existingIndex = existing
|
|
1199
1228
|
? this.sessionIndexRepository.findIndexRecordBySessionId(existing.sessionId)
|
|
1200
1229
|
: null;
|
|
1201
|
-
|
|
1230
|
+
const nextBinding = {
|
|
1202
1231
|
sessionId,
|
|
1203
1232
|
workspaceId: workspace.id,
|
|
1204
1233
|
provider: session.provider,
|
|
@@ -1206,7 +1235,10 @@ export class SessionHistoryService {
|
|
|
1206
1235
|
rawStoreRef: session.rawStoreRef,
|
|
1207
1236
|
createdAt,
|
|
1208
1237
|
updatedAt: timestamp
|
|
1209
|
-
}
|
|
1238
|
+
};
|
|
1239
|
+
if (!areEquivalentSessionBindings(existing, nextBinding)) {
|
|
1240
|
+
this.sessionBindingRepository.upsert(nextBinding);
|
|
1241
|
+
}
|
|
1210
1242
|
const preservedParentSessionId = existingIndex?.parentSessionId
|
|
1211
1243
|
?? this.sessionForkRepository.findBySessionId(sessionId)?.parentSessionId
|
|
1212
1244
|
?? null;
|
|
@@ -1214,7 +1246,7 @@ export class SessionHistoryService {
|
|
|
1214
1246
|
? this.sessionIndexRepository.findIndexRecordBySessionId(preservedParentSessionId)?.title ?? null
|
|
1215
1247
|
: null;
|
|
1216
1248
|
const preservedTitle = resolvePersistedSessionTitle(session.provider, session.title, existingIndex?.title ?? null, preservedParentTitle);
|
|
1217
|
-
|
|
1249
|
+
const nextIndex = {
|
|
1218
1250
|
sessionId,
|
|
1219
1251
|
workspaceId: workspace.id,
|
|
1220
1252
|
provider: session.provider,
|
|
@@ -1226,12 +1258,15 @@ export class SessionHistoryService {
|
|
|
1226
1258
|
subagentLabel: existingIndex?.subagentLabel ?? null,
|
|
1227
1259
|
title: preservedTitle,
|
|
1228
1260
|
messageCount: session.messageCount,
|
|
1229
|
-
isArchived: resolveDiscoveredArchiveState(existingIndex?.isArchived ?? false, session.isArchived),
|
|
1261
|
+
isArchived: resolveDiscoveredArchiveState(session.provider, existingIndex?.isArchived ?? false, session.isArchived),
|
|
1230
1262
|
lastMessageAt: session.lastMessageAt,
|
|
1231
1263
|
createdAt,
|
|
1232
1264
|
updatedAt: timestamp
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1265
|
+
};
|
|
1266
|
+
if (!areEquivalentSessionIndexRecords(existingIndex, nextIndex)) {
|
|
1267
|
+
this.sessionIndexRepository.upsert(nextIndex);
|
|
1268
|
+
}
|
|
1269
|
+
const nextSnapshot = {
|
|
1235
1270
|
sessionId,
|
|
1236
1271
|
syncStatus: currentSnapshot?.syncStatus ?? "idle",
|
|
1237
1272
|
syncCursor: currentSnapshot?.syncCursor ?? null,
|
|
@@ -1240,18 +1275,30 @@ export class SessionHistoryService {
|
|
|
1240
1275
|
lastErrorDetail: currentSnapshot?.lastErrorDetail ?? null,
|
|
1241
1276
|
resumedAt: currentSnapshot?.resumedAt ?? null,
|
|
1242
1277
|
updatedAt: timestamp
|
|
1243
|
-
}
|
|
1278
|
+
};
|
|
1279
|
+
if (!areEquivalentSessionStatusSnapshots(currentSnapshot, nextSnapshot)) {
|
|
1280
|
+
this.sessionStatusSnapshotRepository.upsert(nextSnapshot);
|
|
1281
|
+
}
|
|
1244
1282
|
discoveredSessionIds.set(buildProviderSessionKey(session.provider, session.providerSessionId), sessionId);
|
|
1245
1283
|
persistedSessions.push({
|
|
1246
1284
|
session,
|
|
1247
1285
|
sessionId,
|
|
1248
1286
|
createdAt,
|
|
1249
|
-
existingIndex
|
|
1287
|
+
existingIndex,
|
|
1288
|
+
pass1Index: nextIndex
|
|
1250
1289
|
});
|
|
1251
1290
|
}
|
|
1252
1291
|
});
|
|
1253
1292
|
const persistPass1StartedAt = Date.now();
|
|
1254
|
-
const persistPass1Stats = await runBatchedTransactions(sessions, WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE, persistPass1Transaction
|
|
1293
|
+
const persistPass1Stats = await runBatchedTransactions(sessions, WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE, persistPass1Transaction, {
|
|
1294
|
+
scope: "workspace.discover_sessions.persist_pass1.batch",
|
|
1295
|
+
thresholdMs: SESSION_TRANSACTION_HOTSPOT_THRESHOLD_MS,
|
|
1296
|
+
detail: {
|
|
1297
|
+
workspaceId,
|
|
1298
|
+
workspacePath: workspace.path,
|
|
1299
|
+
phase: "pass1"
|
|
1300
|
+
}
|
|
1301
|
+
});
|
|
1255
1302
|
persistPass1DurationMs = Date.now() - persistPass1StartedAt;
|
|
1256
1303
|
persistPass1BatchCount = persistPass1Stats.batchCount;
|
|
1257
1304
|
persistPass1MaxBatchMs = persistPass1Stats.maxBatchMs;
|
|
@@ -1268,7 +1315,7 @@ export class SessionHistoryService {
|
|
|
1268
1315
|
const resolvedParentTitle = resolvedParentSessionId
|
|
1269
1316
|
? this.sessionIndexRepository.findIndexRecordBySessionId(resolvedParentSessionId)?.title ?? null
|
|
1270
1317
|
: null;
|
|
1271
|
-
|
|
1318
|
+
const nextIndex = {
|
|
1272
1319
|
sessionId: persistedSession.sessionId,
|
|
1273
1320
|
workspaceId: workspace.id,
|
|
1274
1321
|
provider: persistedSession.session.provider,
|
|
@@ -1290,15 +1337,26 @@ export class SessionHistoryService {
|
|
|
1290
1337
|
?? null,
|
|
1291
1338
|
title: resolvePersistedSessionTitle(persistedSession.session.provider, persistedSession.session.title, persistedSession.existingIndex?.title ?? null, resolvedParentTitle),
|
|
1292
1339
|
messageCount: persistedSession.session.messageCount,
|
|
1293
|
-
isArchived: resolveDiscoveredArchiveState(persistedSession.existingIndex?.isArchived ?? false, persistedSession.session.isArchived),
|
|
1340
|
+
isArchived: resolveDiscoveredArchiveState(persistedSession.session.provider, persistedSession.existingIndex?.isArchived ?? false, persistedSession.session.isArchived),
|
|
1294
1341
|
lastMessageAt: persistedSession.session.lastMessageAt,
|
|
1295
1342
|
createdAt: persistedSession.createdAt,
|
|
1296
1343
|
updatedAt: timestamp
|
|
1297
|
-
}
|
|
1344
|
+
};
|
|
1345
|
+
if (!areEquivalentSessionIndexRecords(persistedSession.pass1Index, nextIndex)) {
|
|
1346
|
+
this.sessionIndexRepository.upsert(nextIndex);
|
|
1347
|
+
}
|
|
1298
1348
|
}
|
|
1299
1349
|
});
|
|
1300
1350
|
const persistPass2StartedAt = Date.now();
|
|
1301
|
-
const persistPass2Stats = await runBatchedTransactions(persistedSessions, WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE, persistPass2Transaction
|
|
1351
|
+
const persistPass2Stats = await runBatchedTransactions(persistedSessions, WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE, persistPass2Transaction, {
|
|
1352
|
+
scope: "workspace.discover_sessions.persist_pass2.batch",
|
|
1353
|
+
thresholdMs: SESSION_TRANSACTION_HOTSPOT_THRESHOLD_MS,
|
|
1354
|
+
detail: {
|
|
1355
|
+
workspaceId,
|
|
1356
|
+
workspacePath: workspace.path,
|
|
1357
|
+
phase: "pass2"
|
|
1358
|
+
}
|
|
1359
|
+
});
|
|
1302
1360
|
persistPass2DurationMs = Date.now() - persistPass2StartedAt;
|
|
1303
1361
|
persistPass2BatchCount = persistPass2Stats.batchCount;
|
|
1304
1362
|
persistPass2MaxBatchMs = persistPass2Stats.maxBatchMs;
|
|
@@ -1354,7 +1412,22 @@ export class SessionHistoryService {
|
|
|
1354
1412
|
discoveredSessions: sessions.length,
|
|
1355
1413
|
returnedSessions: nextItems.length,
|
|
1356
1414
|
discoveryComplete: discovery.isComplete,
|
|
1357
|
-
providerDiagnostics: (discovery.providerDiagnostics ?? []).map((entry) =>
|
|
1415
|
+
providerDiagnostics: (discovery.providerDiagnostics ?? []).map((entry) => {
|
|
1416
|
+
const scannedFiles = entry.scannedFiles ?? 0;
|
|
1417
|
+
const skippedByMtimeSize = entry.skippedByMtimeSize ?? 0;
|
|
1418
|
+
const parsedFiles = entry.parsedFiles ?? 0;
|
|
1419
|
+
const bytesRead = entry.bytesRead ?? 0;
|
|
1420
|
+
return [
|
|
1421
|
+
entry.provider,
|
|
1422
|
+
entry.status,
|
|
1423
|
+
`${Math.round(entry.durationMs)}ms`,
|
|
1424
|
+
`sessions=${entry.sessionCount}`,
|
|
1425
|
+
`scanned=${scannedFiles}`,
|
|
1426
|
+
`skipped=${skippedByMtimeSize}`,
|
|
1427
|
+
`parsed=${parsedFiles}`,
|
|
1428
|
+
`bytes=${bytesRead}`
|
|
1429
|
+
].join(":");
|
|
1430
|
+
}),
|
|
1358
1431
|
refreshedStates: refreshCandidates.length,
|
|
1359
1432
|
discoverMs: discoverDurationMs,
|
|
1360
1433
|
persistMs: persistDurationMs,
|
|
@@ -1509,13 +1582,6 @@ export class SessionHistoryService {
|
|
|
1509
1582
|
const originByMessageId = new Map(originRows
|
|
1510
1583
|
.filter((row) => row.messageId)
|
|
1511
1584
|
.map((row) => [row.messageId, row]));
|
|
1512
|
-
const unresolvedRows = originRepository.listUnresolvedBySessionAndContents(sessionId, [...new Set(messages.map((message) => message.content).filter((content) => content.trim().length > 0))]);
|
|
1513
|
-
const unresolvedByContent = new Map();
|
|
1514
|
-
for (const row of unresolvedRows) {
|
|
1515
|
-
const current = unresolvedByContent.get(row.content) ?? [];
|
|
1516
|
-
current.push(row);
|
|
1517
|
-
unresolvedByContent.set(row.content, current);
|
|
1518
|
-
}
|
|
1519
1585
|
return messages.map((message) => {
|
|
1520
1586
|
const resolved = originByMessageId.get(message.messageId) ?? null;
|
|
1521
1587
|
if (resolved) {
|
|
@@ -1532,21 +1598,10 @@ export class SessionHistoryService {
|
|
|
1532
1598
|
originRef: null
|
|
1533
1599
|
};
|
|
1534
1600
|
}
|
|
1535
|
-
const candidates = unresolvedByContent.get(message.content) ?? [];
|
|
1536
|
-
const matched = candidates.find((row) => isMessageAtOrAfter(message.timestamp, row.createdAt)) ?? null;
|
|
1537
|
-
if (!matched) {
|
|
1538
|
-
return {
|
|
1539
|
-
...message,
|
|
1540
|
-
origin: null,
|
|
1541
|
-
originRef: null
|
|
1542
|
-
};
|
|
1543
|
-
}
|
|
1544
|
-
originRepository.resolveMessageId(sessionId, matched.clientRequestId, message.messageId, message.timestamp);
|
|
1545
|
-
unresolvedByContent.set(message.content, candidates.filter((candidate) => candidate.clientRequestId !== matched.clientRequestId));
|
|
1546
1601
|
return {
|
|
1547
1602
|
...message,
|
|
1548
|
-
origin:
|
|
1549
|
-
originRef:
|
|
1603
|
+
origin: null,
|
|
1604
|
+
originRef: null
|
|
1550
1605
|
};
|
|
1551
1606
|
});
|
|
1552
1607
|
}
|
|
@@ -1677,7 +1732,7 @@ export class SessionHistoryService {
|
|
|
1677
1732
|
messages
|
|
1678
1733
|
});
|
|
1679
1734
|
}
|
|
1680
|
-
async syncSessionTitleFromProvider(sessionId, binding) {
|
|
1735
|
+
async syncSessionTitleFromProvider(sessionId, binding, signal) {
|
|
1681
1736
|
const currentIndex = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
|
|
1682
1737
|
if (!currentIndex) {
|
|
1683
1738
|
return;
|
|
@@ -1685,12 +1740,15 @@ export class SessionHistoryService {
|
|
|
1685
1740
|
if (shouldSkipClaudePendingBinding(binding)) {
|
|
1686
1741
|
return;
|
|
1687
1742
|
}
|
|
1743
|
+
if (!shouldSyncSessionTitleFromProvider(binding.provider, currentIndex.title)) {
|
|
1744
|
+
return;
|
|
1745
|
+
}
|
|
1688
1746
|
const nextTitle = (await this.providerDiscoveryHelperClient.readSessionTitle({
|
|
1689
1747
|
config: this.providerSessionDiscoveryConfig,
|
|
1690
1748
|
provider: binding.provider,
|
|
1691
1749
|
providerSessionId: binding.providerSessionId,
|
|
1692
1750
|
rawStoreRef: binding.rawStoreRef
|
|
1693
|
-
})).trim();
|
|
1751
|
+
}, signal)).trim();
|
|
1694
1752
|
const resolvedTitle = resolvePersistedSessionTitle(binding.provider, nextTitle, currentIndex.title);
|
|
1695
1753
|
if (resolvedTitle.length === 0 || resolvedTitle === currentIndex.title) {
|
|
1696
1754
|
return;
|
|
@@ -1893,13 +1951,71 @@ export class SessionHistoryService {
|
|
|
1893
1951
|
return;
|
|
1894
1952
|
}
|
|
1895
1953
|
const inflightKey = `${workspaceId}:${userId}`;
|
|
1896
|
-
|
|
1954
|
+
const refreshState = this.getOrCreateWorkspaceStateRefreshStatus(inflightKey);
|
|
1955
|
+
const now = Date.now();
|
|
1956
|
+
refreshState.lastRequestedAt = now;
|
|
1957
|
+
refreshState.phase = refreshState.phase === "fresh" ? "stale" : refreshState.phase;
|
|
1958
|
+
refreshState.dirtyReasons.add("workspace.discovery.deferred_state_refresh");
|
|
1959
|
+
mergeWorkspaceStateRefreshSessions(refreshState.pendingSessions, sessions);
|
|
1960
|
+
if (refreshState.phase === "running" && refreshState.runningPromise) {
|
|
1961
|
+
return;
|
|
1962
|
+
}
|
|
1963
|
+
if (this.isWorkspaceStateRefreshCoolingDown(refreshState, now)) {
|
|
1964
|
+
refreshState.phase = "stale";
|
|
1965
|
+
this.ensureWorkspaceStateRefreshCooldownTimer(inflightKey, workspaceId, userId, refreshState);
|
|
1897
1966
|
return;
|
|
1898
1967
|
}
|
|
1968
|
+
this.startWorkspaceStateRefreshTask(inflightKey, workspaceId, userId, refreshState);
|
|
1969
|
+
}
|
|
1970
|
+
getOrCreateWorkspaceStateRefreshStatus(key) {
|
|
1971
|
+
const existing = this.workspaceStateRefreshStatuses.get(key);
|
|
1972
|
+
if (existing) {
|
|
1973
|
+
return existing;
|
|
1974
|
+
}
|
|
1975
|
+
const created = {
|
|
1976
|
+
phase: "fresh",
|
|
1977
|
+
dirtyReasons: new Set(),
|
|
1978
|
+
pendingSessions: new Map(),
|
|
1979
|
+
runningPromise: null,
|
|
1980
|
+
cooldownTimer: null,
|
|
1981
|
+
lastRequestedAt: null,
|
|
1982
|
+
lastStartedAt: null,
|
|
1983
|
+
lastCompletedAt: null,
|
|
1984
|
+
lastFailedAt: null,
|
|
1985
|
+
nextAllowedAt: null,
|
|
1986
|
+
runningTaskId: null
|
|
1987
|
+
};
|
|
1988
|
+
this.workspaceStateRefreshStatuses.set(key, created);
|
|
1989
|
+
return created;
|
|
1990
|
+
}
|
|
1991
|
+
isWorkspaceStateRefreshCoolingDown(state, now) {
|
|
1992
|
+
if (state.nextAllowedAt === null || now >= state.nextAllowedAt) {
|
|
1993
|
+
return false;
|
|
1994
|
+
}
|
|
1995
|
+
return state.phase === "cooldown" || state.phase === "failed";
|
|
1996
|
+
}
|
|
1997
|
+
startWorkspaceStateRefreshTask(key, workspaceId, userId, state) {
|
|
1998
|
+
if (state.runningPromise) {
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
const sessions = [...state.pendingSessions.values()];
|
|
2002
|
+
if (sessions.length === 0) {
|
|
2003
|
+
state.phase = "fresh";
|
|
2004
|
+
state.dirtyReasons.clear();
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
2007
|
+
state.pendingSessions.clear();
|
|
2008
|
+
state.phase = "running";
|
|
2009
|
+
state.lastStartedAt = Date.now();
|
|
2010
|
+
state.runningTaskId = `${key}:${++this.workspaceStateRefreshTaskSequence}`;
|
|
1899
2011
|
const startedAt = Date.now();
|
|
1900
2012
|
const task = delay(0)
|
|
1901
2013
|
.then(() => this.refreshRecentSessionStates(sessions, userId))
|
|
1902
2014
|
.then(() => {
|
|
2015
|
+
state.lastCompletedAt = Date.now();
|
|
2016
|
+
state.phase = "cooldown";
|
|
2017
|
+
state.nextAllowedAt = state.lastCompletedAt + WORKSPACE_STATE_REFRESH_COOLDOWN_MS;
|
|
2018
|
+
state.dirtyReasons.clear();
|
|
1903
2019
|
logPerformance("workspace.refresh_recent_session_states", Date.now() - startedAt, {
|
|
1904
2020
|
workspaceId,
|
|
1905
2021
|
refreshedStates: sessions.length
|
|
@@ -1908,6 +2024,9 @@ export class SessionHistoryService {
|
|
|
1908
2024
|
});
|
|
1909
2025
|
})
|
|
1910
2026
|
.catch((error) => {
|
|
2027
|
+
state.lastFailedAt = Date.now();
|
|
2028
|
+
state.phase = "failed";
|
|
2029
|
+
state.nextAllowedAt = state.lastFailedAt + WORKSPACE_STATE_REFRESH_COOLDOWN_MS;
|
|
1911
2030
|
logPerformance("workspace.refresh_recent_session_states.failed", Date.now() - startedAt, {
|
|
1912
2031
|
workspaceId,
|
|
1913
2032
|
refreshedStates: sessions.length,
|
|
@@ -1918,13 +2037,47 @@ export class SessionHistoryService {
|
|
|
1918
2037
|
});
|
|
1919
2038
|
})
|
|
1920
2039
|
.finally(() => {
|
|
1921
|
-
|
|
2040
|
+
state.runningPromise = null;
|
|
2041
|
+
state.runningTaskId = null;
|
|
2042
|
+
if (state.pendingSessions.size === 0) {
|
|
2043
|
+
if (state.phase === "cooldown") {
|
|
2044
|
+
this.ensureWorkspaceStateRefreshCooldownTimer(key, workspaceId, userId, state);
|
|
2045
|
+
return;
|
|
2046
|
+
}
|
|
2047
|
+
if (state.phase === "failed") {
|
|
2048
|
+
this.ensureWorkspaceStateRefreshCooldownTimer(key, workspaceId, userId, state);
|
|
2049
|
+
return;
|
|
2050
|
+
}
|
|
2051
|
+
state.phase = "fresh";
|
|
2052
|
+
return;
|
|
2053
|
+
}
|
|
2054
|
+
state.phase = "stale";
|
|
2055
|
+
this.ensureWorkspaceStateRefreshCooldownTimer(key, workspaceId, userId, state);
|
|
1922
2056
|
});
|
|
1923
|
-
|
|
2057
|
+
state.runningPromise = task;
|
|
2058
|
+
}
|
|
2059
|
+
ensureWorkspaceStateRefreshCooldownTimer(key, workspaceId, userId, state) {
|
|
2060
|
+
if (state.cooldownTimer) {
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
const now = Date.now();
|
|
2064
|
+
const delayMs = Math.max(0, (state.nextAllowedAt ?? now) - now);
|
|
2065
|
+
state.cooldownTimer = setTimeout(() => {
|
|
2066
|
+
state.cooldownTimer = null;
|
|
2067
|
+
if (state.pendingSessions.size === 0) {
|
|
2068
|
+
state.phase = "fresh";
|
|
2069
|
+
state.dirtyReasons.clear();
|
|
2070
|
+
state.nextAllowedAt = null;
|
|
2071
|
+
return;
|
|
2072
|
+
}
|
|
2073
|
+
state.phase = "stale";
|
|
2074
|
+
this.startWorkspaceStateRefreshTask(key, workspaceId, userId, state);
|
|
2075
|
+
}, delayMs);
|
|
1924
2076
|
}
|
|
1925
2077
|
async cleanupStaleHiddenSessions(workspaceId, userId, sessions) {
|
|
1926
2078
|
const discoveredProviderSessionIds = new Set(sessions.map((session) => buildProviderSessionKey(session.provider, session.providerSessionId)));
|
|
1927
2079
|
const discoveredRawStoreRefs = new Set(sessions.map((session) => session.rawStoreRef));
|
|
2080
|
+
const nowMs = Date.now();
|
|
1928
2081
|
const staleHiddenSessions = this.sessionIndexRepository
|
|
1929
2082
|
.listByWorkspace(workspaceId, userId)
|
|
1930
2083
|
.filter((session) => {
|
|
@@ -1936,10 +2089,13 @@ export class SessionHistoryService {
|
|
|
1936
2089
|
}
|
|
1937
2090
|
return ((session.provider === "codex" &&
|
|
1938
2091
|
(isLegacyCodingNsRolloutSession(session.providerSessionId, session.rawStoreRef) ||
|
|
1939
|
-
shouldRemoveMissingSyntheticCodexSession(session.rawStoreRef)
|
|
2092
|
+
(shouldRemoveMissingSyntheticCodexSession(session.rawStoreRef) &&
|
|
2093
|
+
!this.shouldPreserveSyntheticCodexSession(session, nowMs)))) ||
|
|
1940
2094
|
(session.provider === "claude-code" && shouldRemoveHiddenClaudeDebugSession(session)));
|
|
1941
2095
|
});
|
|
1942
|
-
|
|
2096
|
+
const managedButlerSessionIds = this.listManagedButlerSessionIds(staleHiddenSessions.map((session) => session.sessionId));
|
|
2097
|
+
const deletableSessions = staleHiddenSessions.filter((session) => !managedButlerSessionIds.has(session.sessionId));
|
|
2098
|
+
if (deletableSessions.length === 0) {
|
|
1943
2099
|
return;
|
|
1944
2100
|
}
|
|
1945
2101
|
const deleteTransaction = this.db.transaction((ids) => {
|
|
@@ -1947,7 +2103,53 @@ export class SessionHistoryService {
|
|
|
1947
2103
|
this.deleteSessionById(sessionId);
|
|
1948
2104
|
}
|
|
1949
2105
|
});
|
|
1950
|
-
await runBatchedTransactions(
|
|
2106
|
+
await runBatchedTransactions(deletableSessions.map((session) => session.sessionId), WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE, deleteTransaction, {
|
|
2107
|
+
scope: "workspace.discover_sessions.cleanup_hidden.batch",
|
|
2108
|
+
thresholdMs: SESSION_TRANSACTION_HOTSPOT_THRESHOLD_MS,
|
|
2109
|
+
detail: {
|
|
2110
|
+
workspaceId,
|
|
2111
|
+
userId,
|
|
2112
|
+
phase: "cleanup_hidden"
|
|
2113
|
+
}
|
|
2114
|
+
});
|
|
2115
|
+
}
|
|
2116
|
+
listManagedButlerSessionIds(sessionIds) {
|
|
2117
|
+
if (sessionIds.length === 0) {
|
|
2118
|
+
return new Set();
|
|
2119
|
+
}
|
|
2120
|
+
const managedSessionIds = new Set();
|
|
2121
|
+
for (let index = 0; index < sessionIds.length; index += WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE) {
|
|
2122
|
+
const batch = sessionIds.slice(index, index + WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE);
|
|
2123
|
+
const placeholders = batch.map(() => "?").join(", ");
|
|
2124
|
+
const rows = this.db
|
|
2125
|
+
.prepare(`SELECT session_id
|
|
2126
|
+
FROM butler_sessions
|
|
2127
|
+
WHERE session_id IN (${placeholders})`)
|
|
2128
|
+
.all(...batch);
|
|
2129
|
+
for (const row of rows) {
|
|
2130
|
+
managedSessionIds.add(row.session_id);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
return managedSessionIds;
|
|
2134
|
+
}
|
|
2135
|
+
shouldPreserveSyntheticCodexSession(session, nowMs) {
|
|
2136
|
+
if (session.activitySource === "runtime"
|
|
2137
|
+
|| session.runningState === "starting"
|
|
2138
|
+
|| session.runningState === "running") {
|
|
2139
|
+
return true;
|
|
2140
|
+
}
|
|
2141
|
+
const hasActiveRuntimeState = this.listSessionStatesBySessionId(session.sessionId).some((state) => state.activitySource === "runtime"
|
|
2142
|
+
|| state.runningState === "starting"
|
|
2143
|
+
|| state.runningState === "running");
|
|
2144
|
+
if (hasActiveRuntimeState) {
|
|
2145
|
+
return true;
|
|
2146
|
+
}
|
|
2147
|
+
const latestTouchedAt = pickLaterIso(session.updatedAt, session.createdAt) ?? session.updatedAt;
|
|
2148
|
+
const latestTouchedAtMs = Date.parse(latestTouchedAt);
|
|
2149
|
+
if (!Number.isFinite(latestTouchedAtMs)) {
|
|
2150
|
+
return false;
|
|
2151
|
+
}
|
|
2152
|
+
return nowMs - latestTouchedAtMs <= SYNTHETIC_CODEX_SESSION_CLEANUP_GRACE_MS;
|
|
1951
2153
|
}
|
|
1952
2154
|
findSameWorkspaceBindingDuplicate(sessionId, workspaceId, snapshot) {
|
|
1953
2155
|
if (isPendingBindingValue(snapshot.providerSessionId)) {
|
|
@@ -2282,6 +2484,18 @@ export class SessionHistoryService {
|
|
|
2282
2484
|
});
|
|
2283
2485
|
return nextRecord;
|
|
2284
2486
|
}
|
|
2487
|
+
async repairCodexDirtyBindingBeforeHistoryRead(sessionId, userId, binding) {
|
|
2488
|
+
if (!shouldRepairCodexDirtyBinding(binding)) {
|
|
2489
|
+
return binding;
|
|
2490
|
+
}
|
|
2491
|
+
await this.discoverWorkspaceSessions(binding.workspaceId, userId, {
|
|
2492
|
+
force: true,
|
|
2493
|
+
refreshStateMode: "deferred"
|
|
2494
|
+
}).catch(() => {
|
|
2495
|
+
return [];
|
|
2496
|
+
});
|
|
2497
|
+
return this.getBindingOrThrow(sessionId);
|
|
2498
|
+
}
|
|
2285
2499
|
resolveLiveActivityObservation(sessionId) {
|
|
2286
2500
|
for (const resolver of this.liveActivityObservationResolvers) {
|
|
2287
2501
|
const observation = resolver(sessionId);
|
|
@@ -2437,6 +2651,11 @@ function buildSessionStateRefreshCandidates(items, recentCount) {
|
|
|
2437
2651
|
}
|
|
2438
2652
|
return Array.from(deduped.values());
|
|
2439
2653
|
}
|
|
2654
|
+
function mergeWorkspaceStateRefreshSessions(target, sessions) {
|
|
2655
|
+
for (const session of sessions) {
|
|
2656
|
+
target.set(session.sessionId, session);
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2440
2659
|
function isSessionStateRefreshCandidate(item) {
|
|
2441
2660
|
return item.activityState === "running"
|
|
2442
2661
|
|| item.runningState === "starting"
|
|
@@ -2514,12 +2733,20 @@ function mergeSessionIndexRecord(input) {
|
|
|
2514
2733
|
subagentLabel: input.target?.subagentLabel ?? input.source?.subagentLabel ?? null,
|
|
2515
2734
|
title: pickPreferredSessionTitle(input.target?.title ?? null, input.source?.title ?? null),
|
|
2516
2735
|
messageCount: Math.max(input.target?.messageCount ?? 0, input.source?.messageCount ?? 0),
|
|
2517
|
-
isArchived:
|
|
2736
|
+
isArchived: mergePersistedArchiveState(input.provider, input.target?.isArchived, input.source?.isArchived),
|
|
2518
2737
|
lastMessageAt: pickLaterIso(input.target?.lastMessageAt ?? null, input.source?.lastMessageAt ?? null),
|
|
2519
2738
|
createdAt: pickEarlierIso(input.target?.createdAt ?? null, input.source?.createdAt ?? null) ?? input.timestamp,
|
|
2520
2739
|
updatedAt: input.timestamp
|
|
2521
2740
|
};
|
|
2522
2741
|
}
|
|
2742
|
+
function mergePersistedArchiveState(provider, targetArchived, sourceArchived) {
|
|
2743
|
+
// 只有 Codex 这类真实支持归档的 provider 才认底层归档真相;
|
|
2744
|
+
// 其他 provider 的归档完全由 Host 本地索引维护,不能让旧副本把恢复状态再刷回去。
|
|
2745
|
+
if (shouldUseProviderDiscoveredArchiveState(provider)) {
|
|
2746
|
+
return Boolean(targetArchived || sourceArchived);
|
|
2747
|
+
}
|
|
2748
|
+
return targetArchived ?? sourceArchived ?? false;
|
|
2749
|
+
}
|
|
2523
2750
|
function mergeSessionStatusSnapshot(input) {
|
|
2524
2751
|
if (!input.target && !input.source) {
|
|
2525
2752
|
return null;
|
|
@@ -2820,12 +3047,16 @@ function isCloseClaudeSessionTimestamp(left, right, maxGapMs = 2 * 60 * 1000) {
|
|
|
2820
3047
|
}
|
|
2821
3048
|
return Math.abs(leftAt - rightAt) <= maxGapMs;
|
|
2822
3049
|
}
|
|
2823
|
-
function resolveDiscoveredArchiveState(existingArchived, discoveredArchived) {
|
|
2824
|
-
if (
|
|
2825
|
-
return
|
|
3050
|
+
function resolveDiscoveredArchiveState(provider, existingArchived, discoveredArchived) {
|
|
3051
|
+
if (!shouldUseProviderDiscoveredArchiveState(provider)) {
|
|
3052
|
+
return existingArchived;
|
|
2826
3053
|
}
|
|
2827
3054
|
return discoveredArchived === true;
|
|
2828
3055
|
}
|
|
3056
|
+
function shouldUseProviderDiscoveredArchiveState(provider) {
|
|
3057
|
+
// 当前只有 Codex 的归档能稳定映射到底层文件位置;其余 provider 一律信本地 session_indices。
|
|
3058
|
+
return provider === "codex";
|
|
3059
|
+
}
|
|
2829
3060
|
function isMessageAtOrAfter(timestamp, minTimestamp) {
|
|
2830
3061
|
if (!minTimestamp) {
|
|
2831
3062
|
return true;
|
|
@@ -2945,6 +3176,48 @@ function isLegacyCodingNsRolloutSession(providerSessionId, rawStoreRef) {
|
|
|
2945
3176
|
function shouldRemoveMissingSyntheticCodexSession(rawStoreRef) {
|
|
2946
3177
|
return isSyntheticCodexRawStoreRef(rawStoreRef) && !existsSync(rawStoreRef);
|
|
2947
3178
|
}
|
|
3179
|
+
function shouldRepairCodexDirtyBinding(binding) {
|
|
3180
|
+
if (binding.provider !== "codex") {
|
|
3181
|
+
return false;
|
|
3182
|
+
}
|
|
3183
|
+
if (isSyntheticCodexRawStoreRef(binding.rawStoreRef)) {
|
|
3184
|
+
return false;
|
|
3185
|
+
}
|
|
3186
|
+
const expectedThreadId = binding.providerSessionId.trim();
|
|
3187
|
+
if (!expectedThreadId) {
|
|
3188
|
+
return false;
|
|
3189
|
+
}
|
|
3190
|
+
const boundThreadId = readCodexThreadIdFromRawStore(binding.rawStoreRef);
|
|
3191
|
+
if (boundThreadId) {
|
|
3192
|
+
return boundThreadId !== expectedThreadId;
|
|
3193
|
+
}
|
|
3194
|
+
return !existsSync(binding.rawStoreRef);
|
|
3195
|
+
}
|
|
3196
|
+
function readCodexThreadIdFromRawStore(filePath) {
|
|
3197
|
+
if (!existsSync(filePath)) {
|
|
3198
|
+
return null;
|
|
3199
|
+
}
|
|
3200
|
+
try {
|
|
3201
|
+
const firstLine = readFileSync(filePath, "utf8")
|
|
3202
|
+
.split(/\r?\n/, 1)
|
|
3203
|
+
.at(0)
|
|
3204
|
+
?.trim();
|
|
3205
|
+
if (!firstLine) {
|
|
3206
|
+
return null;
|
|
3207
|
+
}
|
|
3208
|
+
const record = JSON.parse(firstLine);
|
|
3209
|
+
if (record.type !== "session_meta") {
|
|
3210
|
+
return null;
|
|
3211
|
+
}
|
|
3212
|
+
const threadId = typeof record.payload?.id === "string"
|
|
3213
|
+
? record.payload.id.trim()
|
|
3214
|
+
: "";
|
|
3215
|
+
return threadId.length > 0 ? threadId : null;
|
|
3216
|
+
}
|
|
3217
|
+
catch {
|
|
3218
|
+
return null;
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
2948
3221
|
function shouldMatchSessionBindingByRawStoreRef(provider) {
|
|
2949
3222
|
return provider !== "codex";
|
|
2950
3223
|
}
|
|
@@ -3016,6 +3289,16 @@ function isSyntheticCodexSessionTitle(title) {
|
|
|
3016
3289
|
return (/^rollout-\d{4}-\d{2}-\d{2}t/i.test(title) ||
|
|
3017
3290
|
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(title));
|
|
3018
3291
|
}
|
|
3292
|
+
function shouldSyncSessionTitleFromProvider(provider, currentTitle) {
|
|
3293
|
+
const normalizedTitle = currentTitle?.trim() ?? "";
|
|
3294
|
+
if (normalizedTitle.length === 0) {
|
|
3295
|
+
return true;
|
|
3296
|
+
}
|
|
3297
|
+
if (provider === "codex" && isSyntheticCodexSessionTitle(normalizedTitle)) {
|
|
3298
|
+
return true;
|
|
3299
|
+
}
|
|
3300
|
+
return false;
|
|
3301
|
+
}
|
|
3019
3302
|
function shouldRemoveHiddenClaudeDebugSession(session) {
|
|
3020
3303
|
const normalizedRawStoreRef = session.rawStoreRef.replaceAll("\\", "/");
|
|
3021
3304
|
if (normalizedRawStoreRef.includes("/subagents/")) {
|
|
@@ -3134,13 +3417,14 @@ function buildReconstructedForkPrompt(input) {
|
|
|
3134
3417
|
function buildProviderCapabilityCacheKey(provider, workspacePath) {
|
|
3135
3418
|
return `${provider}::${workspacePath ?? ""}`;
|
|
3136
3419
|
}
|
|
3137
|
-
async function runWithConcurrency(items, concurrency, worker) {
|
|
3420
|
+
async function runWithConcurrency(items, concurrency, worker, signal) {
|
|
3138
3421
|
const normalizedConcurrency = Math.max(1, Math.floor(concurrency) || 1);
|
|
3139
3422
|
const queue = [...items];
|
|
3140
3423
|
const runners = Array.from({
|
|
3141
3424
|
length: Math.min(normalizedConcurrency, queue.length || 1)
|
|
3142
3425
|
}, async () => {
|
|
3143
3426
|
while (queue.length > 0) {
|
|
3427
|
+
throwIfAborted(signal);
|
|
3144
3428
|
const current = queue.shift();
|
|
3145
3429
|
if (current === undefined) {
|
|
3146
3430
|
return;
|
|
@@ -3150,15 +3434,129 @@ async function runWithConcurrency(items, concurrency, worker) {
|
|
|
3150
3434
|
});
|
|
3151
3435
|
await Promise.all(runners);
|
|
3152
3436
|
}
|
|
3153
|
-
|
|
3437
|
+
function throwIfAborted(signal) {
|
|
3438
|
+
if (signal?.aborted) {
|
|
3439
|
+
throw signal.reason ?? new Error("任务已取消");
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
async function awaitTaskHandleWithSignal(handle, signal) {
|
|
3443
|
+
if (!signal) {
|
|
3444
|
+
return await handle.promise;
|
|
3445
|
+
}
|
|
3446
|
+
if (signal.aborted) {
|
|
3447
|
+
handle.cancel(getAbortMessage(signal.reason));
|
|
3448
|
+
throw signal.reason ?? new Error("任务已取消");
|
|
3449
|
+
}
|
|
3450
|
+
return await new Promise((resolve, reject) => {
|
|
3451
|
+
const onAbort = () => {
|
|
3452
|
+
handle.cancel(getAbortMessage(signal.reason));
|
|
3453
|
+
reject(signal.reason ?? new Error("任务已取消"));
|
|
3454
|
+
};
|
|
3455
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
3456
|
+
handle.promise.then((value) => {
|
|
3457
|
+
signal.removeEventListener("abort", onAbort);
|
|
3458
|
+
resolve(value);
|
|
3459
|
+
}, (error) => {
|
|
3460
|
+
signal.removeEventListener("abort", onAbort);
|
|
3461
|
+
reject(error);
|
|
3462
|
+
});
|
|
3463
|
+
});
|
|
3464
|
+
}
|
|
3465
|
+
function getAbortMessage(reason) {
|
|
3466
|
+
if (reason instanceof Error && reason.message.trim().length > 0) {
|
|
3467
|
+
return reason.message;
|
|
3468
|
+
}
|
|
3469
|
+
if (typeof reason === "string" && reason.trim().length > 0) {
|
|
3470
|
+
return reason;
|
|
3471
|
+
}
|
|
3472
|
+
return "任务已取消";
|
|
3473
|
+
}
|
|
3474
|
+
function areEquivalentSessionBindings(current, next) {
|
|
3475
|
+
if (!current) {
|
|
3476
|
+
return false;
|
|
3477
|
+
}
|
|
3478
|
+
return (current.sessionId === next.sessionId &&
|
|
3479
|
+
current.workspaceId === next.workspaceId &&
|
|
3480
|
+
current.provider === next.provider &&
|
|
3481
|
+
current.providerSessionId === next.providerSessionId &&
|
|
3482
|
+
current.rawStoreRef === next.rawStoreRef &&
|
|
3483
|
+
current.createdAt === next.createdAt);
|
|
3484
|
+
}
|
|
3485
|
+
function areEquivalentSessionIndexRecords(current, next) {
|
|
3486
|
+
if (!current) {
|
|
3487
|
+
return false;
|
|
3488
|
+
}
|
|
3489
|
+
return (current.sessionId === next.sessionId &&
|
|
3490
|
+
current.workspaceId === next.workspaceId &&
|
|
3491
|
+
current.provider === next.provider &&
|
|
3492
|
+
(current.parentSessionId ?? null) === (next.parentSessionId ?? null) &&
|
|
3493
|
+
(current.sessionKind ?? "default") === (next.sessionKind ?? "default") &&
|
|
3494
|
+
(current.annotationSourceMessageId ?? null) === (next.annotationSourceMessageId ?? null) &&
|
|
3495
|
+
(current.annotationSourceText ?? null) === (next.annotationSourceText ?? null) &&
|
|
3496
|
+
(current.isSubagent ?? false) === (next.isSubagent ?? false) &&
|
|
3497
|
+
(current.subagentLabel ?? null) === (next.subagentLabel ?? null) &&
|
|
3498
|
+
current.title === next.title &&
|
|
3499
|
+
current.messageCount === next.messageCount &&
|
|
3500
|
+
current.isArchived === next.isArchived &&
|
|
3501
|
+
(current.lastMessageAt ?? null) === (next.lastMessageAt ?? null) &&
|
|
3502
|
+
current.createdAt === next.createdAt);
|
|
3503
|
+
}
|
|
3504
|
+
function areEquivalentSessionStatusSnapshots(current, next) {
|
|
3505
|
+
if (!current) {
|
|
3506
|
+
return false;
|
|
3507
|
+
}
|
|
3508
|
+
return (current.sessionId === next.sessionId &&
|
|
3509
|
+
current.syncStatus === next.syncStatus &&
|
|
3510
|
+
(current.syncCursor ?? null) === (next.syncCursor ?? null) &&
|
|
3511
|
+
(current.lastSyncAt ?? null) === (next.lastSyncAt ?? null) &&
|
|
3512
|
+
(current.lastErrorCode ?? null) === (next.lastErrorCode ?? null) &&
|
|
3513
|
+
(current.lastErrorDetail ?? null) === (next.lastErrorDetail ?? null) &&
|
|
3514
|
+
(current.resumedAt ?? null) === (next.resumedAt ?? null));
|
|
3515
|
+
}
|
|
3516
|
+
function isSqliteBusyError(error) {
|
|
3517
|
+
if (!error || typeof error !== "object") {
|
|
3518
|
+
return false;
|
|
3519
|
+
}
|
|
3520
|
+
const sqliteCode = "code" in error ? error.code : null;
|
|
3521
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3522
|
+
return sqliteCode === "SQLITE_BUSY" || message.includes("database is locked");
|
|
3523
|
+
}
|
|
3524
|
+
async function runBatchedTransactions(items, batchSize, transaction, logOptions) {
|
|
3154
3525
|
const normalizedBatchSize = Math.max(1, Math.floor(batchSize) || 1);
|
|
3155
3526
|
let batchCount = 0;
|
|
3156
3527
|
let maxBatchMs = 0;
|
|
3157
3528
|
for (let index = 0; index < items.length; index += normalizedBatchSize) {
|
|
3158
3529
|
const batch = items.slice(index, index + normalizedBatchSize);
|
|
3159
3530
|
const batchStartedAt = Date.now();
|
|
3160
|
-
|
|
3531
|
+
let retryCount = 0;
|
|
3532
|
+
while (true) {
|
|
3533
|
+
try {
|
|
3534
|
+
transaction(batch);
|
|
3535
|
+
break;
|
|
3536
|
+
}
|
|
3537
|
+
catch (error) {
|
|
3538
|
+
if (!isSqliteBusyError(error) || retryCount >= SQLITE_BUSY_RETRY_LIMIT) {
|
|
3539
|
+
throw error;
|
|
3540
|
+
}
|
|
3541
|
+
retryCount += 1;
|
|
3542
|
+
await delay(SQLITE_BUSY_RETRY_DELAY_MS * retryCount);
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3161
3545
|
const batchDurationMs = Date.now() - batchStartedAt;
|
|
3546
|
+
const nextBatchIndex = batchCount + 1;
|
|
3547
|
+
if (logOptions) {
|
|
3548
|
+
logPerformance(logOptions.scope, batchDurationMs, {
|
|
3549
|
+
...logOptions.detail,
|
|
3550
|
+
batchIndex: nextBatchIndex,
|
|
3551
|
+
batchSize: batch.length,
|
|
3552
|
+
batchStartIndex: index,
|
|
3553
|
+
retryCount,
|
|
3554
|
+
totalItems: items.length,
|
|
3555
|
+
configuredBatchSize: normalizedBatchSize
|
|
3556
|
+
}, {
|
|
3557
|
+
thresholdMs: logOptions.thresholdMs ?? SESSION_TRANSACTION_HOTSPOT_THRESHOLD_MS
|
|
3558
|
+
});
|
|
3559
|
+
}
|
|
3162
3560
|
batchCount += 1;
|
|
3163
3561
|
maxBatchMs = Math.max(maxBatchMs, batchDurationMs);
|
|
3164
3562
|
if (index + normalizedBatchSize < items.length) {
|