@getrift/rift 0.1.0-beta.2 → 0.1.0-beta.20
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 +35 -9
- package/dist/src/auth/keychain.d.ts +9 -0
- package/dist/src/auth/keychain.d.ts.map +1 -1
- package/dist/src/auth/keychain.js +37 -0
- package/dist/src/auth/keychain.js.map +1 -1
- package/dist/src/capture/auto-capture.d.ts +7 -0
- package/dist/src/capture/auto-capture.d.ts.map +1 -1
- package/dist/src/capture/auto-capture.js +82 -15
- package/dist/src/capture/auto-capture.js.map +1 -1
- package/dist/src/capture/auto-repair.d.ts +110 -0
- package/dist/src/capture/auto-repair.d.ts.map +1 -0
- package/dist/src/capture/auto-repair.js +269 -0
- package/dist/src/capture/auto-repair.js.map +1 -0
- package/dist/src/capture/codex-cli-triage-provider.d.ts.map +1 -1
- package/dist/src/capture/codex-cli-triage-provider.js +4 -3
- package/dist/src/capture/codex-cli-triage-provider.js.map +1 -1
- package/dist/src/capture/observability.d.ts +42 -0
- package/dist/src/capture/observability.d.ts.map +1 -1
- package/dist/src/capture/observability.js +45 -4
- package/dist/src/capture/observability.js.map +1 -1
- package/dist/src/capture/recover-quarantine.d.ts +260 -0
- package/dist/src/capture/recover-quarantine.d.ts.map +1 -0
- package/dist/src/capture/recover-quarantine.js +522 -0
- package/dist/src/capture/recover-quarantine.js.map +1 -0
- package/dist/src/cli/commands/backfill.d.ts.map +1 -1
- package/dist/src/cli/commands/backfill.js +5 -2
- package/dist/src/cli/commands/backfill.js.map +1 -1
- package/dist/src/cli/commands/capture-recover.d.ts +40 -0
- package/dist/src/cli/commands/capture-recover.d.ts.map +1 -0
- package/dist/src/cli/commands/capture-recover.js +184 -0
- package/dist/src/cli/commands/capture-recover.js.map +1 -0
- package/dist/src/cli/commands/capture.d.ts.map +1 -1
- package/dist/src/cli/commands/capture.js +96 -5
- package/dist/src/cli/commands/capture.js.map +1 -1
- package/dist/src/cli/commands/doctor.d.ts +6 -0
- package/dist/src/cli/commands/doctor.d.ts.map +1 -0
- package/dist/src/cli/commands/doctor.js +242 -0
- package/dist/src/cli/commands/doctor.js.map +1 -0
- package/dist/src/cli/commands/feedback.d.ts +12 -0
- package/dist/src/cli/commands/feedback.d.ts.map +1 -1
- package/dist/src/cli/commands/feedback.js +93 -4
- package/dist/src/cli/commands/feedback.js.map +1 -1
- package/dist/src/cli/commands/mcp-install.js +5 -2
- package/dist/src/cli/commands/mcp-install.js.map +1 -1
- package/dist/src/cli/commands/menubar.d.ts +30 -0
- package/dist/src/cli/commands/menubar.d.ts.map +1 -0
- package/dist/src/cli/commands/menubar.js +180 -0
- package/dist/src/cli/commands/menubar.js.map +1 -0
- package/dist/src/cli/commands/onboard.d.ts +129 -0
- package/dist/src/cli/commands/onboard.d.ts.map +1 -1
- package/dist/src/cli/commands/onboard.js +752 -171
- package/dist/src/cli/commands/onboard.js.map +1 -1
- package/dist/src/cli/commands/rebuild.d.ts.map +1 -1
- package/dist/src/cli/commands/rebuild.js +6 -3
- package/dist/src/cli/commands/rebuild.js.map +1 -1
- package/dist/src/cli/commands/reconcile.d.ts.map +1 -1
- package/dist/src/cli/commands/reconcile.js +12 -0
- package/dist/src/cli/commands/reconcile.js.map +1 -1
- package/dist/src/cli/commands/review.d.ts.map +1 -1
- package/dist/src/cli/commands/review.js +22 -7
- package/dist/src/cli/commands/review.js.map +1 -1
- package/dist/src/cli/commands/search.d.ts +2 -0
- package/dist/src/cli/commands/search.d.ts.map +1 -1
- package/dist/src/cli/commands/search.js +34 -4
- package/dist/src/cli/commands/search.js.map +1 -1
- package/dist/src/cli/commands/status.d.ts +9 -7
- package/dist/src/cli/commands/status.d.ts.map +1 -1
- package/dist/src/cli/commands/status.js +113 -12
- package/dist/src/cli/commands/status.js.map +1 -1
- package/dist/src/cli/commands/token-issue.d.ts.map +1 -1
- package/dist/src/cli/commands/token-issue.js +9 -1
- package/dist/src/cli/commands/token-issue.js.map +1 -1
- package/dist/src/cli/commands/triage.d.ts.map +1 -1
- package/dist/src/cli/commands/triage.js +7 -5
- package/dist/src/cli/commands/triage.js.map +1 -1
- package/dist/src/cli/commands/update.d.ts +80 -0
- package/dist/src/cli/commands/update.d.ts.map +1 -0
- package/dist/src/cli/commands/update.js +378 -0
- package/dist/src/cli/commands/update.js.map +1 -0
- package/dist/src/cli/default-config-path.d.ts +15 -0
- package/dist/src/cli/default-config-path.d.ts.map +1 -0
- package/dist/src/cli/default-config-path.js +27 -0
- package/dist/src/cli/default-config-path.js.map +1 -0
- package/dist/src/cli/feedback/feedback-config.d.ts +46 -0
- package/dist/src/cli/feedback/feedback-config.d.ts.map +1 -1
- package/dist/src/cli/feedback/feedback-config.js +130 -4
- package/dist/src/cli/feedback/feedback-config.js.map +1 -1
- package/dist/src/cli/feedback/feedback-history.d.ts +7 -0
- package/dist/src/cli/feedback/feedback-history.d.ts.map +1 -1
- package/dist/src/cli/feedback/feedback-history.js +39 -9
- package/dist/src/cli/feedback/feedback-history.js.map +1 -1
- package/dist/src/cli/feedback/feedback-payload.d.ts +22 -1
- package/dist/src/cli/feedback/feedback-payload.d.ts.map +1 -1
- package/dist/src/cli/feedback/feedback-payload.js.map +1 -1
- package/dist/src/cli/feedback/feedback-relay.d.ts +2 -2
- package/dist/src/cli/feedback/feedback-relay.d.ts.map +1 -1
- package/dist/src/cli/feedback/feedback-relay.js.map +1 -1
- package/dist/src/cli/feedback/invite.d.ts +17 -0
- package/dist/src/cli/feedback/invite.d.ts.map +1 -0
- package/dist/src/cli/feedback/invite.js +67 -0
- package/dist/src/cli/feedback/invite.js.map +1 -0
- package/dist/src/cli/feedback/relay-secret-store.d.ts +32 -0
- package/dist/src/cli/feedback/relay-secret-store.d.ts.map +1 -0
- package/dist/src/cli/feedback/relay-secret-store.js +137 -0
- package/dist/src/cli/feedback/relay-secret-store.js.map +1 -0
- package/dist/src/cli/http-client.d.ts +93 -1
- package/dist/src/cli/http-client.d.ts.map +1 -1
- package/dist/src/cli/http-client.js +254 -6
- package/dist/src/cli/http-client.js.map +1 -1
- package/dist/src/cli/index.d.ts.map +1 -1
- package/dist/src/cli/index.js +29 -6
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/cli/postinstall-menubar.d.ts +22 -0
- package/dist/src/cli/postinstall-menubar.d.ts.map +1 -0
- package/dist/src/cli/postinstall-menubar.js +39 -0
- package/dist/src/cli/postinstall-menubar.js.map +1 -0
- package/dist/src/cli/status/friend-header.d.ts +8 -1
- package/dist/src/cli/status/friend-header.d.ts.map +1 -1
- package/dist/src/cli/status/friend-header.js +334 -26
- package/dist/src/cli/status/friend-header.js.map +1 -1
- package/dist/src/cli/ui.d.ts +47 -0
- package/dist/src/cli/ui.d.ts.map +1 -0
- package/dist/src/cli/ui.js +166 -0
- package/dist/src/cli/ui.js.map +1 -0
- package/dist/src/config/schema.d.ts +79 -0
- package/dist/src/config/schema.d.ts.map +1 -1
- package/dist/src/config/schema.js +44 -0
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/diagnostics/codex-preflight.d.ts +33 -0
- package/dist/src/diagnostics/codex-preflight.d.ts.map +1 -0
- package/dist/src/diagnostics/codex-preflight.js +75 -0
- package/dist/src/diagnostics/codex-preflight.js.map +1 -0
- package/dist/src/diagnostics/doctor.d.ts +106 -0
- package/dist/src/diagnostics/doctor.d.ts.map +1 -0
- package/dist/src/diagnostics/doctor.js +334 -0
- package/dist/src/diagnostics/doctor.js.map +1 -0
- package/dist/src/diagnostics/notify.d.ts +90 -0
- package/dist/src/diagnostics/notify.d.ts.map +1 -0
- package/dist/src/diagnostics/notify.js +177 -0
- package/dist/src/diagnostics/notify.js.map +1 -0
- package/dist/src/diagnostics/repair-prompt.d.ts +49 -0
- package/dist/src/diagnostics/repair-prompt.d.ts.map +1 -0
- package/dist/src/diagnostics/repair-prompt.js +223 -0
- package/dist/src/diagnostics/repair-prompt.js.map +1 -0
- package/dist/src/ingestion/inbox-core/conversation-fingerprint.d.ts +2 -0
- package/dist/src/ingestion/inbox-core/conversation-fingerprint.d.ts.map +1 -0
- package/dist/src/ingestion/inbox-core/conversation-fingerprint.js +27 -0
- package/dist/src/ingestion/inbox-core/conversation-fingerprint.js.map +1 -0
- package/dist/src/ingestion/inbox-core/conversation-key.d.ts +2 -0
- package/dist/src/ingestion/inbox-core/conversation-key.d.ts.map +1 -0
- package/dist/src/ingestion/inbox-core/conversation-key.js +31 -0
- package/dist/src/ingestion/inbox-core/conversation-key.js.map +1 -0
- package/dist/src/ingestion/inbox-core/extensions.d.ts +3 -0
- package/dist/src/ingestion/inbox-core/extensions.d.ts.map +1 -0
- package/dist/src/ingestion/inbox-core/extensions.js +16 -0
- package/dist/src/ingestion/inbox-core/extensions.js.map +1 -0
- package/dist/src/ingestion/inbox-core/idempotency.d.ts +2 -0
- package/dist/src/ingestion/inbox-core/idempotency.d.ts.map +1 -0
- package/dist/src/ingestion/inbox-core/idempotency.js +22 -0
- package/dist/src/ingestion/inbox-core/idempotency.js.map +1 -0
- package/dist/src/ingestion/inbox-core/index.d.ts +20 -0
- package/dist/src/ingestion/inbox-core/index.d.ts.map +1 -0
- package/dist/src/ingestion/inbox-core/index.js +20 -0
- package/dist/src/ingestion/inbox-core/index.js.map +1 -0
- package/dist/src/ingestion/inbox-core/source-detection.d.ts +2 -0
- package/dist/src/ingestion/inbox-core/source-detection.d.ts.map +1 -0
- package/dist/src/ingestion/inbox-core/source-detection.js +23 -0
- package/dist/src/ingestion/inbox-core/source-detection.js.map +1 -0
- package/dist/src/ingestion/inbox-core/source-sniffer.d.ts +11 -0
- package/dist/src/ingestion/inbox-core/source-sniffer.d.ts.map +1 -0
- package/dist/src/ingestion/inbox-core/source-sniffer.js +69 -0
- package/dist/src/ingestion/inbox-core/source-sniffer.js.map +1 -0
- package/dist/src/ingestion/inbox-core/zip-sniffer.d.ts +70 -0
- package/dist/src/ingestion/inbox-core/zip-sniffer.d.ts.map +1 -0
- package/dist/src/ingestion/inbox-core/zip-sniffer.js +161 -0
- package/dist/src/ingestion/inbox-core/zip-sniffer.js.map +1 -0
- package/dist/src/ingestion/inbox-watcher.d.ts.map +1 -1
- package/dist/src/ingestion/inbox-watcher.js +34 -50
- package/dist/src/ingestion/inbox-watcher.js.map +1 -1
- package/dist/src/ingestion/indexer.d.ts +7 -0
- package/dist/src/ingestion/indexer.d.ts.map +1 -1
- package/dist/src/ingestion/indexer.js +36 -2
- package/dist/src/ingestion/indexer.js.map +1 -1
- package/dist/src/ingestion/metadata-extraction.d.ts +8 -5
- package/dist/src/ingestion/metadata-extraction.d.ts.map +1 -1
- package/dist/src/ingestion/metadata-extraction.js +24 -5
- package/dist/src/ingestion/metadata-extraction.js.map +1 -1
- package/dist/src/ingestion/skip-quarantine.d.ts +10 -0
- package/dist/src/ingestion/skip-quarantine.d.ts.map +1 -0
- package/dist/src/ingestion/skip-quarantine.js +35 -0
- package/dist/src/ingestion/skip-quarantine.js.map +1 -0
- package/dist/src/jobs/handlers/compact.d.ts.map +1 -1
- package/dist/src/jobs/handlers/compact.js +30 -4
- package/dist/src/jobs/handlers/compact.js.map +1 -1
- package/dist/src/jobs/handlers/dedupe-conversations.d.ts +134 -0
- package/dist/src/jobs/handlers/dedupe-conversations.d.ts.map +1 -0
- package/dist/src/jobs/handlers/dedupe-conversations.js +371 -0
- package/dist/src/jobs/handlers/dedupe-conversations.js.map +1 -0
- package/dist/src/jobs/handlers/ingest.d.ts.map +1 -1
- package/dist/src/jobs/handlers/ingest.js +295 -41
- package/dist/src/jobs/handlers/ingest.js.map +1 -1
- package/dist/src/jobs/handlers/reconcile.d.ts +28 -0
- package/dist/src/jobs/handlers/reconcile.d.ts.map +1 -1
- package/dist/src/jobs/handlers/reconcile.js +145 -19
- package/dist/src/jobs/handlers/reconcile.js.map +1 -1
- package/dist/src/jobs/handlers/reindex.d.ts.map +1 -1
- package/dist/src/jobs/handlers/reindex.js +13 -2
- package/dist/src/jobs/handlers/reindex.js.map +1 -1
- package/dist/src/jobs/handlers/save.d.ts.map +1 -1
- package/dist/src/jobs/handlers/save.js +57 -3
- package/dist/src/jobs/handlers/save.js.map +1 -1
- package/dist/src/jobs/queue.d.ts +51 -1
- package/dist/src/jobs/queue.d.ts.map +1 -1
- package/dist/src/jobs/queue.js +466 -26
- package/dist/src/jobs/queue.js.map +1 -1
- package/dist/src/jobs/worker-entry.d.ts.map +1 -1
- package/dist/src/jobs/worker-entry.js +35 -7
- package/dist/src/jobs/worker-entry.js.map +1 -1
- package/dist/src/jobs/worker-process.d.ts +11 -0
- package/dist/src/jobs/worker-process.d.ts.map +1 -1
- package/dist/src/jobs/worker-process.js +37 -4
- package/dist/src/jobs/worker-process.js.map +1 -1
- package/dist/src/main.js +199 -46
- package/dist/src/main.js.map +1 -1
- package/dist/src/mcp/errors.d.ts.map +1 -1
- package/dist/src/mcp/errors.js +20 -1
- package/dist/src/mcp/errors.js.map +1 -1
- package/dist/src/mcp/server.d.ts.map +1 -1
- package/dist/src/mcp/server.js +43 -3
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/mcp/tools/context-pack.d.ts.map +1 -1
- package/dist/src/mcp/tools/context-pack.js +164 -23
- package/dist/src/mcp/tools/context-pack.js.map +1 -1
- package/dist/src/mcp/tools/search.d.ts +6 -2
- package/dist/src/mcp/tools/search.d.ts.map +1 -1
- package/dist/src/mcp/tools/search.js +35 -4
- package/dist/src/mcp/tools/search.js.map +1 -1
- package/dist/src/observability/embedding-events.d.ts +52 -0
- package/dist/src/observability/embedding-events.d.ts.map +1 -0
- package/dist/src/observability/embedding-events.js +149 -0
- package/dist/src/observability/embedding-events.js.map +1 -0
- package/dist/src/observability/index-events.d.ts +70 -0
- package/dist/src/observability/index-events.d.ts.map +1 -0
- package/dist/src/observability/index-events.js +148 -0
- package/dist/src/observability/index-events.js.map +1 -0
- package/dist/src/observability/onboarding-metric.d.ts +131 -0
- package/dist/src/observability/onboarding-metric.d.ts.map +1 -0
- package/dist/src/observability/onboarding-metric.js +351 -0
- package/dist/src/observability/onboarding-metric.js.map +1 -0
- package/dist/src/observability/tool-usage-stats.d.ts +77 -4
- package/dist/src/observability/tool-usage-stats.d.ts.map +1 -1
- package/dist/src/observability/tool-usage-stats.js +112 -32
- package/dist/src/observability/tool-usage-stats.js.map +1 -1
- package/dist/src/observability/tool-usage.d.ts +100 -7
- package/dist/src/observability/tool-usage.d.ts.map +1 -1
- package/dist/src/observability/tool-usage.js +196 -33
- package/dist/src/observability/tool-usage.js.map +1 -1
- package/dist/src/observability/version-check.d.ts +71 -0
- package/dist/src/observability/version-check.d.ts.map +1 -0
- package/dist/src/observability/version-check.js +198 -0
- package/dist/src/observability/version-check.js.map +1 -0
- package/dist/src/providers/basic-metadata-extraction.d.ts +60 -0
- package/dist/src/providers/basic-metadata-extraction.d.ts.map +1 -0
- package/dist/src/providers/basic-metadata-extraction.js +114 -0
- package/dist/src/providers/basic-metadata-extraction.js.map +1 -0
- package/dist/src/providers/codex-cli-metadata-extraction.d.ts +1 -0
- package/dist/src/providers/codex-cli-metadata-extraction.d.ts.map +1 -1
- package/dist/src/providers/codex-cli-metadata-extraction.js +6 -2
- package/dist/src/providers/codex-cli-metadata-extraction.js.map +1 -1
- package/dist/src/providers/codex-cli-model.d.ts +61 -0
- package/dist/src/providers/codex-cli-model.d.ts.map +1 -0
- package/dist/src/providers/codex-cli-model.js +194 -0
- package/dist/src/providers/codex-cli-model.js.map +1 -0
- package/dist/src/providers/codex-cli-runner.d.ts +39 -0
- package/dist/src/providers/codex-cli-runner.d.ts.map +1 -1
- package/dist/src/providers/codex-cli-runner.js +234 -48
- package/dist/src/providers/codex-cli-runner.js.map +1 -1
- package/dist/src/providers/conversation-generation.d.ts.map +1 -1
- package/dist/src/providers/conversation-generation.js +43 -6
- package/dist/src/providers/conversation-generation.js.map +1 -1
- package/dist/src/providers/ollama-embed.d.ts +2 -1
- package/dist/src/providers/ollama-embed.d.ts.map +1 -1
- package/dist/src/providers/ollama-embed.js +1 -0
- package/dist/src/providers/ollama-embed.js.map +1 -1
- package/dist/src/providers/openai-metadata-extraction.d.ts +3 -3
- package/dist/src/providers/openai-metadata-extraction.d.ts.map +1 -1
- package/dist/src/providers/openai-metadata-extraction.js +18 -3
- package/dist/src/providers/openai-metadata-extraction.js.map +1 -1
- package/dist/src/providers/placeholder-embed.d.ts +56 -0
- package/dist/src/providers/placeholder-embed.d.ts.map +1 -0
- package/dist/src/providers/placeholder-embed.js +64 -0
- package/dist/src/providers/placeholder-embed.js.map +1 -0
- package/dist/src/providers/stub.d.ts +2 -0
- package/dist/src/providers/stub.d.ts.map +1 -1
- package/dist/src/providers/stub.js +2 -0
- package/dist/src/providers/stub.js.map +1 -1
- package/dist/src/providers/types.d.ts +11 -0
- package/dist/src/providers/types.d.ts.map +1 -1
- package/dist/src/providers/voyage.d.ts +2 -1
- package/dist/src/providers/voyage.d.ts.map +1 -1
- package/dist/src/providers/voyage.js +1 -0
- package/dist/src/providers/voyage.js.map +1 -1
- package/dist/src/retrieval/compact.d.ts +116 -2
- package/dist/src/retrieval/compact.d.ts.map +1 -1
- package/dist/src/retrieval/compact.js +158 -5
- package/dist/src/retrieval/compact.js.map +1 -1
- package/dist/src/retrieval/context-pack.d.ts +114 -0
- package/dist/src/retrieval/context-pack.d.ts.map +1 -1
- package/dist/src/retrieval/context-pack.js +292 -8
- package/dist/src/retrieval/context-pack.js.map +1 -1
- package/dist/src/retrieval/current-truth.d.ts +360 -0
- package/dist/src/retrieval/current-truth.d.ts.map +1 -0
- package/dist/src/retrieval/current-truth.js +766 -0
- package/dist/src/retrieval/current-truth.js.map +1 -0
- package/dist/src/retrieval/git-state.d.ts +53 -0
- package/dist/src/retrieval/git-state.d.ts.map +1 -0
- package/dist/src/retrieval/git-state.js +174 -0
- package/dist/src/retrieval/git-state.js.map +1 -0
- package/dist/src/retrieval/lexical.d.ts.map +1 -1
- package/dist/src/retrieval/lexical.js +19 -3
- package/dist/src/retrieval/lexical.js.map +1 -1
- package/dist/src/retrieval/locator-boost.d.ts +37 -0
- package/dist/src/retrieval/locator-boost.d.ts.map +1 -0
- package/dist/src/retrieval/locator-boost.js +129 -0
- package/dist/src/retrieval/locator-boost.js.map +1 -0
- package/dist/src/retrieval/report-demotion.d.ts +46 -0
- package/dist/src/retrieval/report-demotion.d.ts.map +1 -0
- package/dist/src/retrieval/report-demotion.js +169 -0
- package/dist/src/retrieval/report-demotion.js.map +1 -0
- package/dist/src/retrieval/vector.d.ts.map +1 -1
- package/dist/src/retrieval/vector.js +11 -2
- package/dist/src/retrieval/vector.js.map +1 -1
- package/dist/src/server/app.d.ts.map +1 -1
- package/dist/src/server/app.js +92 -11
- package/dist/src/server/app.js.map +1 -1
- package/dist/src/server/routes/compact.d.ts.map +1 -1
- package/dist/src/server/routes/compact.js +4 -1
- package/dist/src/server/routes/compact.js.map +1 -1
- package/dist/src/server/routes/context.d.ts +1 -1
- package/dist/src/server/routes/context.d.ts.map +1 -1
- package/dist/src/server/routes/context.js +2 -1
- package/dist/src/server/routes/context.js.map +1 -1
- package/dist/src/server/routes/conversations-search.d.ts.map +1 -1
- package/dist/src/server/routes/conversations-search.js +28 -3
- package/dist/src/server/routes/conversations-search.js.map +1 -1
- package/dist/src/server/routes/enqueue.d.ts +11 -0
- package/dist/src/server/routes/enqueue.d.ts.map +1 -0
- package/dist/src/server/routes/enqueue.js +17 -0
- package/dist/src/server/routes/enqueue.js.map +1 -0
- package/dist/src/server/routes/friend-status.d.ts +339 -3
- package/dist/src/server/routes/friend-status.d.ts.map +1 -1
- package/dist/src/server/routes/friend-status.js +447 -13
- package/dist/src/server/routes/friend-status.js.map +1 -1
- package/dist/src/server/routes/ingest.d.ts.map +1 -1
- package/dist/src/server/routes/ingest.js +5 -2
- package/dist/src/server/routes/ingest.js.map +1 -1
- package/dist/src/server/routes/mcp-usage.d.ts +5 -4
- package/dist/src/server/routes/mcp-usage.d.ts.map +1 -1
- package/dist/src/server/routes/mcp-usage.js.map +1 -1
- package/dist/src/server/routes/reconcile.d.ts.map +1 -1
- package/dist/src/server/routes/reconcile.js +20 -1
- package/dist/src/server/routes/reconcile.js.map +1 -1
- package/dist/src/server/routes/reindex.d.ts.map +1 -1
- package/dist/src/server/routes/reindex.js +4 -1
- package/dist/src/server/routes/reindex.js.map +1 -1
- package/dist/src/server/routes/save.d.ts.map +1 -1
- package/dist/src/server/routes/save.js +4 -1
- package/dist/src/server/routes/save.js.map +1 -1
- package/dist/src/server/routes/search.d.ts +1 -1
- package/dist/src/server/routes/search.d.ts.map +1 -1
- package/dist/src/server/routes/search.js +253 -29
- package/dist/src/server/routes/search.js.map +1 -1
- package/dist/src/server/routes/triage.d.ts.map +1 -1
- package/dist/src/server/routes/triage.js +4 -1
- package/dist/src/server/routes/triage.js.map +1 -1
- package/dist/src/storage/rebuild.d.ts +35 -1
- package/dist/src/storage/rebuild.d.ts.map +1 -1
- package/dist/src/storage/rebuild.js +288 -64
- package/dist/src/storage/rebuild.js.map +1 -1
- package/dist/src/storage/tables.d.ts +29 -0
- package/dist/src/storage/tables.d.ts.map +1 -1
- package/dist/src/storage/tables.js +32 -1
- package/dist/src/storage/tables.js.map +1 -1
- package/operator/swiftbar/render-menu.py +524 -0
- package/operator/swiftbar/rift.10s.sh +176 -0
- package/package.json +9 -3
package/dist/src/jobs/queue.d.ts
CHANGED
|
@@ -10,9 +10,13 @@ import type { Job, JobType, CreateJobResult, JobHandler } from "./types.js";
|
|
|
10
10
|
export declare class JobQueue {
|
|
11
11
|
private queuePath;
|
|
12
12
|
private jobs;
|
|
13
|
+
private payloadRefs;
|
|
13
14
|
private handlers;
|
|
14
15
|
private processing;
|
|
15
16
|
private held;
|
|
17
|
+
private persistRetryTimer;
|
|
18
|
+
private startRetryTimer;
|
|
19
|
+
private startRetryDelayMs;
|
|
16
20
|
constructor(queuePath: string);
|
|
17
21
|
/**
|
|
18
22
|
* Hold processing — registerHandler and create won't auto-start queued
|
|
@@ -55,6 +59,17 @@ export declare class JobQueue {
|
|
|
55
59
|
}>;
|
|
56
60
|
/** Get a job by ID, or undefined. */
|
|
57
61
|
get(id: string): Job | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Return all jobs whose `idempotency_key` starts with `prefix`. Used
|
|
64
|
+
* by the friend-status projection to surface inbox freshness — jobs
|
|
65
|
+
* created from the inbox watcher all use keys of the form
|
|
66
|
+
* `inbox:<hex>`, so a prefix scan answers "what's the inbox lane
|
|
67
|
+
* doing" without leaking queue internals to the route layer.
|
|
68
|
+
*
|
|
69
|
+
* Read-only iteration of the in-memory map; safe to call on a hot
|
|
70
|
+
* queue. Returns the snapshot in insertion order.
|
|
71
|
+
*/
|
|
72
|
+
listByIdempotencyPrefix(prefix: string): Job[];
|
|
58
73
|
/**
|
|
59
74
|
* Check whether an idempotency key matches an existing active job.
|
|
60
75
|
* Returns the job if found in queued/running/completed state, undefined otherwise.
|
|
@@ -70,8 +85,35 @@ export declare class JobQueue {
|
|
|
70
85
|
cancel(id: string): Promise<Job | {
|
|
71
86
|
conflict: true;
|
|
72
87
|
} | undefined>;
|
|
73
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* Keep the persisted ledger bounded so `JSON.stringify` can never exceed
|
|
90
|
+
* V8's max string length (the failure mode that turned every save into a
|
|
91
|
+
* 500). Two cheap, safe reductions on terminal jobs only:
|
|
92
|
+
* 1. Drop `payload` — only active jobs need it.
|
|
93
|
+
* 2. Cap how many terminal jobs we retain (oldest dropped first).
|
|
94
|
+
* Non-terminal jobs (queued/running/interrupted) are never touched.
|
|
95
|
+
* Returns true if anything changed.
|
|
96
|
+
*/
|
|
97
|
+
private compactLedger;
|
|
98
|
+
/** Atomic write: temp file + rename. Ledger is compacted first so the
|
|
99
|
+
* serialized form stays well under V8's max string length. */
|
|
74
100
|
private persist;
|
|
101
|
+
private createLedgerTooLargeError;
|
|
102
|
+
private snapshotState;
|
|
103
|
+
private restoreSnapshot;
|
|
104
|
+
private payloadDir;
|
|
105
|
+
private payloadPath;
|
|
106
|
+
private payloadRefPath;
|
|
107
|
+
private payloadAbsPath;
|
|
108
|
+
private isPayloadRef;
|
|
109
|
+
private hydrateJob;
|
|
110
|
+
private readPayloadRef;
|
|
111
|
+
private serializeJobForLedger;
|
|
112
|
+
private writePayloadFile;
|
|
113
|
+
private prunePayloadRefCache;
|
|
114
|
+
private deletePayloadFilesForJob;
|
|
115
|
+
private cleanupPayloadFiles;
|
|
116
|
+
private deletePayloadPath;
|
|
75
117
|
/**
|
|
76
118
|
* Find the active job for a given idempotency key.
|
|
77
119
|
* Skip superseded jobs (those with `retried_by` set) so we always
|
|
@@ -102,6 +144,14 @@ export declare class JobQueue {
|
|
|
102
144
|
private createRetryJob;
|
|
103
145
|
/** Process the next queued job if no job is currently running. */
|
|
104
146
|
private processNext;
|
|
147
|
+
private applyPersistedMutation;
|
|
148
|
+
private applyTerminalMutation;
|
|
149
|
+
private markPersistDirty;
|
|
150
|
+
private schedulePersistRetry;
|
|
151
|
+
private scheduleStartRetry;
|
|
152
|
+
private clearStartRetry;
|
|
153
|
+
private clearPersistRetry;
|
|
154
|
+
private retryDirtyPersist;
|
|
105
155
|
private findNextQueued;
|
|
106
156
|
}
|
|
107
157
|
//# sourceMappingURL=queue.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../../src/jobs/queue.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,GAAG,EAAE,OAAO,EAAa,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../../src/jobs/queue.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,GAAG,EAAE,OAAO,EAAa,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAgEvF;;;;;;;GAOG;AACH,qBAAa,QAAQ;IAUP,OAAO,CAAC,SAAS;IAT7B,OAAO,CAAC,IAAI,CAA0B;IACtC,OAAO,CAAC,WAAW,CAAwC;IAC3D,OAAO,CAAC,QAAQ,CAAkC;IAClD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,iBAAiB,CAA4C;IACrE,OAAO,CAAC,eAAe,CAA4C;IACnE,OAAO,CAAC,iBAAiB,CAAgC;gBAErC,SAAS,EAAE,MAAM;IAErC;;;;OAIG;IACH,IAAI,IAAI,IAAI;IAIZ,6DAA6D;IAC7D,WAAW,IAAI,IAAI;IAKnB,+EAA+E;IAC/E,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI;IAKzD,uDAAuD;IACvD,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO;IAIlC;;;;;;;OAOG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAmD3B;;;;;;;;;;;OAWG;IACG,MAAM,CACV,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GACrD,OAAO,CAAC,eAAe,GAAG;QAAE,QAAQ,EAAE,IAAI,CAAA;KAAE,CAAC;IAiDhD,qCAAqC;IACrC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS;IAIhC;;;;;;;;;OASG;IACH,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,EAAE;IAQ9C;;;;OAIG;IACH,aAAa,CAAC,cAAc,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS;IAatD;;;;;OAKG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG;QAAE,QAAQ,EAAE,IAAI,CAAA;KAAE,GAAG,SAAS,CAAC;IAwBvE;;;;;;;;OAQG;IACH,OAAO,CAAC,aAAa;IAiErB;kEAC8D;YAChD,OAAO;IAkCrB,OAAO,CAAC,yBAAyB;IAWjC,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,UAAU;IAalB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,qBAAqB;IAgD7B,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,oBAAoB;IAQ5B,OAAO,CAAC,wBAAwB;IAmBhC,OAAO,CAAC,mBAAmB;IAqB3B,OAAO,CAAC,iBAAiB;IAYzB;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAO5B;;;;;;;;;OASG;IACG,YAAY,CAChB,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAC3B,OAAO,CAAC,eAAe,CAAC;IA+B3B,sDAAsD;IACtD,qBAAqB,IAAI,OAAO;IAYhC;;;;OAIG;IACH,sBAAsB,IAAI,GAAG,GAAG,IAAI;YAStB,cAAc;IAoC5B,kEAAkE;IAClE,OAAO,CAAC,WAAW;YA8CL,sBAAsB;YAWtB,qBAAqB;IAYnC,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,oBAAoB;IAS5B,OAAO,CAAC,kBAAkB;IAc1B,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,iBAAiB;YAMX,iBAAiB;IAW/B,OAAO,CAAC,cAAc;CAMvB"}
|
package/dist/src/jobs/queue.js
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
3
4
|
import { EXCLUSIVE_JOBS } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Terminal statuses: the job is done and its `payload` (full
|
|
7
|
+
* conversation / export text) is dead weight — the durable record lives
|
|
8
|
+
* in raw files + the vector index, not the queue ledger. Only
|
|
9
|
+
* queued/running/interrupted jobs still need their payload.
|
|
10
|
+
*/
|
|
11
|
+
const TERMINAL_STATUSES = new Set([
|
|
12
|
+
"completed",
|
|
13
|
+
"failed",
|
|
14
|
+
"cancelled",
|
|
15
|
+
]);
|
|
16
|
+
/**
|
|
17
|
+
* Terminal statuses whose `payload` is safe to drop. A `failed` job is
|
|
18
|
+
* still retryable — `create()` with the same idempotency_key turns it into
|
|
19
|
+
* a retry whose chain root (the failed job) must still carry the canonical
|
|
20
|
+
* source/raw payload (save.ts resolveChainRoot). The failure path persists
|
|
21
|
+
* (and thus compacts) the job the instant it fails, before any retry exists,
|
|
22
|
+
* so stripping `failed` payloads here would silently break changed-source
|
|
23
|
+
* retries. Failed jobs are few (cap still bounds them); keep their payload.
|
|
24
|
+
*/
|
|
25
|
+
const STRIPPABLE_STATUSES = new Set([
|
|
26
|
+
"completed",
|
|
27
|
+
"cancelled",
|
|
28
|
+
]);
|
|
29
|
+
/**
|
|
30
|
+
* Cap on retained terminal-job records. Metadata is tiny (~200 B/job),
|
|
31
|
+
* but unbounded retention eventually blows `JSON.stringify`'s max string
|
|
32
|
+
* length (V8 ~512 MB) — the failure that turned every save into a 500.
|
|
33
|
+
* We keep the most recent N; idempotency dedup and the friend-status
|
|
34
|
+
* projection only need recent terminal jobs, and older sessions are
|
|
35
|
+
* re-guarded by capture-state.json + content-hash dedup.
|
|
36
|
+
*/
|
|
37
|
+
const MAX_TERMINAL_JOBS = 2000;
|
|
38
|
+
const PAYLOAD_OFFLOAD_THRESHOLD_BYTES = 64 * 1024;
|
|
39
|
+
const MAX_LEDGER_BYTES = 64 * 1024 * 1024;
|
|
40
|
+
const PAYLOAD_REF_MARKER = "__rift_queue_payload_ref";
|
|
41
|
+
const PERSIST_RETRY_DELAY_MS = 100;
|
|
42
|
+
const START_RETRY_INITIAL_DELAY_MS = 100;
|
|
43
|
+
const START_RETRY_MAX_DELAY_MS = 5000;
|
|
4
44
|
/**
|
|
5
45
|
* In-process job queue with:
|
|
6
46
|
* - Persistence to `queue.json` (atomic write: temp + rename)
|
|
@@ -12,9 +52,13 @@ import { EXCLUSIVE_JOBS } from "./types.js";
|
|
|
12
52
|
export class JobQueue {
|
|
13
53
|
queuePath;
|
|
14
54
|
jobs = new Map();
|
|
55
|
+
payloadRefs = new Map();
|
|
15
56
|
handlers = new Map();
|
|
16
57
|
processing = false;
|
|
17
58
|
held = false;
|
|
59
|
+
persistRetryTimer;
|
|
60
|
+
startRetryTimer;
|
|
61
|
+
startRetryDelayMs = START_RETRY_INITIAL_DELAY_MS;
|
|
18
62
|
constructor(queuePath) {
|
|
19
63
|
this.queuePath = queuePath;
|
|
20
64
|
}
|
|
@@ -53,9 +97,11 @@ export class JobQueue {
|
|
|
53
97
|
const raw = fs.readFileSync(this.queuePath, "utf-8");
|
|
54
98
|
const entries = JSON.parse(raw);
|
|
55
99
|
this.jobs.clear();
|
|
100
|
+
this.payloadRefs.clear();
|
|
56
101
|
// Collect jobs that need recovery (were running at crash time).
|
|
57
102
|
const toRequeue = [];
|
|
58
|
-
for (const
|
|
103
|
+
for (const entry of entries) {
|
|
104
|
+
const job = this.hydrateJob(entry);
|
|
59
105
|
if (job.status === "running") {
|
|
60
106
|
job.status = "interrupted";
|
|
61
107
|
job.updated_at = new Date().toISOString();
|
|
@@ -79,7 +125,10 @@ export class JobQueue {
|
|
|
79
125
|
job.retried_by = requeuedId;
|
|
80
126
|
this.jobs.set(requeuedId, requeued);
|
|
81
127
|
}
|
|
82
|
-
|
|
128
|
+
// Heal a ledger that bloated before this fix shipped: strip terminal
|
|
129
|
+
// payloads / cap retention on load, even when nothing needs re-queuing.
|
|
130
|
+
const compacted = this.compactLedger();
|
|
131
|
+
if (toRequeue.length > 0 || compacted) {
|
|
83
132
|
await this.persist();
|
|
84
133
|
}
|
|
85
134
|
}
|
|
@@ -134,8 +183,16 @@ export class JobQueue {
|
|
|
134
183
|
...(key !== undefined ? { idempotency_key: key } : {}),
|
|
135
184
|
...(opts?.payload !== undefined ? { payload: opts.payload } : {}),
|
|
136
185
|
};
|
|
186
|
+
const snapshot = this.snapshotState();
|
|
137
187
|
this.jobs.set(job.id, job);
|
|
138
|
-
|
|
188
|
+
try {
|
|
189
|
+
await this.persist();
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
this.restoreSnapshot(snapshot);
|
|
193
|
+
this.deletePayloadFilesForJob(job.id);
|
|
194
|
+
throw err;
|
|
195
|
+
}
|
|
139
196
|
this.processNext();
|
|
140
197
|
return { job, duplicate: false };
|
|
141
198
|
}
|
|
@@ -143,6 +200,24 @@ export class JobQueue {
|
|
|
143
200
|
get(id) {
|
|
144
201
|
return this.jobs.get(id);
|
|
145
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Return all jobs whose `idempotency_key` starts with `prefix`. Used
|
|
205
|
+
* by the friend-status projection to surface inbox freshness — jobs
|
|
206
|
+
* created from the inbox watcher all use keys of the form
|
|
207
|
+
* `inbox:<hex>`, so a prefix scan answers "what's the inbox lane
|
|
208
|
+
* doing" without leaking queue internals to the route layer.
|
|
209
|
+
*
|
|
210
|
+
* Read-only iteration of the in-memory map; safe to call on a hot
|
|
211
|
+
* queue. Returns the snapshot in insertion order.
|
|
212
|
+
*/
|
|
213
|
+
listByIdempotencyPrefix(prefix) {
|
|
214
|
+
const out = [];
|
|
215
|
+
for (const job of this.jobs.values()) {
|
|
216
|
+
if (job.idempotency_key?.startsWith(prefix))
|
|
217
|
+
out.push(job);
|
|
218
|
+
}
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
146
221
|
/**
|
|
147
222
|
* Check whether an idempotency key matches an existing active job.
|
|
148
223
|
* Returns the job if found in queued/running/completed state, undefined otherwise.
|
|
@@ -169,9 +244,16 @@ export class JobQueue {
|
|
|
169
244
|
if (!job)
|
|
170
245
|
return undefined;
|
|
171
246
|
if (job.status === "queued" || job.status === "interrupted") {
|
|
247
|
+
const snapshot = this.snapshotState();
|
|
172
248
|
job.status = "cancelled";
|
|
173
249
|
job.updated_at = new Date().toISOString();
|
|
174
|
-
|
|
250
|
+
try {
|
|
251
|
+
await this.persist();
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
this.restoreSnapshot(snapshot);
|
|
255
|
+
throw err;
|
|
256
|
+
}
|
|
175
257
|
return job;
|
|
176
258
|
}
|
|
177
259
|
if (job.status === "running") {
|
|
@@ -179,12 +261,274 @@ export class JobQueue {
|
|
|
179
261
|
}
|
|
180
262
|
return undefined;
|
|
181
263
|
}
|
|
182
|
-
/**
|
|
264
|
+
/**
|
|
265
|
+
* Keep the persisted ledger bounded so `JSON.stringify` can never exceed
|
|
266
|
+
* V8's max string length (the failure mode that turned every save into a
|
|
267
|
+
* 500). Two cheap, safe reductions on terminal jobs only:
|
|
268
|
+
* 1. Drop `payload` — only active jobs need it.
|
|
269
|
+
* 2. Cap how many terminal jobs we retain (oldest dropped first).
|
|
270
|
+
* Non-terminal jobs (queued/running/interrupted) are never touched.
|
|
271
|
+
* Returns true if anything changed.
|
|
272
|
+
*/
|
|
273
|
+
compactLedger() {
|
|
274
|
+
let changed = false;
|
|
275
|
+
// Protect every job reachable by walking `retry_of` from a non-terminal
|
|
276
|
+
// job. The save/ingest handlers call resolveChainRoot() to recover the
|
|
277
|
+
// canonical row id + source from the chain root. If an ancestor is
|
|
278
|
+
// dropped (payload stripped OR row deleted) while a retry is still
|
|
279
|
+
// queued/running, resolveChainRoot stops short (save.ts:55), the handler
|
|
280
|
+
// falls back to the retry's own id/source, and a duplicate row is
|
|
281
|
+
// created. So these ancestors are exempt from BOTH reductions below.
|
|
282
|
+
const protectedIds = new Set();
|
|
283
|
+
for (const job of this.jobs.values()) {
|
|
284
|
+
if (TERMINAL_STATUSES.has(job.status))
|
|
285
|
+
continue;
|
|
286
|
+
let cursor = job;
|
|
287
|
+
let depth = 0;
|
|
288
|
+
while (cursor?.retry_of && depth < 50) {
|
|
289
|
+
const parent = this.jobs.get(cursor.retry_of);
|
|
290
|
+
if (!parent)
|
|
291
|
+
break;
|
|
292
|
+
protectedIds.add(parent.id);
|
|
293
|
+
cursor = parent;
|
|
294
|
+
depth++;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const terminal = [];
|
|
298
|
+
for (const job of this.jobs.values()) {
|
|
299
|
+
if (TERMINAL_STATUSES.has(job.status)) {
|
|
300
|
+
// Keep payload on retry-chain ancestors: the save handler walks
|
|
301
|
+
// `retry_of` to the root job and reads its payload to resolve the
|
|
302
|
+
// canonical source/raw artifact (src/jobs/handlers/save.ts). A job
|
|
303
|
+
// with `retried_by` set is such an ancestor, as is any ancestor of
|
|
304
|
+
// a still-active retry (protectedIds). Standalone/leaf terminal jobs
|
|
305
|
+
// no longer need their payload.
|
|
306
|
+
if (job.payload !== undefined &&
|
|
307
|
+
STRIPPABLE_STATUSES.has(job.status) &&
|
|
308
|
+
job.retried_by === undefined &&
|
|
309
|
+
!protectedIds.has(job.id)) {
|
|
310
|
+
delete job.payload;
|
|
311
|
+
changed = true;
|
|
312
|
+
}
|
|
313
|
+
terminal.push(job);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (terminal.length > MAX_TERMINAL_JOBS) {
|
|
317
|
+
// Drop the oldest terminal jobs beyond the cap (oldest by updated_at),
|
|
318
|
+
// but never an ancestor of a still-active retry chain. If protected
|
|
319
|
+
// jobs prevent reaching the cap, the ledger stays slightly above it —
|
|
320
|
+
// correctness over a hard bound (the overflow is bounded by in-flight
|
|
321
|
+
// work, which is small).
|
|
322
|
+
terminal.sort((a, b) => a.updated_at.localeCompare(b.updated_at));
|
|
323
|
+
const overflow = terminal.length - MAX_TERMINAL_JOBS;
|
|
324
|
+
let dropped = 0;
|
|
325
|
+
for (const job of terminal) {
|
|
326
|
+
if (dropped >= overflow)
|
|
327
|
+
break;
|
|
328
|
+
if (protectedIds.has(job.id))
|
|
329
|
+
continue;
|
|
330
|
+
this.jobs.delete(job.id);
|
|
331
|
+
dropped++;
|
|
332
|
+
changed = true;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return changed;
|
|
336
|
+
}
|
|
337
|
+
/** Atomic write: temp file + rename. Ledger is compacted first so the
|
|
338
|
+
* serialized form stays well under V8's max string length. */
|
|
183
339
|
async persist() {
|
|
184
|
-
|
|
340
|
+
this.compactLedger();
|
|
341
|
+
this.prunePayloadRefCache();
|
|
342
|
+
const activePayloadRefs = new Set();
|
|
343
|
+
const entries = [...this.jobs.values()].map((job) => this.serializeJobForLedger(job, activePayloadRefs));
|
|
344
|
+
let data;
|
|
345
|
+
try {
|
|
346
|
+
data = JSON.stringify(entries, null, 2);
|
|
347
|
+
if (Buffer.byteLength(data, "utf-8") > MAX_LEDGER_BYTES) {
|
|
348
|
+
throw this.createLedgerTooLargeError(`Job ledger exceeds ${MAX_LEDGER_BYTES} bytes after payload offload.`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
catch (err) {
|
|
352
|
+
// Defense in depth: even after compaction, a pathological volume of
|
|
353
|
+
// active payloads could exceed V8's max string length. Surface a
|
|
354
|
+
// typed, actionable error instead of a bare 500 — and never silently
|
|
355
|
+
// corrupt active jobs by dropping their payloads.
|
|
356
|
+
if (err instanceof RangeError) {
|
|
357
|
+
throw this.createLedgerTooLargeError("Job ledger too large to serialize (exceeds V8 max string length).");
|
|
358
|
+
}
|
|
359
|
+
throw err;
|
|
360
|
+
}
|
|
185
361
|
const tmp = this.queuePath + `.tmp.${process.pid}`;
|
|
186
362
|
fs.writeFileSync(tmp, data, "utf-8");
|
|
187
363
|
fs.renameSync(tmp, this.queuePath);
|
|
364
|
+
this.cleanupPayloadFiles(activePayloadRefs);
|
|
365
|
+
this.clearPersistRetry();
|
|
366
|
+
}
|
|
367
|
+
createLedgerTooLargeError(message) {
|
|
368
|
+
const e = new Error(`${message} Payloads above ${PAYLOAD_OFFLOAD_THRESHOLD_BYTES} bytes are ` +
|
|
369
|
+
"offloaded automatically, so this means there are too many queued jobs " +
|
|
370
|
+
"or a malformed payload reference. Retry after current jobs drain, or " +
|
|
371
|
+
"see reports/2026-05-25-capture-500-queue-bloat.md for manual recovery.");
|
|
372
|
+
e.code = "queue_ledger_too_large";
|
|
373
|
+
return e;
|
|
374
|
+
}
|
|
375
|
+
snapshotState() {
|
|
376
|
+
return {
|
|
377
|
+
jobs: new Map([...this.jobs.entries()].map(([id, job]) => [id, { ...job }])),
|
|
378
|
+
payloadRefs: new Map([...this.payloadRefs.entries()].map(([id, entry]) => [
|
|
379
|
+
id,
|
|
380
|
+
{ ref: { ...entry.ref }, value: entry.value },
|
|
381
|
+
])),
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
restoreSnapshot(snapshot) {
|
|
385
|
+
this.jobs = snapshot.jobs;
|
|
386
|
+
this.payloadRefs = snapshot.payloadRefs;
|
|
387
|
+
}
|
|
388
|
+
payloadDir() {
|
|
389
|
+
return path.join(path.dirname(this.queuePath), "payloads");
|
|
390
|
+
}
|
|
391
|
+
payloadPath(jobId, sha256) {
|
|
392
|
+
return path.join(this.payloadDir(), `${jobId}.${sha256}.json`);
|
|
393
|
+
}
|
|
394
|
+
payloadRefPath(jobId, sha256) {
|
|
395
|
+
return `payloads/${jobId}.${sha256}.json`;
|
|
396
|
+
}
|
|
397
|
+
payloadAbsPath(ref) {
|
|
398
|
+
return path.isAbsolute(ref.path)
|
|
399
|
+
? ref.path
|
|
400
|
+
: path.join(path.dirname(this.queuePath), ref.path);
|
|
401
|
+
}
|
|
402
|
+
isPayloadRef(payload) {
|
|
403
|
+
return (typeof payload === "object" &&
|
|
404
|
+
payload !== null &&
|
|
405
|
+
payload[PAYLOAD_REF_MARKER] === true &&
|
|
406
|
+
typeof payload["path"] === "string" &&
|
|
407
|
+
typeof payload["byte_length"] === "number" &&
|
|
408
|
+
typeof payload["sha256"] === "string");
|
|
409
|
+
}
|
|
410
|
+
hydrateJob(entry) {
|
|
411
|
+
const job = { ...entry };
|
|
412
|
+
if (this.isPayloadRef(entry.payload)) {
|
|
413
|
+
const payload = this.readPayloadRef(entry.payload);
|
|
414
|
+
job.payload = payload;
|
|
415
|
+
this.payloadRefs.set(job.id, {
|
|
416
|
+
ref: { ...entry.payload },
|
|
417
|
+
value: payload,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
return job;
|
|
421
|
+
}
|
|
422
|
+
readPayloadRef(ref) {
|
|
423
|
+
const refPath = this.payloadAbsPath(ref);
|
|
424
|
+
const raw = fs.readFileSync(refPath, "utf-8");
|
|
425
|
+
const bytes = Buffer.byteLength(raw, "utf-8");
|
|
426
|
+
const sha256 = crypto.createHash("sha256").update(raw).digest("hex");
|
|
427
|
+
if (bytes !== ref.byte_length || sha256 !== ref.sha256) {
|
|
428
|
+
throw new Error(`Job payload ref failed integrity check: ${ref.path}`);
|
|
429
|
+
}
|
|
430
|
+
return JSON.parse(raw);
|
|
431
|
+
}
|
|
432
|
+
serializeJobForLedger(job, activePayloadRefs) {
|
|
433
|
+
const persisted = { ...job };
|
|
434
|
+
if (job.payload === undefined) {
|
|
435
|
+
delete persisted.payload;
|
|
436
|
+
this.payloadRefs.delete(job.id);
|
|
437
|
+
return persisted;
|
|
438
|
+
}
|
|
439
|
+
const cached = this.payloadRefs.get(job.id);
|
|
440
|
+
if (cached &&
|
|
441
|
+
cached.value === job.payload &&
|
|
442
|
+
fs.existsSync(this.payloadAbsPath(cached.ref))) {
|
|
443
|
+
activePayloadRefs.add(this.payloadAbsPath(cached.ref));
|
|
444
|
+
persisted.payload = cached.ref;
|
|
445
|
+
return persisted;
|
|
446
|
+
}
|
|
447
|
+
const payloadJson = JSON.stringify(job.payload);
|
|
448
|
+
if (payloadJson === undefined) {
|
|
449
|
+
throw new Error(`Job ${job.id} payload is not JSON-serializable`);
|
|
450
|
+
}
|
|
451
|
+
const byteLength = Buffer.byteLength(payloadJson, "utf-8");
|
|
452
|
+
if (byteLength <= PAYLOAD_OFFLOAD_THRESHOLD_BYTES) {
|
|
453
|
+
this.payloadRefs.delete(job.id);
|
|
454
|
+
persisted.payload = job.payload;
|
|
455
|
+
return persisted;
|
|
456
|
+
}
|
|
457
|
+
const sha256 = crypto.createHash("sha256").update(payloadJson).digest("hex");
|
|
458
|
+
const refPath = this.payloadRefPath(job.id, sha256);
|
|
459
|
+
const ref = {
|
|
460
|
+
[PAYLOAD_REF_MARKER]: true,
|
|
461
|
+
path: refPath,
|
|
462
|
+
byte_length: byteLength,
|
|
463
|
+
sha256,
|
|
464
|
+
};
|
|
465
|
+
this.writePayloadFile(this.payloadPath(job.id, sha256), payloadJson);
|
|
466
|
+
this.payloadRefs.set(job.id, { ref, value: job.payload });
|
|
467
|
+
activePayloadRefs.add(this.payloadAbsPath(ref));
|
|
468
|
+
persisted.payload = ref;
|
|
469
|
+
return persisted;
|
|
470
|
+
}
|
|
471
|
+
writePayloadFile(filePath, data) {
|
|
472
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
473
|
+
const tmp = `${filePath}.tmp.${process.pid}`;
|
|
474
|
+
fs.writeFileSync(tmp, data, "utf-8");
|
|
475
|
+
fs.renameSync(tmp, filePath);
|
|
476
|
+
}
|
|
477
|
+
prunePayloadRefCache() {
|
|
478
|
+
for (const jobId of this.payloadRefs.keys()) {
|
|
479
|
+
if (!this.jobs.has(jobId)) {
|
|
480
|
+
this.payloadRefs.delete(jobId);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
deletePayloadFilesForJob(jobId) {
|
|
485
|
+
const dir = this.payloadDir();
|
|
486
|
+
let files;
|
|
487
|
+
try {
|
|
488
|
+
if (!fs.existsSync(dir))
|
|
489
|
+
return;
|
|
490
|
+
files = fs.readdirSync(dir);
|
|
491
|
+
}
|
|
492
|
+
catch (err) {
|
|
493
|
+
process.stderr.write(`queue: warning: failed to scan payload files for ${jobId}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
for (const file of files) {
|
|
497
|
+
if (file === `${jobId}.json` || file.startsWith(`${jobId}.`)) {
|
|
498
|
+
this.deletePayloadPath(path.join(dir, file), jobId);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
cleanupPayloadFiles(activePayloadRefs) {
|
|
503
|
+
const dir = this.payloadDir();
|
|
504
|
+
let files;
|
|
505
|
+
try {
|
|
506
|
+
if (!fs.existsSync(dir))
|
|
507
|
+
return;
|
|
508
|
+
files = fs.readdirSync(dir);
|
|
509
|
+
}
|
|
510
|
+
catch (err) {
|
|
511
|
+
process.stderr.write(`queue: warning: failed to scan payload directory for cleanup: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
for (const file of files) {
|
|
515
|
+
if (!file.endsWith(".json"))
|
|
516
|
+
continue;
|
|
517
|
+
const filePath = path.join(dir, file);
|
|
518
|
+
if (!activePayloadRefs.has(filePath)) {
|
|
519
|
+
this.deletePayloadPath(filePath, file);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
deletePayloadPath(filePath, label) {
|
|
524
|
+
try {
|
|
525
|
+
fs.unlinkSync(filePath);
|
|
526
|
+
}
|
|
527
|
+
catch (err) {
|
|
528
|
+
if (err.code !== "ENOENT") {
|
|
529
|
+
process.stderr.write(`queue: warning: failed to remove payload file ${label}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
188
532
|
}
|
|
189
533
|
/**
|
|
190
534
|
* Find the active job for a given idempotency key.
|
|
@@ -219,13 +563,21 @@ export class JobQueue {
|
|
|
219
563
|
};
|
|
220
564
|
// Rebuild the Map with this job first so findNextQueued() picks it up
|
|
221
565
|
// before any previously queued work.
|
|
566
|
+
const snapshot = this.snapshotState();
|
|
222
567
|
const entries = [...this.jobs.entries()];
|
|
223
568
|
this.jobs.clear();
|
|
224
569
|
this.jobs.set(job.id, job);
|
|
225
570
|
for (const [id, j] of entries) {
|
|
226
571
|
this.jobs.set(id, j);
|
|
227
572
|
}
|
|
228
|
-
|
|
573
|
+
try {
|
|
574
|
+
await this.persist();
|
|
575
|
+
}
|
|
576
|
+
catch (err) {
|
|
577
|
+
this.restoreSnapshot(snapshot);
|
|
578
|
+
this.deletePayloadFilesForJob(job.id);
|
|
579
|
+
throw err;
|
|
580
|
+
}
|
|
229
581
|
this.processNext();
|
|
230
582
|
return { job, duplicate: false };
|
|
231
583
|
}
|
|
@@ -268,10 +620,18 @@ export class JobQueue {
|
|
|
268
620
|
retry_of: failed.id,
|
|
269
621
|
...(opts?.payload !== undefined ? { payload: opts.payload } : {}),
|
|
270
622
|
};
|
|
623
|
+
const snapshot = this.snapshotState();
|
|
271
624
|
failed.retried_by = retryId;
|
|
272
625
|
failed.updated_at = new Date().toISOString();
|
|
273
626
|
this.jobs.set(retryId, retry);
|
|
274
|
-
|
|
627
|
+
try {
|
|
628
|
+
await this.persist();
|
|
629
|
+
}
|
|
630
|
+
catch (err) {
|
|
631
|
+
this.restoreSnapshot(snapshot);
|
|
632
|
+
this.deletePayloadFilesForJob(retryId);
|
|
633
|
+
throw err;
|
|
634
|
+
}
|
|
275
635
|
this.processNext();
|
|
276
636
|
return { job: retry, duplicate: false };
|
|
277
637
|
}
|
|
@@ -288,25 +648,105 @@ export class JobQueue {
|
|
|
288
648
|
if (!handler)
|
|
289
649
|
return; // No handler registered — leave queued.
|
|
290
650
|
this.processing = true;
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
.catch((err) => {
|
|
300
|
-
next.status = "failed";
|
|
301
|
-
next.error =
|
|
302
|
-
err instanceof Error ? err.message : String(err);
|
|
303
|
-
next.updated_at = new Date().toISOString();
|
|
304
|
-
})
|
|
305
|
-
.finally(() => {
|
|
651
|
+
void (async () => {
|
|
652
|
+
try {
|
|
653
|
+
await this.applyPersistedMutation(() => {
|
|
654
|
+
next.status = "running";
|
|
655
|
+
next.updated_at = new Date().toISOString();
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
catch (err) {
|
|
306
659
|
this.processing = false;
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
660
|
+
process.stderr.write(`queue: failed to persist start of job ${next.id}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
661
|
+
this.scheduleStartRetry();
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
this.clearStartRetry();
|
|
665
|
+
try {
|
|
666
|
+
await handler(next);
|
|
667
|
+
await this.applyTerminalMutation(`completion of job ${next.id}`, () => {
|
|
668
|
+
next.status = "completed";
|
|
669
|
+
next.updated_at = new Date().toISOString();
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
catch (err) {
|
|
673
|
+
await this.applyTerminalMutation(`failure of job ${next.id}`, () => {
|
|
674
|
+
next.status = "failed";
|
|
675
|
+
next.error = err instanceof Error ? err.message : String(err);
|
|
676
|
+
next.updated_at = new Date().toISOString();
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
finally {
|
|
680
|
+
this.processing = false;
|
|
681
|
+
this.processNext();
|
|
682
|
+
}
|
|
683
|
+
})();
|
|
684
|
+
}
|
|
685
|
+
async applyPersistedMutation(mutator) {
|
|
686
|
+
const snapshot = this.snapshotState();
|
|
687
|
+
mutator();
|
|
688
|
+
try {
|
|
689
|
+
await this.persist();
|
|
690
|
+
}
|
|
691
|
+
catch (err) {
|
|
692
|
+
this.restoreSnapshot(snapshot);
|
|
693
|
+
throw err;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
async applyTerminalMutation(label, mutator) {
|
|
697
|
+
mutator();
|
|
698
|
+
try {
|
|
699
|
+
await this.persist();
|
|
700
|
+
}
|
|
701
|
+
catch (err) {
|
|
702
|
+
this.markPersistDirty(label, err);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
markPersistDirty(label, err) {
|
|
706
|
+
process.stderr.write(`queue: failed to persist ${label}: ${err instanceof Error ? err.message : String(err)}; will retry\n`);
|
|
707
|
+
this.schedulePersistRetry();
|
|
708
|
+
}
|
|
709
|
+
schedulePersistRetry() {
|
|
710
|
+
if (this.persistRetryTimer)
|
|
711
|
+
return;
|
|
712
|
+
this.persistRetryTimer = setTimeout(() => {
|
|
713
|
+
this.persistRetryTimer = undefined;
|
|
714
|
+
void this.retryDirtyPersist();
|
|
715
|
+
}, PERSIST_RETRY_DELAY_MS);
|
|
716
|
+
this.persistRetryTimer.unref?.();
|
|
717
|
+
}
|
|
718
|
+
scheduleStartRetry() {
|
|
719
|
+
if (this.startRetryTimer)
|
|
720
|
+
return;
|
|
721
|
+
const delayMs = this.startRetryDelayMs;
|
|
722
|
+
this.startRetryDelayMs = Math.min(this.startRetryDelayMs * 2, START_RETRY_MAX_DELAY_MS);
|
|
723
|
+
this.startRetryTimer = setTimeout(() => {
|
|
724
|
+
this.startRetryTimer = undefined;
|
|
725
|
+
this.processNext();
|
|
726
|
+
}, delayMs);
|
|
727
|
+
this.startRetryTimer.unref?.();
|
|
728
|
+
}
|
|
729
|
+
clearStartRetry() {
|
|
730
|
+
if (this.startRetryTimer) {
|
|
731
|
+
clearTimeout(this.startRetryTimer);
|
|
732
|
+
this.startRetryTimer = undefined;
|
|
733
|
+
}
|
|
734
|
+
this.startRetryDelayMs = START_RETRY_INITIAL_DELAY_MS;
|
|
735
|
+
}
|
|
736
|
+
clearPersistRetry() {
|
|
737
|
+
if (!this.persistRetryTimer)
|
|
738
|
+
return;
|
|
739
|
+
clearTimeout(this.persistRetryTimer);
|
|
740
|
+
this.persistRetryTimer = undefined;
|
|
741
|
+
}
|
|
742
|
+
async retryDirtyPersist() {
|
|
743
|
+
try {
|
|
744
|
+
await this.persist();
|
|
745
|
+
}
|
|
746
|
+
catch (err) {
|
|
747
|
+
process.stderr.write(`queue: failed to retry dirty ledger persist: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
748
|
+
this.schedulePersistRetry();
|
|
749
|
+
}
|
|
310
750
|
}
|
|
311
751
|
findNextQueued() {
|
|
312
752
|
for (const job of this.jobs.values()) {
|