devchain-cli 0.23.1 → 0.23.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/drizzle/0083_retire_documents.sql +2 -0
- package/dist/drizzle/0084_retire_codebase_overview.sql +1 -0
- package/dist/drizzle/0085_clumsy_zodiak.sql +166 -0
- package/dist/drizzle/meta/0083_snapshot.json +7422 -0
- package/dist/drizzle/meta/0084_snapshot.json +7422 -0
- package/dist/drizzle/meta/0085_snapshot.json +7446 -0
- package/dist/drizzle/meta/_journal.json +21 -0
- package/dist/server/app.main.module.js +0 -4
- package/dist/server/app.main.module.js.map +1 -1
- package/dist/server/app.normal.module.js +0 -4
- package/dist/server/app.normal.module.js.map +1 -1
- package/dist/server/modules/epic-time/controllers/external-estimate-log.controller.d.ts +1 -0
- package/dist/server/modules/epic-time/controllers/external-estimate-log.controller.js +48 -0
- package/dist/server/modules/epic-time/controllers/external-estimate-log.controller.js.map +1 -1
- package/dist/server/modules/epic-time/models/epic-time.models.d.ts +10 -0
- package/dist/server/modules/epic-time/services/epic-estimate-logging.service.d.ts +4 -1
- package/dist/server/modules/epic-time/services/epic-estimate-logging.service.js +43 -1
- package/dist/server/modules/epic-time/services/epic-estimate-logging.service.js.map +1 -1
- package/dist/server/modules/epics/subscribers/epic-assignment-notifier.subscriber.d.ts +1 -0
- package/dist/server/modules/epics/subscribers/epic-assignment-notifier.subscriber.js +31 -1
- package/dist/server/modules/epics/subscribers/epic-assignment-notifier.subscriber.js.map +1 -1
- package/dist/server/modules/external-integrations/connections/stable-integration-connection.d.ts +14 -0
- package/dist/server/modules/external-integrations/connections/stable-integration-connection.js +56 -0
- package/dist/server/modules/external-integrations/connections/stable-integration-connection.js.map +1 -0
- package/dist/server/modules/external-integrations/my-work/external-edit-session.service.d.ts +2 -1
- package/dist/server/modules/external-integrations/my-work/external-edit-session.service.js +51 -33
- package/dist/server/modules/external-integrations/my-work/external-edit-session.service.js.map +1 -1
- package/dist/server/modules/external-integrations/my-work/external-my-work.controller.js +2 -2
- package/dist/server/modules/external-integrations/my-work/external-my-work.controller.js.map +1 -1
- package/dist/server/modules/external-integrations/my-work/external-my-work.openapi.d.ts +1 -0
- package/dist/server/modules/external-integrations/my-work/external-my-work.openapi.js +33 -1
- package/dist/server/modules/external-integrations/my-work/external-my-work.openapi.js.map +1 -1
- package/dist/server/modules/external-integrations/my-work/external-my-work.service.d.ts +1 -1
- package/dist/server/modules/external-integrations/my-work/external-my-work.service.js +41 -48
- package/dist/server/modules/external-integrations/my-work/external-my-work.service.js.map +1 -1
- package/dist/server/modules/external-integrations/my-work/external-time-mutation.service.js +24 -11
- package/dist/server/modules/external-integrations/my-work/external-time-mutation.service.js.map +1 -1
- package/dist/server/modules/external-integrations/sessions/external-edit-session.store.js +3 -1
- package/dist/server/modules/external-integrations/sessions/external-edit-session.store.js.map +1 -1
- package/dist/server/modules/mcp/constants.d.ts +0 -2
- package/dist/server/modules/mcp/constants.js +1 -11
- package/dist/server/modules/mcp/constants.js.map +1 -1
- package/dist/server/modules/mcp/dtos/mcp.dto.d.ts +0 -165
- package/dist/server/modules/mcp/dtos/mcp.dto.js +1 -47
- package/dist/server/modules/mcp/dtos/mcp.dto.js.map +1 -1
- package/dist/server/modules/mcp/services/instructions-resolver.d.ts +4 -13
- package/dist/server/modules/mcp/services/instructions-resolver.js +26 -166
- package/dist/server/modules/mcp/services/instructions-resolver.js.map +1 -1
- package/dist/server/modules/mcp/services/mappers/dto-mappers.d.ts +2 -4
- package/dist/server/modules/mcp/services/mappers/dto-mappers.js +0 -22
- package/dist/server/modules/mcp/services/mappers/dto-mappers.js.map +1 -1
- package/dist/server/modules/mcp/services/mcp-tool-binding.registry.js +1 -2
- package/dist/server/modules/mcp/services/mcp-tool-binding.registry.js.map +1 -1
- package/dist/server/modules/mcp/services/utils/resource-resolver.d.ts +2 -4
- package/dist/server/modules/mcp/services/utils/resource-resolver.js +0 -59
- package/dist/server/modules/mcp/services/utils/resource-resolver.js.map +1 -1
- package/dist/server/modules/mcp/tool-descriptors/index.js +0 -2
- package/dist/server/modules/mcp/tool-descriptors/index.js.map +1 -1
- package/dist/server/modules/mcp/tool-descriptors/runtime-bindings.js +0 -2
- package/dist/server/modules/mcp/tool-descriptors/runtime-bindings.js.map +1 -1
- package/dist/server/modules/projects/helpers/project-import.d.ts +0 -33
- package/dist/server/modules/projects/helpers/project-import.js +0 -224
- package/dist/server/modules/projects/helpers/project-import.js.map +1 -1
- package/dist/server/modules/projects/services/project-registry-import.service.d.ts +3 -6
- package/dist/server/modules/projects/services/project-registry-import.service.js +7 -28
- package/dist/server/modules/projects/services/project-registry-import.service.js.map +1 -1
- package/dist/server/modules/projects/template-codec/codecs/teams.codec.js.map +1 -1
- package/dist/server/modules/registry/controllers/registry.controller.js +3 -10
- package/dist/server/modules/registry/controllers/registry.controller.js.map +1 -1
- package/dist/server/modules/registry/services/registry-orchestration.service.d.ts +11 -3
- package/dist/server/modules/registry/services/registry-orchestration.service.js +6 -1
- package/dist/server/modules/registry/services/registry-orchestration.service.js.map +1 -1
- package/dist/server/modules/session-reader/adapters/claude-session-reader.adapter.d.ts +8 -0
- package/dist/server/modules/session-reader/adapters/claude-session-reader.adapter.js +9 -0
- package/dist/server/modules/session-reader/adapters/claude-session-reader.adapter.js.map +1 -1
- package/dist/server/modules/session-reader/adapters/codex-session-reader.adapter.d.ts +10 -0
- package/dist/server/modules/session-reader/adapters/codex-session-reader.adapter.js +13 -0
- package/dist/server/modules/session-reader/adapters/codex-session-reader.adapter.js.map +1 -1
- package/dist/server/modules/session-reader/adapters/session-reader-adapter.interface.d.ts +20 -1
- package/dist/server/modules/session-reader/adapters/session-reader-adapter.interface.js.map +1 -1
- package/dist/server/modules/session-reader/controllers/session-reader.controller.d.ts +21 -1
- package/dist/server/modules/session-reader/controllers/session-reader.controller.js +49 -3
- package/dist/server/modules/session-reader/controllers/session-reader.controller.js.map +1 -1
- package/dist/server/modules/session-reader/data/pricing.json +2052 -178
- package/dist/server/modules/session-reader/parsers/bounded-line-read.d.ts +2 -0
- package/dist/server/modules/session-reader/parsers/bounded-line-read.js +66 -0
- package/dist/server/modules/session-reader/parsers/bounded-line-read.js.map +1 -0
- package/dist/server/modules/session-reader/parsers/claude-jsonl.parser.d.ts +6 -0
- package/dist/server/modules/session-reader/parsers/claude-jsonl.parser.js +44 -13
- package/dist/server/modules/session-reader/parsers/claude-jsonl.parser.js.map +1 -1
- package/dist/server/modules/session-reader/parsers/codex-jsonl.parser.d.ts +22 -0
- package/dist/server/modules/session-reader/parsers/codex-jsonl.parser.js +61 -16
- package/dist/server/modules/session-reader/parsers/codex-jsonl.parser.js.map +1 -1
- package/dist/server/modules/session-reader/services/bounded-anchor-proof.d.ts +12 -0
- package/dist/server/modules/session-reader/services/bounded-anchor-proof.js +103 -0
- package/dist/server/modules/session-reader/services/bounded-anchor-proof.js.map +1 -0
- package/dist/server/modules/session-reader/services/metrics-merge.d.ts +17 -0
- package/dist/server/modules/session-reader/services/metrics-merge.js +114 -0
- package/dist/server/modules/session-reader/services/metrics-merge.js.map +1 -0
- package/dist/server/modules/session-reader/services/session-cache.service.d.ts +40 -4
- package/dist/server/modules/session-reader/services/session-cache.service.js +232 -148
- package/dist/server/modules/session-reader/services/session-cache.service.js.map +1 -1
- package/dist/server/modules/session-reader/services/session-reader.service.d.ts +15 -1
- package/dist/server/modules/session-reader/services/session-reader.service.js +70 -2
- package/dist/server/modules/session-reader/services/session-reader.service.js.map +1 -1
- package/dist/server/modules/session-reader/services/transcript-watcher.service.d.ts +20 -3
- package/dist/server/modules/session-reader/services/transcript-watcher.service.js +538 -197
- package/dist/server/modules/session-reader/services/transcript-watcher.service.js.map +1 -1
- package/dist/server/modules/sessions/controllers/sessions.controller.d.ts +4 -1
- package/dist/server/modules/sessions/controllers/sessions.controller.js +32 -0
- package/dist/server/modules/sessions/controllers/sessions.controller.js.map +1 -1
- package/dist/server/modules/sessions/services/message-pool.types.d.ts +23 -0
- package/dist/server/modules/sessions/services/sessions-message-pool.service.d.ts +5 -2
- package/dist/server/modules/sessions/services/sessions-message-pool.service.js +82 -4
- package/dist/server/modules/sessions/services/sessions-message-pool.service.js.map +1 -1
- package/dist/server/modules/storage/db/schema.d.ts +57 -219
- package/dist/server/modules/storage/db/schema.js +15 -27
- package/dist/server/modules/storage/db/schema.js.map +1 -1
- package/dist/server/modules/storage/interfaces/storage.interface.d.ts +6 -23
- package/dist/server/modules/storage/interfaces/storage.interface.js.map +1 -1
- package/dist/server/modules/storage/local/delegates/external-estimate-log.delegate.d.ts +6 -1
- package/dist/server/modules/storage/local/delegates/external-estimate-log.delegate.js +163 -32
- package/dist/server/modules/storage/local/delegates/external-estimate-log.delegate.js.map +1 -1
- package/dist/server/modules/storage/local/delegates/external-managed-subtask.delegate.js +11 -1
- package/dist/server/modules/storage/local/delegates/external-managed-subtask.delegate.js.map +1 -1
- package/dist/server/modules/storage/local/delegates/integration.delegate.d.ts +1 -1
- package/dist/server/modules/storage/local/delegates/integration.delegate.js +17 -13
- package/dist/server/modules/storage/local/delegates/integration.delegate.js.map +1 -1
- package/dist/server/modules/storage/local/delegates/project.delegate.js +1 -10
- package/dist/server/modules/storage/local/delegates/project.delegate.js.map +1 -1
- package/dist/server/modules/storage/local/local-storage.service.d.ts +6 -9
- package/dist/server/modules/storage/local/local-storage.service.js +8 -22
- package/dist/server/modules/storage/local/local-storage.service.js.map +1 -1
- package/dist/server/modules/storage/models/domain.models.d.ts +14 -20
- package/dist/server/modules/storage/models/domain.models.js +2 -1
- package/dist/server/modules/storage/models/domain.models.js.map +1 -1
- package/dist/server/modules/terminal/services/human-prompt-state.service.d.ts +7 -0
- package/dist/server/modules/terminal/services/human-prompt-state.service.js +26 -0
- package/dist/server/modules/terminal/services/human-prompt-state.service.js.map +1 -1
- package/dist/server/modules/terminal/services/terminal-session/terminal-session.js +3 -2
- package/dist/server/modules/terminal/services/terminal-session/terminal-session.js.map +1 -1
- package/dist/server/modules/terminal/utils/terminal-activity.js +8 -1
- package/dist/server/modules/terminal/utils/terminal-activity.js.map +1 -1
- package/dist/server/templates/teams-dev.json +2 -2
- package/dist/server/tsconfig.tsbuildinfo +1 -1
- package/dist/server/ui/assets/{ExternalRichEditor-FwCAalXO.js → ExternalRichEditor-akgwTdms.js} +24 -24
- package/dist/server/ui/assets/ReviewDetailPage-10JU-Gdr.js +1 -0
- package/dist/server/ui/assets/ReviewsPage-BIVn-pS5.js +6 -0
- package/dist/server/ui/assets/index-Bg8fZbSZ.css +32 -0
- package/dist/server/ui/assets/index-DHmiffN1.js +1000 -0
- package/dist/server/ui/assets/useReviewSubscription-Xua63feT.js +69 -0
- package/dist/server/ui/index.html +2 -2
- package/dist/templates/teams-dev.json +2 -2
- package/package.json +13 -1
- package/dist/server/modules/codebase-overview-analyzer/codebase-overview-analyzer.module.d.ts +0 -2
- package/dist/server/modules/codebase-overview-analyzer/codebase-overview-analyzer.module.js +0 -52
- package/dist/server/modules/codebase-overview-analyzer/codebase-overview-analyzer.module.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/controllers/codebase-overview.controller.d.ts +0 -30
- package/dist/server/modules/codebase-overview-analyzer/controllers/codebase-overview.controller.js +0 -230
- package/dist/server/modules/codebase-overview-analyzer/controllers/codebase-overview.controller.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/repositories/overview-scope.repository.d.ts +0 -24
- package/dist/server/modules/codebase-overview-analyzer/repositories/overview-scope.repository.js +0 -191
- package/dist/server/modules/codebase-overview-analyzer/repositories/overview-scope.repository.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/services/codebase-overview-analyzer.service.d.ts +0 -73
- package/dist/server/modules/codebase-overview-analyzer/services/codebase-overview-analyzer.service.js +0 -1133
- package/dist/server/modules/codebase-overview-analyzer/services/codebase-overview-analyzer.service.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/services/dependency-aggregation.service.d.ts +0 -14
- package/dist/server/modules/codebase-overview-analyzer/services/dependency-aggregation.service.js +0 -114
- package/dist/server/modules/codebase-overview-analyzer/services/dependency-aggregation.service.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/services/district-splitting.service.d.ts +0 -8
- package/dist/server/modules/codebase-overview-analyzer/services/district-splitting.service.js +0 -200
- package/dist/server/modules/codebase-overview-analyzer/services/district-splitting.service.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/services/evidence-query.service.d.ts +0 -11
- package/dist/server/modules/codebase-overview-analyzer/services/evidence-query.service.js +0 -164
- package/dist/server/modules/codebase-overview-analyzer/services/evidence-query.service.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/services/go-adapter.d.ts +0 -2
- package/dist/server/modules/codebase-overview-analyzer/services/go-adapter.js +0 -166
- package/dist/server/modules/codebase-overview-analyzer/services/go-adapter.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/services/hotspot-scoring.service.d.ts +0 -21
- package/dist/server/modules/codebase-overview-analyzer/services/hotspot-scoring.service.js +0 -253
- package/dist/server/modules/codebase-overview-analyzer/services/hotspot-scoring.service.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/services/identity-resolver.service.d.ts +0 -24
- package/dist/server/modules/codebase-overview-analyzer/services/identity-resolver.service.js +0 -98
- package/dist/server/modules/codebase-overview-analyzer/services/identity-resolver.service.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/services/java-adapter.d.ts +0 -2
- package/dist/server/modules/codebase-overview-analyzer/services/java-adapter.js +0 -155
- package/dist/server/modules/codebase-overview-analyzer/services/java-adapter.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/services/language-adapter-registry.service.d.ts +0 -19
- package/dist/server/modules/codebase-overview-analyzer/services/language-adapter-registry.service.js +0 -88
- package/dist/server/modules/codebase-overview-analyzer/services/language-adapter-registry.service.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/services/php-adapter.d.ts +0 -2
- package/dist/server/modules/codebase-overview-analyzer/services/php-adapter.js +0 -173
- package/dist/server/modules/codebase-overview-analyzer/services/php-adapter.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/services/python-adapter.d.ts +0 -2
- package/dist/server/modules/codebase-overview-analyzer/services/python-adapter.js +0 -171
- package/dist/server/modules/codebase-overview-analyzer/services/python-adapter.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/services/ruby-adapter.d.ts +0 -2
- package/dist/server/modules/codebase-overview-analyzer/services/ruby-adapter.js +0 -158
- package/dist/server/modules/codebase-overview-analyzer/services/ruby-adapter.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/services/rust-adapter.d.ts +0 -2
- package/dist/server/modules/codebase-overview-analyzer/services/rust-adapter.js +0 -222
- package/dist/server/modules/codebase-overview-analyzer/services/rust-adapter.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/services/scope-auto-detector.service.d.ts +0 -4
- package/dist/server/modules/codebase-overview-analyzer/services/scope-auto-detector.service.js +0 -72
- package/dist/server/modules/codebase-overview-analyzer/services/scope-auto-detector.service.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/services/scope-resolver.service.d.ts +0 -6
- package/dist/server/modules/codebase-overview-analyzer/services/scope-resolver.service.js +0 -37
- package/dist/server/modules/codebase-overview-analyzer/services/scope-resolver.service.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/services/typescript-adapter.d.ts +0 -2
- package/dist/server/modules/codebase-overview-analyzer/services/typescript-adapter.js +0 -108
- package/dist/server/modules/codebase-overview-analyzer/services/typescript-adapter.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/types/index.d.ts +0 -2
- package/dist/server/modules/codebase-overview-analyzer/types/index.js +0 -6
- package/dist/server/modules/codebase-overview-analyzer/types/index.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/types/overview.types.d.ts +0 -231
- package/dist/server/modules/codebase-overview-analyzer/types/overview.types.js +0 -3
- package/dist/server/modules/codebase-overview-analyzer/types/overview.types.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/types/scope-defaults.d.ts +0 -2
- package/dist/server/modules/codebase-overview-analyzer/types/scope-defaults.js +0 -13
- package/dist/server/modules/codebase-overview-analyzer/types/scope-defaults.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/types/scope.schema.d.ts +0 -54
- package/dist/server/modules/codebase-overview-analyzer/types/scope.schema.js +0 -27
- package/dist/server/modules/codebase-overview-analyzer/types/scope.schema.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/types/scope.types.d.ts +0 -12
- package/dist/server/modules/codebase-overview-analyzer/types/scope.types.js +0 -3
- package/dist/server/modules/codebase-overview-analyzer/types/scope.types.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/utils/constants.d.ts +0 -1
- package/dist/server/modules/codebase-overview-analyzer/utils/constants.js +0 -5
- package/dist/server/modules/codebase-overview-analyzer/utils/constants.js.map +0 -1
- package/dist/server/modules/codebase-overview-analyzer/utils/path-matcher.d.ts +0 -2
- package/dist/server/modules/codebase-overview-analyzer/utils/path-matcher.js +0 -17
- package/dist/server/modules/codebase-overview-analyzer/utils/path-matcher.js.map +0 -1
- package/dist/server/modules/documents/controllers/documents.controller.d.ts +0 -12
- package/dist/server/modules/documents/controllers/documents.controller.js +0 -174
- package/dist/server/modules/documents/controllers/documents.controller.js.map +0 -1
- package/dist/server/modules/documents/documents.module.d.ts +0 -2
- package/dist/server/modules/documents/documents.module.js +0 -22
- package/dist/server/modules/documents/documents.module.js.map +0 -1
- package/dist/server/modules/mcp/services/handlers/document-context.d.ts +0 -8
- package/dist/server/modules/mcp/services/handlers/document-context.js +0 -3
- package/dist/server/modules/mcp/services/handlers/document-context.js.map +0 -1
- package/dist/server/modules/mcp/services/handlers/document-tools.d.ts +0 -6
- package/dist/server/modules/mcp/services/handlers/document-tools.js +0 -109
- package/dist/server/modules/mcp/services/handlers/document-tools.js.map +0 -1
- package/dist/server/modules/mcp/services/utils/document-link-resolver.d.ts +0 -10
- package/dist/server/modules/mcp/services/utils/document-link-resolver.js +0 -124
- package/dist/server/modules/mcp/services/utils/document-link-resolver.js.map +0 -1
- package/dist/server/modules/mcp/tool-descriptors/document.bindings.d.ts +0 -1
- package/dist/server/modules/mcp/tool-descriptors/document.bindings.js +0 -19
- package/dist/server/modules/mcp/tool-descriptors/document.bindings.js.map +0 -1
- package/dist/server/modules/mcp/tool-descriptors/document.metadata.d.ts +0 -2
- package/dist/server/modules/mcp/tool-descriptors/document.metadata.js +0 -96
- package/dist/server/modules/mcp/tool-descriptors/document.metadata.js.map +0 -1
- package/dist/server/modules/storage/local/delegates/document.delegate.d.ts +0 -18
- package/dist/server/modules/storage/local/delegates/document.delegate.js +0 -275
- package/dist/server/modules/storage/local/delegates/document.delegate.js.map +0 -1
- package/dist/server/ui/assets/ReviewDetailPage-D8DVpsJm.js +0 -1
- package/dist/server/ui/assets/ReviewsPage-DcWpRFbm.js +0 -6
- package/dist/server/ui/assets/index-CV8ydm7w.css +0 -32
- package/dist/server/ui/assets/index-Cwe7LxcW.js +0 -1197
- package/dist/server/ui/assets/useReviewSubscription-CYoSg-88.js +0 -69
|
@@ -52,10 +52,15 @@ const session_cache_service_1 = require("./session-cache.service");
|
|
|
52
52
|
const session_reader_adapter_factory_1 = require("../adapters/session-reader-adapter.factory");
|
|
53
53
|
const events_service_1 = require("../../events/services/events.service");
|
|
54
54
|
const chunk_builder_1 = require("../builders/chunk-builder");
|
|
55
|
+
const bounded_anchor_proof_1 = require("./bounded-anchor-proof");
|
|
56
|
+
const metrics_merge_1 = require("./metrics-merge");
|
|
57
|
+
const estimate_content_tokens_1 = require("../adapters/utils/estimate-content-tokens");
|
|
55
58
|
const transcript_cursor_1 = require("./transcript-cursor");
|
|
56
59
|
const transcript_truncation_1 = require("./transcript-truncation");
|
|
57
60
|
const transcript_serialization_1 = require("./transcript-serialization");
|
|
58
61
|
const DEBOUNCE_MS = 100;
|
|
62
|
+
const COSTLY_REFRESH_MS = 50;
|
|
63
|
+
const FILE_REFRESH_COOLDOWN_MS = 2_000;
|
|
59
64
|
const STAT_POLL_INTERVAL_MS = 3_000;
|
|
60
65
|
const MAX_INCREMENTAL_BYTES = 10 * 1024 * 1024;
|
|
61
66
|
function requiresCanonicalRefetch(sourceChangeKind) {
|
|
@@ -76,18 +81,27 @@ function requiresCanonicalRefetch(sourceChangeKind) {
|
|
|
76
81
|
function assertNever(value) {
|
|
77
82
|
throw new Error(`Unhandled source change kind: ${String(value)}`);
|
|
78
83
|
}
|
|
84
|
+
function toFreshnessSnapshot(stat) {
|
|
85
|
+
return {
|
|
86
|
+
size: stat.size,
|
|
87
|
+
mtimeMs: stat.mtime.getTime(),
|
|
88
|
+
fileIdentity: `${stat.dev}:${stat.ino}`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
79
91
|
let TranscriptWatcherService = TranscriptWatcherService_1 = class TranscriptWatcherService {
|
|
80
92
|
cacheService;
|
|
81
93
|
adapterFactory;
|
|
82
94
|
events;
|
|
83
95
|
logger = new common_1.Logger(TranscriptWatcherService_1.name);
|
|
84
96
|
watchers = new Map();
|
|
97
|
+
endingWatchers = new Map();
|
|
85
98
|
constructor(cacheService, adapterFactory, events) {
|
|
86
99
|
this.cacheService = cacheService;
|
|
87
100
|
this.adapterFactory = adapterFactory;
|
|
88
101
|
this.events = events;
|
|
89
102
|
}
|
|
90
103
|
onModuleDestroy() {
|
|
104
|
+
this.endingWatchers.clear();
|
|
91
105
|
const sessionIds = [...this.watchers.keys()];
|
|
92
106
|
for (const sessionId of sessionIds) {
|
|
93
107
|
this.cleanupResources(sessionId);
|
|
@@ -114,106 +128,107 @@ let TranscriptWatcherService = TranscriptWatcherService_1 = class TranscriptWatc
|
|
|
114
128
|
}
|
|
115
129
|
const adapter = this.adapterFactory.getAdapter(providerName);
|
|
116
130
|
const sourceKind = adapter?.sourceKind ?? 'file';
|
|
131
|
+
if (sourceKind === 'db' && !providerSessionId) {
|
|
132
|
+
this.logger.warn({ sessionId, filePath, providerName }, 'DB-backed watcher requires providerSessionId — skipping watcher');
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const sourceRef = sourceKind === 'db' ? { filePath, providerName, providerSessionId, kind: 'db' } : undefined;
|
|
136
|
+
const state = {
|
|
137
|
+
sessionId,
|
|
138
|
+
filePath,
|
|
139
|
+
providerName,
|
|
140
|
+
sourceKind,
|
|
141
|
+
sourceRef,
|
|
142
|
+
watchPath: sourceKind === 'db' ? `${filePath}-wal` : filePath,
|
|
143
|
+
fsWatcher: null,
|
|
144
|
+
pollTimer: null,
|
|
145
|
+
debounceTimer: null,
|
|
146
|
+
active: true,
|
|
147
|
+
pending: false,
|
|
148
|
+
nextEligibleAt: 0,
|
|
149
|
+
lastDev: 0,
|
|
150
|
+
lastIno: 0,
|
|
151
|
+
lastSize: 0,
|
|
152
|
+
replacementPending: false,
|
|
153
|
+
pendingCreation: false,
|
|
154
|
+
lastMessageCount: 0,
|
|
155
|
+
lastChunkCount: 0,
|
|
156
|
+
lastSourceVersion: 0,
|
|
157
|
+
lastMetrics: { totalTokens: 0, inputTokens: 0, outputTokens: 0, costUsd: 0, messageCount: 0 },
|
|
158
|
+
lastSummaryMetrics: null,
|
|
159
|
+
inLane: false,
|
|
160
|
+
costlyLanePass: false,
|
|
161
|
+
};
|
|
162
|
+
this.endingWatchers.delete(sessionId);
|
|
163
|
+
this.watchers.set(sessionId, state);
|
|
117
164
|
let stat = null;
|
|
118
165
|
try {
|
|
119
166
|
stat = await fsPromises.stat(filePath);
|
|
120
167
|
}
|
|
121
168
|
catch (error) {
|
|
169
|
+
if (!this.isCurrent(state))
|
|
170
|
+
return;
|
|
122
171
|
const fsCode = error.code;
|
|
123
172
|
if (sourceKind !== 'file' || fsCode !== 'ENOENT') {
|
|
124
173
|
this.logger.warn({ sessionId, filePath, fsCode }, 'Cannot stat transcript file — skipping watcher');
|
|
174
|
+
this.cleanupResources(sessionId);
|
|
125
175
|
return;
|
|
126
176
|
}
|
|
127
|
-
|
|
177
|
+
state.pendingCreation = true;
|
|
128
178
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
sourceRef = { filePath, providerName, providerSessionId, kind: 'db' };
|
|
136
|
-
}
|
|
137
|
-
const watchPath = sourceKind === 'db' ? `${filePath}-wal` : filePath;
|
|
138
|
-
let fsWatcher = null;
|
|
179
|
+
if (!this.isCurrent(state))
|
|
180
|
+
return;
|
|
181
|
+
state.lastDev = stat?.dev ?? 0;
|
|
182
|
+
state.lastIno = stat?.ino ?? 0;
|
|
183
|
+
state.lastSize = stat?.size ?? 0;
|
|
184
|
+
state.lastSourceVersion = stat?.size ?? 0;
|
|
139
185
|
if (stat) {
|
|
140
186
|
try {
|
|
141
|
-
fsWatcher = this.createFsWatcher(
|
|
187
|
+
state.fsWatcher = this.createFsWatcher(state);
|
|
142
188
|
}
|
|
143
189
|
catch {
|
|
144
|
-
this.logger.warn({ sessionId, watchPath }, 'fs.watch failed to start — using poll only (expected for not-yet-created -wal)');
|
|
190
|
+
this.logger.warn({ sessionId, watchPath: state.watchPath }, 'fs.watch failed to start — using poll only (expected for not-yet-created -wal)');
|
|
145
191
|
}
|
|
146
192
|
}
|
|
147
|
-
let lastMessageCount = 0;
|
|
148
|
-
let lastChunkCount = 0;
|
|
149
|
-
let lastSourceVersion = stat?.size ?? 0;
|
|
150
|
-
let lastFreshnessToken;
|
|
151
|
-
let lastMetrics = {
|
|
152
|
-
totalTokens: 0,
|
|
153
|
-
inputTokens: 0,
|
|
154
|
-
outputTokens: 0,
|
|
155
|
-
costUsd: 0,
|
|
156
|
-
messageCount: 0,
|
|
157
|
-
};
|
|
158
|
-
let lastSummaryMetrics = null;
|
|
159
193
|
try {
|
|
160
|
-
if (adapter && sourceKind === 'db' &&
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
194
|
+
if (adapter && (sourceKind === 'db' || (stat && stat.size > 0))) {
|
|
195
|
+
const seeded = this.isLaneEligible(adapter) && !this.cacheService.getEntry(sessionId)
|
|
196
|
+
? await this.buildLaneFromSummary(state, adapter)
|
|
197
|
+
: false;
|
|
198
|
+
if (!this.isCurrent(state))
|
|
199
|
+
return;
|
|
200
|
+
if (!seeded) {
|
|
201
|
+
const result = await this.cacheService.getOrParseWithMeta(sessionId, sourceRef ?? filePath, adapter);
|
|
202
|
+
if (!this.isCurrent(state))
|
|
203
|
+
return;
|
|
204
|
+
this.applyBodyResultToState(state, adapter, result, stat);
|
|
205
|
+
}
|
|
206
|
+
if (sourceRef && adapter.getFreshnessToken) {
|
|
207
|
+
const token = await adapter.getFreshnessToken(sourceRef);
|
|
208
|
+
if (!this.isCurrent(state))
|
|
209
|
+
return;
|
|
210
|
+
state.lastFreshnessToken = token;
|
|
169
211
|
}
|
|
170
|
-
}
|
|
171
|
-
else if (adapter && stat && stat.size > 0) {
|
|
172
|
-
const { session, sourceVersion } = await this.cacheService.getOrParseWithMeta(sessionId, filePath, adapter);
|
|
173
|
-
lastMessageCount = session.metrics.messageCount;
|
|
174
|
-
lastChunkCount = (0, chunk_builder_1.buildChunks)(session.messages).length;
|
|
175
|
-
lastSourceVersion = sourceVersion;
|
|
176
|
-
lastMetrics = this.toMetricsSnapshot(session.metrics);
|
|
177
|
-
lastSummaryMetrics = session.metrics;
|
|
178
212
|
}
|
|
179
213
|
}
|
|
180
214
|
catch (error) {
|
|
215
|
+
if (!this.isCurrent(state))
|
|
216
|
+
return;
|
|
181
217
|
this.logger.warn({ error, sessionId }, 'Failed to seed watcher state — starting from zero');
|
|
182
218
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
sessionId,
|
|
190
|
-
filePath,
|
|
191
|
-
watchPath,
|
|
192
|
-
providerName,
|
|
193
|
-
sourceKind,
|
|
194
|
-
sourceRef,
|
|
195
|
-
fsWatcher,
|
|
196
|
-
pollTimer,
|
|
197
|
-
debounceTimer: null,
|
|
198
|
-
lastDev: stat?.dev ?? 0,
|
|
199
|
-
lastIno: stat?.ino ?? 0,
|
|
200
|
-
lastSize: stat?.size ?? 0,
|
|
201
|
-
replacementPending: false,
|
|
202
|
-
pendingCreation: stat === null,
|
|
203
|
-
lastMessageCount,
|
|
204
|
-
lastChunkCount,
|
|
205
|
-
lastMetrics,
|
|
206
|
-
lastSummaryMetrics,
|
|
207
|
-
lastFreshnessToken,
|
|
208
|
-
lastSourceVersion,
|
|
209
|
-
});
|
|
219
|
+
if (!this.isCurrent(state))
|
|
220
|
+
return;
|
|
221
|
+
state.pollTimer = setInterval(() => this.requestRefresh(state, true), STAT_POLL_INTERVAL_MS);
|
|
222
|
+
state.active = false;
|
|
223
|
+
if (state.pending)
|
|
224
|
+
this.requestRefresh(state);
|
|
210
225
|
this.logger.log({
|
|
211
226
|
sessionId,
|
|
212
227
|
filePath,
|
|
213
|
-
watchPath,
|
|
228
|
+
watchPath: state.watchPath,
|
|
214
229
|
sourceKind,
|
|
215
|
-
pendingCreation:
|
|
216
|
-
hasFsWatch: !!fsWatcher,
|
|
230
|
+
pendingCreation: state.pendingCreation,
|
|
231
|
+
hasFsWatch: !!state.fsWatcher,
|
|
217
232
|
}, 'Started transcript watcher');
|
|
218
233
|
}
|
|
219
234
|
toMetricsSnapshot(metrics) {
|
|
@@ -231,24 +246,26 @@ let TranscriptWatcherService = TranscriptWatcherService_1 = class TranscriptWatc
|
|
|
231
246
|
return;
|
|
232
247
|
const { filePath, providerName, lastMetrics } = state;
|
|
233
248
|
this.cleanupResources(sessionId);
|
|
249
|
+
this.endingWatchers.set(sessionId, state);
|
|
234
250
|
let finalMetrics = lastMetrics;
|
|
235
251
|
try {
|
|
236
252
|
const adapter = this.adapterFactory.getAdapter(providerName);
|
|
237
253
|
if (adapter) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
};
|
|
254
|
+
if (this.isLaneEligible(adapter) && !this.cacheService.getEntry(sessionId)) {
|
|
255
|
+
finalMetrics = await this.finalizeLaneMetrics(state, adapter, lastMetrics);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
const source = state.sourceKind === 'db' && state.sourceRef ? state.sourceRef : filePath;
|
|
259
|
+
const session = await this.cacheService.getOrParse(sessionId, source, adapter);
|
|
260
|
+
finalMetrics = this.toMetricsSnapshot(session.metrics);
|
|
261
|
+
}
|
|
247
262
|
}
|
|
248
263
|
}
|
|
249
264
|
catch (error) {
|
|
250
265
|
this.logger.warn({ error, sessionId }, 'Final parse failed — using last known metrics');
|
|
251
266
|
}
|
|
267
|
+
if (this.endingWatchers.get(sessionId) !== state)
|
|
268
|
+
return;
|
|
252
269
|
try {
|
|
253
270
|
await this.events.publish('session.transcript.ended', {
|
|
254
271
|
sessionId,
|
|
@@ -260,6 +277,10 @@ let TranscriptWatcherService = TranscriptWatcherService_1 = class TranscriptWatc
|
|
|
260
277
|
catch (error) {
|
|
261
278
|
this.logger.error({ error, sessionId }, 'Failed to emit transcript ended event');
|
|
262
279
|
}
|
|
280
|
+
finally {
|
|
281
|
+
if (this.endingWatchers.get(sessionId) === state)
|
|
282
|
+
this.endingWatchers.delete(sessionId);
|
|
283
|
+
}
|
|
263
284
|
}
|
|
264
285
|
get activeWatcherCount() {
|
|
265
286
|
return this.watchers.size;
|
|
@@ -275,40 +296,73 @@ let TranscriptWatcherService = TranscriptWatcherService_1 = class TranscriptWatc
|
|
|
275
296
|
if (watcher)
|
|
276
297
|
watcher.lastSummaryMetrics = null;
|
|
277
298
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
299
|
+
isCurrent(state) {
|
|
300
|
+
return this.watchers.get(state.sessionId) === state;
|
|
301
|
+
}
|
|
302
|
+
laneOwned(state, ending) {
|
|
303
|
+
const map = ending ? this.endingWatchers : this.watchers;
|
|
304
|
+
return map.get(state.sessionId) === state;
|
|
305
|
+
}
|
|
306
|
+
requestRefresh(state, poll = false) {
|
|
307
|
+
if (!this.isCurrent(state))
|
|
308
|
+
return;
|
|
309
|
+
state.pending = true;
|
|
310
|
+
if (state.active || state.debounceTimer)
|
|
311
|
+
return;
|
|
312
|
+
const remaining = state.nextEligibleAt - performance.now();
|
|
313
|
+
if (poll && remaining <= 0) {
|
|
314
|
+
this.launchRefresh(state, true);
|
|
281
315
|
return;
|
|
282
|
-
if (state.debounceTimer) {
|
|
283
|
-
clearTimeout(state.debounceTimer);
|
|
284
316
|
}
|
|
317
|
+
const delay = remaining > 0 ? remaining : DEBOUNCE_MS;
|
|
285
318
|
state.debounceTimer = setTimeout(() => {
|
|
286
319
|
state.debounceTimer = null;
|
|
287
|
-
this.
|
|
288
|
-
|
|
289
|
-
});
|
|
290
|
-
}, DEBOUNCE_MS);
|
|
320
|
+
this.launchRefresh(state, false);
|
|
321
|
+
}, delay);
|
|
291
322
|
}
|
|
292
|
-
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
else {
|
|
300
|
-
await this.checkStatPoll(sessionId);
|
|
301
|
-
}
|
|
323
|
+
launchRefresh(state, poll) {
|
|
324
|
+
const pass = this.runRefresh(state, poll);
|
|
325
|
+
state.activeRefresh = pass;
|
|
326
|
+
void pass.finally(() => {
|
|
327
|
+
if (state.activeRefresh === pass)
|
|
328
|
+
state.activeRefresh = undefined;
|
|
329
|
+
});
|
|
302
330
|
}
|
|
303
|
-
async
|
|
304
|
-
|
|
305
|
-
if (!state)
|
|
331
|
+
async runRefresh(state, poll) {
|
|
332
|
+
if (!this.isCurrent(state))
|
|
306
333
|
return;
|
|
307
|
-
|
|
308
|
-
|
|
334
|
+
state.active = true;
|
|
335
|
+
state.pending = false;
|
|
336
|
+
state.costlyLanePass = false;
|
|
337
|
+
const startedAt = performance.now();
|
|
338
|
+
try {
|
|
339
|
+
if (poll) {
|
|
340
|
+
if (state.sourceKind === 'db')
|
|
341
|
+
await this.checkDbPoll(state);
|
|
342
|
+
else
|
|
343
|
+
await this.checkStatPoll(state);
|
|
344
|
+
}
|
|
345
|
+
else if (state.sourceKind === 'db') {
|
|
346
|
+
await this.handleDbChanged(state);
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
await this.handleFileChanged(state);
|
|
350
|
+
}
|
|
309
351
|
}
|
|
310
|
-
|
|
311
|
-
|
|
352
|
+
catch (error) {
|
|
353
|
+
this.logger.error({ error, sessionId: state.sessionId }, 'Source refresh failed');
|
|
354
|
+
}
|
|
355
|
+
finally {
|
|
356
|
+
if (this.isCurrent(state)) {
|
|
357
|
+
state.active = false;
|
|
358
|
+
if (!poll && state.sourceKind === 'file') {
|
|
359
|
+
const completedAt = performance.now();
|
|
360
|
+
const costly = completedAt - startedAt >= COSTLY_REFRESH_MS || state.costlyLanePass;
|
|
361
|
+
state.nextEligibleAt = costly ? completedAt + FILE_REFRESH_COOLDOWN_MS : 0;
|
|
362
|
+
}
|
|
363
|
+
if (state.pending)
|
|
364
|
+
this.requestRefresh(state);
|
|
365
|
+
}
|
|
312
366
|
}
|
|
313
367
|
}
|
|
314
368
|
async checkDbPoll(state) {
|
|
@@ -323,19 +377,21 @@ let TranscriptWatcherService = TranscriptWatcherService_1 = class TranscriptWatc
|
|
|
323
377
|
this.logger.debug({ error, sessionId: state.sessionId }, 'DB freshness poll failed — retry');
|
|
324
378
|
return;
|
|
325
379
|
}
|
|
380
|
+
if (!this.isCurrent(state))
|
|
381
|
+
return;
|
|
326
382
|
if (JSON.stringify(token) !== JSON.stringify(state.lastFreshnessToken)) {
|
|
327
|
-
this.
|
|
383
|
+
this.requestRefresh(state);
|
|
328
384
|
}
|
|
329
385
|
}
|
|
330
|
-
async checkStatPoll(
|
|
331
|
-
const
|
|
332
|
-
if (!state)
|
|
333
|
-
return;
|
|
386
|
+
async checkStatPoll(state) {
|
|
387
|
+
const { sessionId } = state;
|
|
334
388
|
let stat;
|
|
335
389
|
try {
|
|
336
390
|
stat = await fsPromises.stat(state.filePath);
|
|
337
391
|
}
|
|
338
392
|
catch (error) {
|
|
393
|
+
if (!this.isCurrent(state))
|
|
394
|
+
return;
|
|
339
395
|
if (error.code === 'ENOENT') {
|
|
340
396
|
if (state.pendingCreation)
|
|
341
397
|
return;
|
|
@@ -345,6 +401,8 @@ let TranscriptWatcherService = TranscriptWatcherService_1 = class TranscriptWatc
|
|
|
345
401
|
}
|
|
346
402
|
throw error;
|
|
347
403
|
}
|
|
404
|
+
if (!this.isCurrent(state))
|
|
405
|
+
return;
|
|
348
406
|
if (state.pendingCreation) {
|
|
349
407
|
state.pendingCreation = false;
|
|
350
408
|
state.lastDev = stat.dev;
|
|
@@ -352,7 +410,7 @@ let TranscriptWatcherService = TranscriptWatcherService_1 = class TranscriptWatc
|
|
|
352
410
|
state.lastSize = 0;
|
|
353
411
|
state.replacementPending = true;
|
|
354
412
|
this.reopenFsWatcher(state);
|
|
355
|
-
this.
|
|
413
|
+
this.requestRefresh(state);
|
|
356
414
|
this.logger.log({ sessionId, filePath: state.filePath }, 'Transcript file materialized — activating watcher');
|
|
357
415
|
return;
|
|
358
416
|
}
|
|
@@ -365,36 +423,39 @@ let TranscriptWatcherService = TranscriptWatcherService_1 = class TranscriptWatc
|
|
|
365
423
|
state.replacementPending = true;
|
|
366
424
|
}
|
|
367
425
|
if (identityChanged || state.replacementPending || stat.size !== state.lastSize) {
|
|
368
|
-
this.
|
|
426
|
+
this.requestRefresh(state);
|
|
369
427
|
}
|
|
370
428
|
}
|
|
371
|
-
async handleFileChanged(
|
|
372
|
-
const
|
|
373
|
-
if (!state)
|
|
374
|
-
return;
|
|
429
|
+
async handleFileChanged(state) {
|
|
430
|
+
const { sessionId } = state;
|
|
375
431
|
try {
|
|
376
432
|
let stat;
|
|
377
433
|
try {
|
|
378
434
|
stat = await fsPromises.stat(state.filePath);
|
|
379
435
|
}
|
|
380
436
|
catch (error) {
|
|
437
|
+
if (!this.isCurrent(state))
|
|
438
|
+
return;
|
|
381
439
|
if (error.code === 'ENOENT') {
|
|
440
|
+
if (state.pendingCreation)
|
|
441
|
+
return;
|
|
382
442
|
this.logger.warn({ sessionId }, 'Transcript file deleted during change handling');
|
|
383
443
|
await this.stopWatching(sessionId, 'file.deleted');
|
|
384
444
|
return;
|
|
385
445
|
}
|
|
386
446
|
throw error;
|
|
387
447
|
}
|
|
448
|
+
if (!this.isCurrent(state))
|
|
449
|
+
return;
|
|
388
450
|
const identityChanged = stat.dev !== state.lastDev || stat.ino !== state.lastIno;
|
|
389
|
-
const isReplacement = state.replacementPending || identityChanged;
|
|
390
|
-
if (identityChanged) {
|
|
451
|
+
const isReplacement = state.pendingCreation || state.replacementPending || identityChanged;
|
|
452
|
+
if (identityChanged || state.pendingCreation) {
|
|
391
453
|
this.logger.debug({ sessionId }, 'Inode rotation detected');
|
|
392
454
|
this.reopenFsWatcher(state);
|
|
393
455
|
state.lastDev = stat.dev;
|
|
394
456
|
state.lastIno = stat.ino;
|
|
457
|
+
state.pendingCreation = false;
|
|
395
458
|
}
|
|
396
|
-
if (stat.size === state.lastSize && !isReplacement)
|
|
397
|
-
return;
|
|
398
459
|
const delta = stat.size - state.lastSize;
|
|
399
460
|
if (delta > MAX_INCREMENTAL_BYTES) {
|
|
400
461
|
this.logger.warn({ sessionId, delta, maxBytes: MAX_INCREMENTAL_BYTES }, 'Incremental delta exceeds 10MB bound — proceeding with parse');
|
|
@@ -404,77 +465,343 @@ let TranscriptWatcherService = TranscriptWatcherService_1 = class TranscriptWatc
|
|
|
404
465
|
this.logger.error({ sessionId, providerName: state.providerName }, 'No adapter found');
|
|
405
466
|
return;
|
|
406
467
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
468
|
+
if (this.isLaneEligible(adapter)) {
|
|
469
|
+
const outcome = await this.cacheService.refreshIfPresent(sessionId, state.filePath, adapter);
|
|
470
|
+
if (!this.isCurrent(state))
|
|
471
|
+
return;
|
|
472
|
+
if (!outcome.present) {
|
|
473
|
+
const last = outcome.lastFullParse;
|
|
474
|
+
const readerPaidCostlyFullParse = last !== undefined &&
|
|
475
|
+
last.seq !== state.lastFullParseSeqSeen &&
|
|
476
|
+
last.durationMs >= COSTLY_REFRESH_MS;
|
|
477
|
+
state.lastFullParseSeqSeen = last?.seq;
|
|
478
|
+
await this.runFileLane(state, stat, adapter, isReplacement, {
|
|
479
|
+
readerPaidCostlyFullParse,
|
|
480
|
+
});
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
if (state.inLane) {
|
|
484
|
+
state.lastSourceVersion = outcome.preParse.sourceVersion;
|
|
485
|
+
state.lastMessageCount = outcome.preParse.messageCount;
|
|
486
|
+
state.lastChunkCount = outcome.preParse.chunkCount;
|
|
487
|
+
state.inLane = false;
|
|
488
|
+
}
|
|
489
|
+
await this.publishBodyResult(state, stat, adapter, outcome.result, isReplacement);
|
|
490
|
+
return;
|
|
418
491
|
}
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
const cursor = (0, transcript_cursor_1.encodeCursor)(sourceVersion, session.metrics.messageCount, chunks.length);
|
|
422
|
-
const replaceFromChunkIndex = isFullRefresh ? 0 : Math.max(0, state.lastChunkCount - 1);
|
|
423
|
-
const sliceFromMessage = isFullRefresh ? 0 : state.lastMessageCount;
|
|
424
|
-
const newChunkIds = chunks.slice(replaceFromChunkIndex).map((c) => c.id);
|
|
425
|
-
const truncatedDeltaMessages = (0, transcript_truncation_1.truncateMessages)(session.messages.slice(sliceFromMessage));
|
|
426
|
-
const truncatedDeltaChunks = (0, transcript_truncation_1.truncateChunks)(chunks.slice(replaceFromChunkIndex));
|
|
427
|
-
const deltaChunks = truncatedDeltaChunks.map(transcript_serialization_1.serializeChunk);
|
|
428
|
-
const deltaMessages = truncatedDeltaMessages.map(transcript_serialization_1.serializeMessage);
|
|
429
|
-
state.lastSize = stat.size;
|
|
430
|
-
state.lastSourceVersion = sourceVersion;
|
|
431
|
-
state.lastDev = stat.dev;
|
|
432
|
-
state.lastIno = stat.ino;
|
|
433
|
-
state.replacementPending = false;
|
|
434
|
-
state.lastMessageCount = session.metrics.messageCount;
|
|
435
|
-
state.lastChunkCount = chunks.length;
|
|
436
|
-
state.lastMetrics = {
|
|
437
|
-
totalTokens: session.metrics.totalTokens,
|
|
438
|
-
inputTokens: session.metrics.inputTokens,
|
|
439
|
-
outputTokens: session.metrics.outputTokens,
|
|
440
|
-
costUsd: session.metrics.costUsd,
|
|
441
|
-
messageCount: session.metrics.messageCount,
|
|
442
|
-
};
|
|
443
|
-
state.lastSummaryMetrics = session.metrics;
|
|
444
|
-
if (isReplacement || requiresCanonicalRefetch(sourceChangeKind)) {
|
|
445
|
-
await this.events.publish('session.transcript.updated', {
|
|
446
|
-
kind: 'full-refetch-required',
|
|
447
|
-
sessionId,
|
|
448
|
-
transcriptPath: state.filePath,
|
|
449
|
-
sourceChangeKind,
|
|
450
|
-
});
|
|
492
|
+
const result = await this.cacheService.getOrParseWithMeta(sessionId, state.filePath, adapter);
|
|
493
|
+
if (!this.isCurrent(state))
|
|
451
494
|
return;
|
|
495
|
+
await this.publishBodyResult(state, stat, adapter, result, isReplacement);
|
|
496
|
+
}
|
|
497
|
+
catch (error) {
|
|
498
|
+
this.logger.error({ error, sessionId }, 'File change handler failed — watcher continues');
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
isLaneEligible(adapter) {
|
|
502
|
+
return (adapter.sourceKind ?? 'file') === 'file' && adapter.incrementalMode === 'delta';
|
|
503
|
+
}
|
|
504
|
+
laneRef(state) {
|
|
505
|
+
return (state.sourceRef ?? {
|
|
506
|
+
filePath: state.filePath,
|
|
507
|
+
providerName: state.providerName,
|
|
508
|
+
kind: 'file',
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
async laneFreshness(filePath) {
|
|
512
|
+
try {
|
|
513
|
+
return toFreshnessSnapshot(await fsPromises.stat(filePath));
|
|
514
|
+
}
|
|
515
|
+
catch {
|
|
516
|
+
return undefined;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
async tryHashAnchors(filePath, expected, offset, allowGrowth = false) {
|
|
520
|
+
try {
|
|
521
|
+
return await (0, bounded_anchor_proof_1.hashFileAnchors)(filePath, expected, offset, allowGrowth);
|
|
522
|
+
}
|
|
523
|
+
catch (error) {
|
|
524
|
+
this.logger.debug({ error, filePath }, 'Lane anchor proof unavailable — rescanning');
|
|
525
|
+
return undefined;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
async buildLaneFromSummary(state, adapter, ending = false) {
|
|
529
|
+
if (!adapter.getSummary)
|
|
530
|
+
return false;
|
|
531
|
+
const summary = await adapter.getSummary(this.laneRef(state));
|
|
532
|
+
if (!this.laneOwned(state, ending))
|
|
533
|
+
return true;
|
|
534
|
+
if (!summary?.laneSeed)
|
|
535
|
+
return false;
|
|
536
|
+
const seed = summary.laneSeed;
|
|
537
|
+
const fresh = await this.laneFreshness(state.filePath);
|
|
538
|
+
if (!this.laneOwned(state, ending))
|
|
539
|
+
return true;
|
|
540
|
+
const anchors = fresh
|
|
541
|
+
? await this.tryHashAnchors(state.filePath, fresh, seed.endOffset)
|
|
542
|
+
: undefined;
|
|
543
|
+
if (!this.laneOwned(state, ending))
|
|
544
|
+
return true;
|
|
545
|
+
state.lane = {
|
|
546
|
+
offset: seed.endOffset,
|
|
547
|
+
anchors,
|
|
548
|
+
fileIdentity: fresh?.fileIdentity,
|
|
549
|
+
revision: fresh?.mtimeMs,
|
|
550
|
+
metrics: { ...summary.metrics },
|
|
551
|
+
tail: seed.tail,
|
|
552
|
+
visibleContextTokens: seed.visibleContextTokens,
|
|
553
|
+
firstMessageTimestamp: seed.firstMessageTimestamp,
|
|
554
|
+
lastMessageTimestamp: seed.lastMessageTimestamp,
|
|
555
|
+
continuationState: seed.continuationState,
|
|
556
|
+
messageCount: seed.messageCount,
|
|
557
|
+
};
|
|
558
|
+
state.inLane = true;
|
|
559
|
+
this.applyLaneToState(state);
|
|
560
|
+
return true;
|
|
561
|
+
}
|
|
562
|
+
markStatConsumed(state, stat) {
|
|
563
|
+
state.lastSize = stat.size;
|
|
564
|
+
state.lastDev = stat.dev;
|
|
565
|
+
state.lastIno = stat.ino;
|
|
566
|
+
state.replacementPending = false;
|
|
567
|
+
}
|
|
568
|
+
applyLaneToState(state) {
|
|
569
|
+
const lane = state.lane;
|
|
570
|
+
if (!lane)
|
|
571
|
+
return;
|
|
572
|
+
state.lastMessageCount = lane.messageCount;
|
|
573
|
+
state.lastMetrics = this.toMetricsSnapshot(lane.metrics);
|
|
574
|
+
state.lastSummaryMetrics = lane.metrics;
|
|
575
|
+
}
|
|
576
|
+
refreshLaneFromResult(state, result, stat) {
|
|
577
|
+
const msgs = result.session.messages;
|
|
578
|
+
const last = msgs[msgs.length - 1];
|
|
579
|
+
const visibleContextTokens = result.sourceChangeKind === 'same-file-append'
|
|
580
|
+
? result.session.metrics.visibleContextTokens
|
|
581
|
+
: (0, estimate_content_tokens_1.estimateVisibleFromMessages)(msgs);
|
|
582
|
+
const fresh = toFreshnessSnapshot(stat);
|
|
583
|
+
state.lane = {
|
|
584
|
+
offset: result.lastOffset,
|
|
585
|
+
anchors: result.fileContentAnchors,
|
|
586
|
+
fileIdentity: fresh.fileIdentity,
|
|
587
|
+
revision: fresh.mtimeMs,
|
|
588
|
+
metrics: result.session.metrics,
|
|
589
|
+
tail: (0, metrics_merge_1.describeTail)(last),
|
|
590
|
+
visibleContextTokens,
|
|
591
|
+
firstMessageTimestamp: msgs.length > 0 ? msgs[0].timestamp.getTime() : undefined,
|
|
592
|
+
lastMessageTimestamp: last ? last.timestamp.getTime() : undefined,
|
|
593
|
+
continuationState: result.continuationState,
|
|
594
|
+
messageCount: msgs.length,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
advanceVisible(base, messages) {
|
|
598
|
+
let visible = base;
|
|
599
|
+
for (const m of messages) {
|
|
600
|
+
if (m.isCompactSummary) {
|
|
601
|
+
visible = 0;
|
|
602
|
+
continue;
|
|
452
603
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
604
|
+
if (m.isSidechain)
|
|
605
|
+
continue;
|
|
606
|
+
visible += (0, estimate_content_tokens_1.estimateMessageTokens)(m.content);
|
|
607
|
+
}
|
|
608
|
+
return visible;
|
|
609
|
+
}
|
|
610
|
+
advanceLaneWithSlice(lane, slice, proofAnchors, fresh) {
|
|
611
|
+
const newMessages = slice.entries;
|
|
612
|
+
const fold = (0, metrics_merge_1.computeLeadingContinuationFold)(lane.tail, newMessages);
|
|
613
|
+
const netNew = newMessages.length - fold.foldCount;
|
|
614
|
+
const newCount = lane.messageCount + netNew;
|
|
615
|
+
const newVisible = this.advanceVisible(lane.visibleContextTokens, newMessages);
|
|
616
|
+
const firstTs = lane.firstMessageTimestamp ??
|
|
617
|
+
(newMessages.length > 0 ? newMessages[0].timestamp.getTime() : undefined);
|
|
618
|
+
const lastTs = netNew > 0
|
|
619
|
+
? newMessages[newMessages.length - 1].timestamp.getTime()
|
|
620
|
+
: lane.lastMessageTimestamp;
|
|
621
|
+
let durationMs = lane.metrics.durationMs;
|
|
622
|
+
if (newCount >= 2 && firstTs !== undefined && lastTs !== undefined) {
|
|
623
|
+
durationMs = lastTs - firstTs;
|
|
624
|
+
}
|
|
625
|
+
const derived = { messageCount: newCount, visibleContextTokens: newVisible, durationMs };
|
|
626
|
+
lane.metrics = slice.metrics
|
|
627
|
+
? (0, metrics_merge_1.mergeMetrics)(lane.metrics, slice.metrics, derived)
|
|
628
|
+
: { ...lane.metrics, ...derived };
|
|
629
|
+
lane.offset = slice.nextByteOffset;
|
|
630
|
+
lane.anchors = proofAnchors;
|
|
631
|
+
lane.fileIdentity = fresh.fileIdentity;
|
|
632
|
+
lane.revision = fresh.mtimeMs;
|
|
633
|
+
lane.tail = fold.newTail;
|
|
634
|
+
lane.visibleContextTokens = newVisible;
|
|
635
|
+
lane.firstMessageTimestamp = firstTs;
|
|
636
|
+
lane.lastMessageTimestamp = lastTs;
|
|
637
|
+
lane.continuationState = slice.continuationState;
|
|
638
|
+
lane.messageCount = newCount;
|
|
639
|
+
return netNew > 0 || fold.tailMutatedWithoutNewMessage;
|
|
640
|
+
}
|
|
641
|
+
async runFileLane(state, stat, adapter, isReplacement, { readerPaidCostlyFullParse = false, ending = false, } = {}) {
|
|
642
|
+
state.inLane = true;
|
|
643
|
+
const fresh = toFreshnessSnapshot(stat);
|
|
644
|
+
const lane = state.lane;
|
|
645
|
+
if (!isReplacement &&
|
|
646
|
+
lane !== undefined &&
|
|
647
|
+
lane.fileIdentity === fresh.fileIdentity &&
|
|
648
|
+
lane.revision === fresh.mtimeMs &&
|
|
649
|
+
fresh.size === lane.offset) {
|
|
650
|
+
this.markStatConsumed(state, stat);
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
const canAppend = !isReplacement &&
|
|
654
|
+
lane !== undefined &&
|
|
655
|
+
lane.anchors !== undefined &&
|
|
656
|
+
lane.fileIdentity === fresh.fileIdentity &&
|
|
657
|
+
fresh.size > lane.offset;
|
|
658
|
+
let advanced = false;
|
|
659
|
+
let changed = false;
|
|
660
|
+
if (canAppend && lane) {
|
|
661
|
+
const preAnchors = await this.tryHashAnchors(state.filePath, fresh, lane.offset);
|
|
662
|
+
if (!this.laneOwned(state, ending))
|
|
663
|
+
return;
|
|
664
|
+
if ((0, bounded_anchor_proof_1.anchorsEqual)(preAnchors, lane.anchors)) {
|
|
665
|
+
const slice = await adapter.parseIncremental(state.filePath, {
|
|
666
|
+
byteOffset: lane.offset,
|
|
667
|
+
endByteOffset: fresh.size,
|
|
668
|
+
includeToolCalls: true,
|
|
669
|
+
continuationState: lane.continuationState,
|
|
468
670
|
});
|
|
671
|
+
if (!this.laneOwned(state, ending))
|
|
672
|
+
return;
|
|
673
|
+
const newOffset = slice.nextByteOffset;
|
|
674
|
+
const prefixHeld = newOffset >= lane.offset &&
|
|
675
|
+
(0, bounded_anchor_proof_1.anchorsEqual)(await this.tryHashAnchors(state.filePath, fresh, lane.offset, true), lane.anchors);
|
|
676
|
+
if (!this.laneOwned(state, ending))
|
|
677
|
+
return;
|
|
678
|
+
const proofAnchors = prefixHeld
|
|
679
|
+
? await this.tryHashAnchors(state.filePath, fresh, newOffset, true)
|
|
680
|
+
: undefined;
|
|
681
|
+
if (!this.laneOwned(state, ending))
|
|
682
|
+
return;
|
|
683
|
+
if (proofAnchors) {
|
|
684
|
+
changed = this.advanceLaneWithSlice(lane, slice, proofAnchors, fresh);
|
|
685
|
+
advanced = true;
|
|
686
|
+
}
|
|
469
687
|
}
|
|
470
688
|
}
|
|
471
|
-
|
|
472
|
-
this.
|
|
689
|
+
if (!advanced) {
|
|
690
|
+
const reseeded = await this.buildLaneFromSummary(state, adapter, ending);
|
|
691
|
+
if (!this.laneOwned(state, ending))
|
|
692
|
+
return;
|
|
693
|
+
if (!reseeded)
|
|
694
|
+
return;
|
|
695
|
+
changed = true;
|
|
696
|
+
}
|
|
697
|
+
this.markStatConsumed(state, stat);
|
|
698
|
+
this.applyLaneToState(state);
|
|
699
|
+
if (changed && !ending) {
|
|
700
|
+
if (readerPaidCostlyFullParse)
|
|
701
|
+
state.costlyLanePass = true;
|
|
702
|
+
await this.events.publish('session.transcript.updated', {
|
|
703
|
+
kind: 'full-refetch-required',
|
|
704
|
+
sessionId: state.sessionId,
|
|
705
|
+
transcriptPath: state.filePath,
|
|
706
|
+
sourceChangeKind: 'unknown-full-parse',
|
|
707
|
+
});
|
|
473
708
|
}
|
|
474
709
|
}
|
|
475
|
-
async
|
|
476
|
-
const
|
|
477
|
-
|
|
710
|
+
async finalizeLaneMetrics(state, adapter, fallback) {
|
|
711
|
+
const statIfOwned = async () => {
|
|
712
|
+
try {
|
|
713
|
+
const stat = await fsPromises.stat(state.filePath);
|
|
714
|
+
return this.laneOwned(state, true) ? stat : undefined;
|
|
715
|
+
}
|
|
716
|
+
catch {
|
|
717
|
+
return undefined;
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
let stat = await statIfOwned();
|
|
721
|
+
if (!stat)
|
|
722
|
+
return fallback;
|
|
723
|
+
const inflight = state.activeRefresh;
|
|
724
|
+
if (inflight) {
|
|
725
|
+
await inflight.catch(() => undefined);
|
|
726
|
+
stat = await statIfOwned();
|
|
727
|
+
if (!stat)
|
|
728
|
+
return fallback;
|
|
729
|
+
}
|
|
730
|
+
await this.runFileLane(state, stat, adapter, false, { ending: true });
|
|
731
|
+
return this.laneOwned(state, true) ? state.lastMetrics : fallback;
|
|
732
|
+
}
|
|
733
|
+
applyBodyResultToState(state, adapter, result, stat) {
|
|
734
|
+
state.lastMessageCount = result.session.metrics.messageCount;
|
|
735
|
+
state.lastChunkCount = (0, chunk_builder_1.buildChunks)(result.session.messages).length;
|
|
736
|
+
state.lastSourceVersion = result.sourceVersion;
|
|
737
|
+
state.lastMetrics = this.toMetricsSnapshot(result.session.metrics);
|
|
738
|
+
state.lastSummaryMetrics = result.session.metrics;
|
|
739
|
+
state.inLane = false;
|
|
740
|
+
if (stat && this.isLaneEligible(adapter))
|
|
741
|
+
this.refreshLaneFromResult(state, result, stat);
|
|
742
|
+
}
|
|
743
|
+
async publishBodyResult(state, stat, adapter, result, isReplacement) {
|
|
744
|
+
const { sessionId } = state;
|
|
745
|
+
const { session, sourceChangeKind, sourceVersion, boundaryFold } = result;
|
|
746
|
+
const changedCacheHit = sourceChangeKind === 'cache-hit' && sourceVersion !== state.lastSourceVersion;
|
|
747
|
+
const newMessageCount = session.metrics.messageCount - state.lastMessageCount;
|
|
748
|
+
const isFullRefresh = isReplacement || session.metrics.messageCount < state.lastMessageCount;
|
|
749
|
+
if (isFullRefresh) {
|
|
750
|
+
this.logger.debug({
|
|
751
|
+
sessionId,
|
|
752
|
+
isReplacement,
|
|
753
|
+
previousMessageCount: state.lastMessageCount,
|
|
754
|
+
messageCount: session.metrics.messageCount,
|
|
755
|
+
}, 'Transcript source replaced or messageCount deflated — requiring canonical refetch');
|
|
756
|
+
}
|
|
757
|
+
const chunks = (0, chunk_builder_1.buildChunks)(session.messages);
|
|
758
|
+
const prevCursor = (0, transcript_cursor_1.encodeCursor)(state.lastSourceVersion, state.lastMessageCount, state.lastChunkCount);
|
|
759
|
+
const cursor = (0, transcript_cursor_1.encodeCursor)(sourceVersion, session.metrics.messageCount, chunks.length);
|
|
760
|
+
const replaceFromChunkIndex = isFullRefresh ? 0 : Math.max(0, state.lastChunkCount - 1);
|
|
761
|
+
const sliceFromMessage = isFullRefresh ? 0 : state.lastMessageCount;
|
|
762
|
+
const newChunkIds = chunks.slice(replaceFromChunkIndex).map((c) => c.id);
|
|
763
|
+
const truncatedDeltaMessages = (0, transcript_truncation_1.truncateMessages)(session.messages.slice(sliceFromMessage));
|
|
764
|
+
const truncatedDeltaChunks = (0, transcript_truncation_1.truncateChunks)(chunks.slice(replaceFromChunkIndex));
|
|
765
|
+
const deltaChunks = truncatedDeltaChunks.map(transcript_serialization_1.serializeChunk);
|
|
766
|
+
const deltaMessages = truncatedDeltaMessages.map(transcript_serialization_1.serializeMessage);
|
|
767
|
+
this.markStatConsumed(state, stat);
|
|
768
|
+
state.lastSourceVersion = sourceVersion;
|
|
769
|
+
state.lastMessageCount = session.metrics.messageCount;
|
|
770
|
+
state.lastChunkCount = chunks.length;
|
|
771
|
+
state.lastMetrics = this.toMetricsSnapshot(session.metrics);
|
|
772
|
+
state.lastSummaryMetrics = session.metrics;
|
|
773
|
+
if (this.isLaneEligible(adapter))
|
|
774
|
+
this.refreshLaneFromResult(state, result, stat);
|
|
775
|
+
if (isReplacement || changedCacheHit || requiresCanonicalRefetch(sourceChangeKind)) {
|
|
776
|
+
await this.events.publish('session.transcript.updated', {
|
|
777
|
+
kind: 'full-refetch-required',
|
|
778
|
+
sessionId,
|
|
779
|
+
transcriptPath: state.filePath,
|
|
780
|
+
sourceChangeKind,
|
|
781
|
+
});
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
const isInPlaceTailFold = boundaryFold && newMessageCount === 0 && !isFullRefresh;
|
|
785
|
+
if (newMessageCount > 0 || isFullRefresh || isInPlaceTailFold) {
|
|
786
|
+
await this.events.publish('session.transcript.updated', {
|
|
787
|
+
kind: 'delta',
|
|
788
|
+
sessionId,
|
|
789
|
+
transcriptPath: state.filePath,
|
|
790
|
+
newMessageCount: Math.max(0, newMessageCount),
|
|
791
|
+
metrics: state.lastMetrics,
|
|
792
|
+
cursor,
|
|
793
|
+
prevCursor,
|
|
794
|
+
replaceFromChunkIndex,
|
|
795
|
+
newChunkIds,
|
|
796
|
+
totalChunkCount: chunks.length,
|
|
797
|
+
deltaChunks,
|
|
798
|
+
deltaMessages,
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
async handleDbChanged(state) {
|
|
803
|
+
const { sessionId } = state;
|
|
804
|
+
if (!state.sourceRef)
|
|
478
805
|
return;
|
|
479
806
|
try {
|
|
480
807
|
const adapter = this.adapterFactory.getAdapter(state.providerName);
|
|
@@ -491,11 +818,15 @@ let TranscriptWatcherService = TranscriptWatcherService_1 = class TranscriptWatc
|
|
|
491
818
|
this.logger.debug({ error, sessionId }, 'DB freshness check failed during change — retry');
|
|
492
819
|
return;
|
|
493
820
|
}
|
|
821
|
+
if (!this.isCurrent(state))
|
|
822
|
+
return;
|
|
494
823
|
if (JSON.stringify(token) === JSON.stringify(state.lastFreshnessToken)) {
|
|
495
824
|
return;
|
|
496
825
|
}
|
|
497
826
|
}
|
|
498
827
|
const { session, sourceVersion } = await this.cacheService.getOrParseWithMeta(sessionId, state.sourceRef, adapter);
|
|
828
|
+
if (!this.isCurrent(state))
|
|
829
|
+
return;
|
|
499
830
|
const isFullRefresh = session.metrics.messageCount < state.lastMessageCount;
|
|
500
831
|
if (isFullRefresh) {
|
|
501
832
|
this.logger.debug({
|
|
@@ -540,29 +871,34 @@ let TranscriptWatcherService = TranscriptWatcherService_1 = class TranscriptWatc
|
|
|
540
871
|
this.logger.error({ error, sessionId }, 'DB change handler failed — watcher continues');
|
|
541
872
|
}
|
|
542
873
|
}
|
|
543
|
-
createFsWatcher(
|
|
544
|
-
const
|
|
874
|
+
createFsWatcher(state) {
|
|
875
|
+
const { sessionId } = state;
|
|
876
|
+
const watcher = fs.watch(state.watchPath, (eventType) => {
|
|
877
|
+
if (!this.isCurrent(state) || state.fsWatcher !== watcher)
|
|
878
|
+
return;
|
|
545
879
|
if (eventType === 'change' || eventType === 'rename') {
|
|
546
|
-
this.
|
|
880
|
+
this.requestRefresh(state);
|
|
547
881
|
}
|
|
548
882
|
});
|
|
549
883
|
watcher.on('error', (err) => {
|
|
884
|
+
if (!this.isCurrent(state) || state.fsWatcher !== watcher)
|
|
885
|
+
return;
|
|
550
886
|
this.logger.warn({ error: err, sessionId }, 'fs.watch error — relying on stat-poll fallback');
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
state.fsWatcher.close();
|
|
554
|
-
state.fsWatcher = null;
|
|
555
|
-
}
|
|
887
|
+
state.fsWatcher = null;
|
|
888
|
+
watcher.close();
|
|
556
889
|
});
|
|
557
890
|
return watcher;
|
|
558
891
|
}
|
|
559
892
|
reopenFsWatcher(state) {
|
|
893
|
+
if (!this.isCurrent(state))
|
|
894
|
+
return;
|
|
560
895
|
if (state.fsWatcher) {
|
|
561
|
-
state.fsWatcher
|
|
896
|
+
const previous = state.fsWatcher;
|
|
562
897
|
state.fsWatcher = null;
|
|
898
|
+
previous.close();
|
|
563
899
|
}
|
|
564
900
|
try {
|
|
565
|
-
state.fsWatcher = this.createFsWatcher(state
|
|
901
|
+
state.fsWatcher = this.createFsWatcher(state);
|
|
566
902
|
}
|
|
567
903
|
catch {
|
|
568
904
|
this.logger.warn({ sessionId: state.sessionId }, 'Failed to reopen fs.watch after inode rotation');
|
|
@@ -572,12 +908,17 @@ let TranscriptWatcherService = TranscriptWatcherService_1 = class TranscriptWatc
|
|
|
572
908
|
const state = this.watchers.get(sessionId);
|
|
573
909
|
if (!state)
|
|
574
910
|
return;
|
|
911
|
+
this.watchers.delete(sessionId);
|
|
575
912
|
if (state.debounceTimer)
|
|
576
913
|
clearTimeout(state.debounceTimer);
|
|
577
|
-
|
|
914
|
+
state.debounceTimer = null;
|
|
915
|
+
state.pending = false;
|
|
916
|
+
if (state.pollTimer)
|
|
917
|
+
clearInterval(state.pollTimer);
|
|
918
|
+
state.pollTimer = null;
|
|
578
919
|
if (state.fsWatcher)
|
|
579
920
|
state.fsWatcher.close();
|
|
580
|
-
|
|
921
|
+
state.fsWatcher = null;
|
|
581
922
|
this.logger.debug({ sessionId }, 'Cleaned up transcript watcher');
|
|
582
923
|
}
|
|
583
924
|
};
|