sensorium-mcp 3.1.2 → 3.1.3-board.10
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/Install-Sensorium.ps1 +257 -43
- package/README.md +2 -0
- package/dist/board/api/api.js +84 -0
- package/dist/board/api/api.test.ts +23 -0
- package/dist/board/api/client.js +113 -0
- package/dist/board/components/BottomNav.js +32 -0
- package/dist/board/components/Card.js +78 -0
- package/dist/board/components/Combobox.js +79 -0
- package/dist/board/components/CommentComposer.js +29 -0
- package/dist/board/components/CommentList.js +42 -0
- package/dist/board/components/DirTreePicker.js +128 -0
- package/dist/board/components/Dropdown.js +67 -0
- package/dist/board/components/Lane.js +47 -0
- package/dist/board/components/PriorityChip.js +20 -0
- package/dist/board/components/ProfileSummary.js +89 -0
- package/dist/board/components/StateBanner.js +24 -0
- package/dist/board/components/StatusChip.js +26 -0
- package/dist/board/components/rateLimit.js +32 -0
- package/dist/board/components/rateLimit.test.ts +16 -0
- package/dist/board/components/time.js +14 -0
- package/dist/board/config.js +56 -0
- package/dist/board/index.html +18 -0
- package/dist/board/main.js +37 -0
- package/dist/board/mock/api.mock.js +491 -0
- package/dist/board/mock/api.mock.test.ts +460 -0
- package/dist/board/mock/fixtures.js +512 -0
- package/dist/board/schema/policy.js +261 -0
- package/dist/board/state/recur.js +24 -0
- package/dist/board/state/recur.test.ts +24 -0
- package/dist/board/state/useBoard.js +161 -0
- package/dist/board/state/useBuilder.js +357 -0
- package/dist/board/state/useBuilder.test.ts +468 -0
- package/dist/board/state/useCard.js +193 -0
- package/dist/board/state/useCard.test.ts +435 -0
- package/dist/board/state/useDirBrowser.js +23 -0
- package/dist/board/styles/board.css +2885 -0
- package/dist/board/styles/tokens.css +72 -0
- package/dist/board/vendor/vue.esm-browser.js +9 -0
- package/dist/board/views/App.js +61 -0
- package/dist/board/views/BoardView.js +153 -0
- package/dist/board/views/BuilderView.js +582 -0
- package/dist/board/views/CardView.js +382 -0
- package/dist/board/views/SettingsView.js +152 -0
- package/dist/config/agent-settings.d.ts +11 -0
- package/dist/config/agent-settings.d.ts.map +1 -1
- package/dist/config/agent-settings.js +79 -0
- package/dist/config/agent-settings.js.map +1 -1
- package/dist/config/detail-grant.d.ts +7 -16
- package/dist/config/detail-grant.d.ts.map +1 -1
- package/dist/config/detail-grant.js +12 -35
- package/dist/config/detail-grant.js.map +1 -1
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +17 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/mcp-settings.d.ts +7 -1
- package/dist/config/mcp-settings.d.ts.map +1 -1
- package/dist/config/mcp-settings.js +9 -0
- package/dist/config/mcp-settings.js.map +1 -1
- package/dist/config/misc-settings.d.ts +22 -1
- package/dist/config/misc-settings.d.ts.map +1 -1
- package/dist/config/misc-settings.js +93 -3
- package/dist/config/misc-settings.js.map +1 -1
- package/dist/config/operator-grant.d.ts +9 -0
- package/dist/config/operator-grant.d.ts.map +1 -0
- package/dist/config/operator-grant.js +24 -0
- package/dist/config/operator-grant.js.map +1 -0
- package/dist/config/voice-grant.d.ts +10 -0
- package/dist/config/voice-grant.d.ts.map +1 -0
- package/dist/config/voice-grant.js +18 -0
- package/dist/config/voice-grant.js.map +1 -0
- package/dist/dashboard/board-static.d.ts +22 -0
- package/dist/dashboard/board-static.d.ts.map +1 -0
- package/dist/dashboard/board-static.js +130 -0
- package/dist/dashboard/board-static.js.map +1 -0
- package/dist/dashboard/presets.d.ts +1 -1
- package/dist/dashboard/presets.d.ts.map +1 -1
- package/dist/dashboard/presets.js +1 -1
- package/dist/dashboard/presets.js.map +1 -1
- package/dist/dashboard/routes/agent-templates.d.ts.map +1 -1
- package/dist/dashboard/routes/agent-templates.js +6 -9
- package/dist/dashboard/routes/agent-templates.js.map +1 -1
- package/dist/dashboard/routes/board-body.d.ts +8 -0
- package/dist/dashboard/routes/board-body.d.ts.map +1 -0
- package/dist/dashboard/routes/board-body.js +20 -0
- package/dist/dashboard/routes/board-body.js.map +1 -0
- package/dist/dashboard/routes/board-dir-listing.d.ts +70 -0
- package/dist/dashboard/routes/board-dir-listing.d.ts.map +1 -0
- package/dist/dashboard/routes/board-dir-listing.js +192 -0
- package/dist/dashboard/routes/board-dir-listing.js.map +1 -0
- package/dist/dashboard/routes/board-lane-groups.d.ts +6 -0
- package/dist/dashboard/routes/board-lane-groups.d.ts.map +1 -0
- package/dist/dashboard/routes/board-lane-groups.js +52 -0
- package/dist/dashboard/routes/board-lane-groups.js.map +1 -0
- package/dist/dashboard/routes/board-policy.d.ts +224 -0
- package/dist/dashboard/routes/board-policy.d.ts.map +1 -0
- package/dist/dashboard/routes/board-policy.js +214 -0
- package/dist/dashboard/routes/board-policy.js.map +1 -0
- package/dist/dashboard/routes/board-profile-parse.d.ts +32 -0
- package/dist/dashboard/routes/board-profile-parse.d.ts.map +1 -0
- package/dist/dashboard/routes/board-profile-parse.js +338 -0
- package/dist/dashboard/routes/board-profile-parse.js.map +1 -0
- package/dist/dashboard/routes/board.d.ts +90 -0
- package/dist/dashboard/routes/board.d.ts.map +1 -0
- package/dist/dashboard/routes/board.js +721 -0
- package/dist/dashboard/routes/board.js.map +1 -0
- package/dist/dashboard/routes/codex.d.ts +14 -0
- package/dist/dashboard/routes/codex.d.ts.map +1 -0
- package/dist/dashboard/routes/codex.js +92 -0
- package/dist/dashboard/routes/codex.js.map +1 -0
- package/dist/dashboard/routes/prompts.d.ts +2 -2
- package/dist/dashboard/routes/prompts.d.ts.map +1 -1
- package/dist/dashboard/routes/prompts.js +2 -2
- package/dist/dashboard/routes/prompts.js.map +1 -1
- package/dist/dashboard/routes/settings.d.ts +2 -0
- package/dist/dashboard/routes/settings.d.ts.map +1 -1
- package/dist/dashboard/routes/settings.js +24 -1
- package/dist/dashboard/routes/settings.js.map +1 -1
- package/dist/dashboard/routes/skills.d.ts +7 -1
- package/dist/dashboard/routes/skills.d.ts.map +1 -1
- package/dist/dashboard/routes/skills.js +6 -6
- package/dist/dashboard/routes/skills.js.map +1 -1
- package/dist/dashboard/routes/snapshots.d.ts +2 -1
- package/dist/dashboard/routes/snapshots.d.ts.map +1 -1
- package/dist/dashboard/routes/snapshots.js +24 -18
- package/dist/dashboard/routes/snapshots.js.map +1 -1
- package/dist/dashboard/routes/templates.js +5 -5
- package/dist/dashboard/routes/templates.js.map +1 -1
- package/dist/dashboard/routes/threads-create.d.ts +17 -0
- package/dist/dashboard/routes/threads-create.d.ts.map +1 -1
- package/dist/dashboard/routes/threads-create.js +32 -10
- package/dist/dashboard/routes/threads-create.js.map +1 -1
- package/dist/dashboard/routes/threads-helpers.d.ts +11 -2
- package/dist/dashboard/routes/threads-helpers.d.ts.map +1 -1
- package/dist/dashboard/routes/threads-helpers.js +20 -3
- package/dist/dashboard/routes/threads-helpers.js.map +1 -1
- package/dist/dashboard/routes/threads-read.d.ts +16 -4
- package/dist/dashboard/routes/threads-read.d.ts.map +1 -1
- package/dist/dashboard/routes/threads-read.js +61 -55
- package/dist/dashboard/routes/threads-read.js.map +1 -1
- package/dist/dashboard/routes/threads.d.ts +2 -2
- package/dist/dashboard/routes/threads.d.ts.map +1 -1
- package/dist/dashboard/routes/threads.js +143 -29
- package/dist/dashboard/routes/threads.js.map +1 -1
- package/dist/dashboard/routes/types.d.ts +6 -0
- package/dist/dashboard/routes/types.d.ts.map +1 -1
- package/dist/dashboard/routes/types.js.map +1 -1
- package/dist/dashboard/routes.d.ts +2 -2
- package/dist/dashboard/routes.d.ts.map +1 -1
- package/dist/dashboard/routes.js +85 -7
- package/dist/dashboard/routes.js.map +1 -1
- package/dist/dashboard/spa.html +15 -12
- package/dist/data/board/board-schema.d.ts +29 -0
- package/dist/data/board/board-schema.d.ts.map +1 -0
- package/dist/data/board/board-schema.js +202 -0
- package/dist/data/board/board-schema.js.map +1 -0
- package/dist/data/board/board-types.d.ts +175 -0
- package/dist/data/board/board-types.d.ts.map +1 -0
- package/dist/data/board/board-types.js +9 -0
- package/dist/data/board/board-types.js.map +1 -0
- package/dist/data/board/card.repository.d.ts +137 -0
- package/dist/data/board/card.repository.d.ts.map +1 -0
- package/dist/data/board/card.repository.js +470 -0
- package/dist/data/board/card.repository.js.map +1 -0
- package/dist/data/board/comment.repository.d.ts +14 -0
- package/dist/data/board/comment.repository.d.ts.map +1 -0
- package/dist/data/board/comment.repository.js +42 -0
- package/dist/data/board/comment.repository.js.map +1 -0
- package/dist/data/board/lane-group.repository.d.ts +19 -0
- package/dist/data/board/lane-group.repository.d.ts.map +1 -0
- package/dist/data/board/lane-group.repository.js +66 -0
- package/dist/data/board/lane-group.repository.js.map +1 -0
- package/dist/data/board/profile-defaults.d.ts +17 -0
- package/dist/data/board/profile-defaults.d.ts.map +1 -0
- package/dist/data/board/profile-defaults.js +107 -0
- package/dist/data/board/profile-defaults.js.map +1 -0
- package/dist/data/interfaces.d.ts +1 -0
- package/dist/data/interfaces.d.ts.map +1 -1
- package/dist/data/memory/bootstrap.d.ts +1 -8
- package/dist/data/memory/bootstrap.d.ts.map +1 -1
- package/dist/data/memory/bootstrap.js +27 -37
- package/dist/data/memory/bootstrap.js.map +1 -1
- package/dist/data/memory/consolidation.d.ts +9 -31
- package/dist/data/memory/consolidation.d.ts.map +1 -1
- package/dist/data/memory/consolidation.js +22 -32
- package/dist/data/memory/consolidation.js.map +1 -1
- package/dist/data/memory/db-maintenance.d.ts +54 -0
- package/dist/data/memory/db-maintenance.d.ts.map +1 -0
- package/dist/data/memory/db-maintenance.js +81 -0
- package/dist/data/memory/db-maintenance.js.map +1 -0
- package/dist/data/memory/episodes.d.ts +2 -1
- package/dist/data/memory/episodes.d.ts.map +1 -1
- package/dist/data/memory/episodes.js +5 -1
- package/dist/data/memory/episodes.js.map +1 -1
- package/dist/data/memory/index.d.ts +0 -4
- package/dist/data/memory/index.d.ts.map +1 -1
- package/dist/data/memory/index.js +0 -4
- package/dist/data/memory/index.js.map +1 -1
- package/dist/data/memory/key-knowledge.d.ts +51 -0
- package/dist/data/memory/key-knowledge.d.ts.map +1 -0
- package/dist/data/memory/key-knowledge.js +128 -0
- package/dist/data/memory/key-knowledge.js.map +1 -0
- package/dist/data/memory/migration-runner.d.ts +1 -1
- package/dist/data/memory/migration-runner.d.ts.map +1 -1
- package/dist/data/memory/migration-runner.js +407 -1
- package/dist/data/memory/migration-runner.js.map +1 -1
- package/dist/data/memory/narrative-lint.d.ts +2 -2
- package/dist/data/memory/narrative-lint.d.ts.map +1 -1
- package/dist/data/memory/narrative-lint.js +23 -13
- package/dist/data/memory/narrative-lint.js.map +1 -1
- package/dist/data/memory/narrative-resolutions.d.ts +9 -2
- package/dist/data/memory/narrative-resolutions.d.ts.map +1 -1
- package/dist/data/memory/narrative-resolutions.js +9 -8
- package/dist/data/memory/narrative-resolutions.js.map +1 -1
- package/dist/data/memory/narrative-sources.d.ts +1 -1
- package/dist/data/memory/narrative-sources.d.ts.map +1 -1
- package/dist/data/memory/narrative-sources.js +8 -3
- package/dist/data/memory/narrative-sources.js.map +1 -1
- package/dist/data/memory/narrative-store.d.ts +24 -0
- package/dist/data/memory/narrative-store.d.ts.map +1 -1
- package/dist/data/memory/narrative-store.js +73 -35
- package/dist/data/memory/narrative-store.js.map +1 -1
- package/dist/data/memory/narrative.d.ts.map +1 -1
- package/dist/data/memory/narrative.js +47 -29
- package/dist/data/memory/narrative.js.map +1 -1
- package/dist/data/memory/note-archival.d.ts +1 -1
- package/dist/data/memory/note-archival.d.ts.map +1 -1
- package/dist/data/memory/note-archival.js +7 -3
- package/dist/data/memory/note-archival.js.map +1 -1
- package/dist/data/memory/note-embeddings.d.ts +7 -0
- package/dist/data/memory/note-embeddings.d.ts.map +1 -1
- package/dist/data/memory/note-embeddings.js +6 -2
- package/dist/data/memory/note-embeddings.js.map +1 -1
- package/dist/data/memory/passive-notice.repository.d.ts +70 -0
- package/dist/data/memory/passive-notice.repository.d.ts.map +1 -0
- package/dist/data/memory/passive-notice.repository.js +85 -0
- package/dist/data/memory/passive-notice.repository.js.map +1 -0
- package/dist/data/memory/schema-ddl.d.ts +120 -1
- package/dist/data/memory/schema-ddl.d.ts.map +1 -1
- package/dist/data/memory/schema-ddl.js +171 -4
- package/dist/data/memory/schema-ddl.js.map +1 -1
- package/dist/data/memory/schema-guard.d.ts.map +1 -1
- package/dist/data/memory/schema-guard.js +5 -41
- package/dist/data/memory/schema-guard.js.map +1 -1
- package/dist/data/memory/schema.d.ts.map +1 -1
- package/dist/data/memory/schema.js +10 -34
- package/dist/data/memory/schema.js.map +1 -1
- package/dist/data/memory/semantic-notes.d.ts +13 -8
- package/dist/data/memory/semantic-notes.d.ts.map +1 -1
- package/dist/data/memory/semantic-notes.js +12 -17
- package/dist/data/memory/semantic-notes.js.map +1 -1
- package/dist/data/memory/thread-registry.d.ts +330 -15
- package/dist/data/memory/thread-registry.d.ts.map +1 -1
- package/dist/data/memory/thread-registry.js +523 -67
- package/dist/data/memory/thread-registry.js.map +1 -1
- package/dist/data/memory/utils.d.ts +6 -0
- package/dist/data/memory/utils.d.ts.map +1 -1
- package/dist/data/memory/utils.js +13 -0
- package/dist/data/memory/utils.js.map +1 -1
- package/dist/data/memory/worker-lifecycle.repository.d.ts +308 -0
- package/dist/data/memory/worker-lifecycle.repository.d.ts.map +1 -0
- package/dist/data/memory/worker-lifecycle.repository.js +428 -0
- package/dist/data/memory/worker-lifecycle.repository.js.map +1 -0
- package/dist/data/optional.d.ts +16 -0
- package/dist/data/optional.d.ts.map +1 -0
- package/dist/data/optional.js +47 -0
- package/dist/data/optional.js.map +1 -0
- package/dist/index.js +156 -24
- package/dist/index.js.map +1 -1
- package/dist/integrations/openai/speech.d.ts.map +1 -1
- package/dist/integrations/openai/speech.js +52 -19
- package/dist/integrations/openai/speech.js.map +1 -1
- package/dist/integrations/openai/video.d.ts +1 -1
- package/dist/integrations/openai/video.d.ts.map +1 -1
- package/dist/integrations/openai/video.js +13 -8
- package/dist/integrations/openai/video.js.map +1 -1
- package/dist/integrations/telegram/types.d.ts +11 -0
- package/dist/integrations/telegram/types.d.ts.map +1 -1
- package/dist/lib/http-retry.d.ts +91 -0
- package/dist/lib/http-retry.d.ts.map +1 -0
- package/dist/lib/http-retry.js +177 -0
- package/dist/lib/http-retry.js.map +1 -0
- package/dist/lib/mcp-http-port.d.ts +12 -0
- package/dist/lib/mcp-http-port.d.ts.map +1 -0
- package/dist/lib/mcp-http-port.js +19 -0
- package/dist/lib/mcp-http-port.js.map +1 -0
- package/dist/lib/paths.d.ts +29 -4
- package/dist/lib/paths.d.ts.map +1 -1
- package/dist/lib/paths.js +31 -4
- package/dist/lib/paths.js.map +1 -1
- package/dist/lib/types.d.ts +2 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/usage-metering/pricing.js +1 -1
- package/dist/lib/usage-metering/pricing.js.map +1 -1
- package/dist/lib/usage-metering/sink.d.ts +4 -0
- package/dist/lib/usage-metering/sink.d.ts.map +1 -1
- package/dist/lib/usage-metering/sink.js +6 -4
- package/dist/lib/usage-metering/sink.js.map +1 -1
- package/dist/lib/utils.d.ts +31 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +49 -0
- package/dist/lib/utils.js.map +1 -1
- package/dist/llm/prompts/narrative.d.ts.map +1 -1
- package/dist/llm/prompts/narrative.js +27 -86
- package/dist/llm/prompts/narrative.js.map +1 -1
- package/dist/log-parsing/log-events.d.ts +38 -0
- package/dist/log-parsing/log-events.d.ts.map +1 -0
- package/dist/log-parsing/log-events.js +2 -0
- package/dist/log-parsing/log-events.js.map +1 -0
- package/dist/log-parsing/parser-registry.d.ts +4 -0
- package/dist/log-parsing/parser-registry.d.ts.map +1 -0
- package/dist/log-parsing/parser-registry.js +18 -0
- package/dist/log-parsing/parser-registry.js.map +1 -0
- package/dist/log-parsing/parsers/claude.d.ts +6 -0
- package/dist/log-parsing/parsers/claude.d.ts.map +1 -0
- package/dist/log-parsing/parsers/claude.js +100 -0
- package/dist/log-parsing/parsers/claude.js.map +1 -0
- package/dist/log-parsing/parsers/codex.d.ts +6 -0
- package/dist/log-parsing/parsers/codex.d.ts.map +1 -0
- package/dist/log-parsing/parsers/codex.js +116 -0
- package/dist/log-parsing/parsers/codex.js.map +1 -0
- package/dist/log-parsing/parsers/copilot.d.ts +6 -0
- package/dist/log-parsing/parsers/copilot.d.ts.map +1 -0
- package/dist/log-parsing/parsers/copilot.js +65 -0
- package/dist/log-parsing/parsers/copilot.js.map +1 -0
- package/dist/log-parsing/parsers/json-lines.d.ts +9 -0
- package/dist/log-parsing/parsers/json-lines.d.ts.map +1 -0
- package/dist/log-parsing/parsers/json-lines.js +25 -0
- package/dist/log-parsing/parsers/json-lines.js.map +1 -0
- package/dist/log-parsing/render.d.ts +33 -0
- package/dist/log-parsing/render.d.ts.map +1 -0
- package/dist/log-parsing/render.js +113 -0
- package/dist/log-parsing/render.js.map +1 -0
- package/dist/log-parsing/text.d.ts +8 -0
- package/dist/log-parsing/text.d.ts.map +1 -0
- package/dist/log-parsing/text.js +20 -0
- package/dist/log-parsing/text.js.map +1 -0
- package/dist/server/http-server.d.ts +8 -1
- package/dist/server/http-server.d.ts.map +1 -1
- package/dist/server/http-server.js +41 -19
- package/dist/server/http-server.js.map +1 -1
- package/dist/server/port-holder.d.ts +94 -0
- package/dist/server/port-holder.d.ts.map +1 -0
- package/dist/server/port-holder.js +122 -0
- package/dist/server/port-holder.js.map +1 -0
- package/dist/services/agent-spawn.service.d.ts +50 -5
- package/dist/services/agent-spawn.service.d.ts.map +1 -1
- package/dist/services/agent-spawn.service.js +59 -66
- package/dist/services/agent-spawn.service.js.map +1 -1
- package/dist/services/background-runner.d.ts +0 -1
- package/dist/services/background-runner.d.ts.map +1 -1
- package/dist/services/background-runner.js +0 -58
- package/dist/services/background-runner.js.map +1 -1
- package/dist/services/board/board-signals.d.ts +45 -0
- package/dist/services/board/board-signals.d.ts.map +1 -0
- package/dist/services/board/board-signals.js +170 -0
- package/dist/services/board/board-signals.js.map +1 -0
- package/dist/services/board/card-briefing.d.ts +24 -0
- package/dist/services/board/card-briefing.d.ts.map +1 -0
- package/dist/services/board/card-briefing.js +132 -0
- package/dist/services/board/card-briefing.js.map +1 -0
- package/dist/services/board/card-diagnostics.d.ts +25 -0
- package/dist/services/board/card-diagnostics.d.ts.map +1 -0
- package/dist/services/board/card-diagnostics.js +50 -0
- package/dist/services/board/card-diagnostics.js.map +1 -0
- package/dist/services/board/live-root-thread.d.ts +8 -0
- package/dist/services/board/live-root-thread.d.ts.map +1 -0
- package/dist/services/board/live-root-thread.js +2 -0
- package/dist/services/board/live-root-thread.js.map +1 -0
- package/dist/services/board/orchestrator.service.d.ts +119 -0
- package/dist/services/board/orchestrator.service.d.ts.map +1 -0
- package/dist/services/board/orchestrator.service.js +387 -0
- package/dist/services/board/orchestrator.service.js.map +1 -0
- package/dist/services/board/release-worker.d.ts +43 -0
- package/dist/services/board/release-worker.d.ts.map +1 -0
- package/dist/services/board/release-worker.js +96 -0
- package/dist/services/board/release-worker.js.map +1 -0
- package/dist/services/board/report-capture.d.ts +94 -0
- package/dist/services/board/report-capture.d.ts.map +1 -0
- package/dist/services/board/report-capture.js +398 -0
- package/dist/services/board/report-capture.js.map +1 -0
- package/dist/services/board/worker-spawn.d.ts +89 -0
- package/dist/services/board/worker-spawn.d.ts.map +1 -0
- package/dist/services/board/worker-spawn.js +177 -0
- package/dist/services/board/worker-spawn.js.map +1 -0
- package/dist/services/board/worktree.d.ts +45 -0
- package/dist/services/board/worktree.d.ts.map +1 -0
- package/dist/services/board/worktree.js +183 -0
- package/dist/services/board/worktree.js.map +1 -0
- package/dist/services/broker-client.d.ts +84 -2
- package/dist/services/broker-client.d.ts.map +1 -1
- package/dist/services/broker-client.js +72 -6
- package/dist/services/broker-client.js.map +1 -1
- package/dist/services/claude-spawn.service.d.ts +5 -0
- package/dist/services/claude-spawn.service.d.ts.map +1 -1
- package/dist/services/claude-spawn.service.js +37 -21
- package/dist/services/claude-spawn.service.js.map +1 -1
- package/dist/services/codex-spawn.service.d.ts +8 -3
- package/dist/services/codex-spawn.service.d.ts.map +1 -1
- package/dist/services/codex-spawn.service.js +84 -67
- package/dist/services/codex-spawn.service.js.map +1 -1
- package/dist/services/consolidation.service.d.ts.map +1 -1
- package/dist/services/consolidation.service.js +5 -49
- package/dist/services/consolidation.service.js.map +1 -1
- package/dist/services/copilot-spawn.service.d.ts +1 -1
- package/dist/services/copilot-spawn.service.d.ts.map +1 -1
- package/dist/services/copilot-spawn.service.js +10 -5
- package/dist/services/copilot-spawn.service.js.map +1 -1
- package/dist/services/daily-session.d.ts +24 -11
- package/dist/services/daily-session.d.ts.map +1 -1
- package/dist/services/daily-session.js +113 -33
- package/dist/services/daily-session.js.map +1 -1
- package/dist/services/death-alert.service.d.ts +55 -0
- package/dist/services/death-alert.service.d.ts.map +1 -0
- package/dist/services/death-alert.service.js +64 -0
- package/dist/services/death-alert.service.js.map +1 -0
- package/dist/services/dispatcher/broker.d.ts +66 -0
- package/dist/services/dispatcher/broker.d.ts.map +1 -1
- package/dist/services/dispatcher/broker.js +60 -52
- package/dist/services/dispatcher/broker.js.map +1 -1
- package/dist/services/dispatcher/poller.d.ts.map +1 -1
- package/dist/services/dispatcher/poller.js +31 -24
- package/dist/services/dispatcher/poller.js.map +1 -1
- package/dist/services/http-retry.d.ts +1 -90
- package/dist/services/http-retry.d.ts.map +1 -1
- package/dist/services/http-retry.js +1 -176
- package/dist/services/http-retry.js.map +1 -1
- package/dist/services/intent.d.ts +15 -1
- package/dist/services/intent.d.ts.map +1 -1
- package/dist/services/intent.js +39 -27
- package/dist/services/intent.js.map +1 -1
- package/dist/services/keeper.service.d.ts +76 -3
- package/dist/services/keeper.service.d.ts.map +1 -1
- package/dist/services/keeper.service.js +112 -35
- package/dist/services/keeper.service.js.map +1 -1
- package/dist/services/mcp-config.service.d.ts.map +1 -1
- package/dist/services/mcp-config.service.js +14 -1
- package/dist/services/mcp-config.service.js.map +1 -1
- package/dist/services/memory-briefing.service.d.ts +1 -1
- package/dist/services/memory-briefing.service.d.ts.map +1 -1
- package/dist/services/memory-briefing.service.js +28 -73
- package/dist/services/memory-briefing.service.js.map +1 -1
- package/dist/services/passive-notice.service.d.ts +56 -0
- package/dist/services/passive-notice.service.d.ts.map +1 -0
- package/dist/services/passive-notice.service.js +92 -0
- package/dist/services/passive-notice.service.js.map +1 -0
- package/dist/services/predefined-prompts.d.ts +11 -0
- package/dist/services/predefined-prompts.d.ts.map +1 -0
- package/dist/services/predefined-prompts.js +25 -0
- package/dist/services/predefined-prompts.js.map +1 -0
- package/dist/services/process.service.d.ts +111 -27
- package/dist/services/process.service.d.ts.map +1 -1
- package/dist/services/process.service.js +245 -173
- package/dist/services/process.service.js.map +1 -1
- package/dist/services/spawn-common.d.ts +281 -5
- package/dist/services/spawn-common.d.ts.map +1 -1
- package/dist/services/spawn-common.js +884 -188
- package/dist/services/spawn-common.js.map +1 -1
- package/dist/services/spawn-exit-watcher.d.ts +45 -35
- package/dist/services/spawn-exit-watcher.d.ts.map +1 -1
- package/dist/services/spawn-exit-watcher.js +120 -100
- package/dist/services/spawn-exit-watcher.js.map +1 -1
- package/dist/services/telegram.d.ts +31 -8
- package/dist/services/telegram.d.ts.map +1 -1
- package/dist/services/telegram.js +58 -26
- package/dist/services/telegram.js.map +1 -1
- package/dist/services/thread-health.service.d.ts +8 -0
- package/dist/services/thread-health.service.d.ts.map +1 -1
- package/dist/services/thread-health.service.js +14 -10
- package/dist/services/thread-health.service.js.map +1 -1
- package/dist/services/thread-lifecycle.service.d.ts +17 -1
- package/dist/services/thread-lifecycle.service.d.ts.map +1 -1
- package/dist/services/thread-lifecycle.service.js +47 -6
- package/dist/services/thread-lifecycle.service.js.map +1 -1
- package/dist/services/thread-log-reader.d.ts +81 -0
- package/dist/services/thread-log-reader.d.ts.map +1 -0
- package/dist/services/thread-log-reader.js +141 -0
- package/dist/services/thread-log-reader.js.map +1 -0
- package/dist/services/thread-purge.service.d.ts +200 -0
- package/dist/services/thread-purge.service.d.ts.map +1 -0
- package/dist/services/thread-purge.service.js +644 -0
- package/dist/services/thread-purge.service.js.map +1 -0
- package/dist/services/topic.service.d.ts +44 -2
- package/dist/services/topic.service.d.ts.map +1 -1
- package/dist/services/topic.service.js +74 -13
- package/dist/services/topic.service.js.map +1 -1
- package/dist/services/usage/usage-inbox-notifier.d.ts +50 -0
- package/dist/services/usage/usage-inbox-notifier.d.ts.map +1 -0
- package/dist/services/usage/usage-inbox-notifier.js +118 -0
- package/dist/services/usage/usage-inbox-notifier.js.map +1 -0
- package/dist/services/usage/usage-poller.service.d.ts +41 -0
- package/dist/services/usage/usage-poller.service.d.ts.map +1 -0
- package/dist/services/usage/usage-poller.service.js +67 -0
- package/dist/services/usage/usage-poller.service.js.map +1 -0
- package/dist/services/usage/usage-snapshot-provider.d.ts +40 -0
- package/dist/services/usage/usage-snapshot-provider.d.ts.map +1 -0
- package/dist/services/usage/usage-snapshot-provider.js +89 -0
- package/dist/services/usage/usage-snapshot-provider.js.map +1 -0
- package/dist/services/usage/usage-types.d.ts +28 -0
- package/dist/services/usage/usage-types.d.ts.map +1 -0
- package/dist/services/usage/usage-types.js +14 -0
- package/dist/services/usage/usage-types.js.map +1 -0
- package/dist/services/worker-birth.d.ts +154 -0
- package/dist/services/worker-birth.d.ts.map +1 -0
- package/dist/services/worker-birth.js +274 -0
- package/dist/services/worker-birth.js.map +1 -0
- package/dist/services/worker-cleanup.service.d.ts +242 -19
- package/dist/services/worker-cleanup.service.d.ts.map +1 -1
- package/dist/services/worker-cleanup.service.js +893 -355
- package/dist/services/worker-cleanup.service.js.map +1 -1
- package/dist/services/worker-lifecycle-executor.d.ts +42 -0
- package/dist/services/worker-lifecycle-executor.d.ts.map +1 -0
- package/dist/services/worker-lifecycle-executor.js +63 -0
- package/dist/services/worker-lifecycle-executor.js.map +1 -0
- package/dist/services/worker-single-use.d.ts +92 -0
- package/dist/services/worker-single-use.d.ts.map +1 -0
- package/dist/services/worker-single-use.js +155 -0
- package/dist/services/worker-single-use.js.map +1 -0
- package/dist/sessions/mcp-registry.d.ts +33 -2
- package/dist/sessions/mcp-registry.d.ts.map +1 -1
- package/dist/sessions/mcp-registry.js +153 -30
- package/dist/sessions/mcp-registry.js.map +1 -1
- package/dist/test-bootstrap.d.ts +19 -12
- package/dist/test-bootstrap.d.ts.map +1 -1
- package/dist/test-bootstrap.js +69 -21
- package/dist/test-bootstrap.js.map +1 -1
- package/dist/test-tripwire.test-support.d.ts +75 -0
- package/dist/test-tripwire.test-support.d.ts.map +1 -0
- package/dist/test-tripwire.test-support.js +353 -0
- package/dist/test-tripwire.test-support.js.map +1 -0
- package/dist/tools/defs/memory-defs.d.ts.map +1 -1
- package/dist/tools/defs/memory-defs.js +12 -4
- package/dist/tools/defs/memory-defs.js.map +1 -1
- package/dist/tools/defs/session-defs.d.ts.map +1 -1
- package/dist/tools/defs/session-defs.js +26 -10
- package/dist/tools/defs/session-defs.js.map +1 -1
- package/dist/tools/defs/utility-defs.js +1 -1
- package/dist/tools/defs/utility-defs.js.map +1 -1
- package/dist/tools/delegate-tool.d.ts +81 -1
- package/dist/tools/delegate-tool.d.ts.map +1 -1
- package/dist/tools/delegate-tool.js +840 -219
- package/dist/tools/delegate-tool.js.map +1 -1
- package/dist/tools/memory-tools.d.ts.map +1 -1
- package/dist/tools/memory-tools.js +66 -9
- package/dist/tools/memory-tools.js.map +1 -1
- package/dist/tools/response-builders.d.ts.map +1 -1
- package/dist/tools/response-builders.js +12 -8
- package/dist/tools/response-builders.js.map +1 -1
- package/dist/tools/session-tools.d.ts.map +1 -1
- package/dist/tools/session-tools.js +3 -32
- package/dist/tools/session-tools.js.map +1 -1
- package/dist/tools/skill-tools.js +1 -1
- package/dist/tools/skill-tools.js.map +1 -1
- package/dist/tools/start-session-tool.d.ts.map +1 -1
- package/dist/tools/start-session-tool.js +66 -20
- package/dist/tools/start-session-tool.js.map +1 -1
- package/dist/tools/utility-tools.d.ts.map +1 -1
- package/dist/tools/utility-tools.js +38 -3
- package/dist/tools/utility-tools.js.map +1 -1
- package/dist/tools/wait/auto-forward.d.ts.map +1 -1
- package/dist/tools/wait/auto-forward.js +8 -3
- package/dist/tools/wait/auto-forward.js.map +1 -1
- package/dist/tools/wait/consolidation-strategy.d.ts +42 -0
- package/dist/tools/wait/consolidation-strategy.d.ts.map +1 -0
- package/dist/tools/wait/consolidation-strategy.js +50 -0
- package/dist/tools/wait/consolidation-strategy.js.map +1 -0
- package/dist/tools/wait/drive-handler.d.ts +5 -7
- package/dist/tools/wait/drive-handler.d.ts.map +1 -1
- package/dist/tools/wait/drive-handler.js +45 -59
- package/dist/tools/wait/drive-handler.js.map +1 -1
- package/dist/tools/wait/media-processor.d.ts.map +1 -1
- package/dist/tools/wait/media-processor.js +8 -4
- package/dist/tools/wait/media-processor.js.map +1 -1
- package/dist/tools/wait/message-delivery.d.ts +9 -6
- package/dist/tools/wait/message-delivery.d.ts.map +1 -1
- package/dist/tools/wait/message-delivery.js +77 -58
- package/dist/tools/wait/message-delivery.js.map +1 -1
- package/dist/tools/wait/message-processing.d.ts.map +1 -1
- package/dist/tools/wait/message-processing.js +2 -1
- package/dist/tools/wait/message-processing.js.map +1 -1
- package/dist/tools/wait/poll-loop.d.ts.map +1 -1
- package/dist/tools/wait/poll-loop.js +339 -239
- package/dist/tools/wait/poll-loop.js.map +1 -1
- package/package.json +4 -3
- package/templates/azure-devops-securevault.default.md +20 -6
- package/templates/board-quality-checklist.default.md +121 -0
- package/templates/buildless-vue-preview.default.md +47 -0
- package/templates/expert-code-review.default.md +12 -0
- package/templates/handoff.default.md +41 -18
- package/templates/increment-pr-pipeline.default.md +79 -0
- package/templates/swarm.default.md +1 -0
- package/templates/worker-autonomous.default.md +1 -0
- package/templates/worker-delegate.default.md +1 -0
- package/dist/dashboard/log-parser.d.ts +0 -65
- package/dist/dashboard/log-parser.d.ts.map +0 -1
- package/dist/dashboard/log-parser.js +0 -219
- package/dist/dashboard/log-parser.js.map +0 -1
- package/dist/data/memory/reflection-dedup.d.ts +0 -17
- package/dist/data/memory/reflection-dedup.d.ts.map +0 -1
- package/dist/data/memory/reflection-dedup.js +0 -55
- package/dist/data/memory/reflection-dedup.js.map +0 -1
- package/dist/data/memory/reflection-gates.d.ts +0 -40
- package/dist/data/memory/reflection-gates.d.ts.map +0 -1
- package/dist/data/memory/reflection-gates.js +0 -220
- package/dist/data/memory/reflection-gates.js.map +0 -1
- package/dist/data/memory/reflection-sources.d.ts +0 -13
- package/dist/data/memory/reflection-sources.d.ts.map +0 -1
- package/dist/data/memory/reflection-sources.js +0 -115
- package/dist/data/memory/reflection-sources.js.map +0 -1
- package/dist/data/memory/reflection.d.ts +0 -43
- package/dist/data/memory/reflection.d.ts.map +0 -1
- package/dist/data/memory/reflection.js +0 -329
- package/dist/data/memory/reflection.js.map +0 -1
- package/dist/llm/prompts/reflection.d.ts +0 -6
- package/dist/llm/prompts/reflection.d.ts.map +0 -1
- package/dist/llm/prompts/reflection.js +0 -77
- package/dist/llm/prompts/reflection.js.map +0 -1
- package/dist/services/memory-pruning.service.d.ts +0 -12
- package/dist/services/memory-pruning.service.d.ts.map +0 -1
- package/dist/services/memory-pruning.service.js +0 -179
- package/dist/services/memory-pruning.service.js.map +0 -1
- package/templates/daily-review.default.md +0 -26
- package/templates/fire-and-forget.default.md +0 -76
- package/templates/offload.default.md +0 -33
- package/templates/sensorium-repair.default.md +0 -114
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* (root, daily, branch, worker) with their configuration and status.
|
|
6
6
|
*/
|
|
7
7
|
import { nowISO } from "./utils.js";
|
|
8
|
+
import { Optional } from "../optional.js";
|
|
8
9
|
import { restoreNotesForThread } from "./note-archival.js";
|
|
10
|
+
import { MINTED_THREAD_ID_FLOOR } from "./schema-ddl.js";
|
|
9
11
|
export class ThreadRepository {
|
|
10
12
|
registerThread(db, entry) {
|
|
11
13
|
return registerThread(db, entry);
|
|
@@ -35,6 +37,7 @@ function rowToEntry(row) {
|
|
|
35
37
|
name: row.name,
|
|
36
38
|
type: row.type,
|
|
37
39
|
rootThreadId: row.root_thread_id ?? null,
|
|
40
|
+
notifyThreadId: row.notify_thread_id ?? null, // eslint-disable-line no-restricted-syntax -- nullable DB column, mirrors sibling rootThreadId at the thread_registry row boundary
|
|
38
41
|
badge: row.badge,
|
|
39
42
|
client: row.client,
|
|
40
43
|
maxRetries: row.max_retries,
|
|
@@ -55,21 +58,54 @@ function rowToEntry(row) {
|
|
|
55
58
|
};
|
|
56
59
|
}
|
|
57
60
|
// ─── CRUD ────────────────────────────────────────────────────────────────────
|
|
61
|
+
/**
|
|
62
|
+
* The row seam, and the one place the minted-id rule is stated for `thread_registry`.
|
|
63
|
+
*
|
|
64
|
+
* A worker's identity is MINTED ({@link mintThreadId}, called from `birthWorker`), so a
|
|
65
|
+
* `type = 'worker'` row below {@link MINTED_THREAD_ID_FLOOR} is not a worker anything
|
|
66
|
+
* mints — it is a Telegram topic number wearing a worker's type, and every id-reuse
|
|
67
|
+
* failure this line has chased (a worker inheriting an earlier occupant's lifecycle, a
|
|
68
|
+
* sweep deleting a live thread's forum topic because the id IS the topic) starts there.
|
|
69
|
+
*
|
|
70
|
+
* It is refused HERE because this statement is where `type` enters the table: the INSERT
|
|
71
|
+
* writes `entry.type`, and the ON CONFLICT arm below either PRESERVES the stored type
|
|
72
|
+
* (while `keep_alive = 1`) or writes `excluded.type`, which is this same `entry.type`.
|
|
73
|
+
* No other writer introduces one — `UpdateThreadPatch` has no `type` field, and
|
|
74
|
+
* `handleConvertToRoot`'s direct UPDATE writes the literal 'root'. A preserved type was
|
|
75
|
+
* therefore introduced by an earlier call through this guard, or predates migration 39,
|
|
76
|
+
* which purged what did.
|
|
77
|
+
*
|
|
78
|
+
* It THROWS where the entrances above it answer in prose. `handleCreateThread` and
|
|
79
|
+
* `handleStartThread` refuse their own callers with a sentence naming the one way to get
|
|
80
|
+
* a worker, because those callers are an agent or an HTTP client that can act on it;
|
|
81
|
+
* an entry arriving here already carries the contradiction, and there is no row this
|
|
82
|
+
* function could write instead that the caller asked for.
|
|
83
|
+
*/
|
|
58
84
|
export function registerThread(db, entry) {
|
|
85
|
+
if (entry.type === "worker" && entry.threadId < MINTED_THREAD_ID_FLOOR) {
|
|
86
|
+
throw new Error(`registerThread: refusing to write a worker row on thread_id ${entry.threadId}, which is below ` +
|
|
87
|
+
`MINTED_THREAD_ID_FLOOR (${MINTED_THREAD_ID_FLOOR}) — a worker's id is minted, and a number in ` +
|
|
88
|
+
`the Telegram topic range names a topic rather than a worker. Mint the id first (birthWorker), ` +
|
|
89
|
+
`or register this row as the type it actually is.`);
|
|
90
|
+
}
|
|
59
91
|
const now = nowISO();
|
|
60
92
|
// Root and branch threads are the persistent tier: keep-alive (watcher
|
|
61
|
-
// respawn)
|
|
62
|
-
// worker
|
|
93
|
+
// respawn) is on by default. The nightly MEMORY pass runs for every
|
|
94
|
+
// non-worker thread regardless of this flag (see getConsolidationThreads),
|
|
95
|
+
// so daily_rotation now only gates the OPT-IN session RESTART and therefore
|
|
96
|
+
// defaults OFF — a fresh root is never force-restarted unless explicitly
|
|
97
|
+
// enabled. Explicit values still win over the type default.
|
|
63
98
|
const persistentByDefault = entry.type === "root" || entry.type === "branch";
|
|
64
|
-
const dailyRotation = entry.dailyRotation ??
|
|
99
|
+
const dailyRotation = entry.dailyRotation ?? false;
|
|
65
100
|
const keepAlive = entry.keepAlive ?? persistentByDefault;
|
|
66
101
|
db.prepare(`INSERT INTO thread_registry
|
|
67
|
-
(thread_id, name, type, root_thread_id, badge, client, max_retries, cooldown_ms, keep_alive, daily_rotation, working_directory, created_at, last_active_at, status)
|
|
68
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active')
|
|
102
|
+
(thread_id, name, type, root_thread_id, notify_thread_id, badge, client, max_retries, cooldown_ms, keep_alive, daily_rotation, working_directory, created_at, last_active_at, status)
|
|
103
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active')
|
|
69
104
|
ON CONFLICT(thread_id) DO UPDATE SET
|
|
70
105
|
name = excluded.name,
|
|
71
106
|
type = CASE WHEN thread_registry.keep_alive = 1 THEN thread_registry.type ELSE excluded.type END,
|
|
72
107
|
root_thread_id = excluded.root_thread_id,
|
|
108
|
+
notify_thread_id = excluded.notify_thread_id,
|
|
73
109
|
badge = excluded.badge,
|
|
74
110
|
client = CASE WHEN thread_registry.keep_alive = 1 THEN thread_registry.client ELSE excluded.client END,
|
|
75
111
|
max_retries = excluded.max_retries,
|
|
@@ -78,17 +114,82 @@ export function registerThread(db, entry) {
|
|
|
78
114
|
daily_rotation = CASE WHEN thread_registry.keep_alive = 1 THEN thread_registry.daily_rotation ELSE excluded.daily_rotation END,
|
|
79
115
|
working_directory = COALESCE(excluded.working_directory, thread_registry.working_directory),
|
|
80
116
|
last_active_at = excluded.last_active_at,
|
|
81
|
-
status = 'active'
|
|
117
|
+
status = 'active',
|
|
118
|
+
-- Re-registering an archived thread revives it, so its archive stamp is
|
|
119
|
+
-- no longer true. Cleared with the status in the same statement, keeping
|
|
120
|
+
-- "archived_at is set exactly while status = 'archived'" an invariant the
|
|
121
|
+
-- purge anchor can rely on.
|
|
122
|
+
archived_at = NULL
|
|
123
|
+
-- A row a purge has marked is not revived: its files are being removed and
|
|
124
|
+
-- its rows are next (see "The row is the lock" below).
|
|
125
|
+
WHERE thread_registry.purging_at IS NULL`).run(entry.threadId, entry.name, entry.type, entry.rootThreadId ?? null, entry.notifyThreadId ?? null, // eslint-disable-line no-restricted-syntax -- nullable DB column, mirrors sibling rootThreadId at the thread_registry row boundary
|
|
126
|
+
entry.badge ?? "root", entry.client ?? "claude", entry.maxRetries ?? 5, entry.cooldownMs ?? 300000, keepAlive ? 1 : 0, dailyRotation ? 1 : 0, entry.workingDirectory ?? null, now, now);
|
|
127
|
+
// The upsert's WHERE leaves a row a purge has marked untouched, and a marked row
|
|
128
|
+
// is still archived, so reading the mark back is the refusal.
|
|
129
|
+
if (isThreadMarkedForPurge(db, entry.threadId)) {
|
|
130
|
+
throw new Error(`registerThread: thread ${entry.threadId} is being permanently deleted — register it again once the delete has finished`);
|
|
131
|
+
}
|
|
82
132
|
const result = getThread(db, entry.threadId);
|
|
83
133
|
if (!result) {
|
|
84
134
|
throw new Error(`registerThread: failed to retrieve thread after insert (threadId=${entry.threadId})`);
|
|
85
135
|
}
|
|
86
136
|
return result;
|
|
87
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Allocate a thread id that no thread has ever held and none ever will again —
|
|
140
|
+
* GIVEN the allocating INSERT autocommits, which is what the first guard enforces.
|
|
141
|
+
* Inside an enclosing `db.transaction`, SQLite's AUTOINCREMENT mark rolls back with
|
|
142
|
+
* everything else and the next mint is handed the same number (measured), so the
|
|
143
|
+
* promise is not available mid-transaction and is refused rather than weakened.
|
|
144
|
+
*
|
|
145
|
+
* That refusal breaks no correct caller, because the sound order is mint, commit,
|
|
146
|
+
* then register: the id has to be durable before any row, file, process or wire
|
|
147
|
+
* message names it. A caller wanting mint-and-register atomically still mints first
|
|
148
|
+
* and tolerates the gap on a crash — a minted-never-used id is this design's native
|
|
149
|
+
* waste product, and never reusing one is exactly what makes those gaps free.
|
|
150
|
+
*
|
|
151
|
+
* Why the sequence is shaped the way it is — and why the mark survives a hard
|
|
152
|
+
* `deleteThread` and the 180-day purge — is argued on THREAD_ID_SEQUENCE_DDL. The
|
|
153
|
+
* trim that keeps the table at one row carries a WHERE and so cannot lower it.
|
|
154
|
+
*
|
|
155
|
+
* The floor is asserted rather than assumed. An id below it is a Telegram topic
|
|
156
|
+
* number as far as every reader of `thread_registry.thread_id` is concerned, and the
|
|
157
|
+
* two states that produce one — an unseeded sequence, or a lowered floor — are
|
|
158
|
+
* both silent. Refusing to mint is the only answer a caller cannot paper over.
|
|
159
|
+
*/
|
|
160
|
+
export function mintThreadId(db) {
|
|
161
|
+
if (db.inTransaction) {
|
|
162
|
+
throw new Error(`mintThreadId: refusing to mint inside a transaction — a rollback would discard the ` +
|
|
163
|
+
`allocating row and reissue its id, so the id would not be mint-once. Mint and let it ` +
|
|
164
|
+
`commit first, then open the transaction that records it.`);
|
|
165
|
+
}
|
|
166
|
+
const allocated = db
|
|
167
|
+
.prepare(`INSERT INTO thread_id_sequence (minted_at) VALUES (?)`)
|
|
168
|
+
.run(nowISO()).lastInsertRowid;
|
|
169
|
+
const minted = Number(allocated);
|
|
170
|
+
if (!Number.isSafeInteger(minted) || minted < MINTED_THREAD_ID_FLOOR) {
|
|
171
|
+
throw new Error(`mintThreadId: thread_id_sequence produced ${allocated}, which is not a safe integer at or above MINTED_THREAD_ID_FLOOR (${MINTED_THREAD_ID_FLOOR})`);
|
|
172
|
+
}
|
|
173
|
+
db.prepare(`DELETE FROM thread_id_sequence WHERE id < ?`).run(minted);
|
|
174
|
+
return minted;
|
|
175
|
+
}
|
|
88
176
|
export function getThread(db, threadId) {
|
|
89
177
|
const row = db.prepare(`SELECT * FROM thread_registry WHERE thread_id = ?`).get(threadId);
|
|
90
178
|
return row ? rowToEntry(row) : null;
|
|
91
179
|
}
|
|
180
|
+
/** Registry badge marking an ephemeral task-board card worker. */
|
|
181
|
+
export const BOARD_WORKER_BADGE = "board-worker";
|
|
182
|
+
/**
|
|
183
|
+
* Whether a thread's own episodic memory writes are suppressed. Currently true
|
|
184
|
+
* for ephemeral board-card workers, whose state lives in card comments rather
|
|
185
|
+
* than Sensorium episodes. Named for the memory concern so the episode layer
|
|
186
|
+
* asks a memory-domain question and never learns the board badge value.
|
|
187
|
+
*/
|
|
188
|
+
export function suppressesEpisodicMemory(db, threadId) {
|
|
189
|
+
return Optional.fromNullable(getThread(db, threadId))
|
|
190
|
+
.map((thread) => thread.badge === BOARD_WORKER_BADGE)
|
|
191
|
+
.orElse(false);
|
|
192
|
+
}
|
|
92
193
|
export function getThreadByName(db, name) {
|
|
93
194
|
const row = db
|
|
94
195
|
.prepare(`SELECT * FROM thread_registry WHERE name = ? AND status = 'active' ORDER BY last_active_at DESC LIMIT 1`)
|
|
@@ -107,9 +208,15 @@ export function getRootThreads(db) {
|
|
|
107
208
|
.all();
|
|
108
209
|
return rows.map(rowToEntry);
|
|
109
210
|
}
|
|
110
|
-
|
|
211
|
+
/**
|
|
212
|
+
* All non-worker, non-archived threads. The nightly memory pass (consolidation,
|
|
213
|
+
* reflection, narrative) runs for every one of these — memory integrity is
|
|
214
|
+
* required by all threads, not just those opted into session rotation. Workers
|
|
215
|
+
* are excluded because their memory is no-persist (discarded on decommission).
|
|
216
|
+
*/
|
|
217
|
+
export function getConsolidationThreads(db) {
|
|
111
218
|
const rows = db
|
|
112
|
-
.prepare(`SELECT * FROM thread_registry WHERE
|
|
219
|
+
.prepare(`SELECT * FROM thread_registry WHERE type != 'worker' AND status != 'archived' ORDER BY created_at DESC`)
|
|
113
220
|
.all();
|
|
114
221
|
return rows.map(rowToEntry);
|
|
115
222
|
}
|
|
@@ -146,8 +253,13 @@ export function updateThread(db, threadId, updates) {
|
|
|
146
253
|
params.push(updates.name);
|
|
147
254
|
}
|
|
148
255
|
if (updates.status !== undefined) {
|
|
149
|
-
|
|
150
|
-
|
|
256
|
+
// `archived_at` is DERIVED from the status in this one general status write, so
|
|
257
|
+
// "archived_at is set exactly while status = 'archived'" holds for every path that
|
|
258
|
+
// archives or revives a thread through here, with no caller stamping it by hand.
|
|
259
|
+
// It is the purge window's anchor. A row that is already archived keeps its
|
|
260
|
+
// stamp (COALESCE): re-archiving does not restart the clock it was archived on.
|
|
261
|
+
setClauses.push("status = ?", "archived_at = CASE WHEN ? = 'archived' THEN COALESCE(archived_at, ?) END");
|
|
262
|
+
params.push(updates.status, updates.status, nowISO());
|
|
151
263
|
}
|
|
152
264
|
if (updates.lastActiveAt !== undefined) {
|
|
153
265
|
setClauses.push("last_active_at = ?");
|
|
@@ -200,14 +312,25 @@ export function updateThread(db, threadId, updates) {
|
|
|
200
312
|
if (setClauses.length === 0)
|
|
201
313
|
return false;
|
|
202
314
|
params.push(threadId);
|
|
315
|
+
// A status write is a door like every start: a row a purge has marked keeps its
|
|
316
|
+
// status, or the sweep's mid-purge finisher would delete a thread revived here.
|
|
317
|
+
// And a 'spawning' row is held by a start (claimThreadStart): archiving it would
|
|
318
|
+
// hand it to the purge while that start is still writing the id's files (inc66 M2).
|
|
319
|
+
const statusGuard = updates.status === undefined
|
|
320
|
+
? ""
|
|
321
|
+
: " AND purging_at IS NULL" +
|
|
322
|
+
(updates.status === "archived" ? " AND status <> 'spawning'" : "");
|
|
203
323
|
const result = db
|
|
204
|
-
.prepare(`UPDATE thread_registry SET ${setClauses.join(", ")} WHERE thread_id =
|
|
324
|
+
.prepare(`UPDATE thread_registry SET ${setClauses.join(", ")} WHERE thread_id = ?${statusGuard}`)
|
|
205
325
|
.run(...params);
|
|
206
326
|
return result.changes > 0;
|
|
207
327
|
}
|
|
328
|
+
/** Archive a row. False when there is no row, a start holds it ('spawning'), or a
|
|
329
|
+
* purge has marked it — re-stamping archived_at under a purge would be a false 200. */
|
|
208
330
|
export function archiveThread(db, threadId) {
|
|
209
331
|
const result = db
|
|
210
|
-
.prepare(`UPDATE thread_registry SET status = 'archived', keep_alive = 0, clean_shutdown = 1, archived_at = ?
|
|
332
|
+
.prepare(`UPDATE thread_registry SET status = 'archived', keep_alive = 0, clean_shutdown = 1, archived_at = ?
|
|
333
|
+
WHERE thread_id = ? AND status <> 'spawning' AND purging_at IS NULL`)
|
|
211
334
|
.run(nowISO(), threadId);
|
|
212
335
|
return result.changes > 0;
|
|
213
336
|
}
|
|
@@ -219,7 +342,8 @@ export function unarchiveThread(db, threadId) {
|
|
|
219
342
|
// transaction rolls the status change back on any restore failure.
|
|
220
343
|
return db.transaction(() => {
|
|
221
344
|
const result = db
|
|
222
|
-
.prepare(`UPDATE thread_registry SET status = 'active', keep_alive = 1, archived_at = NULL
|
|
345
|
+
.prepare(`UPDATE thread_registry SET status = 'active', keep_alive = 1, archived_at = NULL
|
|
346
|
+
WHERE thread_id = ? AND status = 'archived' AND purging_at IS NULL`)
|
|
223
347
|
.run(threadId);
|
|
224
348
|
if (result.changes === 0)
|
|
225
349
|
return false;
|
|
@@ -232,50 +356,219 @@ export function unarchiveThread(db, threadId) {
|
|
|
232
356
|
return true;
|
|
233
357
|
})();
|
|
234
358
|
}
|
|
235
|
-
|
|
359
|
+
/**
|
|
360
|
+
* Archived threads, workers EXCLUDED by default.
|
|
361
|
+
*
|
|
362
|
+
* The exclusion is right for the overview this feed was written for — a swarm
|
|
363
|
+
* run leaves dozens of decommissioned workers that would bury the handful of
|
|
364
|
+
* roots and branches an operator actually recognises. It was wrong as the ONLY
|
|
365
|
+
* answer: this is the single feed behind the archived-threads tab, which is the
|
|
366
|
+
* single place the permanent delete can be invoked from, and 220 of the 225
|
|
367
|
+
* per-thread config dirs on the live host (230 of its 320 MB) belong to exactly
|
|
368
|
+
* the archived workers it hid. The delete API itself never had a type filter —
|
|
369
|
+
* only the list did — so the bytes were unreachable rather than protected.
|
|
370
|
+
*
|
|
371
|
+
* `includeWorkers` is opt-in so every existing caller keeps the behaviour it
|
|
372
|
+
* was written against.
|
|
373
|
+
*/
|
|
374
|
+
export function getArchivedThreads(db, opts = {}) {
|
|
375
|
+
const workerFilter = opts.includeWorkers ? "" : ` AND type != 'worker'`;
|
|
236
376
|
const rows = db
|
|
237
|
-
.prepare(`SELECT * FROM thread_registry WHERE status = 'archived'
|
|
377
|
+
.prepare(`SELECT * FROM thread_registry WHERE status = 'archived'${workerFilter}
|
|
378
|
+
ORDER BY archived_at DESC, last_active_at DESC`)
|
|
238
379
|
.all();
|
|
239
380
|
return rows.map(rowToEntry);
|
|
240
381
|
}
|
|
382
|
+
/**
|
|
383
|
+
* Drop ONLY the registry row. Not a purge, and not reachable from the operator's
|
|
384
|
+
* delete any more — that goes through `purgeThread`, which takes every dependent
|
|
385
|
+
* table with it.
|
|
386
|
+
*
|
|
387
|
+
* It stays because worker-birth's PRE-SPAWN teardown wants exactly this: a row it
|
|
388
|
+
* registered moments ago, for a worker no process has run as, so nothing has been
|
|
389
|
+
* written against the id yet and there is nothing for a purge to find.
|
|
390
|
+
* `purgeThread` would refuse that row (no purge marked it: it is active, not
|
|
391
|
+
* archived). Do not reach for this to retire a thread that has ever run.
|
|
392
|
+
*/
|
|
241
393
|
export function deleteThread(db, threadId) {
|
|
242
394
|
const result = db.prepare(`DELETE FROM thread_registry WHERE thread_id = ?`).run(threadId);
|
|
243
395
|
return result.changes > 0;
|
|
244
396
|
}
|
|
397
|
+
// ─── Purge ───────────────────────────────────────────────────────────────────
|
|
245
398
|
/**
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
399
|
+
* How long an archived thread survives before the daily sweep purges it.
|
|
400
|
+
*
|
|
401
|
+
* Was 180 days. The sweep is a floor, not the only way out — an archived thread
|
|
402
|
+
* now also has an explicit, operator-driven permanent delete (purgeThread +
|
|
403
|
+
* purgeThreadFiles, surfaced as `DELETE /api/threads/:id?hard=true`). With a
|
|
404
|
+
* deliberate path available, the automatic one no longer has to be the thing
|
|
405
|
+
* that reclaims disk, so the window is long enough that nothing is ever lost to
|
|
406
|
+
* a timer the operator forgot about. Exported so operator-facing text states
|
|
407
|
+
* the real number instead of a stale literal.
|
|
249
408
|
*/
|
|
250
|
-
export
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
409
|
+
export const ARCHIVED_THREAD_PURGE_DAYS = 730;
|
|
410
|
+
/**
|
|
411
|
+
* Every table that stores rows keyed to one thread, in an order that clears
|
|
412
|
+
* child rows before their parents (note_embeddings via semantic_notes,
|
|
413
|
+
* voice_signatures via episodes). The thread_registry row itself is not on
|
|
414
|
+
* this list: purgeThread deletes it FIRST, so that a purge whose row was never
|
|
415
|
+
* marked (or was taken by a start) has touched none of these.
|
|
416
|
+
*
|
|
417
|
+
* THIS IS THE ONE HOME FOR THE LIST. A table added here is purged by both the
|
|
418
|
+
* single-thread delete and the daily sweep; a table missed here orphans its
|
|
419
|
+
* rows in both.
|
|
420
|
+
*/
|
|
421
|
+
const THREAD_DEPENDENT_DELETES = [
|
|
422
|
+
`DELETE FROM note_embeddings WHERE note_id IN (SELECT note_id FROM semantic_notes WHERE thread_id = ?)`,
|
|
423
|
+
`DELETE FROM temporal_narratives WHERE thread_id = ?`,
|
|
424
|
+
`DELETE FROM voice_signatures WHERE episode_id IN (SELECT episode_id FROM episodes WHERE thread_id = ?)`,
|
|
425
|
+
`DELETE FROM semantic_notes WHERE thread_id = ?`,
|
|
426
|
+
`DELETE FROM episodes WHERE thread_id = ?`,
|
|
427
|
+
`DELETE FROM sent_messages WHERE thread_id = ?`,
|
|
428
|
+
`DELETE FROM topic_registry WHERE thread_id = ?`,
|
|
429
|
+
// A worker's launch/death/release record. Purge runs only once no process can
|
|
430
|
+
// be alive for the thread (liveProcessRefusal), so nothing is left for the row
|
|
431
|
+
// to arbitrate. Dropping it also removes the worker from its card's
|
|
432
|
+
// prior-worker candidate set (listCardWorkers), which is correct for a worker
|
|
433
|
+
// proven gone.
|
|
434
|
+
`DELETE FROM worker_lifecycle WHERE thread_id = ?`,
|
|
435
|
+
// FYI lines queued for the thread's next turn, which it will never take.
|
|
436
|
+
`DELETE FROM passive_notices WHERE thread_id = ?`,
|
|
437
|
+
];
|
|
438
|
+
// ─── The row is the lock ─────────────────────────────────────────────────────
|
|
439
|
+
//
|
|
440
|
+
// A purge and a start of the SAME id race on one question: who owns the id right
|
|
441
|
+
// now? The registry row answers it, and SQLite changes a row atomically. So both
|
|
442
|
+
// sides take the row with one conditional UPDATE BEFORE any side effect, and the
|
|
443
|
+
// loser sees `changes = 0` and touches nothing:
|
|
444
|
+
// - a purge marks the row (`purging_at`), then removes files, then deletes rows;
|
|
445
|
+
// - a start flips an archived row out of 'archived' before its first file write,
|
|
446
|
+
// and only while it is not marked.
|
|
447
|
+
// This holds across an `await` on either side, and across two server processes
|
|
448
|
+
// (a self-update overlap), which no in-memory lock can see. A crash mid-purge
|
|
449
|
+
// leaves the mark, and the next sweep finishes the purge (selectThreadsMidPurge).
|
|
450
|
+
// `purging_at` is a column, not a status, because a new status would mean
|
|
451
|
+
// rebuilding the table's CHECK.
|
|
452
|
+
/**
|
|
453
|
+
* Mark an archived row as being purged, answering the stamp written: the fence
|
|
454
|
+
* a finisher must still see to act. Empty means the purge lost: the row is not
|
|
455
|
+
* archived any more (a start claimed it), already marked, gone, or — when
|
|
456
|
+
* `archivedAt` is given — re-archived with a newer stamp than the one judged.
|
|
457
|
+
*/
|
|
458
|
+
export function markThreadPurging(db, threadId, archivedAt) {
|
|
459
|
+
const stamp = archivedAt === undefined ? "" : " AND archived_at = ?";
|
|
460
|
+
const params = archivedAt === undefined ? [] : [archivedAt];
|
|
461
|
+
const purgingAt = nowISO();
|
|
462
|
+
const changes = db
|
|
463
|
+
.prepare(`UPDATE thread_registry SET purging_at = ?
|
|
464
|
+
WHERE thread_id = ? AND status = 'archived' AND purging_at IS NULL${stamp}`)
|
|
465
|
+
.run(purgingAt, threadId, ...params).changes;
|
|
466
|
+
return changes > 0 ? Optional.of(purgingAt) : Optional.empty();
|
|
467
|
+
}
|
|
468
|
+
/** True while a purge owns the row (markThreadPurging): every door refuses it. */
|
|
469
|
+
export function isThreadMarkedForPurge(db, threadId) {
|
|
470
|
+
return threadPurgeMark(db, threadId).has();
|
|
471
|
+
}
|
|
472
|
+
/** The mark a purge holds on the row now, or empty when none does. */
|
|
473
|
+
export function threadPurgeMark(db, threadId) {
|
|
474
|
+
return Optional.fromNullable(db
|
|
475
|
+
.prepare(`SELECT purging_at FROM thread_registry WHERE thread_id = ? AND purging_at IS NOT NULL`)
|
|
476
|
+
// eslint-disable-next-line no-restricted-syntax -- SQLite returns undefined for an absent row
|
|
477
|
+
.get(threadId)).map((marked) => marked.purging_at);
|
|
478
|
+
}
|
|
479
|
+
/** The resting states a start takes a row out of: everything a process is not on. */
|
|
480
|
+
const START_CLAIMABLE_STATUSES = ["archived", "exited", "created"];
|
|
481
|
+
/**
|
|
482
|
+
* Every start of an existing id calls this BEFORE its first file write. It flips
|
|
483
|
+
* a resting row to `status` ('spawning' for a spawn, 'active' for a session that
|
|
484
|
+
* revives its thread), which is a state a purge refuses to mark. Synchronous and
|
|
485
|
+
* in one transaction, so the read, the flip and the note restore see the same row.
|
|
486
|
+
* A revived ARCHIVED row gets its archived notes back, exactly as unarchiveThread.
|
|
487
|
+
*/
|
|
488
|
+
export function claimThreadStart(db, threadId, status) {
|
|
489
|
+
return db.transaction(() => {
|
|
490
|
+
if (isThreadMarkedForPurge(db, threadId))
|
|
491
|
+
return { kind: "purging" };
|
|
492
|
+
const previous = Optional.fromNullable(getThread(db, threadId)).map((row) => row.status);
|
|
493
|
+
if (previous.orElse("") === "spawning")
|
|
494
|
+
return { kind: "starting" };
|
|
495
|
+
if (!previous.has() || !START_CLAIMABLE_STATUSES.includes(previous.get())) {
|
|
496
|
+
return { kind: "free" };
|
|
497
|
+
}
|
|
498
|
+
db.prepare(`UPDATE thread_registry SET status = ?, archived_at = NULL WHERE thread_id = ?`).run(status, threadId);
|
|
499
|
+
if (previous.get() === "archived") {
|
|
500
|
+
restoreNotesForThread(db, threadId, { includeUnknownLegacy: true });
|
|
276
501
|
}
|
|
502
|
+
return { kind: "claimed", previous: previous.get(), claimedAs: status };
|
|
277
503
|
})();
|
|
278
|
-
|
|
504
|
+
}
|
|
505
|
+
/** Hand a claimed row back to the status it was taken from when its start failed. */
|
|
506
|
+
export function releaseThreadStart(db, threadId, claim) {
|
|
507
|
+
if (claim.kind !== "claimed")
|
|
508
|
+
return;
|
|
509
|
+
db.prepare(`UPDATE thread_registry SET status = ?, archived_at = CASE WHEN ? = 'archived' THEN ? END
|
|
510
|
+
WHERE thread_id = ? AND status = ?`).run(claim.previous, claim.previous, nowISO(), threadId, claim.claimedAs);
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Delete ONE thread's entire DB footprint — the registry row, then every table in
|
|
514
|
+
* THREAD_DEPENDENT_DELETES — in a single transaction. This is the LAST step of a
|
|
515
|
+
* purge: only a row still carrying the exact mark `purgingAt` this purge holds is
|
|
516
|
+
* deleted, so a row nobody marked, one a start took first, or a successor's row
|
|
517
|
+
* marked again matches nothing and the purge touches no dependent row. A row
|
|
518
|
+
* already gone means another purge finished it, and anything still keyed to the
|
|
519
|
+
* id is an orphan, which this cleans up. Returns true when this call deleted the
|
|
520
|
+
* registry row.
|
|
521
|
+
*
|
|
522
|
+
* DB only. On-disk per-thread artifacts are purgeThreadFiles' job
|
|
523
|
+
* (src/services/thread-purge.service.ts), and they are removed BEFORE this runs.
|
|
524
|
+
*/
|
|
525
|
+
export function purgeThread(db, threadId, purgingAt) {
|
|
526
|
+
const dependents = THREAD_DEPENDENT_DELETES.map((sql) => db.prepare(sql));
|
|
527
|
+
return db.transaction(() => {
|
|
528
|
+
const removed = db
|
|
529
|
+
.prepare(`DELETE FROM thread_registry WHERE thread_id = ? AND purging_at = ?`)
|
|
530
|
+
.run(threadId, purgingAt).changes > 0;
|
|
531
|
+
const rowLeft = db.prepare(`SELECT 1 FROM thread_registry WHERE thread_id = ?`).get(threadId);
|
|
532
|
+
if (rowLeft !== undefined)
|
|
533
|
+
return false;
|
|
534
|
+
for (const statement of dependents)
|
|
535
|
+
statement.run(threadId);
|
|
536
|
+
return removed;
|
|
537
|
+
})();
|
|
538
|
+
}
|
|
539
|
+
/** Rows a purge marked and has not finished, with each mark: a crash between
|
|
540
|
+
* mark and row delete, a leftover that kept the rows, or a purge still awaiting
|
|
541
|
+
* its file work. */
|
|
542
|
+
export function selectThreadsMidPurge(db) {
|
|
543
|
+
return db
|
|
544
|
+
.prepare(`SELECT thread_id, purging_at FROM thread_registry WHERE purging_at IS NOT NULL`)
|
|
545
|
+
.all().map((row) => ({ threadId: row.thread_id, purgingAt: row.purging_at }));
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Every archived thread that has an archive anchor — NOT a cutoff query, and
|
|
549
|
+
* nothing is deleted here. The age test lives in the sweep, in JavaScript, on
|
|
550
|
+
* parsed instants: `archived_at < cutoff` in SQL is a TEXT comparison, so a
|
|
551
|
+
* stamp that is not a canonical UTC ISO instant (`...-02:00`, a different
|
|
552
|
+
* precision) compares by characters rather than by time, and this is the
|
|
553
|
+
* predicate of an unattended delete.
|
|
554
|
+
*
|
|
555
|
+
* The sweep also has to gate each candidate on liveness — an async question no
|
|
556
|
+
* SQLite transaction can hold open — which is why selection and deletion are
|
|
557
|
+
* separate calls at all: `sweepOldArchivedThreads` in
|
|
558
|
+
* services/thread-purge.service.ts selects, validates, gates, marks, then purges.
|
|
559
|
+
*
|
|
560
|
+
* ANCHORED ON `archived_at`, NOT `last_active_at`. "730 days of grace after
|
|
561
|
+
* archiving" is what the window means, and last activity does not express it: a
|
|
562
|
+
* thread archived today whose last message was three years ago was eligible on
|
|
563
|
+
* the very next sweep. Every archived row has an anchor: updateThread derives it
|
|
564
|
+
* from the status, and migration 40 stamped the rows archived before it did.
|
|
565
|
+
*/
|
|
566
|
+
export function selectAnchoredArchivedThreads(db) {
|
|
567
|
+
const rows = db
|
|
568
|
+
.prepare(`SELECT thread_id, archived_at FROM thread_registry
|
|
569
|
+
WHERE status = 'archived' AND archived_at IS NOT NULL AND purging_at IS NULL`)
|
|
570
|
+
.all();
|
|
571
|
+
return rows.map((row) => ({ threadId: row.thread_id, archivedAt: row.archived_at }));
|
|
279
572
|
}
|
|
280
573
|
/**
|
|
281
574
|
* Reset the daily session timestamp for a thread.
|
|
@@ -288,9 +581,16 @@ export function resetDailySession(db, threadId) {
|
|
|
288
581
|
return result.changes > 0;
|
|
289
582
|
}
|
|
290
583
|
/**
|
|
291
|
-
* Mark a thread's session as running (clean_shutdown = 0). Called
|
|
292
|
-
*
|
|
293
|
-
*
|
|
584
|
+
* Mark a thread's session as running (clean_shutdown = 0). Called from
|
|
585
|
+
* start_session, and the only write that clears the flag.
|
|
586
|
+
*
|
|
587
|
+
* The column now carries ONE meaning that code acts on: 0 = "the process
|
|
588
|
+
* currently attached to this thread reached start_session". spawn-common's
|
|
589
|
+
* registerSpawnedProcess sets it back to 1 for every new process, and the
|
|
590
|
+
* birth-death respawn net reads it to tell a thread that died before it ever
|
|
591
|
+
* registered from one that ran and then exited. (Its original job — letting the
|
|
592
|
+
* next resume notice an interrupted session — is handled by the memory
|
|
593
|
+
* briefing's self-judge prompt instead.)
|
|
294
594
|
*/
|
|
295
595
|
export function markSessionRunning(db, threadId) {
|
|
296
596
|
const result = db
|
|
@@ -303,8 +603,13 @@ export function markSessionRunning(db, threadId) {
|
|
|
303
603
|
* alive at graceful exit (SIGTERM/SIGINT, maintenance restart), so a planned
|
|
304
604
|
* server stop is not mistaken for a crash by the threads it takes down.
|
|
305
605
|
* A thread that had already crashed lingers as status='active' but is absent
|
|
306
|
-
* from aliveThreadIds, so its
|
|
307
|
-
*
|
|
606
|
+
* from aliveThreadIds, so its flag stays 0. An empty list marks nothing.
|
|
607
|
+
*
|
|
608
|
+
* In the {@link markSessionRunning} reading this says "no live process of ours
|
|
609
|
+
* is registered on that thread any more", which is true — the server is exiting.
|
|
610
|
+
* Agent children are detached and outlive it, so nothing re-reads the flag until
|
|
611
|
+
* they are re-adopted or respawned, and the respawn net's uptime window keeps a
|
|
612
|
+
* long-lived survivor out of scope.
|
|
308
613
|
*/
|
|
309
614
|
export function markAliveThreadsCleanShutdown(db, aliveThreadIds) {
|
|
310
615
|
if (aliveThreadIds.length === 0)
|
|
@@ -338,23 +643,174 @@ export function getExplicitTelegramTopicId(db, threadId) {
|
|
|
338
643
|
return row?.telegram_topic_id ?? null;
|
|
339
644
|
}
|
|
340
645
|
/**
|
|
341
|
-
*
|
|
342
|
-
*
|
|
646
|
+
* Record that `topicId` is gone by clearing the thread's stored pointer — only while
|
|
647
|
+
* the pointer still names `topicId`. A compare-and-clear, because every caller clears
|
|
648
|
+
* after an AWAITED delete: a pointer written in the meantime (the dashboard PATCH)
|
|
649
|
+
* names a topic that caller never deleted, and blanking it would hide that topic from
|
|
650
|
+
* the orphan sweep, which selects only stored pointers. Returns whether it cleared.
|
|
343
651
|
*/
|
|
344
|
-
export function
|
|
345
|
-
|
|
346
|
-
.prepare(`UPDATE thread_registry
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
652
|
+
export function clearTelegramTopicIdIfStill(db, threadId, topicId) {
|
|
653
|
+
return (db
|
|
654
|
+
.prepare(`UPDATE thread_registry SET telegram_topic_id = NULL
|
|
655
|
+
WHERE thread_id = ? AND telegram_topic_id = ?`)
|
|
656
|
+
.run(threadId, topicId).changes > 0);
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* The topic number a DESTRUCTIVE teardown may aim at, stated once so no call site
|
|
660
|
+
* has to guess. Three arms, and the third is the point:
|
|
661
|
+
*
|
|
662
|
+
* 1. the explicit pointer when the row carries one — the only positive evidence of
|
|
663
|
+
* which topic this thread owns;
|
|
664
|
+
* 2. else the thread id itself, but ONLY below {@link MINTED_THREAD_ID_FLOOR}: a
|
|
665
|
+
* NULL column on a topic-numbered thread means the id IS the topic, which is
|
|
666
|
+
* still true of every thread not born on the board's minting path;
|
|
667
|
+
* 3. else NOTHING. A minted id is not a topic number — the floor sits above int32
|
|
668
|
+
* and Telegram topic ids are int32 — so falling back to it aims the delete at a
|
|
669
|
+
* number no topic can have. Telegram answers TOPIC_ID_INVALID, which teardown
|
|
670
|
+
* reads as "already gone", and the real topic is archived out of reach of every
|
|
671
|
+
* sweep that looks for a stored pointer. Returning nothing is safe precisely
|
|
672
|
+
* because no topic is recorded: there is no delete to retry later, so a caller
|
|
673
|
+
* may treat the empty answer as a reached end state rather than a failure.
|
|
674
|
+
*
|
|
675
|
+
* Arm 2 becomes dead code once no below-floor worker remains.
|
|
676
|
+
*/
|
|
677
|
+
export function resolveDeletableTelegramTopicId(db, threadId) {
|
|
678
|
+
const explicit = Optional.fromNullable(getExplicitTelegramTopicId(db, threadId));
|
|
679
|
+
if (explicit.has())
|
|
680
|
+
return explicit;
|
|
681
|
+
return threadId < MINTED_THREAD_ID_FLOOR ? Optional.of(threadId) : Optional.empty();
|
|
682
|
+
}
|
|
683
|
+
/** The statuses in which a thread still OWNS its Telegram topic. Deliberately
|
|
684
|
+
* expressed as the complement of the three terminal ones, so a status added to
|
|
685
|
+
* the CHECK constraint later counts as live by default — the fail-safe
|
|
686
|
+
* direction, since this predicate can only ever PREVENT a delete.
|
|
687
|
+
*
|
|
688
|
+
* `exiting` is live, and that is the non-obvious member: `decommissionWorker`
|
|
689
|
+
* ABORTS teardown and LEAVES the row `exiting` when the kill failed and the
|
|
690
|
+
* agent is still running, so another actor must not delete that topic out from
|
|
691
|
+
* under it. A row wedged in `exiting` cannot leak forever — the stale-row pass
|
|
692
|
+
* of `cleanupExpiredWorkers` re-reaps any non-archived worker past its TTL.
|
|
693
|
+
*
|
|
694
|
+
* `archived` is NOT live: the archived-worker orphan sweep exists precisely to
|
|
695
|
+
* delete the topics of archived rows, so counting archived as live would turn
|
|
696
|
+
* that sweep into a permanent no-op.
|
|
697
|
+
*
|
|
698
|
+
* Two callers share this set, and they do NOT share the same answer.
|
|
699
|
+
* `liveThreadClaimingTopic` below (may I destroy this topic) stops here: all
|
|
700
|
+
* three terminal statuses are non-claimants. `threadOwningTopic` (who should
|
|
701
|
+
* receive a message arriving in a topic) uses the set as its FIRST stage only,
|
|
702
|
+
* then admits `exited` in a second, lower-priority one — because a thread that
|
|
703
|
+
* may no longer DESTROY a topic can still be owed the messages arriving in it.
|
|
704
|
+
* The fail-safe argument above is strict for the delete site; in routing's
|
|
705
|
+
* stage 1 the same set decides only who WINS a contested topic, and the
|
|
706
|
+
* membership rationale for stage 2 lives on `threadOwningTopic`. */
|
|
707
|
+
const TERMINAL_TOPIC_STATUSES = ["archived", "expired", "exited"];
|
|
708
|
+
/**
|
|
709
|
+
* The liveness predicate for a Telegram topic: which OTHER live thread, if any,
|
|
710
|
+
* still claims `topicId`.
|
|
711
|
+
*
|
|
712
|
+
* A Telegram topic is a shared, externally owned, DESTRUCTIVE resource, and this
|
|
713
|
+
* is the one question every delete site must ask first. Three separate paths
|
|
714
|
+
* (a stale `decommissionWorker` that continued past a failed transition, the
|
|
715
|
+
* periodic archived-worker orphan sweep, and the dashboard archive/hard-delete
|
|
716
|
+
* route) each resolved a topic and deleted it while a live thread still owned it,
|
|
717
|
+
* because each checked only its OWN row.
|
|
718
|
+
*
|
|
719
|
+
* Two arms, both load-bearing:
|
|
720
|
+
* - `telegram_topic_id = ?` — the explicit claim.
|
|
721
|
+
* - `telegram_topic_id IS NULL AND thread_id = ?` — the IMPLICIT claim.
|
|
722
|
+
* `resolveTelegramTopicId` reads a NULL column as "my topic is my thread_id",
|
|
723
|
+
* so a live row with a NULL column genuinely owns that topic number. Without
|
|
724
|
+
* this arm the orphan sweep can delete a live thread's topic.
|
|
725
|
+
*
|
|
726
|
+
* `excludingThreadId` is the thread doing the deleting: it must never count
|
|
727
|
+
* itself as a claimant, or the ordinary case (a row tearing down the topic it
|
|
728
|
+
* alone holds) would stop deleting anything at all.
|
|
729
|
+
*
|
|
730
|
+
* Synchronous, like every other read here — which is what lets the dashboard's
|
|
731
|
+
* sync `handleDeleteThread` gate its fire-and-forget delete without becoming
|
|
732
|
+
* async and without moving its response.
|
|
733
|
+
*/
|
|
734
|
+
export function liveThreadClaimingTopic(db, topicId, excludingThreadId) {
|
|
735
|
+
const row = db
|
|
736
|
+
.prepare(`SELECT thread_id FROM thread_registry
|
|
737
|
+
WHERE thread_id != ?
|
|
738
|
+
AND status NOT IN (${TERMINAL_TOPIC_STATUSES.map(() => "?").join(", ")})
|
|
739
|
+
AND (telegram_topic_id = ? OR (telegram_topic_id IS NULL AND thread_id = ?))
|
|
740
|
+
LIMIT 1`)
|
|
741
|
+
.get(excludingThreadId, ...TERMINAL_TOPIC_STATUSES, topicId, topicId);
|
|
742
|
+
// `get` hands back `unknown`: absence is answered by `fromNullable`, which
|
|
743
|
+
// checks it at runtime, and the row SHAPE is asserted only inside `map`, where
|
|
744
|
+
// a row is known to be present.
|
|
745
|
+
return Optional.fromNullable(row).map((claimant) => claimant.thread_id);
|
|
746
|
+
}
|
|
747
|
+
/** One stage of `threadOwningTopic`: the first row its `WHERE` admits, or empty.
|
|
748
|
+
* `get` hands back `unknown`: absence is answered by `fromNullable`, which checks
|
|
749
|
+
* it at runtime, and the row SHAPE is asserted only inside `map`, where a row is
|
|
750
|
+
* known to be present. */
|
|
751
|
+
function topicClaimantWhere(db, where, binds) {
|
|
752
|
+
const row = db
|
|
753
|
+
.prepare(`SELECT thread_id FROM thread_registry WHERE ${where} LIMIT 1`)
|
|
754
|
+
.get(...binds);
|
|
755
|
+
return Optional.fromNullable(row).map((owner) => owner.thread_id);
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* The routing half of the ownership question: which thread, if any, should
|
|
759
|
+
* receive a message that arrived in `topicId`.
|
|
760
|
+
*
|
|
761
|
+
* TWO STAGES, and the priority between them is the point:
|
|
762
|
+
* 1. the LIVE owner — `TERMINAL_TOPIC_STATUSES` excluded, as the delete gate
|
|
763
|
+
* does;
|
|
764
|
+
* 2. only when stage 1 is empty, the `exited` row that explicitly claims it.
|
|
765
|
+
*
|
|
766
|
+
* The stages are two SEQUENTIAL QUERIES with an early return, NOT an ordering
|
|
767
|
+
* inside one SELECT, and the distinction is worth stating because the paragraph
|
|
768
|
+
* below argues against an `ORDER BY`. The thing rejected there is an ARBITRARY
|
|
769
|
+
* tiebreak between rows no requirement ranks; this is a named priority between
|
|
770
|
+
* two different QUESTIONS, asked in order. A reader must not take one for the
|
|
771
|
+
* other.
|
|
772
|
+
*
|
|
773
|
+
* LIVE ALWAYS BEATS EXITED. That ordering is what stops a dead predecessor
|
|
774
|
+
* stealing a live successor's messages when both claim one topic — a state the
|
|
775
|
+
* dashboard PATCH route makes reachable today, and one that mint-once thread ids
|
|
776
|
+
* will GUARANTEE, since every respawn then has an exited row and a live row on
|
|
777
|
+
* the same topic by design.
|
|
778
|
+
*
|
|
779
|
+
* Stage 2 admits `exited` and only `exited`. `exited` → Active is a legal
|
|
780
|
+
* transition (`VALID_TRANSITIONS`), the dashboard permits starting an exited
|
|
781
|
+
* thread, and the wait poll loop evicts a parked agent only on `archived` /
|
|
782
|
+
* `expired` — so an exited row is one that may still be listening at its logical
|
|
783
|
+
* id, or be resumed to consume its backlog, while a message left at
|
|
784
|
+
* `<topicId>.jsonl` is read by nothing. `expired` stays OUT because `expired` →
|
|
785
|
+
* Active is structurally forbidden (Expired goes only to Archived) and the
|
|
786
|
+
* dashboard refuses to start it; `archived` stays OUT because an archived thread
|
|
787
|
+
* resumes by being REMAPPED onto a new topic while its old one is torn down by
|
|
788
|
+
* design.
|
|
789
|
+
*
|
|
790
|
+
* Only the EXPLICIT claim, in both stages. `liveThreadClaimingTopic`'s second arm
|
|
791
|
+
* (`telegram_topic_id IS NULL AND thread_id = ?`) is dead for routing: it can
|
|
792
|
+
* only ever answer `thread_id`, which equals `topicId`, which is already what the
|
|
793
|
+
* caller falls back to when this returns empty.
|
|
794
|
+
*
|
|
795
|
+
* No ORDER BY, matching the sibling. No LIFECYCLE writer can give two rows the
|
|
796
|
+
* same explicit `telegram_topic_id`: the column's one write seam is
|
|
797
|
+
* `updateThread`, every lifecycle caller writes either the row's own `thread_id`
|
|
798
|
+
* or an id Telegram just minted for that write, and the orphan sweep and
|
|
799
|
+
* `decommissionWorker` only clear.
|
|
800
|
+
* Minted ids are assumed unique within the configured chat — Telegram's
|
|
801
|
+
* numbering, not ours; unenforced here, and void if this registry is ever pointed
|
|
802
|
+
* at a different chat. The dashboard PATCH route (`buildThreadUpdates`) is the
|
|
803
|
+
* known exception: it writes any number the operator sends, so a two-claimant
|
|
804
|
+
* pair is operator-reachable, and WITHIN one stage this query then returns
|
|
805
|
+
* whichever row SQLite scans first — exactly the base behaviour. An ORDER BY
|
|
806
|
+
* would encode a winner no requirement names. The typed-open
|
|
807
|
+
* `ActivateThreadInput` / `TouchThreadInput` seam accepts a `telegramTopicId`
|
|
808
|
+
* too, with no production caller passing one today.
|
|
809
|
+
*/
|
|
810
|
+
export function threadOwningTopic(db, topicId) {
|
|
811
|
+
const liveOwner = topicClaimantWhere(db, `status NOT IN (${TERMINAL_TOPIC_STATUSES.map(() => "?").join(", ")}) AND telegram_topic_id = ?`, [...TERMINAL_TOPIC_STATUSES, topicId]);
|
|
812
|
+
if (liveOwner.has())
|
|
813
|
+
return liveOwner;
|
|
814
|
+
return topicClaimantWhere(db, `status = ? AND telegram_topic_id = ?`, ["exited", topicId]);
|
|
359
815
|
}
|
|
360
816
|
//# sourceMappingURL=thread-registry.js.map
|