negotium 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/chunk-0ay6cjdy.js +1331 -0
- package/dist/chunk-0ay6cjdy.js.map +16 -0
- package/dist/chunk-1k7t7bnx.js +97 -0
- package/dist/chunk-1k7t7bnx.js.map +10 -0
- package/dist/chunk-5ahkew9c.js +806 -0
- package/dist/chunk-5ahkew9c.js.map +17 -0
- package/dist/chunk-6cpn7ht2.js +287 -0
- package/dist/chunk-6cpn7ht2.js.map +13 -0
- package/dist/chunk-6kctn20e.js +3529 -0
- package/dist/chunk-6kctn20e.js.map +20 -0
- package/dist/chunk-7xqj7aex.js +77 -0
- package/dist/chunk-7xqj7aex.js.map +11 -0
- package/dist/chunk-9p0ww3tk.js +41 -0
- package/dist/chunk-9p0ww3tk.js.map +10 -0
- package/dist/chunk-a92a7nrk.js +252 -0
- package/dist/chunk-a92a7nrk.js.map +10 -0
- package/dist/chunk-c500xe9z.js +33 -0
- package/dist/chunk-c500xe9z.js.map +10 -0
- package/dist/chunk-cds1qxsf.js +1644 -0
- package/dist/chunk-cds1qxsf.js.map +17 -0
- package/dist/chunk-e4407g8x.js +274 -0
- package/dist/chunk-e4407g8x.js.map +10 -0
- package/dist/chunk-e5pbggsh.js +363 -0
- package/dist/chunk-e5pbggsh.js.map +11 -0
- package/dist/chunk-edb07sjp.js +567 -0
- package/dist/chunk-edb07sjp.js.map +14 -0
- package/dist/chunk-exd0v74a.js +3012 -0
- package/dist/chunk-exd0v74a.js.map +32 -0
- package/dist/chunk-fgrme9tr.js +4398 -0
- package/dist/chunk-fgrme9tr.js.map +47 -0
- package/dist/chunk-fka9eqe1.js +581 -0
- package/dist/chunk-fka9eqe1.js.map +11 -0
- package/dist/chunk-g82jygez.js +108 -0
- package/dist/chunk-g82jygez.js.map +10 -0
- package/dist/chunk-ghs2j1fr.js +312 -0
- package/dist/chunk-ghs2j1fr.js.map +10 -0
- package/dist/chunk-gmy11efb.js +125 -0
- package/dist/chunk-gmy11efb.js.map +10 -0
- package/dist/chunk-gxma4c70.js +110 -0
- package/dist/chunk-gxma4c70.js.map +10 -0
- package/dist/chunk-hkdqhm34.js +61 -0
- package/dist/chunk-hkdqhm34.js.map +10 -0
- package/dist/chunk-hpa0cpzh.js +134 -0
- package/dist/chunk-hpa0cpzh.js.map +10 -0
- package/dist/chunk-hwy3n31w.js +21 -0
- package/dist/chunk-hwy3n31w.js.map +9 -0
- package/dist/chunk-j2behpy7.js +214 -0
- package/dist/chunk-j2behpy7.js.map +10 -0
- package/dist/chunk-jc0at0tc.js +102 -0
- package/dist/chunk-jc0at0tc.js.map +10 -0
- package/dist/chunk-jkqq5ef6.js +243 -0
- package/dist/chunk-jkqq5ef6.js.map +11 -0
- package/dist/chunk-jzkw2f01.js +51 -0
- package/dist/chunk-jzkw2f01.js.map +10 -0
- package/dist/chunk-jzz91jwh.js +70 -0
- package/dist/chunk-jzz91jwh.js.map +10 -0
- package/dist/chunk-k9z47mab.js +247 -0
- package/dist/chunk-k9z47mab.js.map +12 -0
- package/dist/chunk-n17yxpa8.js +23 -0
- package/dist/chunk-n17yxpa8.js.map +10 -0
- package/dist/chunk-naa09ags.js +2143 -0
- package/dist/chunk-naa09ags.js.map +15 -0
- package/dist/chunk-ns3ke88n.js +142 -0
- package/dist/chunk-ns3ke88n.js.map +10 -0
- package/dist/chunk-p3cr2ejg.js +1626 -0
- package/dist/chunk-p3cr2ejg.js.map +15 -0
- package/dist/chunk-q5bcpf23.js +2925 -0
- package/dist/chunk-q5bcpf23.js.map +20 -0
- package/dist/chunk-rafc55bs.js +424 -0
- package/dist/chunk-rafc55bs.js.map +11 -0
- package/dist/chunk-rbrxjvbj.js +2830 -0
- package/dist/chunk-rbrxjvbj.js.map +23 -0
- package/dist/chunk-sa874j5c.js +9 -0
- package/dist/chunk-sa874j5c.js.map +10 -0
- package/dist/chunk-sgsz2fpq.js +289 -0
- package/dist/chunk-sgsz2fpq.js.map +10 -0
- package/dist/chunk-sm6fh5c5.js +569 -0
- package/dist/chunk-sm6fh5c5.js.map +14 -0
- package/dist/chunk-t9zcg8f1.js +499 -0
- package/dist/chunk-t9zcg8f1.js.map +13 -0
- package/dist/chunk-vp2db42d.js +121 -0
- package/dist/chunk-vp2db42d.js.map +10 -0
- package/dist/chunk-w13jjf4a.js +46 -0
- package/dist/chunk-w13jjf4a.js.map +10 -0
- package/dist/chunk-x1arr7ze.js +63 -0
- package/dist/chunk-x1arr7ze.js.map +10 -0
- package/dist/chunk-xfq68rch.js +583 -0
- package/dist/chunk-xfq68rch.js.map +11 -0
- package/dist/main.js +132 -20
- package/dist/main.js.map +4 -4
- package/dist/runtime/scripts/faster-whisper-wrapper.py +87 -0
- package/dist/runtime/scripts/mcp-patchright-http.mjs +349 -0
- package/dist/runtime/src/agents/api-topic-agent-switch.ts +178 -0
- package/dist/runtime/src/agents/archiver.ts +396 -0
- package/dist/runtime/src/agents/auth-check.ts +64 -0
- package/dist/runtime/src/agents/claude-provider.ts +737 -0
- package/dist/runtime/src/agents/claude-registry.ts +115 -0
- package/dist/runtime/src/agents/codex-native-multi-agent.ts +177 -0
- package/dist/runtime/src/agents/codex-provider.ts +785 -0
- package/dist/runtime/src/agents/codex-registry.ts +115 -0
- package/dist/runtime/src/agents/codex-tree-kill.ts +365 -0
- package/dist/runtime/src/agents/contracts.ts +69 -0
- package/dist/runtime/src/agents/deep-map.ts +13 -0
- package/dist/runtime/src/agents/execution-host.ts +91 -0
- package/dist/runtime/src/agents/fixtures/claude-attachments.jsonl +2 -0
- package/dist/runtime/src/agents/fixtures/codex-shell.jsonl +5 -0
- package/dist/runtime/src/agents/fork.ts +65 -0
- package/dist/runtime/src/agents/hosted-agent.ts +48 -0
- package/dist/runtime/src/agents/idle-archiver.ts +128 -0
- package/dist/runtime/src/agents/index.ts +205 -0
- package/dist/runtime/src/agents/maestro-provider.ts +206 -0
- package/dist/runtime/src/agents/maestro-registry.ts +31 -0
- package/dist/runtime/src/agents/mcp-tools/ask-user.ts +280 -0
- package/dist/runtime/src/agents/mcp-tools/common.ts +21 -0
- package/dist/runtime/src/agents/mcp-tools/self-config.ts +220 -0
- package/dist/runtime/src/agents/mcp-tools/spawn-subagent.ts +525 -0
- package/dist/runtime/src/agents/mcp-tools/visuals.ts +88 -0
- package/dist/runtime/src/agents/model-catalog.ts +151 -0
- package/dist/runtime/src/agents/registry.ts +24 -0
- package/dist/runtime/src/agents/rollout/claude.ts +331 -0
- package/dist/runtime/src/agents/rollout/codex.ts +569 -0
- package/dist/runtime/src/agents/rollout/shared.ts +205 -0
- package/dist/runtime/src/agents/self-config-core.ts +415 -0
- package/dist/runtime/src/agents/task-events.ts +44 -0
- package/dist/runtime/src/agents/tool-format.ts +352 -0
- package/dist/runtime/src/agents/topic-agent-switch.ts +193 -0
- package/dist/runtime/src/agents/topic-cleanup.ts +231 -0
- package/dist/runtime/src/agents/vault-tool-policy.ts +33 -0
- package/dist/runtime/src/application/execute-external-user-turn.ts +23 -0
- package/dist/runtime/src/application/submit-user-message.ts +47 -0
- package/dist/runtime/src/application/switch-topic-effort.ts +48 -0
- package/dist/runtime/src/application/switch-topic-model.ts +79 -0
- package/dist/runtime/src/application/topic-service.ts +139 -0
- package/dist/runtime/src/application/vault-command.ts +170 -0
- package/dist/runtime/src/bus.ts +319 -0
- package/dist/runtime/src/index.ts +333 -0
- package/dist/runtime/src/mcp/agent-health-server.ts +309 -0
- package/dist/runtime/src/mcp/background-bash-server.ts +346 -0
- package/dist/runtime/src/mcp/canonical-bridge-config.ts +84 -0
- package/dist/runtime/src/mcp/canonical-proxy-server.ts +171 -0
- package/dist/runtime/src/mcp/dm-ipc.ts +60 -0
- package/dist/runtime/src/mcp/mcp-helpers.ts +40 -0
- package/dist/runtime/src/mcp/paddleocr-server.ts +105 -0
- package/dist/runtime/src/mcp/peer-bridge.ts +114 -0
- package/dist/runtime/src/mcp/runtime-spec.ts +134 -0
- package/dist/runtime/src/mcp/session-comm/bridge-ipc-config.ts +27 -0
- package/dist/runtime/src/mcp/session-comm/peer-forward.ts +165 -0
- package/dist/runtime/src/mcp/session-comm/runtime.ts +115 -0
- package/dist/runtime/src/mcp/session-comm/server.ts +649 -0
- package/dist/runtime/src/mcp/session-comm/topic-config.ts +131 -0
- package/dist/runtime/src/mcp/session-comm/topics.ts +229 -0
- package/dist/runtime/src/mcp/stdio-protect.ts +84 -0
- package/dist/runtime/src/mcp/system-health-server.ts +130 -0
- package/dist/runtime/src/mcp/task-server.ts +173 -0
- package/dist/runtime/src/mcp/token-stats-server.ts +98 -0
- package/dist/runtime/src/mcp/vault-http.ts +219 -0
- package/dist/runtime/src/mcp/vault-run.ts +153 -0
- package/dist/runtime/src/mcp/vault-server.ts +104 -0
- package/dist/runtime/src/mcp/wiki-server.ts +755 -0
- package/dist/runtime/src/media/file-events.ts +33 -0
- package/dist/runtime/src/media/text-extractor.ts +312 -0
- package/dist/runtime/src/media/video.ts +92 -0
- package/dist/runtime/src/outbox/file-ops.ts +210 -0
- package/dist/runtime/src/outbox/index.ts +8 -0
- package/dist/runtime/src/outbox/utils.ts +51 -0
- package/dist/runtime/src/platform/background-bash/manager.ts +279 -0
- package/dist/runtime/src/platform/config.ts +371 -0
- package/dist/runtime/src/platform/constants.ts +18 -0
- package/dist/runtime/src/platform/debug-users.ts +14 -0
- package/dist/runtime/src/platform/delay.ts +1 -0
- package/dist/runtime/src/platform/error.ts +9 -0
- package/dist/runtime/src/platform/file-utils.ts +16 -0
- package/dist/runtime/src/platform/jsonl.ts +202 -0
- package/dist/runtime/src/platform/lifecycle.ts +174 -0
- package/dist/runtime/src/platform/logger.ts +31 -0
- package/dist/runtime/src/platform/maestro-bootstrap-env.ts +3 -0
- package/dist/runtime/src/platform/mcp-config.ts +865 -0
- package/dist/runtime/src/platform/modules.ts +145 -0
- package/dist/runtime/src/platform/node-plugins.ts +46 -0
- package/dist/runtime/src/platform/playwright/manager.ts +1233 -0
- package/dist/runtime/src/prompts/agents/wiki-archiver.md +261 -0
- package/dist/runtime/src/prompts/builders.ts +357 -0
- package/dist/runtime/src/prompts/sessions/channel-system.md +48 -0
- package/dist/runtime/src/prompts/sessions/manager-system.md +37 -0
- package/dist/runtime/src/prompts/sessions/topic-system.md +47 -0
- package/dist/runtime/src/prompts/sessions/visual-design.md +86 -0
- package/dist/runtime/src/query/active-rooms.ts +532 -0
- package/dist/runtime/src/query/control.ts +313 -0
- package/dist/runtime/src/query/index.ts +24 -0
- package/dist/runtime/src/query/session-inbox-cleanup.ts +44 -0
- package/dist/runtime/src/query/session-inbox-path.ts +42 -0
- package/dist/runtime/src/query/state.ts +94 -0
- package/dist/runtime/src/query/types.ts +83 -0
- package/dist/runtime/src/runtime/ask-callbacks.ts +153 -0
- package/dist/runtime/src/runtime/attachments.ts +151 -0
- package/dist/runtime/src/runtime/background-sessions.ts +83 -0
- package/dist/runtime/src/runtime/channel-context.ts +107 -0
- package/dist/runtime/src/runtime/errors.ts +69 -0
- package/dist/runtime/src/runtime/event-heartbeat.ts +77 -0
- package/dist/runtime/src/runtime/file-hooks.ts +81 -0
- package/dist/runtime/src/runtime/footer.ts +23 -0
- package/dist/runtime/src/runtime/inbox.ts +946 -0
- package/dist/runtime/src/runtime/tasks.ts +56 -0
- package/dist/runtime/src/runtime/topic-config.ts +29 -0
- package/dist/runtime/src/runtime/turn-runner.ts +2286 -0
- package/dist/runtime/src/runtime/usage-alert.ts +84 -0
- package/dist/runtime/src/runtime/visual-html.ts +109 -0
- package/dist/runtime/src/runtime/visual-store.ts +430 -0
- package/dist/runtime/src/runtime/visuals.ts +171 -0
- package/dist/runtime/src/security/sanitize.ts +27 -0
- package/dist/runtime/src/security/sensitive-path.ts +34 -0
- package/dist/runtime/src/storage/activity-log.ts +152 -0
- package/dist/runtime/src/storage/api-messages.ts +617 -0
- package/dist/runtime/src/storage/api-topic-brief.ts +127 -0
- package/dist/runtime/src/storage/api-topic-config.ts +192 -0
- package/dist/runtime/src/storage/api-topics.ts +844 -0
- package/dist/runtime/src/storage/app-settings.ts +44 -0
- package/dist/runtime/src/storage/browser-profiles.ts +133 -0
- package/dist/runtime/src/storage/conversations.ts +268 -0
- package/dist/runtime/src/storage/forum/agent-settings.ts +20 -0
- package/dist/runtime/src/storage/forum/index.ts +4 -0
- package/dist/runtime/src/storage/forum/repository.ts +283 -0
- package/dist/runtime/src/storage/forum/schema.ts +300 -0
- package/dist/runtime/src/storage/forum-db.ts +23 -0
- package/dist/runtime/src/storage/runtime-events.ts +130 -0
- package/dist/runtime/src/storage/runtime-leases.ts +189 -0
- package/dist/runtime/src/storage/runtime-process-leases.ts +216 -0
- package/dist/runtime/src/storage/runtime-topic-state.ts +148 -0
- package/dist/runtime/src/storage/runtime-turn-requests.ts +300 -0
- package/dist/runtime/src/storage/self-schedules.ts +298 -0
- package/dist/runtime/src/storage/session-asks.ts +347 -0
- package/dist/runtime/src/storage/sqlite.ts +129 -0
- package/dist/runtime/src/storage/tasks.ts +179 -0
- package/dist/runtime/src/storage/token-stats.ts +144 -0
- package/dist/runtime/src/storage/topic-archive-state.ts +59 -0
- package/dist/runtime/src/storage/topic-archive.ts +74 -0
- package/dist/runtime/src/storage/topic-settings.ts +67 -0
- package/dist/runtime/src/storage/topic-transcript.ts +98 -0
- package/dist/runtime/src/storage/vault-crypto.ts +65 -0
- package/dist/runtime/src/storage/vault.ts +225 -0
- package/dist/runtime/src/storage/wiki-summary-names.ts +33 -0
- package/dist/runtime/src/storage/wiki.ts +85 -0
- package/dist/runtime/src/topics/create.ts +123 -0
- package/dist/runtime/src/topics/derive.ts +398 -0
- package/dist/runtime/src/topics/lifecycle.ts +311 -0
- package/dist/runtime/src/topics/links.ts +7 -0
- package/dist/runtime/src/topics/personal-general.ts +65 -0
- package/dist/runtime/src/topics/session.ts +381 -0
- package/dist/runtime/src/types/api.ts +497 -0
- package/dist/runtime/src/types.ts +255 -0
- package/dist/runtime/src/version.ts +1 -0
- package/dist/runtime/tsconfig.json +13 -0
- package/package.json +16 -3
- package/dist/main.d.ts +0 -3
- package/dist/main.d.ts.map +0 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../packages/core/src/topics/lifecycle.ts", "../../../packages/core/src/agents/topic-cleanup.ts", "../../../packages/core/src/query/session-inbox-cleanup.ts", "../../../packages/core/src/query/session-inbox-path.ts", "../../../packages/core/src/storage/self-schedules.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Topic hard-delete with full memory preservation — the single source of\n * truth shared by every host surface (negotium MCP `delete_topic`, CLI, any\n * channel adapter). Ported from otium runtime-api `api/topic-lifecycle.ts`\n * minus peer/cron cascades (deferred subsystems on a standalone node).\n */\n\nimport { rmSync } from \"node:fs\";\nimport { runArchiverTurn } from \"#agents/archiver\";\nimport { cancelIdleArchiveForTopic } from \"#agents/idle-archiver\";\nimport { cancelSubagentWatchForDeletedTopic } from \"#agents/mcp-tools/spawn-subagent\";\nimport { type PurgeSessionRef, purgeTopicLogs } from \"#agents/topic-cleanup\";\nimport { WsHub } from \"#bus\";\nimport { killBgBash } from \"#platform/background-bash/manager\";\nimport { resolveTopicWorkspaceDir } from \"#platform/config\";\nimport { GENERAL_TOPIC_ID } from \"#platform/constants\";\nimport { delay } from \"#platform/delay\";\nimport { logger } from \"#platform/logger\";\nimport { closeBrowserOwnerTabs, deleteTopicProfileDir } from \"#platform/playwright/manager\";\nimport { abortRoom, getRoomQuery, interSessionQueue } from \"#query/active-rooms\";\nimport { cleanupSessionInboxFiles } from \"#query/session-inbox-cleanup\";\nimport { clearQueryState } from \"#query/state\";\nimport { cancelAskCallbacksForTopic } from \"#runtime/ask-callbacks\";\nimport { deleteFilesForTopic } from \"#runtime/file-hooks\";\nimport { clearQueryUsageAlert } from \"#runtime/usage-alert\";\nimport { deleteTopicVisuals } from \"#runtime/visual-store\";\nimport { deleteMessagesForTopic } from \"#storage/api-messages\";\nimport { deleteTopicBrief } from \"#storage/api-topic-brief\";\nimport { deleteApiTopicConfig } from \"#storage/api-topic-config\";\nimport {\n deleteTopic as deleteTopicDB,\n getTopicMemoryOrigin,\n getTopicSessionId,\n listTopics,\n reparentTopicChildren,\n} from \"#storage/api-topics\";\nimport { getBrowserProfileOwner, getTopicBrowserProfile } from \"#storage/browser-profiles\";\nimport { getRuntimeTurnLease } from \"#storage/runtime-leases\";\nimport { beginRuntimeTopicMaintenance } from \"#storage/runtime-topic-state\";\nimport {\n cancelRuntimeUserTurnRequests,\n cancelRuntimeUserTurnRequestsBeforeEpoch,\n} from \"#storage/runtime-turn-requests\";\nimport { deleteSelfSchedulesForTopic } from \"#storage/self-schedules\";\nimport { deletePendingAsksForTopic } from \"#storage/session-asks\";\nimport { archiveTopicMessages } from \"#storage/topic-archive\";\nimport { deleteTopicArchiveState } from \"#storage/topic-archive-state\";\nimport type { TopicDto } from \"#types/api\";\n\nconst DELETE_TURN_WAIT_MS = 5_000;\n\nasync function abortAndWaitForTopic(topicId: string): Promise<boolean> {\n // Drop first: the dying turn's finally block must not dispatch queued work.\n interSessionQueue.drop(topicId);\n cancelIdleArchiveForTopic(topicId);\n const aborted = abortRoom(topicId);\n if (!aborted) return true;\n\n logger.info({ topicId }, \"deleteTopicCascade: aborted in-flight turn\");\n const deadline = Date.now() + DELETE_TURN_WAIT_MS;\n while ((getRoomQuery(topicId) || getRuntimeTurnLease(topicId)) && Date.now() < deadline) {\n await delay(50);\n }\n if (getRoomQuery(topicId) || getRuntimeTurnLease(topicId)) {\n logger.warn(\n { topicId, timeoutMs: DELETE_TURN_WAIT_MS },\n \"deleteTopicCascade: timed out waiting for in-flight turn cleanup\",\n );\n return false;\n }\n return true;\n}\n\nfunction currentSessionRef(topic: TopicDto, sessionId: string | null): PurgeSessionRef[] {\n return topic.agent && sessionId ? [{ agent: topic.agent, sessionId }] : [];\n}\n\nasync function cleanupParticipantResources(\n topic: TopicDto,\n userIds: string[],\n sessionId: string | null,\n cwd: string,\n): Promise<void> {\n for (const [index, participantUserId] of userIds.entries()) {\n try {\n await purgeTopicLogs({\n userId: participantUserId,\n topicName: topic.title,\n cwd,\n // The DB only has one current native session. Its unified-log owner is\n // not knowable after multi-user turns, so include it once as a fallback;\n // every participant manifest is still scanned for older sessions.\n extraSessions: index === 0 ? currentSessionRef(topic, sessionId) : [],\n });\n } catch (err) {\n logger.warn(\n { err, topicId: topic.id, userId: participantUserId },\n \"deleteTopicCascade: participant rollout cleanup failed\",\n );\n }\n\n cleanupSessionInboxFiles(participantUserId, topic.id, topic.title);\n clearQueryState(participantUserId, topic.title);\n clearQueryUsageAlert(participantUserId, topic.id);\n deletePendingAsksForTopic({ userId: participantUserId, topicName: topic.title });\n }\n}\n\nexport class TopicArchiveRequiredError extends Error {\n readonly code = \"TOPIC_ARCHIVE_FAILED\";\n readonly topicId: string;\n\n constructor(topicId: string, cause: unknown) {\n super(\"Topic archive failed; deletion was blocked to avoid losing conversation history.\");\n this.name = \"TopicArchiveRequiredError\";\n this.topicId = topicId;\n this.cause = cause;\n }\n}\n\nexport class TopicTurnStillActiveError extends Error {\n readonly code = \"TOPIC_TURN_STILL_ACTIVE\";\n readonly topicId: string;\n\n constructor(topicId: string) {\n super(\n \"The active turn did not stop in time; deletion was blocked. Use force-delete explicitly.\",\n );\n this.name = \"TopicTurnStillActiveError\";\n this.topicId = topicId;\n }\n}\n\nexport interface DeleteTopicCascadeOptions {\n force?: boolean;\n /** Account deletion may remove that account's otherwise-protected private General. */\n allowManager?: boolean;\n /** Account deletion must not recreate a private General via the archiver. */\n skipArchive?: boolean;\n}\n\n/**\n * Sequence (memory BEFORE wipe):\n * 1. archiveTopicMessages → dump SQLite conversation to the shared wiki\n * `archive/` as forensic JSONL.\n * 2. runArchiverTurn → background wiki-archiver (summaries/articles/brief).\n * 3. purge topic-owned runtime/filesystem/adapter state.\n * 4. delete messages + config + brief + the topic row.\n *\n * Step 1 is required by default: if the raw archive cannot be written, deletion\n * is blocked to avoid losing conversation history. Pass `force: true` only for\n * an explicit force-delete escape hatch. Step 2 is best-effort because the raw\n * transcript has already been preserved.\n * Caller is responsible for guards (essential-topic / owner / existence).\n */\nexport async function deleteTopicCascade(\n topic: TopicDto,\n userId: string,\n options: DeleteTopicCascadeOptions = {},\n): Promise<void> {\n await deleteTopicCascadeImpl(topic, userId, options, new Set());\n}\n\nasync function deleteTopicCascadeImpl(\n topic: TopicDto,\n userId: string,\n options: DeleteTopicCascadeOptions,\n deletingAncestorIds: ReadonlySet<string>,\n): Promise<void> {\n const topicId = topic.id;\n if (topicId === GENERAL_TOPIC_ID || (topic.kind === \"manager\" && !options.allowManager)) {\n logger.warn({ topicId }, \"deleteTopicCascade: refused to delete essential topic\");\n return;\n }\n const deletingTopicIds = new Set(deletingAncestorIds);\n deletingTopicIds.add(topicId);\n const force = options.force === true;\n const maintenance = beginRuntimeTopicMaintenance(topicId);\n if (!maintenance) throw new Error(\"Topic maintenance is already in progress.\");\n let deleted = false;\n const cancelledQueryIds = cancelRuntimeUserTurnRequestsBeforeEpoch(topicId, maintenance.epoch);\n for (const queryId of cancelledQueryIds) {\n WsHub.get().broadcastAborted(topicId, queryId, \"stopped\");\n }\n\n try {\n // Wait for the aborted turn's final session/log writes before taking the\n // forensic snapshot. Normal deletion blocks on timeout; only the explicit\n // force-delete escape hatch may proceed while a provider is still wedged.\n const turnStopped = await abortAndWaitForTopic(topicId);\n if (!turnStopped && !force) throw new TopicTurnStillActiveError(topicId);\n if (!maintenance.isOwned()) throw new Error(\"Topic maintenance ownership was lost.\");\n let archived: ReturnType<typeof archiveTopicMessages> = null;\n\n if (!options.skipArchive) {\n try {\n archived = archiveTopicMessages(topicId, topic.title);\n } catch (err) {\n if (!force) {\n logger.warn(\n { err, topicId },\n \"deleteTopicCascade: archive failed - blocking delete unless force=true\",\n );\n throw new TopicArchiveRequiredError(topicId, err);\n }\n logger.warn(\n { err, topicId },\n \"deleteTopicCascade: archive failed but force=true - continuing with hard delete\",\n );\n }\n }\n\n if (archived) {\n try {\n const memoryTopic = getTopicMemoryOrigin(topicId) ?? topic;\n runArchiverTurn({\n userId,\n // A memory origin that is also disappearing in this cascade must use\n // file + General memory only. Passing its id to wiki MCP would let the\n // detached archiver recreate an orphan api_topic_brief row later.\n ...(memoryTopic.id !== topicId && !deletingTopicIds.has(memoryTopic.id)\n ? { topicId: memoryTopic.id }\n : {}),\n topicTitle: memoryTopic.title,\n archivePath: archived.path,\n messageCount: archived.messageCount,\n });\n } catch (err) {\n logger.warn(\n { err, topicId, archive: archived.path },\n \"deleteTopicCascade: background archiver launch failed - raw archive preserved\",\n );\n }\n }\n\n cancelSubagentWatchForDeletedTopic(topicId);\n\n // spawn_subagent rooms are owned by the room that created them. Delete\n // them through the same lifecycle before removing the parent so their\n // active turns, archives, rollouts, and filesystem state are all cleaned\n // up. Other derived rooms (fork/spawn) remain independent and are\n // reparented below as before.\n const spawnedChildren = listTopics().filter(\n (candidate) =>\n candidate.parentTopicId === topicId &&\n candidate.isSubagent &&\n !deletingTopicIds.has(candidate.id),\n );\n for (const child of spawnedChildren) {\n await deleteTopicCascadeImpl(child, userId, options, deletingTopicIds);\n }\n\n const sessionId = getTopicSessionId(topicId);\n const cwd = resolveTopicWorkspaceDir(topicId);\n const participantUserIds = Array.from(\n new Set([userId, ...topic.participants.map((participant) => participant.userId)]),\n );\n\n // Background shells remain topic-owned. Browser profiles are shared, so\n // deletion closes this topic's tabs without deleting login state.\n killBgBash(userId, topicId);\n const browserProfileOwner = getBrowserProfileOwner(topicId, userId);\n const browserProfile = getTopicBrowserProfile(topicId);\n try {\n await closeBrowserOwnerTabs(browserProfileOwner, browserProfile, `topic:${topicId}`);\n } catch (err) {\n logger.warn({ err, topicId }, \"deleteTopicCascade: browser owner cleanup failed\");\n }\n deleteTopicProfileDir(userId, topicId);\n cancelAskCallbacksForTopic(topicId);\n deleteSelfSchedulesForTopic(topicId);\n\n await cleanupParticipantResources(topic, participantUserIds, sessionId, cwd);\n try {\n await deleteFilesForTopic(topicId);\n } catch (err) {\n logger.warn({ err, topicId }, \"deleteTopicCascade: host file cleanup failed\");\n }\n try {\n rmSync(cwd, { recursive: true, force: true });\n } catch (err) {\n logger.warn({ err, topicId, cwd }, \"deleteTopicCascade: workspace cleanup failed\");\n }\n\n deleteTopicVisuals(topicId);\n deleteTopicArchiveState(topicId);\n deleteMessagesForTopic(topicId);\n deleteApiTopicConfig(topicId);\n deleteTopicBrief(topicId);\n const reparentedChildIds = reparentTopicChildren(topicId, topic.parentTopicId ?? null);\n const rowDeleted = deleteTopicDB(topicId, { allowManager: options.allowManager });\n if (!rowDeleted) {\n logger.warn({ topicId }, \"deleteTopicCascade: topic row was not deleted\");\n return;\n }\n deleted = true;\n // Mirror the deletion onto the bus so channel adapters (Telegram forum\n // threads, web clients, …) can drop their bindings for the topic.\n WsHub.get().broadcastTopicDeleted(topicId);\n for (const childId of reparentedChildIds) WsHub.get().broadcastTopicUpdated(childId);\n } finally {\n if (deleted) {\n for (const queryId of cancelRuntimeUserTurnRequests(topicId)) {\n WsHub.get().broadcastAborted(topicId, queryId, \"stopped\");\n }\n maintenance.finish({ deleteState: true });\n } else {\n maintenance.finish();\n }\n }\n}\n",
|
|
6
|
+
"/**\n * Topic-level conversation cleanup.\n *\n * The unified conversation log (`data/conversations/<userId>/<topic>.jsonl`)\n * is **transient** by design — its only job is to feed the cross-agent\n * bridge in `set_agent` and to give per-agent SDK rollout reconstruction a\n * provider-agnostic source. Once a topic is reset (`/new`) or deleted\n * (`/del`, MCP `delete_topic`), the unified log AND every per-agent SDK\n * rollout file the topic produced should disappear together so a subsequent\n * fresh-start on the same topic name (or a stray `set_agent` call against a\n * recreated row) cannot resurrect orphan history.\n *\n * Permanent forensic preservation lives in `wiki/archive/` via\n * `archiveSessionLogs`; this module is the matching teardown side.\n */\n\nimport { mkdirSync, renameSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { SUPPORTED_AGENTS } from \"#agents/index\";\nimport { getRegistry } from \"#agents/registry\";\nimport { logger } from \"#platform/logger\";\nimport {\n type ConversationEntry,\n getConversationPath,\n readConversation,\n} from \"#storage/conversations\";\nimport type { AgentKind } from \"#types\";\n\nexport interface PurgeSessionRef {\n agent: AgentKind;\n sessionId: string;\n}\n\n/**\n * Group every SDK session id this topic ever produced by its emitting\n * agent. `set_agent` round-trips can leave many ids per agent (one per\n * switch back), so we collect into a Set to deduplicate before deletion.\n */\nfunction collectSessionIdsByAgent(\n entries: ConversationEntry[],\n extraSessions: PurgeSessionRef[] = [],\n): Map<AgentKind, Set<string>> {\n const out = new Map<AgentKind, Set<string>>();\n for (const e of entries) {\n if (e.event.type !== \"session\") continue;\n const set = out.get(e.agent) ?? new Set<string>();\n set.add(e.event.sessionId);\n out.set(e.agent, set);\n }\n for (const { agent, sessionId } of extraSessions) {\n if (!sessionId) continue;\n const set = out.get(agent) ?? new Set<string>();\n set.add(sessionId);\n out.set(agent, set);\n }\n return out;\n}\n\nexport interface PurgeTopicLogsOptions {\n userId: number | string;\n /** Topic key for the unified log path. Use `__dm__` for the DM session. */\n topicName: string;\n /**\n * Working directory the topic ran with — Claude embeds an encoded copy\n * in its rollout path, so a wrong value would silently miss the actual\n * files. API topics should pass `resolveTopicWorkspaceDir(topicId)`.\n */\n cwd: string;\n /**\n * Additional SDK resume keys that may not have reached the unified log yet.\n *\n * `set_agent` writes a synthetic rollout and stores its id in DB before the\n * target agent's first real turn emits a `session` event. If the user resets\n * or deletes the topic in that window, the DB session id is the only manifest\n * entry for that synthetic file.\n */\n extraSessions?: PurgeSessionRef[];\n}\n\nasync function cleanupSessionRollouts(\n opts: PurgeTopicLogsOptions,\n entries: ConversationEntry[],\n): Promise<boolean> {\n const { userId, topicName, cwd, extraSessions = [] } = opts;\n const idsByAgent = collectSessionIdsByAgent(entries, extraSessions);\n let cleanupFailed = false;\n\n await Promise.all(\n SUPPORTED_AGENTS.map(async (agent) => {\n const ids = idsByAgent.get(agent);\n if (!ids || ids.size === 0) return;\n try {\n await getRegistry(agent).cleanupRollouts({\n cwd,\n sessionIds: Array.from(ids),\n });\n } catch (e) {\n logger.warn(\n { err: e, userId, topicName, agent, count: ids.size },\n \"topic logs: agent cleanup failed\",\n );\n cleanupFailed = true;\n }\n }),\n );\n\n return !cleanupFailed;\n}\n\n/** Remove every provider rollout currently manifested by a topic log. */\nexport async function cleanupTopicRollouts(opts: PurgeTopicLogsOptions): Promise<boolean> {\n return cleanupSessionRollouts(opts, readConversation(opts.userId, opts.topicName));\n}\n\nexport interface RotateTopicLogsOptions extends PurgeTopicLogsOptions {\n /** Number of complete user turns carried into the replacement session. */\n retainTurns: number;\n}\n\nexport interface RotateTopicLogsResult {\n rotated: boolean;\n totalTurns: number;\n retainedTurns: number;\n retainedEntries: number;\n}\n\n/**\n * Replace a topic's native provider sessions while retaining a bounded tail\n * of its provider-neutral conversation log. This is used by long-lived Cron\n * topics: a fresh native rollout keeps prompt/session state bounded, and the\n * retained tail gives the next rollout enough context to continue naturally.\n */\nexport async function rotateTopicLogs(\n opts: RotateTopicLogsOptions,\n): Promise<RotateTopicLogsResult> {\n const retainTurns = Math.max(0, Math.floor(opts.retainTurns));\n const entries = readConversation(opts.userId, opts.topicName);\n const userEntryIndexes = entries.flatMap((entry, index) =>\n entry.event.type === \"user_message\" ? [index] : [],\n );\n const totalTurns = userEntryIndexes.length;\n const firstRetainedIndex =\n retainTurns === 0\n ? entries.length\n : (userEntryIndexes[Math.max(0, totalTurns - retainTurns)] ?? 0);\n // Native session ids point at rollouts that are removed below. Keeping\n // those manifest entries would make later cleanup treat deleted files as\n // live context, so only conversational events cross the rotation boundary.\n const retained = entries\n .slice(firstRetainedIndex)\n .filter((entry) => entry.event.type !== \"session\");\n\n if (!(await cleanupSessionRollouts(opts, entries))) {\n logger.warn(\n { userId: opts.userId, topicName: opts.topicName },\n \"rotateTopicLogs: keeping current context because rollout cleanup failed\",\n );\n return {\n rotated: false,\n totalTurns,\n retainedTurns: Math.min(totalTurns, retainTurns),\n retainedEntries: retained.length,\n };\n }\n\n const path = getConversationPath(opts.userId, opts.topicName);\n const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;\n try {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(\n tempPath,\n retained.length > 0 ? `${retained.map((entry) => JSON.stringify(entry)).join(\"\\n\")}\\n` : \"\",\n { flag: \"wx\" },\n );\n renameSync(tempPath, path);\n } catch (e) {\n try {\n unlinkSync(tempPath);\n } catch {}\n logger.warn({ err: e, path }, \"rotateTopicLogs: conversation replacement failed\");\n return {\n rotated: false,\n totalTurns,\n retainedTurns: Math.min(totalTurns, retainTurns),\n retainedEntries: retained.length,\n };\n }\n\n return {\n rotated: true,\n totalTurns,\n retainedTurns: Math.min(totalTurns, retainTurns),\n retainedEntries: retained.length,\n };\n}\n\n/**\n * Best-effort teardown of every artifact associated with a topic's\n * conversation lifecycle. Order matters and is intentional:\n *\n * 1. Read the unified log first — it's the authoritative manifest of\n * every SDK session id the topic has emitted. Merge any DB session id\n * passed via `extraSessions` for synthetic rollouts not yet emitted.\n * 2. Dispatch per-agent rollout cleanup in parallel (independent\n * filesystems, no shared state).\n * 3. Unlink the unified log LAST so a partial rollout-cleanup failure\n * leaves the manifest in place for a future retry.\n *\n * Errors at every step are logged but never thrown — cleanup runs at\n * topic teardown when there is nothing left to abort to.\n */\nexport async function purgeTopicLogs(opts: PurgeTopicLogsOptions): Promise<void> {\n const { userId, topicName } = opts;\n const entries = readConversation(userId, topicName);\n if (!(await cleanupSessionRollouts(opts, entries))) {\n logger.warn(\n { userId, topicName },\n \"purgeTopicLogs: keeping unified log because one or more rollout cleanups failed\",\n );\n return;\n }\n\n const path = getConversationPath(userId, topicName);\n try {\n unlinkSync(path);\n } catch (e) {\n if ((e as NodeJS.ErrnoException)?.code !== \"ENOENT\") {\n logger.warn({ err: e, path }, \"purgeTopicLogs: unified log unlink failed\");\n }\n }\n}\n",
|
|
7
|
+
"import { unlinkSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport { SESSION_INBOX_DIR } from \"#platform/config\";\nimport { logger } from \"#platform/logger\";\nimport { scheduledSessionInboxPath, sessionInboxPath } from \"#query/session-inbox-path\";\n\n/**\n * Delete pending, scheduled, and crash-claimed inbox files for a topic.\n * Canonical files are id-keyed; title-keyed candidates cover rolling upgrades.\n */\nexport function cleanupSessionInboxFiles(\n userId: string,\n topicId: string,\n legacyTopicTitle?: string,\n): number {\n const live = sessionInboxPath(userId, topicId);\n const scheduled = scheduledSessionInboxPath(userId, topicId);\n const candidates = new Set([live, `${live}.processing`, scheduled, `${scheduled}.processing`]);\n\n if (\n legacyTopicTitle &&\n legacyTopicTitle !== \".\" &&\n legacyTopicTitle !== \"..\" &&\n basename(legacyTopicTitle) === legacyTopicTitle\n ) {\n const legacyBase = join(SESSION_INBOX_DIR, userId, legacyTopicTitle);\n for (const suffix of [\".jsonl\", \".jsonl.processing\", \".schedule\", \".schedule.processing\"]) {\n candidates.add(`${legacyBase}${suffix}`);\n }\n }\n\n let deleted = 0;\n for (const path of candidates) {\n try {\n unlinkSync(path);\n deleted++;\n } catch (err) {\n if ((err as NodeJS.ErrnoException)?.code !== \"ENOENT\") {\n logger.warn({ err, path, topicId, userId }, \"topic cleanup: inbox unlink failed\");\n }\n }\n }\n return deleted;\n}\n",
|
|
8
|
+
"import { join } from \"node:path\";\nimport { SESSION_INBOX_DIR } from \"#platform/config\";\n\nconst TOPIC_ID_FILE_PREFIX = \"topic-id-\";\nconst JSONL_SUFFIX = \".jsonl\";\nconst SCHEDULE_SUFFIX = \".schedule\";\n\n/** Build a flat inbox path from the canonical topic id, never its title. */\nexport function sessionInboxPath(userId: string, topicId: string): string {\n const key = Buffer.from(topicId, \"utf8\").toString(\"base64url\");\n return join(SESSION_INBOX_DIR, userId, `${TOPIC_ID_FILE_PREFIX}${key}${JSONL_SUFFIX}`);\n}\n\n/** Durable sidecar consumed by the session-inbox worker once deliverAt passes. */\nexport function scheduledSessionInboxPath(userId: string, topicId: string): string {\n const key = Buffer.from(topicId, \"utf8\").toString(\"base64url\");\n return join(SESSION_INBOX_DIR, userId, `${TOPIC_ID_FILE_PREFIX}${key}${SCHEDULE_SUFFIX}`);\n}\n\nfunction decodeTopicIdFileName(fileName: string, suffix: string): string | null {\n if (!fileName.startsWith(TOPIC_ID_FILE_PREFIX) || !fileName.endsWith(suffix)) return null;\n const encoded = fileName.slice(TOPIC_ID_FILE_PREFIX.length, -suffix.length);\n if (!encoded || !/^[A-Za-z0-9_-]+$/.test(encoded)) return null;\n try {\n return Buffer.from(encoded, \"base64url\").toString(\"utf8\") || null;\n } catch {\n return null;\n }\n}\n\n/** Null denotes a legacy `{topicTitle}.jsonl` filename from an older runtime. */\nexport function topicIdFromSessionInboxFileName(fileName: string): string | null {\n return decodeTopicIdFileName(fileName, JSONL_SUFFIX);\n}\n\n/** Accept both a live .schedule file and a crash-residue .schedule.processing claim. */\nexport function topicIdFromScheduledSessionInboxFileName(fileName: string): string | null {\n const base = fileName.endsWith(\".processing\")\n ? fileName.slice(0, -\".processing\".length)\n : fileName;\n return decodeTopicIdFileName(base, SCHEDULE_SUFFIX);\n}\n",
|
|
9
|
+
"import { randomUUID } from \"node:crypto\";\nimport { db } from \"#storage/forum-db\";\nimport { TURN_LEASE_STALE_MS } from \"#storage/runtime-leases\";\nimport { TOPIC_MAINTENANCE_STALE_MS } from \"#storage/runtime-topic-state\";\n\nexport type SelfScheduleStatus = \"pending\" | \"running\";\n\nexport interface SelfSchedule {\n id: string;\n topicId: string;\n userId: string;\n message: string;\n deliverAt: number;\n createdAt: number;\n updatedAt: number;\n status: SelfScheduleStatus;\n claimedBy?: string;\n claimedAt?: number;\n runningQueryId?: string;\n}\n\ninterface SelfScheduleRow {\n id: string;\n topic_id: string;\n user_id: string;\n message: string;\n deliver_at: number | bigint;\n created_at: number | bigint;\n updated_at: number | bigint;\n status: string;\n claimed_by: string | null;\n claimed_at: number | bigint | null;\n running_query_id: string | null;\n}\n\ndb.exec(`\n CREATE TABLE IF NOT EXISTS runtime_self_schedules (\n id TEXT PRIMARY KEY,\n topic_id TEXT NOT NULL,\n user_id TEXT NOT NULL,\n message TEXT NOT NULL,\n deliver_at INTEGER NOT NULL,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running')),\n claimed_by TEXT,\n claimed_at INTEGER,\n running_query_id TEXT\n )\n`);\ndb.exec(`\n CREATE UNIQUE INDEX IF NOT EXISTS idx_runtime_self_schedules_one_pending\n ON runtime_self_schedules(topic_id)\n WHERE status = 'pending'\n`);\ndb.exec(`\n CREATE INDEX IF NOT EXISTS idx_runtime_self_schedules_due\n ON runtime_self_schedules(status, deliver_at)\n`);\n\nfunction rowToSchedule(row: SelfScheduleRow): SelfSchedule {\n return {\n id: row.id,\n topicId: row.topic_id,\n userId: row.user_id,\n message: row.message,\n deliverAt: Number(row.deliver_at),\n createdAt: Number(row.created_at),\n updatedAt: Number(row.updated_at),\n status: row.status === \"running\" ? \"running\" : \"pending\",\n claimedBy: row.claimed_by ?? undefined,\n claimedAt: row.claimed_at === null ? undefined : Number(row.claimed_at),\n runningQueryId: row.running_query_id ?? undefined,\n };\n}\n\nexport type CreatePendingSelfScheduleResult =\n | { ok: true; schedule: SelfSchedule }\n | { ok: false; existing: SelfSchedule };\n\n/** Create the topic's sole pending self-schedule without replacing an existing one. */\nexport function createPendingSelfSchedule(input: {\n topicId: string;\n userId: string;\n message: string;\n deliverAt: number;\n now?: number;\n}): CreatePendingSelfScheduleResult {\n return db\n .transaction(() => {\n const existing = getPendingSelfSchedule(input.topicId);\n if (existing) return { ok: false as const, existing };\n\n const now = input.now ?? Date.now();\n const schedule: SelfSchedule = {\n id: `scheduled-${randomUUID()}`,\n topicId: input.topicId,\n userId: input.userId,\n message: input.message,\n deliverAt: input.deliverAt,\n createdAt: now,\n updatedAt: now,\n status: \"pending\",\n };\n db.query(\n `INSERT INTO runtime_self_schedules\n (id, topic_id, user_id, message, deliver_at, created_at, updated_at, status)\n VALUES (?, ?, ?, ?, ?, ?, ?, 'pending')`,\n ).run(\n schedule.id,\n schedule.topicId,\n schedule.userId,\n schedule.message,\n schedule.deliverAt,\n schedule.createdAt,\n schedule.updatedAt,\n );\n return { ok: true as const, schedule };\n })\n .immediate();\n}\n\nexport function getPendingSelfSchedule(topicId: string): SelfSchedule | null {\n const row = db\n .query<SelfScheduleRow, [string]>(\n \"SELECT * FROM runtime_self_schedules WHERE topic_id = ? AND status = 'pending'\",\n )\n .get(topicId);\n return row ? rowToSchedule(row) : null;\n}\n\nexport function updatePendingSelfSchedule(input: {\n topicId: string;\n scheduleId: string;\n message?: string;\n deliverAt?: number;\n now?: number;\n}): SelfSchedule | null {\n const existing = getPendingSelfSchedule(input.topicId);\n if (!existing || existing.id !== input.scheduleId) return null;\n const result = db\n .query(\n `UPDATE runtime_self_schedules\n SET message = ?, deliver_at = ?, updated_at = ?\n WHERE id = ? AND topic_id = ? AND status = 'pending'`,\n )\n .run(\n input.message ?? existing.message,\n input.deliverAt ?? existing.deliverAt,\n input.now ?? Date.now(),\n input.scheduleId,\n input.topicId,\n );\n return Number(result.changes ?? 0) > 0 ? getPendingSelfSchedule(input.topicId) : null;\n}\n\nexport function cancelPendingSelfSchedule(topicId: string, scheduleId: string): boolean {\n const result = db\n .query(\n \"DELETE FROM runtime_self_schedules WHERE id = ? AND topic_id = ? AND status = 'pending'\",\n )\n .run(scheduleId, topicId);\n return Number(result.changes ?? 0) > 0;\n}\n\n/**\n * Claim one due schedule whose topic is idle. A stale running row is retryable\n * after its process and topic lease disappear, preserving work across crashes.\n */\nexport function claimNextDueSelfSchedule(ownerId: string, now = Date.now()): SelfSchedule | null {\n const staleBefore = now - TURN_LEASE_STALE_MS;\n return db\n .transaction(() => {\n const row = db\n .query<SelfScheduleRow, [number, number, number, number]>(\n `SELECT s.*\n FROM runtime_self_schedules s\n LEFT JOIN runtime_turn_leases l ON l.topic_id = s.topic_id\n LEFT JOIN runtime_topic_state t ON t.topic_id = s.topic_id\n WHERE (l.topic_id IS NULL OR l.heartbeat_at < ?)\n AND (t.topic_id IS NULL OR t.maintenance = 0 OR t.heartbeat_at < ?)\n AND (\n (s.status = 'pending' AND s.deliver_at <= ?)\n OR (s.status = 'running' AND (s.claimed_at IS NULL OR s.claimed_at < ?))\n )\n ORDER BY CASE s.status WHEN 'running' THEN 0 ELSE 1 END, s.deliver_at ASC\n LIMIT 1`,\n )\n .get(staleBefore, now - TOPIC_MAINTENANCE_STALE_MS, now, staleBefore);\n if (!row) return null;\n\n const result = db\n .query(\n `UPDATE runtime_self_schedules\n SET status = 'running', claimed_by = ?, claimed_at = ?, running_query_id = NULL\n WHERE id = ? AND (\n (status = 'pending' AND deliver_at <= ?)\n OR (status = 'running' AND (claimed_at IS NULL OR claimed_at < ?))\n )`,\n )\n .run(ownerId, now, row.id, now, staleBefore);\n if (Number(result.changes ?? 0) === 0) return null;\n return rowToSchedule({\n ...row,\n status: \"running\",\n claimed_by: ownerId,\n claimed_at: now,\n running_query_id: null,\n });\n })\n .immediate();\n}\n\nexport function markSelfScheduleRunning(\n scheduleId: string,\n ownerId: string,\n queryId: string,\n): boolean {\n const result = db\n .query(\n `UPDATE runtime_self_schedules\n SET running_query_id = ?, claimed_at = ?\n WHERE id = ? AND status = 'running' AND claimed_by = ?`,\n )\n .run(queryId, Date.now(), scheduleId, ownerId);\n return Number(result.changes ?? 0) > 0;\n}\n\nexport function heartbeatSelfScheduleClaim(\n scheduleId: string,\n ownerId: string,\n now = Date.now(),\n): boolean {\n const result = db\n .query(\n `UPDATE runtime_self_schedules\n SET claimed_at = ?\n WHERE id = ? AND status = 'running' AND claimed_by = ?`,\n )\n .run(now, scheduleId, ownerId);\n return Number(result.changes ?? 0) > 0;\n}\n\n/**\n * Put an interrupted run back into the single pending slot. If its own turn\n * already created a newer pending schedule, the newer explicit schedule wins.\n */\nexport function releaseSelfScheduleClaim(\n scheduleId: string,\n ownerId: string,\n): \"released\" | \"dropped\" {\n return db\n .transaction(() => {\n const row = db\n .query<SelfScheduleRow, [string, string]>(\n \"SELECT * FROM runtime_self_schedules WHERE id = ? AND status = 'running' AND claimed_by = ?\",\n )\n .get(scheduleId, ownerId);\n if (!row) return \"dropped\" as const;\n const newer = getPendingSelfSchedule(row.topic_id);\n if (newer) {\n db.query(\"DELETE FROM runtime_self_schedules WHERE id = ?\").run(scheduleId);\n return \"dropped\" as const;\n }\n db.query(\n `UPDATE runtime_self_schedules\n SET status = 'pending', claimed_by = NULL, claimed_at = NULL,\n running_query_id = NULL, updated_at = ?\n WHERE id = ? AND claimed_by = ?`,\n ).run(Date.now(), scheduleId, ownerId);\n return \"released\" as const;\n })\n .immediate();\n}\n\nexport function completeSelfSchedule(scheduleId: string, ownerId?: string): boolean {\n const result = ownerId\n ? db\n .query(\"DELETE FROM runtime_self_schedules WHERE id = ? AND claimed_by = ?\")\n .run(scheduleId, ownerId)\n : db.query(\"DELETE FROM runtime_self_schedules WHERE id = ?\").run(scheduleId);\n return Number(result.changes ?? 0) > 0;\n}\n\nexport function deleteSelfSchedulesForTopic(topicId: string): number {\n const result = db.query(\"DELETE FROM runtime_self_schedules WHERE topic_id = ?\").run(topicId);\n return Number(result.changes ?? 0);\n}\n\n/** Test/diagnostic helper covering pending and in-flight rows. */\nexport function listSelfSchedulesForTopic(topicId: string): SelfSchedule[] {\n return db\n .query<SelfScheduleRow, [string]>(\n \"SELECT * FROM runtime_self_schedules WHERE topic_id = ? ORDER BY deliver_at, created_at\",\n )\n .all(topicId)\n .map(rowToSchedule);\n}\n"
|
|
10
|
+
],
|
|
11
|
+
"mappings": "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",
|
|
12
|
+
"debugId": "CD7A49E8BD3277ED64756E2164756E21",
|
|
13
|
+
"names": []
|
|
14
|
+
}
|
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
db
|
|
4
|
+
} from "./chunk-7xqj7aex.js";
|
|
5
|
+
import {
|
|
6
|
+
logger
|
|
7
|
+
} from "./chunk-k9z47mab.js";
|
|
8
|
+
import"./chunk-hwy3n31w.js";
|
|
9
|
+
|
|
10
|
+
// ../../packages/core/src/storage/runtime-leases.ts
|
|
11
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
12
|
+
|
|
13
|
+
// ../../packages/core/src/storage/runtime-topic-state.ts
|
|
14
|
+
import { randomUUID } from "crypto";
|
|
15
|
+
var TOPIC_MAINTENANCE_STALE_MS = 30000;
|
|
16
|
+
var TOPIC_MAINTENANCE_HEARTBEAT_MS = 1000;
|
|
17
|
+
db.exec(`
|
|
18
|
+
CREATE TABLE IF NOT EXISTS runtime_topic_state (
|
|
19
|
+
topic_id TEXT PRIMARY KEY,
|
|
20
|
+
epoch INTEGER NOT NULL DEFAULT 0,
|
|
21
|
+
maintenance INTEGER NOT NULL DEFAULT 0 CHECK (maintenance IN (0, 1)),
|
|
22
|
+
maintenance_owner TEXT,
|
|
23
|
+
heartbeat_at INTEGER
|
|
24
|
+
)
|
|
25
|
+
`);
|
|
26
|
+
function rowToState(row, now = Date.now()) {
|
|
27
|
+
const heartbeatAt = row.heartbeat_at === null ? undefined : Number(row.heartbeat_at);
|
|
28
|
+
const maintenance = row.maintenance !== 0 && heartbeatAt !== undefined && now - heartbeatAt <= TOPIC_MAINTENANCE_STALE_MS;
|
|
29
|
+
return {
|
|
30
|
+
topicId: row.topic_id,
|
|
31
|
+
epoch: Number(row.epoch),
|
|
32
|
+
maintenance,
|
|
33
|
+
maintenanceOwner: maintenance ? row.maintenance_owner ?? undefined : undefined,
|
|
34
|
+
heartbeatAt
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function getRuntimeTopicState(topicId, now = Date.now()) {
|
|
38
|
+
const row = db.query("SELECT * FROM runtime_topic_state WHERE topic_id = ?").get(topicId);
|
|
39
|
+
return row ? rowToState(row, now) : { topicId, epoch: 0, maintenance: false };
|
|
40
|
+
}
|
|
41
|
+
function getRuntimeTopicEpoch(topicId) {
|
|
42
|
+
return getRuntimeTopicState(topicId).epoch;
|
|
43
|
+
}
|
|
44
|
+
function isRuntimeTopicMaintenance(topicId, now = Date.now()) {
|
|
45
|
+
return getRuntimeTopicState(topicId, now).maintenance;
|
|
46
|
+
}
|
|
47
|
+
function beginRuntimeTopicMaintenance(topicId, options = {}) {
|
|
48
|
+
const ownerId = options.ownerId ?? `${process.pid}-${randomUUID()}`;
|
|
49
|
+
const now = options.now ?? Date.now();
|
|
50
|
+
const result = db.query(`INSERT INTO runtime_topic_state
|
|
51
|
+
(topic_id, epoch, maintenance, maintenance_owner, heartbeat_at)
|
|
52
|
+
VALUES (?, 1, 1, ?, ?)
|
|
53
|
+
ON CONFLICT(topic_id) DO UPDATE SET
|
|
54
|
+
epoch = runtime_topic_state.epoch + 1,
|
|
55
|
+
maintenance = 1,
|
|
56
|
+
maintenance_owner = excluded.maintenance_owner,
|
|
57
|
+
heartbeat_at = excluded.heartbeat_at
|
|
58
|
+
WHERE runtime_topic_state.maintenance = 0
|
|
59
|
+
OR runtime_topic_state.heartbeat_at IS NULL
|
|
60
|
+
OR runtime_topic_state.heartbeat_at < ?`).run(topicId, ownerId, now, now - TOPIC_MAINTENANCE_STALE_MS);
|
|
61
|
+
if (Number(result.changes ?? 0) === 0)
|
|
62
|
+
return null;
|
|
63
|
+
const epoch = getRuntimeTopicEpoch(topicId);
|
|
64
|
+
let finished = false;
|
|
65
|
+
const heartbeatMs = options.heartbeatMs ?? TOPIC_MAINTENANCE_HEARTBEAT_MS;
|
|
66
|
+
const timer = setInterval(() => {
|
|
67
|
+
if (finished)
|
|
68
|
+
return;
|
|
69
|
+
const updated = db.query(`UPDATE runtime_topic_state
|
|
70
|
+
SET heartbeat_at = ?
|
|
71
|
+
WHERE topic_id = ? AND epoch = ? AND maintenance_owner = ? AND maintenance = 1`).run(Date.now(), topicId, epoch, ownerId);
|
|
72
|
+
if (Number(updated.changes ?? 0) > 0)
|
|
73
|
+
return;
|
|
74
|
+
finished = true;
|
|
75
|
+
clearInterval(timer);
|
|
76
|
+
}, heartbeatMs);
|
|
77
|
+
timer.unref?.();
|
|
78
|
+
return {
|
|
79
|
+
topicId,
|
|
80
|
+
epoch,
|
|
81
|
+
ownerId,
|
|
82
|
+
isOwned() {
|
|
83
|
+
if (finished)
|
|
84
|
+
return false;
|
|
85
|
+
const row = db.query(`SELECT 1 AS owned FROM runtime_topic_state
|
|
86
|
+
WHERE topic_id = ? AND epoch = ? AND maintenance_owner = ? AND maintenance = 1`).get(topicId, epoch, ownerId);
|
|
87
|
+
return Boolean(row);
|
|
88
|
+
},
|
|
89
|
+
finish(finishOptions = {}) {
|
|
90
|
+
if (finished)
|
|
91
|
+
return;
|
|
92
|
+
finished = true;
|
|
93
|
+
clearInterval(timer);
|
|
94
|
+
if (finishOptions.deleteState) {
|
|
95
|
+
db.query(`DELETE FROM runtime_topic_state
|
|
96
|
+
WHERE topic_id = ? AND epoch = ? AND maintenance_owner = ?`).run(topicId, epoch, ownerId);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
db.query(`UPDATE runtime_topic_state
|
|
100
|
+
SET maintenance = 0, maintenance_owner = NULL, heartbeat_at = NULL
|
|
101
|
+
WHERE topic_id = ? AND epoch = ? AND maintenance_owner = ?`).run(topicId, epoch, ownerId);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ../../packages/core/src/storage/runtime-leases.ts
|
|
107
|
+
var RUNTIME_INSTANCE_ID = `${process.pid}-${randomUUID2()}`;
|
|
108
|
+
var TURN_LEASE_STALE_MS = 1e4;
|
|
109
|
+
db.exec(`
|
|
110
|
+
CREATE TABLE IF NOT EXISTS runtime_turn_leases (
|
|
111
|
+
topic_id TEXT PRIMARY KEY,
|
|
112
|
+
query_id TEXT NOT NULL UNIQUE,
|
|
113
|
+
owner_id TEXT NOT NULL,
|
|
114
|
+
origin TEXT NOT NULL,
|
|
115
|
+
started_at INTEGER NOT NULL,
|
|
116
|
+
heartbeat_at INTEGER NOT NULL,
|
|
117
|
+
abort_requested INTEGER NOT NULL DEFAULT 0 CHECK (abort_requested IN (0, 1)),
|
|
118
|
+
abort_reason TEXT CHECK (abort_reason IS NULL OR abort_reason IN ('internal', 'external'))
|
|
119
|
+
)
|
|
120
|
+
`);
|
|
121
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_runtime_turn_leases_heartbeat ON runtime_turn_leases(heartbeat_at)");
|
|
122
|
+
function rowToLease(row) {
|
|
123
|
+
const abortReason = row.abort_reason === "internal" || row.abort_reason === "external" ? row.abort_reason : undefined;
|
|
124
|
+
return {
|
|
125
|
+
topicId: row.topic_id,
|
|
126
|
+
queryId: row.query_id,
|
|
127
|
+
ownerId: row.owner_id,
|
|
128
|
+
origin: row.origin,
|
|
129
|
+
startedAt: Number(row.started_at),
|
|
130
|
+
heartbeatAt: Number(row.heartbeat_at),
|
|
131
|
+
abortRequested: row.abort_requested !== 0,
|
|
132
|
+
abortReason
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function getRuntimeTurnLease(topicId, now = Date.now()) {
|
|
136
|
+
const row = db.query("SELECT * FROM runtime_turn_leases WHERE topic_id = ?").get(topicId);
|
|
137
|
+
if (!row)
|
|
138
|
+
return null;
|
|
139
|
+
const lease = rowToLease(row);
|
|
140
|
+
return now - lease.heartbeatAt <= TURN_LEASE_STALE_MS ? lease : null;
|
|
141
|
+
}
|
|
142
|
+
function listRuntimeTurnLeases(now = Date.now()) {
|
|
143
|
+
return db.query("SELECT * FROM runtime_turn_leases ORDER BY started_at ASC").all().map(rowToLease).filter((lease) => now - lease.heartbeatAt <= TURN_LEASE_STALE_MS);
|
|
144
|
+
}
|
|
145
|
+
function claimRuntimeTurnLease(input, now = Date.now()) {
|
|
146
|
+
const ownerId = input.ownerId ?? RUNTIME_INSTANCE_ID;
|
|
147
|
+
const result = db.query(`INSERT INTO runtime_turn_leases
|
|
148
|
+
(topic_id, query_id, owner_id, origin, started_at, heartbeat_at, abort_requested, abort_reason)
|
|
149
|
+
SELECT ?, ?, ?, ?, ?, ?, 0, NULL
|
|
150
|
+
WHERE NOT EXISTS (
|
|
151
|
+
SELECT 1 FROM runtime_topic_state
|
|
152
|
+
WHERE topic_id = ? AND maintenance = 1 AND heartbeat_at >= ?
|
|
153
|
+
)
|
|
154
|
+
ON CONFLICT(topic_id) DO UPDATE SET
|
|
155
|
+
query_id = excluded.query_id,
|
|
156
|
+
owner_id = excluded.owner_id,
|
|
157
|
+
origin = excluded.origin,
|
|
158
|
+
started_at = excluded.started_at,
|
|
159
|
+
heartbeat_at = excluded.heartbeat_at,
|
|
160
|
+
abort_requested = 0,
|
|
161
|
+
abort_reason = NULL
|
|
162
|
+
WHERE (runtime_turn_leases.owner_id = excluded.owner_id
|
|
163
|
+
OR runtime_turn_leases.heartbeat_at < ?)
|
|
164
|
+
AND NOT EXISTS (
|
|
165
|
+
SELECT 1 FROM runtime_topic_state
|
|
166
|
+
WHERE topic_id = excluded.topic_id AND maintenance = 1 AND heartbeat_at >= ?
|
|
167
|
+
)`).run(input.topicId, input.queryId, ownerId, input.origin, now, now, input.topicId, now - TOPIC_MAINTENANCE_STALE_MS, now - TURN_LEASE_STALE_MS, now - TOPIC_MAINTENANCE_STALE_MS);
|
|
168
|
+
return Number(result.changes ?? 0) > 0;
|
|
169
|
+
}
|
|
170
|
+
function heartbeatRuntimeTurnLease(topicId, queryId, ownerId = RUNTIME_INSTANCE_ID, now = Date.now()) {
|
|
171
|
+
const updated = db.query(`UPDATE runtime_turn_leases
|
|
172
|
+
SET heartbeat_at = ?
|
|
173
|
+
WHERE topic_id = ? AND query_id = ? AND owner_id = ?`).run(now, topicId, queryId, ownerId);
|
|
174
|
+
if (Number(updated.changes ?? 0) === 0)
|
|
175
|
+
return { owned: false, abortRequested: false };
|
|
176
|
+
const row = db.query(`SELECT abort_requested, abort_reason
|
|
177
|
+
FROM runtime_turn_leases
|
|
178
|
+
WHERE topic_id = ? AND query_id = ? AND owner_id = ?`).get(topicId, queryId, ownerId);
|
|
179
|
+
const abortReason = row?.abort_reason === "internal" || row?.abort_reason === "external" ? row.abort_reason : undefined;
|
|
180
|
+
return {
|
|
181
|
+
owned: Boolean(row),
|
|
182
|
+
abortRequested: row?.abort_requested !== 0,
|
|
183
|
+
abortReason
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function requestRuntimeTurnAbort(topicId, reason) {
|
|
187
|
+
const result = db.query(`UPDATE runtime_turn_leases
|
|
188
|
+
SET abort_requested = 1,
|
|
189
|
+
abort_reason = CASE
|
|
190
|
+
WHEN abort_reason = 'external' THEN abort_reason
|
|
191
|
+
ELSE ?
|
|
192
|
+
END
|
|
193
|
+
WHERE topic_id = ? AND heartbeat_at >= ?`).run(reason, topicId, Date.now() - TURN_LEASE_STALE_MS);
|
|
194
|
+
return Number(result.changes ?? 0) > 0;
|
|
195
|
+
}
|
|
196
|
+
function releaseRuntimeTurnLease(topicId, queryId, ownerId = RUNTIME_INSTANCE_ID) {
|
|
197
|
+
const result = db.query(`DELETE FROM runtime_turn_leases
|
|
198
|
+
WHERE topic_id = ? AND query_id = ? AND owner_id = ?`).run(topicId, queryId, ownerId);
|
|
199
|
+
return Number(result.changes ?? 0) > 0;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ../../packages/core/src/query/types.ts
|
|
203
|
+
var AbortReason;
|
|
204
|
+
((AbortReason2) => {
|
|
205
|
+
AbortReason2["None"] = "none";
|
|
206
|
+
AbortReason2["Internal"] = "internal";
|
|
207
|
+
AbortReason2["External"] = "external";
|
|
208
|
+
})(AbortReason ||= {});
|
|
209
|
+
|
|
210
|
+
// ../../packages/core/src/query/active-rooms.ts
|
|
211
|
+
var activeByRoom = new Map;
|
|
212
|
+
var leaseMonitors = new Map;
|
|
213
|
+
function getRoomQuery(topicId) {
|
|
214
|
+
return activeByRoom.get(topicId);
|
|
215
|
+
}
|
|
216
|
+
function listRunningTopicQueries() {
|
|
217
|
+
const queries = new Map([...activeByRoom.values()].map((control) => [control.topicId, control.queryId]));
|
|
218
|
+
for (const lease of listRuntimeTurnLeases()) {
|
|
219
|
+
if (!queries.has(lease.topicId))
|
|
220
|
+
queries.set(lease.topicId, lease.queryId);
|
|
221
|
+
}
|
|
222
|
+
return queries;
|
|
223
|
+
}
|
|
224
|
+
function listRunningTopicIds() {
|
|
225
|
+
return new Set(listRunningTopicQueries().keys());
|
|
226
|
+
}
|
|
227
|
+
function isTopicRunning(topicId) {
|
|
228
|
+
return activeByRoom.has(topicId) || getRuntimeTurnLease(topicId) !== null;
|
|
229
|
+
}
|
|
230
|
+
function getRoomQueryStatus(topicId, queryId) {
|
|
231
|
+
if (activeByRoom.get(topicId)?.queryId === queryId)
|
|
232
|
+
return "running";
|
|
233
|
+
return getRuntimeTurnLease(topicId)?.queryId === queryId ? "running" : "not_found";
|
|
234
|
+
}
|
|
235
|
+
function setRoomQuery(control) {
|
|
236
|
+
control.startedAt ??= Date.now();
|
|
237
|
+
const claimed = claimRuntimeTurnLease({
|
|
238
|
+
topicId: control.topicId,
|
|
239
|
+
queryId: control.queryId,
|
|
240
|
+
origin: control.origin
|
|
241
|
+
});
|
|
242
|
+
if (!claimed)
|
|
243
|
+
return false;
|
|
244
|
+
const previousMonitor = leaseMonitors.get(control.topicId);
|
|
245
|
+
if (previousMonitor)
|
|
246
|
+
clearInterval(previousMonitor);
|
|
247
|
+
activeByRoom.set(control.topicId, control);
|
|
248
|
+
const monitor = setInterval(() => {
|
|
249
|
+
const current = activeByRoom.get(control.topicId);
|
|
250
|
+
if (!current || current.queryId !== control.queryId) {
|
|
251
|
+
clearInterval(monitor);
|
|
252
|
+
if (leaseMonitors.get(control.topicId) === monitor) {
|
|
253
|
+
leaseMonitors.delete(control.topicId);
|
|
254
|
+
}
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const heartbeat = heartbeatRuntimeTurnLease(control.topicId, control.queryId);
|
|
258
|
+
if (!heartbeat.owned) {
|
|
259
|
+
control.abortReason = "external" /* External */;
|
|
260
|
+
control.abortController.abort();
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (heartbeat.abortRequested && !control.abortController.signal.aborted) {
|
|
264
|
+
control.abortReason = heartbeat.abortReason === "internal" ? "internal" /* Internal */ : "external" /* External */;
|
|
265
|
+
control.abortController.abort();
|
|
266
|
+
}
|
|
267
|
+
}, 1000);
|
|
268
|
+
monitor.unref?.();
|
|
269
|
+
leaseMonitors.set(control.topicId, monitor);
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
function clearRoomQuery(topicId, queryId) {
|
|
273
|
+
const cur = activeByRoom.get(topicId);
|
|
274
|
+
if (cur && cur.queryId === queryId) {
|
|
275
|
+
activeByRoom.delete(topicId);
|
|
276
|
+
const monitor = leaseMonitors.get(topicId);
|
|
277
|
+
if (monitor)
|
|
278
|
+
clearInterval(monitor);
|
|
279
|
+
leaseMonitors.delete(topicId);
|
|
280
|
+
}
|
|
281
|
+
releaseRuntimeTurnLease(topicId, queryId);
|
|
282
|
+
}
|
|
283
|
+
function abortRoom(topicId, reason = "external" /* External */) {
|
|
284
|
+
const running = activeByRoom.get(topicId);
|
|
285
|
+
if (running) {
|
|
286
|
+
running.abortReason = reason;
|
|
287
|
+
running.abortController.abort();
|
|
288
|
+
requestRuntimeTurnAbort(topicId, reason === "internal" /* Internal */ ? "internal" : "external");
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
return requestRuntimeTurnAbort(topicId, reason === "internal" /* Internal */ ? "internal" : "external");
|
|
292
|
+
}
|
|
293
|
+
function abortAllRooms(reason = "external" /* External */) {
|
|
294
|
+
let aborted = 0;
|
|
295
|
+
for (const running of activeByRoom.values()) {
|
|
296
|
+
running.abortReason = reason;
|
|
297
|
+
if (!running.abortController.signal.aborted) {
|
|
298
|
+
running.abortController.abort();
|
|
299
|
+
aborted++;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return aborted;
|
|
303
|
+
}
|
|
304
|
+
function isUserOrigin(origin) {
|
|
305
|
+
return !origin || origin === "user";
|
|
306
|
+
}
|
|
307
|
+
function decideNewQuery(topicId, incomingOrigin) {
|
|
308
|
+
const running = activeByRoom.get(topicId);
|
|
309
|
+
if (!running) {
|
|
310
|
+
const remote = getRuntimeTurnLease(topicId);
|
|
311
|
+
if (!remote || remote.ownerId === RUNTIME_INSTANCE_ID)
|
|
312
|
+
return { action: "proceed" };
|
|
313
|
+
return isUserOrigin(incomingOrigin) ? { action: "remote-abort-wait", running: remote } : { action: "remote-defer", running: remote };
|
|
314
|
+
}
|
|
315
|
+
if (!isUserOrigin(incomingOrigin))
|
|
316
|
+
return { action: "defer" };
|
|
317
|
+
return { action: "abort-replace", running };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
class InterSessionQueue {
|
|
321
|
+
queue = new Map;
|
|
322
|
+
compactOrDelete(topicId, bucket) {
|
|
323
|
+
if (bucket.head >= bucket.items.length) {
|
|
324
|
+
this.queue.delete(topicId);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (bucket.head >= 64 && bucket.head * 2 >= bucket.items.length) {
|
|
328
|
+
bucket.items = bucket.items.slice(bucket.head);
|
|
329
|
+
bucket.head = 0;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
enqueue(topicId, inject) {
|
|
333
|
+
if (!inject.requestId) {
|
|
334
|
+
logger.error({ topicId, origin: inject.origin }, "BUG: session inject reached enqueue without requestId \u2014 DROPPING inject");
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
let bucket = this.queue.get(topicId);
|
|
338
|
+
if (!bucket) {
|
|
339
|
+
bucket = { items: [], head: 0, requestIds: new Set };
|
|
340
|
+
this.queue.set(topicId, bucket);
|
|
341
|
+
}
|
|
342
|
+
if (bucket.requestIds.has(inject.requestId)) {
|
|
343
|
+
logger.info({ topicId, requestId: inject.requestId, origin: inject.origin }, "session inject deduped (existing entry with same requestId)");
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
bucket.items.push(inject);
|
|
347
|
+
bucket.requestIds.add(inject.requestId);
|
|
348
|
+
logger.info({ topicId, origin: inject.origin, requestId: inject.requestId, depth: inject.depth }, "session inject enqueued");
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
hasRequest(topicId, requestId) {
|
|
352
|
+
return this.queue.get(topicId)?.requestIds.has(requestId) ?? false;
|
|
353
|
+
}
|
|
354
|
+
dequeueNext(topicId) {
|
|
355
|
+
const bucket = this.queue.get(topicId);
|
|
356
|
+
if (!bucket || bucket.head >= bucket.items.length)
|
|
357
|
+
return;
|
|
358
|
+
const next = bucket.items[bucket.head++];
|
|
359
|
+
if (next.requestId)
|
|
360
|
+
bucket.requestIds.delete(next.requestId);
|
|
361
|
+
this.compactOrDelete(topicId, bucket);
|
|
362
|
+
return next;
|
|
363
|
+
}
|
|
364
|
+
dequeueAll(topicId) {
|
|
365
|
+
const bucket = this.queue.get(topicId);
|
|
366
|
+
if (!bucket || bucket.head >= bucket.items.length)
|
|
367
|
+
return;
|
|
368
|
+
const base = bucket.items[bucket.head];
|
|
369
|
+
const isAskReplyInject = (e) => !e.silent && Boolean(e.askReplySources?.length);
|
|
370
|
+
const baseIsAskReplyInject = isAskReplyInject(base);
|
|
371
|
+
const mergeable = (e) => (baseIsAskReplyInject ? isAskReplyInject(e) : !isAskReplyInject(e) && e.origin === base.origin && (e.silent ?? false) === (base.silent ?? false)) && e.onDispatched !== undefined === (base.onDispatched !== undefined) && (e.agentOverride ?? null) === (base.agentOverride ?? null) && (e.modelOverride ?? null) === (base.modelOverride ?? null) && (e.effortOverride ?? null) === (base.effortOverride ?? null) && (e.runtimeEpoch ?? 0) === (base.runtimeEpoch ?? 0) && (e.sessionId ?? null) === (base.sessionId ?? null) && (e.forkHandle?.forkId ?? null) === (base.forkHandle?.forkId ?? null) && (e.prepareSession ?? null) === (base.prepareSession ?? null) && (e.cwd ?? null) === (base.cwd ?? null) && (e.sessionName ?? null) === (base.sessionName ?? null) && (e.sessionType ?? null) === (base.sessionType ?? null) && (e.onSessionId ?? null) === (base.onSessionId ?? null) && (e.onSessionReset ?? null) === (base.onSessionReset ?? null) && (e.bridgeSessionFromHistory ?? false) === (base.bridgeSessionFromHistory ?? false) && (e.onSettled ?? null) === (base.onSettled ?? null);
|
|
372
|
+
let end = bucket.head + 1;
|
|
373
|
+
while (end < bucket.items.length && mergeable(bucket.items[end]))
|
|
374
|
+
end++;
|
|
375
|
+
const batch = bucket.items.slice(bucket.head, end);
|
|
376
|
+
bucket.head = end;
|
|
377
|
+
for (const entry of batch) {
|
|
378
|
+
if (entry.requestId)
|
|
379
|
+
bucket.requestIds.delete(entry.requestId);
|
|
380
|
+
}
|
|
381
|
+
this.compactOrDelete(topicId, bucket);
|
|
382
|
+
if (batch.length === 1)
|
|
383
|
+
return batch[0];
|
|
384
|
+
const callbacks = batch.map((e) => e.onDispatched).filter((cb) => !!cb);
|
|
385
|
+
const askReplySources = batch.flatMap((e) => {
|
|
386
|
+
if (e.askReplySources?.length)
|
|
387
|
+
return e.askReplySources;
|
|
388
|
+
if (!e.requestId || !e.origin)
|
|
389
|
+
return [];
|
|
390
|
+
return [{ from: e.origin, requestId: e.requestId, contextId: e.contextId }];
|
|
391
|
+
});
|
|
392
|
+
const depths = batch.map((e) => e.depth).filter((depth) => typeof depth === "number");
|
|
393
|
+
return {
|
|
394
|
+
...base,
|
|
395
|
+
prompt: batch.map((e) => e.prompt).join(`
|
|
396
|
+
|
|
397
|
+
`),
|
|
398
|
+
origin: baseIsAskReplyInject ? [...new Set(batch.map((e) => e.origin).filter(Boolean))].join(", ") : base.origin,
|
|
399
|
+
depth: depths.length ? Math.min(...depths) : base.depth,
|
|
400
|
+
requestId: `merged-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
401
|
+
contextId: batch.every((e) => e.contextId === base.contextId) ? base.contextId : undefined,
|
|
402
|
+
askReplySources,
|
|
403
|
+
onDispatched: callbacks.length ? (queryId) => {
|
|
404
|
+
callbacks.forEach((cb) => {
|
|
405
|
+
cb(queryId);
|
|
406
|
+
});
|
|
407
|
+
} : undefined
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
keysForRoom() {
|
|
411
|
+
return Array.from(this.queue.keys());
|
|
412
|
+
}
|
|
413
|
+
drop(topicId) {
|
|
414
|
+
this.queue.delete(topicId);
|
|
415
|
+
}
|
|
416
|
+
size(topicId) {
|
|
417
|
+
const bucket = this.queue.get(topicId);
|
|
418
|
+
return bucket ? bucket.items.length - bucket.head : 0;
|
|
419
|
+
}
|
|
420
|
+
remove(topicId, requestId) {
|
|
421
|
+
const bucket = this.queue.get(topicId);
|
|
422
|
+
if (!bucket?.requestIds.has(requestId))
|
|
423
|
+
return;
|
|
424
|
+
const index = bucket.items.findIndex((entry, entryIndex) => entryIndex >= bucket.head && entry.requestId === requestId);
|
|
425
|
+
if (index < 0)
|
|
426
|
+
return;
|
|
427
|
+
const [removed] = bucket.items.splice(index, 1);
|
|
428
|
+
bucket.requestIds.delete(requestId);
|
|
429
|
+
this.compactOrDelete(topicId, bucket);
|
|
430
|
+
return removed;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
class DeferredInjectBatcher {
|
|
435
|
+
options;
|
|
436
|
+
timers = new Map;
|
|
437
|
+
scheduleTimer;
|
|
438
|
+
constructor(options) {
|
|
439
|
+
this.options = options;
|
|
440
|
+
this.scheduleTimer = options.schedule ?? ((callback, delayMs) => setTimeout(callback, delayMs));
|
|
441
|
+
}
|
|
442
|
+
enqueue(inject) {
|
|
443
|
+
const queued = this.options.queue.enqueue(inject.topicId, inject);
|
|
444
|
+
if (!queued)
|
|
445
|
+
return false;
|
|
446
|
+
if (this.timers.has(inject.topicId))
|
|
447
|
+
return true;
|
|
448
|
+
const timer = this.scheduleTimer(() => {
|
|
449
|
+
this.timers.delete(inject.topicId);
|
|
450
|
+
if (this.options.isBusy(inject.topicId))
|
|
451
|
+
return;
|
|
452
|
+
const batch = this.options.queue.dequeueAll(inject.topicId);
|
|
453
|
+
if (batch)
|
|
454
|
+
this.options.dispatch(batch);
|
|
455
|
+
}, this.options.delayMs);
|
|
456
|
+
this.timers.set(inject.topicId, timer);
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
459
|
+
isScheduled(topicId) {
|
|
460
|
+
return this.timers.has(topicId);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
var interSessionQueue = new InterSessionQueue;
|
|
464
|
+
function deferInject(inject) {
|
|
465
|
+
return interSessionQueue.enqueue(inject.topicId, inject);
|
|
466
|
+
}
|
|
467
|
+
function takeDeferredInject(topicId) {
|
|
468
|
+
return interSessionQueue.dequeueAll(topicId);
|
|
469
|
+
}
|
|
470
|
+
function cancelDeferredInject(topicId, requestId) {
|
|
471
|
+
return interSessionQueue.remove(topicId, requestId) !== undefined;
|
|
472
|
+
}
|
|
473
|
+
function wsAbortReason(reason) {
|
|
474
|
+
return reason === "internal" /* Internal */ ? "superseded" : "stopped";
|
|
475
|
+
}
|
|
476
|
+
export {
|
|
477
|
+
wsAbortReason,
|
|
478
|
+
takeDeferredInject,
|
|
479
|
+
setRoomQuery,
|
|
480
|
+
listRunningTopicQueries,
|
|
481
|
+
listRunningTopicIds,
|
|
482
|
+
isUserOrigin,
|
|
483
|
+
isTopicRunning,
|
|
484
|
+
interSessionQueue,
|
|
485
|
+
getRoomQueryStatus,
|
|
486
|
+
getRoomQuery,
|
|
487
|
+
deferInject,
|
|
488
|
+
decideNewQuery,
|
|
489
|
+
clearRoomQuery,
|
|
490
|
+
cancelDeferredInject,
|
|
491
|
+
abortRoom,
|
|
492
|
+
abortAllRooms,
|
|
493
|
+
InterSessionQueue,
|
|
494
|
+
DeferredInjectBatcher
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
export { TOPIC_MAINTENANCE_STALE_MS, getRuntimeTopicEpoch, isRuntimeTopicMaintenance, beginRuntimeTopicMaintenance, RUNTIME_INSTANCE_ID, TURN_LEASE_STALE_MS, getRuntimeTurnLease, listRuntimeTurnLeases, claimRuntimeTurnLease, requestRuntimeTurnAbort, releaseRuntimeTurnLease, AbortReason, getRoomQuery, listRunningTopicQueries, listRunningTopicIds, isTopicRunning, setRoomQuery, clearRoomQuery, abortRoom, abortAllRooms, isUserOrigin, decideNewQuery, DeferredInjectBatcher, interSessionQueue, deferInject, takeDeferredInject, cancelDeferredInject, wsAbortReason };
|
|
498
|
+
|
|
499
|
+
//# debugId=9C61ECBAAB0F028064756E2164756E21
|