@getrift/rift 0.1.0-beta.20 → 0.1.0-beta.22
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 +7 -3
- package/dist/src/capture/auto-capture.d.ts +105 -4
- package/dist/src/capture/auto-capture.d.ts.map +1 -1
- package/dist/src/capture/auto-capture.js +313 -34
- package/dist/src/capture/auto-capture.js.map +1 -1
- package/dist/src/capture/claude-cli-triage-provider.d.ts +28 -0
- package/dist/src/capture/claude-cli-triage-provider.d.ts.map +1 -0
- package/dist/src/capture/claude-cli-triage-provider.js +88 -0
- package/dist/src/capture/claude-cli-triage-provider.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 +1 -33
- package/dist/src/capture/codex-cli-triage-provider.js.map +1 -1
- package/dist/src/capture/cursor-capture.d.ts +89 -0
- package/dist/src/capture/cursor-capture.d.ts.map +1 -0
- package/dist/src/capture/cursor-capture.js +121 -0
- package/dist/src/capture/cursor-capture.js.map +1 -0
- package/dist/src/capture/observability.d.ts +30 -0
- package/dist/src/capture/observability.d.ts.map +1 -1
- package/dist/src/capture/observability.js +29 -0
- package/dist/src/capture/observability.js.map +1 -1
- package/dist/src/capture/sources.d.ts +41 -3
- package/dist/src/capture/sources.d.ts.map +1 -1
- package/dist/src/capture/sources.js +43 -1
- package/dist/src/capture/sources.js.map +1 -1
- package/dist/src/capture/triage-classification.d.ts +69 -0
- package/dist/src/capture/triage-classification.d.ts.map +1 -0
- package/dist/src/capture/triage-classification.js +62 -0
- package/dist/src/capture/triage-classification.js.map +1 -0
- package/dist/src/capture/triage-provider-factory.d.ts +36 -0
- package/dist/src/capture/triage-provider-factory.d.ts.map +1 -0
- package/dist/src/capture/triage-provider-factory.js +55 -0
- package/dist/src/capture/triage-provider-factory.js.map +1 -0
- package/dist/src/capture/triage.d.ts +1 -1
- package/dist/src/capture/triage.d.ts.map +1 -1
- package/dist/src/capture/triage.js +8 -6
- package/dist/src/capture/triage.js.map +1 -1
- package/dist/src/cli/commands/capture.d.ts.map +1 -1
- package/dist/src/cli/commands/capture.js +72 -17
- package/dist/src/cli/commands/capture.js.map +1 -1
- package/dist/src/cli/commands/chunk-backfill.d.ts +13 -0
- package/dist/src/cli/commands/chunk-backfill.d.ts.map +1 -0
- package/dist/src/cli/commands/chunk-backfill.js +157 -0
- package/dist/src/cli/commands/chunk-backfill.js.map +1 -0
- package/dist/src/cli/commands/cursor-probe.d.ts +20 -0
- package/dist/src/cli/commands/cursor-probe.d.ts.map +1 -0
- package/dist/src/cli/commands/cursor-probe.js +162 -0
- package/dist/src/cli/commands/cursor-probe.js.map +1 -0
- package/dist/src/cli/commands/menubar.d.ts +50 -0
- package/dist/src/cli/commands/menubar.d.ts.map +1 -1
- package/dist/src/cli/commands/menubar.js +224 -16
- package/dist/src/cli/commands/menubar.js.map +1 -1
- package/dist/src/cli/commands/onboard.d.ts +36 -7
- package/dist/src/cli/commands/onboard.d.ts.map +1 -1
- package/dist/src/cli/commands/onboard.js +256 -53
- package/dist/src/cli/commands/onboard.js.map +1 -1
- package/dist/src/cli/commands/status.d.ts.map +1 -1
- package/dist/src/cli/commands/status.js +16 -0
- package/dist/src/cli/commands/status.js.map +1 -1
- package/dist/src/cli/commands/update.d.ts +34 -1
- package/dist/src/cli/commands/update.d.ts.map +1 -1
- package/dist/src/cli/commands/update.js +179 -2
- package/dist/src/cli/commands/update.js.map +1 -1
- package/dist/src/cli/index.d.ts.map +1 -1
- package/dist/src/cli/index.js +4 -0
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/cli/postinstall-menubar.d.ts.map +1 -1
- package/dist/src/cli/postinstall-menubar.js +14 -0
- package/dist/src/cli/postinstall-menubar.js.map +1 -1
- package/dist/src/cli/status/friend-header.d.ts +18 -0
- package/dist/src/cli/status/friend-header.d.ts.map +1 -1
- package/dist/src/cli/status/friend-header.js +137 -0
- package/dist/src/cli/status/friend-header.js.map +1 -1
- package/dist/src/cli/status/local-signals.d.ts +41 -0
- package/dist/src/cli/status/local-signals.d.ts.map +1 -1
- package/dist/src/cli/status/local-signals.js +48 -0
- package/dist/src/cli/status/local-signals.js.map +1 -1
- package/dist/src/config/schema.d.ts +220 -14
- package/dist/src/config/schema.d.ts.map +1 -1
- package/dist/src/config/schema.js +82 -7
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/diagnostics/claude-preflight.d.ts +34 -0
- package/dist/src/diagnostics/claude-preflight.d.ts.map +1 -0
- package/dist/src/diagnostics/claude-preflight.js +89 -0
- package/dist/src/diagnostics/claude-preflight.js.map +1 -0
- package/dist/src/diagnostics/codex-preflight.d.ts +1 -1
- package/dist/src/diagnostics/codex-preflight.d.ts.map +1 -1
- package/dist/src/diagnostics/codex-preflight.js +14 -0
- package/dist/src/diagnostics/codex-preflight.js.map +1 -1
- package/dist/src/diagnostics/doctor.d.ts +9 -1
- package/dist/src/diagnostics/doctor.d.ts.map +1 -1
- package/dist/src/diagnostics/doctor.js +57 -2
- package/dist/src/diagnostics/doctor.js.map +1 -1
- package/dist/src/ingestion/chunk-meta.d.ts +85 -0
- package/dist/src/ingestion/chunk-meta.d.ts.map +1 -0
- package/dist/src/ingestion/chunk-meta.js +167 -0
- package/dist/src/ingestion/chunk-meta.js.map +1 -0
- package/dist/src/ingestion/chunk-text.d.ts +39 -0
- package/dist/src/ingestion/chunk-text.d.ts.map +1 -0
- package/dist/src/ingestion/chunk-text.js +114 -0
- package/dist/src/ingestion/chunk-text.js.map +1 -0
- package/dist/src/ingestion/cursor/cursor-store.d.ts +177 -0
- package/dist/src/ingestion/cursor/cursor-store.d.ts.map +1 -0
- package/dist/src/ingestion/cursor/cursor-store.js +243 -0
- package/dist/src/ingestion/cursor/cursor-store.js.map +1 -0
- package/dist/src/ingestion/cursor/enrich-roots.d.ts +16 -0
- package/dist/src/ingestion/cursor/enrich-roots.d.ts.map +1 -0
- package/dist/src/ingestion/cursor/enrich-roots.js +22 -0
- package/dist/src/ingestion/cursor/enrich-roots.js.map +1 -0
- package/dist/src/ingestion/cursor/vscdb-reader.d.ts +32 -0
- package/dist/src/ingestion/cursor/vscdb-reader.d.ts.map +1 -0
- package/dist/src/ingestion/cursor/vscdb-reader.js +113 -0
- package/dist/src/ingestion/cursor/vscdb-reader.js.map +1 -0
- package/dist/src/ingestion/cursor/workspace-root.d.ts +96 -0
- package/dist/src/ingestion/cursor/workspace-root.d.ts.map +1 -0
- package/dist/src/ingestion/cursor/workspace-root.js +187 -0
- package/dist/src/ingestion/cursor/workspace-root.js.map +1 -0
- package/dist/src/ingestion/indexer.d.ts.map +1 -1
- package/dist/src/ingestion/indexer.js +41 -32
- package/dist/src/ingestion/indexer.js.map +1 -1
- package/dist/src/jobs/handlers/compact.d.ts.map +1 -1
- package/dist/src/jobs/handlers/compact.js +9 -4
- package/dist/src/jobs/handlers/compact.js.map +1 -1
- package/dist/src/jobs/handlers/ingest.d.ts.map +1 -1
- package/dist/src/jobs/handlers/ingest.js +60 -30
- package/dist/src/jobs/handlers/ingest.js.map +1 -1
- package/dist/src/jobs/handlers/reconcile.d.ts.map +1 -1
- package/dist/src/jobs/handlers/reconcile.js +128 -45
- package/dist/src/jobs/handlers/reconcile.js.map +1 -1
- package/dist/src/jobs/handlers/save.d.ts.map +1 -1
- package/dist/src/jobs/handlers/save.js +122 -72
- package/dist/src/jobs/handlers/save.js.map +1 -1
- package/dist/src/jobs/types.d.ts +1 -1
- package/dist/src/main.js +26 -15
- package/dist/src/main.js.map +1 -1
- package/dist/src/mcp/server.d.ts.map +1 -1
- package/dist/src/mcp/server.js +10 -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 +7 -1
- package/dist/src/mcp/tools/context-pack.js.map +1 -1
- package/dist/src/mcp/tools/conversations-search.d.ts +1 -1
- package/dist/src/mcp/tools/conversations-search.d.ts.map +1 -1
- package/dist/src/mcp/tools/conversations-search.js +7 -1
- package/dist/src/mcp/tools/conversations-search.js.map +1 -1
- package/dist/src/mcp/tools/evidence-feedback.d.ts +60 -0
- package/dist/src/mcp/tools/evidence-feedback.d.ts.map +1 -0
- package/dist/src/mcp/tools/evidence-feedback.js +62 -0
- package/dist/src/mcp/tools/evidence-feedback.js.map +1 -0
- package/dist/src/mcp/tools/log-outcome.d.ts +72 -0
- package/dist/src/mcp/tools/log-outcome.d.ts.map +1 -0
- package/dist/src/mcp/tools/log-outcome.js +59 -0
- package/dist/src/mcp/tools/log-outcome.js.map +1 -0
- package/dist/src/mcp/tools/open-evidence.d.ts +37 -0
- package/dist/src/mcp/tools/open-evidence.d.ts.map +1 -0
- package/dist/src/mcp/tools/open-evidence.js +72 -0
- package/dist/src/mcp/tools/open-evidence.js.map +1 -0
- package/dist/src/mcp/tools/save.d.ts +7 -2
- package/dist/src/mcp/tools/save.d.ts.map +1 -1
- package/dist/src/mcp/tools/save.js +7 -2
- package/dist/src/mcp/tools/save.js.map +1 -1
- package/dist/src/mcp/tools/search.d.ts.map +1 -1
- package/dist/src/mcp/tools/search.js +7 -1
- package/dist/src/mcp/tools/search.js.map +1 -1
- package/dist/src/observability/retrieval-feedback.d.ts +82 -0
- package/dist/src/observability/retrieval-feedback.d.ts.map +1 -0
- package/dist/src/observability/retrieval-feedback.js +231 -0
- package/dist/src/observability/retrieval-feedback.js.map +1 -0
- package/dist/src/observability/rift-context.d.ts.map +1 -1
- package/dist/src/observability/rift-context.js +3 -0
- package/dist/src/observability/rift-context.js.map +1 -1
- package/dist/src/observability/tool-usage-stats.d.ts +13 -0
- package/dist/src/observability/tool-usage-stats.d.ts.map +1 -1
- package/dist/src/observability/tool-usage-stats.js +15 -0
- package/dist/src/observability/tool-usage-stats.js.map +1 -1
- package/dist/src/observability/tool-usage.d.ts +56 -0
- package/dist/src/observability/tool-usage.d.ts.map +1 -1
- package/dist/src/observability/tool-usage.js +86 -0
- package/dist/src/observability/tool-usage.js.map +1 -1
- package/dist/src/providers/claude-cli-metadata-extraction.d.ts +47 -0
- package/dist/src/providers/claude-cli-metadata-extraction.d.ts.map +1 -0
- package/dist/src/providers/claude-cli-metadata-extraction.js +120 -0
- package/dist/src/providers/claude-cli-metadata-extraction.js.map +1 -0
- package/dist/src/providers/claude-cli-runner.d.ts +92 -0
- package/dist/src/providers/claude-cli-runner.d.ts.map +1 -0
- package/dist/src/providers/claude-cli-runner.js +598 -0
- package/dist/src/providers/claude-cli-runner.js.map +1 -0
- package/dist/src/providers/codex-cli-metadata-extraction.d.ts.map +1 -1
- package/dist/src/providers/codex-cli-metadata-extraction.js +1 -40
- package/dist/src/providers/codex-cli-metadata-extraction.js.map +1 -1
- package/dist/src/providers/codex-cli-runner.d.ts +7 -0
- package/dist/src/providers/codex-cli-runner.d.ts.map +1 -1
- package/dist/src/providers/codex-cli-runner.js +131 -5
- package/dist/src/providers/codex-cli-runner.js.map +1 -1
- package/dist/src/providers/conversation-generation.d.ts +10 -0
- package/dist/src/providers/conversation-generation.d.ts.map +1 -1
- package/dist/src/providers/conversation-generation.js +54 -13
- package/dist/src/providers/conversation-generation.js.map +1 -1
- package/dist/src/providers/openai-metadata-extraction.d.ts +48 -1
- package/dist/src/providers/openai-metadata-extraction.d.ts.map +1 -1
- package/dist/src/providers/openai-metadata-extraction.js +51 -2
- package/dist/src/providers/openai-metadata-extraction.js.map +1 -1
- package/dist/src/providers/types.d.ts +1 -1
- package/dist/src/providers/types.d.ts.map +1 -1
- package/dist/src/providers/types.js +4 -0
- package/dist/src/providers/types.js.map +1 -1
- package/dist/src/retrieval/compact.d.ts +81 -0
- package/dist/src/retrieval/compact.d.ts.map +1 -1
- package/dist/src/retrieval/compact.js +248 -8
- package/dist/src/retrieval/compact.js.map +1 -1
- package/dist/src/retrieval/context-pack.d.ts.map +1 -1
- package/dist/src/retrieval/context-pack.js +28 -14
- package/dist/src/retrieval/context-pack.js.map +1 -1
- package/dist/src/retrieval/evidence-key.d.ts +48 -0
- package/dist/src/retrieval/evidence-key.d.ts.map +1 -0
- package/dist/src/retrieval/evidence-key.js +131 -0
- package/dist/src/retrieval/evidence-key.js.map +1 -0
- package/dist/src/retrieval/group-by-parent.d.ts +38 -0
- package/dist/src/retrieval/group-by-parent.d.ts.map +1 -0
- package/dist/src/retrieval/group-by-parent.js +40 -0
- package/dist/src/retrieval/group-by-parent.js.map +1 -0
- package/dist/src/retrieval/lexical.d.ts.map +1 -1
- package/dist/src/retrieval/lexical.js +1 -3
- package/dist/src/retrieval/lexical.js.map +1 -1
- package/dist/src/retrieval/receipt.d.ts +57 -0
- package/dist/src/retrieval/receipt.d.ts.map +1 -0
- package/dist/src/retrieval/receipt.js +119 -0
- package/dist/src/retrieval/receipt.js.map +1 -0
- package/dist/src/retrieval/reranker.d.ts +12 -2
- package/dist/src/retrieval/reranker.d.ts.map +1 -1
- package/dist/src/retrieval/reranker.js +11 -4
- package/dist/src/retrieval/reranker.js.map +1 -1
- package/dist/src/retrieval/stitch-chunks.d.ts +73 -0
- package/dist/src/retrieval/stitch-chunks.d.ts.map +1 -0
- package/dist/src/retrieval/stitch-chunks.js +106 -0
- package/dist/src/retrieval/stitch-chunks.js.map +1 -0
- package/dist/src/server/app.d.ts.map +1 -1
- package/dist/src/server/app.js +17 -1
- package/dist/src/server/app.js.map +1 -1
- package/dist/src/server/routes/conversations-search.d.ts.map +1 -1
- package/dist/src/server/routes/conversations-search.js +12 -3
- package/dist/src/server/routes/conversations-search.js.map +1 -1
- package/dist/src/server/routes/friend-status.d.ts +44 -5
- package/dist/src/server/routes/friend-status.d.ts.map +1 -1
- package/dist/src/server/routes/friend-status.js +74 -6
- package/dist/src/server/routes/friend-status.js.map +1 -1
- package/dist/src/server/routes/mcp-usage.d.ts +9 -6
- 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/retrieval-feedback.d.ts +3 -0
- package/dist/src/server/routes/retrieval-feedback.d.ts.map +1 -0
- package/dist/src/server/routes/retrieval-feedback.js +290 -0
- package/dist/src/server/routes/retrieval-feedback.js.map +1 -0
- package/dist/src/server/routes/save.d.ts +3 -3
- package/dist/src/server/routes/save.d.ts.map +1 -1
- package/dist/src/server/routes/save.js +6 -2
- package/dist/src/server/routes/save.js.map +1 -1
- package/dist/src/server/routes/search.d.ts.map +1 -1
- package/dist/src/server/routes/search.js +19 -7
- package/dist/src/server/routes/search.js.map +1 -1
- package/dist/src/server/serving-marker.d.ts +85 -0
- package/dist/src/server/serving-marker.d.ts.map +1 -0
- package/dist/src/server/serving-marker.js +226 -0
- package/dist/src/server/serving-marker.js.map +1 -0
- package/dist/src/storage/chunk-backfill.d.ts +39 -0
- package/dist/src/storage/chunk-backfill.d.ts.map +1 -0
- package/dist/src/storage/chunk-backfill.js +295 -0
- package/dist/src/storage/chunk-backfill.js.map +1 -0
- package/dist/src/storage/filter.d.ts +42 -0
- package/dist/src/storage/filter.d.ts.map +1 -0
- package/dist/src/storage/filter.js +70 -0
- package/dist/src/storage/filter.js.map +1 -0
- package/dist/src/storage/rebuild.d.ts.map +1 -1
- package/dist/src/storage/rebuild.js +44 -27
- package/dist/src/storage/rebuild.js.map +1 -1
- package/dist/src/storage/tables.d.ts +41 -0
- package/dist/src/storage/tables.d.ts.map +1 -1
- package/dist/src/storage/tables.js +64 -1
- package/dist/src/storage/tables.js.map +1 -1
- package/operator/swiftbar/render-menu.py +57 -15
- package/package.json +5 -3
|
@@ -21,12 +21,16 @@ import os from "node:os";
|
|
|
21
21
|
import { z } from "zod";
|
|
22
22
|
import { discoverClaudeCodeSessions, parseClaudeCodeSession, } from "../ingestion/parsers/claude-code-jsonl.js";
|
|
23
23
|
import { discoverCodexSessions, parseCodexSession, } from "../ingestion/parsers/codex-jsonl.js";
|
|
24
|
+
import { parseCursorComposerSession } from "../ingestion/cursor/cursor-store.js";
|
|
25
|
+
import { defaultCursorDir } from "../ingestion/cursor/vscdb-reader.js";
|
|
26
|
+
import { cursorCaptureEnabled, cursorCapturePersistEnabled, cursorTitlesRevealed, discoverCursorCaptureSessions, redactedCursorTitle, } from "./cursor-capture.js";
|
|
24
27
|
import { triageConversation } from "./triage.js";
|
|
25
28
|
import { addToReview } from "./review-queue.js";
|
|
26
29
|
import { atomicWrite } from "../storage/atomic.js";
|
|
27
|
-
import {
|
|
30
|
+
import { createConfiguredTriageProvider, getConfiguredTriageProviderName, } from "./triage-provider-factory.js";
|
|
28
31
|
import { CodexCliTimeoutError } from "../providers/codex-cli-runner.js";
|
|
29
|
-
import {
|
|
32
|
+
import { ClaudeCliTimeoutError } from "../providers/claude-cli-runner.js";
|
|
33
|
+
import { AUTO_CAPTURE_SOURCE_VALUES, CURSOR_CAPTURE_SOURCE, DEFAULT_AUTO_CAPTURE_SOURCES, } from "./sources.js";
|
|
30
34
|
/**
|
|
31
35
|
* Default upper bound for a Codex CLI session that auto-capture will hand
|
|
32
36
|
* to the triage subprocess. Real sessions in `~/.codex/sessions/` are tens
|
|
@@ -36,6 +40,16 @@ import { DEFAULT_AUTO_CAPTURE_SOURCES, } from "./sources.js";
|
|
|
36
40
|
* Operators raise this in `config.json` under `capture.codex_cli.max_session_bytes`.
|
|
37
41
|
*/
|
|
38
42
|
export const DEFAULT_CODEX_MAX_SESSION_BYTES = 8 * 1024 * 1024;
|
|
43
|
+
/**
|
|
44
|
+
* Default upper bound for a native Cursor session's PARSED transcript before the
|
|
45
|
+
* size guard quarantines it instead of handing it to the triage subprocess.
|
|
46
|
+
* Unlike Codex (a session file we can `stat`), Cursor content is reassembled
|
|
47
|
+
* from `state.vscdb` bubbles in memory, so the bound is measured on the merged
|
|
48
|
+
* user+assistant text. 4 MB of text is ~1M tokens — far past any healthy
|
|
49
|
+
* session and well past what triage should ever receive. Overridable per run
|
|
50
|
+
* via `AutoCaptureDeps.cursorMaxSessionBytes`.
|
|
51
|
+
*/
|
|
52
|
+
export const DEFAULT_CURSOR_MAX_SESSION_BYTES = 4 * 1024 * 1024;
|
|
39
53
|
/** Sentinel idempotency_key written into capture-state.json for oversized sessions. */
|
|
40
54
|
export const QUARANTINED_OVERSIZED_KEY = "quarantined:oversized";
|
|
41
55
|
/**
|
|
@@ -54,6 +68,16 @@ export function resolveCodexCaptureDeps(config) {
|
|
|
54
68
|
const override = config.capture?.codex_cli?.max_session_bytes;
|
|
55
69
|
return override !== undefined ? { codexMaxSessionBytes: override } : {};
|
|
56
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Resolve the native-Cursor size override into the partial deps every call site
|
|
73
|
+
* (daemon scheduled run, `rift capture`) spreads into `runAutoCapture()`. Mirrors
|
|
74
|
+
* `resolveCodexCaptureDeps`. Returns an empty object (no-op spread) when absent,
|
|
75
|
+
* so `runAutoCapture` falls through to `DEFAULT_CURSOR_MAX_SESSION_BYTES`.
|
|
76
|
+
*/
|
|
77
|
+
export function resolveCursorCaptureDeps(config) {
|
|
78
|
+
const override = config.capture?.cursor?.max_session_bytes;
|
|
79
|
+
return override !== undefined ? { cursorMaxSessionBytes: override } : {};
|
|
80
|
+
}
|
|
57
81
|
/**
|
|
58
82
|
* Build the full daemon-scheduled `runAutoCapture` deps from config + the
|
|
59
83
|
* job-queue-backed `saveFn`. The factory exists so the daemon path is
|
|
@@ -66,11 +90,62 @@ export function buildDaemonAutoCaptureDeps(opts) {
|
|
|
66
90
|
return {
|
|
67
91
|
dataDir: opts.config.data_paths.data_dir,
|
|
68
92
|
sources: opts.config.capture.sources,
|
|
93
|
+
// Config membership IS the Cursor opt-in: when the daemon's configured
|
|
94
|
+
// sources include cursor_composer, authorize discovery + persistence for
|
|
95
|
+
// this run without the env flags.
|
|
96
|
+
cursorConfigEnabled: opts.config.capture.sources.includes(CURSOR_CAPTURE_SOURCE),
|
|
69
97
|
...resolveCodexCaptureDeps(opts.config),
|
|
98
|
+
...resolveCursorCaptureDeps(opts.config),
|
|
70
99
|
saveFn: opts.saveFn,
|
|
71
100
|
};
|
|
72
101
|
}
|
|
73
102
|
const STATE_FILE = "capture-state.json";
|
|
103
|
+
/**
|
|
104
|
+
* Whether a source may be PERSISTED. Claude/Codex always persist. Native Cursor
|
|
105
|
+
* persists when EITHER:
|
|
106
|
+
* - it is scheduled via config (`cursorConfigEnabled`, i.e. `cursor_composer`
|
|
107
|
+
* is in `config.capture.sources`) — the deliberate, persisted opt-in that
|
|
108
|
+
* authorizes the daemon (and a config-driven manual run) to capture + save
|
|
109
|
+
* Cursor without any env flags; OR
|
|
110
|
+
* - BOTH env flags are set (`RIFT_CURSOR_CAPTURE=1` discovery master switch and
|
|
111
|
+
* `CURSOR_CAPTURE_PERSIST=1`) — the ad-hoc/dogfood path for an explicit
|
|
112
|
+
* `--source cursor_composer` run with no config opt-in.
|
|
113
|
+
*
|
|
114
|
+
* The env discovery flag is part of the env gate on purpose: persisting a source
|
|
115
|
+
* we are not even discovering is incoherent, and — critically — it would let an
|
|
116
|
+
* explicit `--source cursor_composer` run with persist on but discovery OFF write
|
|
117
|
+
* a bootstrap watermark for zero discovered sessions, falsely marking history as
|
|
118
|
+
* "already baselined." A later run with discovery on would then skip bootstrap
|
|
119
|
+
* and save real existing sessions as new — the exact historical backfill this
|
|
120
|
+
* gate exists to prevent. Requiring both env flags (or a config opt-in, which
|
|
121
|
+
* always also enables discovery — see `cursorOn`) closes that hole.
|
|
122
|
+
*
|
|
123
|
+
* With persistence off, Cursor sessions are discovered, fingerprinted, and
|
|
124
|
+
* triaged, but never saved, queued for review, or written to capture-state —
|
|
125
|
+
* "no writes" by construction, independent of `--dry-run`. The check reads the
|
|
126
|
+
* env at call time, so it is consistent within a single run.
|
|
127
|
+
*/
|
|
128
|
+
function writesEnabledForSource(source, cursorConfigEnabled) {
|
|
129
|
+
if (source === CURSOR_CAPTURE_SOURCE) {
|
|
130
|
+
return (cursorConfigEnabled ||
|
|
131
|
+
(cursorCaptureEnabled() && cursorCapturePersistEnabled()));
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Content fingerprint for a discovered session. Cursor sessions hash their
|
|
137
|
+
* precomputed ordered-bubble material; file-backed sources hash size + tail.
|
|
138
|
+
*/
|
|
139
|
+
function sessionFingerprint(session) {
|
|
140
|
+
if (session.cursor) {
|
|
141
|
+
return crypto
|
|
142
|
+
.createHash("sha256")
|
|
143
|
+
.update(session.cursor.fingerprintInput)
|
|
144
|
+
.digest("hex")
|
|
145
|
+
.slice(0, 16);
|
|
146
|
+
}
|
|
147
|
+
return contentFingerprint(session.path);
|
|
148
|
+
}
|
|
74
149
|
export function zeroCounts() {
|
|
75
150
|
return {
|
|
76
151
|
discovered: 0,
|
|
@@ -134,19 +209,20 @@ function shouldBootstrapSource(state, source, dryRun) {
|
|
|
134
209
|
return !hasPriorStateForSource(state, source);
|
|
135
210
|
}
|
|
136
211
|
/**
|
|
137
|
-
* Whether a preflight failure is transient (a
|
|
212
|
+
* Whether a preflight failure is transient (a worker timeout/termination) and
|
|
138
213
|
* therefore safe to continue past, vs. a hard auth/path failure that should
|
|
139
214
|
* abort the run. Timeouts are already retried once at the runner; if the probe
|
|
140
215
|
* still timed out, individual sessions may still succeed, so we let them try.
|
|
216
|
+
* Covers both worker runtimes (Codex and Claude).
|
|
141
217
|
*/
|
|
142
218
|
function isTransientPreflightError(err) {
|
|
143
|
-
return err instanceof CodexCliTimeoutError;
|
|
219
|
+
return (err instanceof CodexCliTimeoutError || err instanceof ClaudeCliTimeoutError);
|
|
144
220
|
}
|
|
145
221
|
async function ensureAutoCaptureTriageReady() {
|
|
146
|
-
// This is intentionally a tiny real
|
|
147
|
-
// version check: it catches expired
|
|
148
|
-
// a capture run starts mutating state.
|
|
149
|
-
await
|
|
222
|
+
// This is intentionally a tiny real CLI model call on the CONFIGURED worker
|
|
223
|
+
// (codex_cli or claude_code), not a cheap version check: it catches expired
|
|
224
|
+
// auth and runtime/model failures before a capture run starts mutating state.
|
|
225
|
+
await createConfiguredTriageProvider({ timeoutMs: 120_000 }).triage({
|
|
150
226
|
content: "User: Auto-capture preflight check.\n\nAssistant: Ready.",
|
|
151
227
|
source: "codex_cli",
|
|
152
228
|
});
|
|
@@ -181,7 +257,11 @@ function backupUnreadableState(statePath, error) {
|
|
|
181
257
|
}
|
|
182
258
|
function normalizeBootstrappedSources(sources) {
|
|
183
259
|
const bootstrappedSources = {};
|
|
184
|
-
|
|
260
|
+
// Iterate ALL auto-capture sources, not just the scheduled defaults: a
|
|
261
|
+
// flag-gated source that persists (Cursor, when CURSOR_CAPTURE_PERSIST is on)
|
|
262
|
+
// writes a bootstrap watermark, and that marker must survive a state reload —
|
|
263
|
+
// otherwise the source re-bootstraps every run and never captures anything.
|
|
264
|
+
for (const source of AUTO_CAPTURE_SOURCE_VALUES) {
|
|
185
265
|
const value = sources?.[source];
|
|
186
266
|
if (typeof value === "string") {
|
|
187
267
|
bootstrappedSources[source] = value;
|
|
@@ -236,9 +316,20 @@ function loadState(dataDir) {
|
|
|
236
316
|
: {}),
|
|
237
317
|
};
|
|
238
318
|
}
|
|
239
|
-
function discoverAllSessions(allowedSources, claudeDir, codexDir) {
|
|
319
|
+
function discoverAllSessions(allowedSources, claudeDir, codexDir, cursor) {
|
|
240
320
|
const sessions = [];
|
|
321
|
+
// Sources whose discovery actually RAN (and, for sources that catch their own
|
|
322
|
+
// errors, SUCCEEDED) this run. Only these may be bootstrapped: writing a
|
|
323
|
+
// first-enable watermark for a source we never read — because its flag was off
|
|
324
|
+
// or its store was unreadable — would falsely baseline real history and let a
|
|
325
|
+
// later run save it as new. Claude/Codex don't catch discovery errors, so a
|
|
326
|
+
// failure aborts the whole run before any watermark; they are live whenever
|
|
327
|
+
// attempted. Cursor catches (opt-in, best-effort), so it is live only when
|
|
328
|
+
// enabled AND its read succeeds.
|
|
329
|
+
const liveSources = new Set();
|
|
330
|
+
let cursorDiscovery;
|
|
241
331
|
if (!allowedSources || allowedSources.has("claude_code")) {
|
|
332
|
+
liveSources.add("claude_code");
|
|
242
333
|
sessions.push(...discoverClaudeCodeSessions(claudeDir).map((session) => ({
|
|
243
334
|
source: "claude_code",
|
|
244
335
|
sessionId: session.sessionId,
|
|
@@ -247,6 +338,7 @@ function discoverAllSessions(allowedSources, claudeDir, codexDir) {
|
|
|
247
338
|
})));
|
|
248
339
|
}
|
|
249
340
|
if (!allowedSources || allowedSources.has("codex_cli")) {
|
|
341
|
+
liveSources.add("codex_cli");
|
|
250
342
|
sessions.push(...discoverCodexSessions(codexDir).map((session) => ({
|
|
251
343
|
source: "codex_cli",
|
|
252
344
|
sessionId: session.sessionId,
|
|
@@ -254,18 +346,78 @@ function discoverAllSessions(allowedSources, claudeDir, codexDir) {
|
|
|
254
346
|
project: session.root,
|
|
255
347
|
})));
|
|
256
348
|
}
|
|
257
|
-
|
|
349
|
+
if (cursor.enabled &&
|
|
350
|
+
(!allowedSources || allowedSources.has(CURSOR_CAPTURE_SOURCE))) {
|
|
351
|
+
try {
|
|
352
|
+
const disco = discoverCursorCaptureSessions({
|
|
353
|
+
cursorDir: cursor.cursorDir,
|
|
354
|
+
...(cursor.reader ? { reader: cursor.reader } : {}),
|
|
355
|
+
});
|
|
356
|
+
for (const s of disco.sessions) {
|
|
357
|
+
sessions.push({
|
|
358
|
+
source: CURSOR_CAPTURE_SOURCE,
|
|
359
|
+
sessionId: s.composerId,
|
|
360
|
+
path: disco.dbPath,
|
|
361
|
+
project: s.project,
|
|
362
|
+
cursor: {
|
|
363
|
+
raw: disco.raw,
|
|
364
|
+
fingerprintInput: s.fingerprintInput,
|
|
365
|
+
projectRoot: s.projectRoot,
|
|
366
|
+
projectRootStatus: s.projectRootStatus,
|
|
367
|
+
stableKey: s.stableKey,
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
// Reached only on a clean read — now Cursor is safe to bootstrap. If the
|
|
372
|
+
// store throws below, we deliberately leave it OUT of liveSources so a
|
|
373
|
+
// transient read failure on the first-enable run cannot watermark (and
|
|
374
|
+
// thereby skip) real history that a later, readable run would discover.
|
|
375
|
+
liveSources.add(CURSOR_CAPTURE_SOURCE);
|
|
376
|
+
cursorDiscovery = { ok: true };
|
|
377
|
+
}
|
|
378
|
+
catch (err) {
|
|
379
|
+
// Cursor not installed / sqlite3 missing / store read failed: a benign
|
|
380
|
+
// skip for the RUN (the opt-in must never fail the whole capture), but NO
|
|
381
|
+
// LONGER a silent one. Record the outcome so status can warn that a
|
|
382
|
+
// scheduled Cursor opt-in physically can't read its store — instead of
|
|
383
|
+
// claiming "capture on" off the file-presence probe alone.
|
|
384
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
385
|
+
const code = err.code;
|
|
386
|
+
cursorDiscovery = {
|
|
387
|
+
ok: false,
|
|
388
|
+
error: message,
|
|
389
|
+
...(typeof code === "string" ? { error_code: code } : {}),
|
|
390
|
+
};
|
|
391
|
+
process.stderr.write(`Cursor capture: skipped (store unreadable): ${message}\n`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return {
|
|
395
|
+
sessions,
|
|
396
|
+
liveSources,
|
|
397
|
+
...(cursorDiscovery ? { cursorDiscovery } : {}),
|
|
398
|
+
};
|
|
258
399
|
}
|
|
259
|
-
function applyBootstrap(ctx, sessions, enabledSources, dryRun) {
|
|
400
|
+
function applyBootstrap(ctx, sessions, enabledSources, liveSources, cursorConfigEnabled, dryRun) {
|
|
260
401
|
const bootstrappedSessionKeys = new Set();
|
|
261
402
|
const nowIso = new Date().toISOString();
|
|
262
403
|
for (const source of enabledSources) {
|
|
404
|
+
// Only bootstrap a source whose discovery actually ran (and succeeded) this
|
|
405
|
+
// run — see `liveSources` in discoverAllSessions. A source that was listed
|
|
406
|
+
// but not read (flag off, or store unreadable) must NOT get a watermark, or
|
|
407
|
+
// it would falsely baseline history we never looked at.
|
|
408
|
+
if (!liveSources.has(source))
|
|
409
|
+
continue;
|
|
410
|
+
// Skip sources that don't persist (Cursor with neither the config opt-in nor
|
|
411
|
+
// both env flags): a watermark is a write we must not make for a triage-only
|
|
412
|
+
// source.
|
|
413
|
+
if (!writesEnabledForSource(source, cursorConfigEnabled))
|
|
414
|
+
continue;
|
|
263
415
|
if (!shouldBootstrapSource(ctx.state, source, dryRun)) {
|
|
264
416
|
continue;
|
|
265
417
|
}
|
|
266
418
|
const sourceSessions = sessions.filter((session) => session.source === source);
|
|
267
419
|
for (const session of sourceSessions) {
|
|
268
|
-
const fingerprint =
|
|
420
|
+
const fingerprint = sessionFingerprint(session);
|
|
269
421
|
ctx.state.processed[stateKey(session.source, session.sessionId)] = {
|
|
270
422
|
fingerprint,
|
|
271
423
|
idempotency_key: "",
|
|
@@ -290,6 +442,8 @@ function deriveTopLevelTotals(report) {
|
|
|
290
442
|
review: 0,
|
|
291
443
|
quarantined: 0,
|
|
292
444
|
errors: 0,
|
|
445
|
+
deferred: 0,
|
|
446
|
+
bootstrapped: 0,
|
|
293
447
|
};
|
|
294
448
|
for (const counts of Object.values(report.counts_by_source)) {
|
|
295
449
|
if (!counts)
|
|
@@ -301,6 +455,8 @@ function deriveTopLevelTotals(report) {
|
|
|
301
455
|
totals.review += counts.review;
|
|
302
456
|
totals.quarantined += counts.quarantined;
|
|
303
457
|
totals.errors += counts.errors;
|
|
458
|
+
totals.deferred += counts.deferred;
|
|
459
|
+
totals.bootstrapped += counts.bootstrapped;
|
|
304
460
|
}
|
|
305
461
|
report.total_discovered = totals.total_discovered;
|
|
306
462
|
report.new_conversations = totals.new_conversations;
|
|
@@ -309,6 +465,8 @@ function deriveTopLevelTotals(report) {
|
|
|
309
465
|
report.review = totals.review;
|
|
310
466
|
report.quarantined = totals.quarantined;
|
|
311
467
|
report.errors = totals.errors;
|
|
468
|
+
report.deferred = totals.deferred;
|
|
469
|
+
report.bootstrapped = totals.bootstrapped;
|
|
312
470
|
return report;
|
|
313
471
|
}
|
|
314
472
|
function applySessionOutcome(ctx, outcome) {
|
|
@@ -320,6 +478,7 @@ function applySessionOutcome(ctx, outcome) {
|
|
|
320
478
|
counts.discarded += outcome.counts.discarded ?? 0;
|
|
321
479
|
counts.quarantined += outcome.counts.quarantined ?? 0;
|
|
322
480
|
counts.errors += outcome.counts.errors ?? 0;
|
|
481
|
+
counts.deferred += outcome.counts.deferred ?? 0;
|
|
323
482
|
ctx.report.results.push(...outcome.results);
|
|
324
483
|
ctx.report.error_details.push(...outcome.errorDetails);
|
|
325
484
|
ctx.stateChanged ||= outcome.stateChanged;
|
|
@@ -389,19 +548,22 @@ async function saveState(dataDir, state, initialProcessed) {
|
|
|
389
548
|
async function writeOversizedQuarantineRecord(opts) {
|
|
390
549
|
const dir = path.join(opts.dataDir, "quarantine");
|
|
391
550
|
fs.mkdirSync(dir, { recursive: true });
|
|
392
|
-
const filename =
|
|
551
|
+
const filename = `${opts.source}_${opts.sessionId}_${opts.fingerprint}.json`;
|
|
393
552
|
const target = path.join(dir, filename);
|
|
394
553
|
if (fs.existsSync(target))
|
|
395
554
|
return;
|
|
555
|
+
const remediation = opts.source === CURSOR_CAPTURE_SOURCE
|
|
556
|
+
? "Raise AutoCaptureDeps.cursorMaxSessionBytes for the run, or split the Cursor session."
|
|
557
|
+
: "Raise capture.codex_cli.max_session_bytes (and restart the daemon), or use the future opt-in chunked-summarize backfill.";
|
|
396
558
|
const record = {
|
|
397
|
-
source:
|
|
559
|
+
source: opts.source,
|
|
398
560
|
session_id: opts.sessionId,
|
|
399
561
|
session_path: opts.sessionPath,
|
|
400
562
|
size_bytes: opts.sizeBytes,
|
|
401
563
|
threshold_bytes: opts.thresholdBytes,
|
|
402
564
|
fingerprint: opts.fingerprint,
|
|
403
565
|
reason: "oversized-session",
|
|
404
|
-
remediation
|
|
566
|
+
remediation,
|
|
405
567
|
quarantined_at: new Date().toISOString(),
|
|
406
568
|
};
|
|
407
569
|
await atomicWrite(target, JSON.stringify(record, null, 2));
|
|
@@ -436,7 +598,27 @@ async function processSession(session, state, deps) {
|
|
|
436
598
|
stateChanged: false,
|
|
437
599
|
});
|
|
438
600
|
try {
|
|
439
|
-
const fingerprint =
|
|
601
|
+
const fingerprint = sessionFingerprint(session);
|
|
602
|
+
// Cursor is triage-only this slice: no /save, no review-queue, no
|
|
603
|
+
// capture-state write — regardless of --dry-run. See writesEnabledForSource.
|
|
604
|
+
const writesAllowed = !deps.dryRun &&
|
|
605
|
+
writesEnabledForSource(session.source, Boolean(deps.cursorConfigEnabled));
|
|
606
|
+
const cursorResultFields = session.cursor
|
|
607
|
+
? {
|
|
608
|
+
projectRootStatus: session.cursor.projectRootStatus,
|
|
609
|
+
stableKey: session.cursor.stableKey,
|
|
610
|
+
}
|
|
611
|
+
: {};
|
|
612
|
+
// Cursor titles are private (user's session name / first prompt), and even
|
|
613
|
+
// the triage-only dry-run prints result titles to the terminal and JSON.
|
|
614
|
+
// Redact to a non-identifying label by default; reveal only behind an
|
|
615
|
+
// explicit opt-in. Claude/Codex titles are untouched.
|
|
616
|
+
const displayTitle = (parsedTitle) => {
|
|
617
|
+
if (session.cursor && !cursorTitlesRevealed()) {
|
|
618
|
+
return redactedCursorTitle(session.sessionId);
|
|
619
|
+
}
|
|
620
|
+
return parsedTitle ?? "(empty)";
|
|
621
|
+
};
|
|
440
622
|
const prev = getPreviousEntry(state, session.source, session.sessionId);
|
|
441
623
|
const prevFingerprint = typeof prev === "string" ? prev : prev?.fingerprint;
|
|
442
624
|
const prevKey = typeof prev === "object" ? prev?.idempotency_key : undefined;
|
|
@@ -454,6 +636,7 @@ async function processSession(session, state, deps) {
|
|
|
454
636
|
outcome.counts.quarantined = 1;
|
|
455
637
|
if (!deps.dryRun) {
|
|
456
638
|
await writeOversizedQuarantineRecord({
|
|
639
|
+
source: session.source,
|
|
457
640
|
dataDir: deps.dataDir,
|
|
458
641
|
sessionId: session.sessionId,
|
|
459
642
|
sessionPath: session.path,
|
|
@@ -473,12 +656,18 @@ async function processSession(session, state, deps) {
|
|
|
473
656
|
const isUpdate = prevFingerprint !== undefined;
|
|
474
657
|
const outcome = empty();
|
|
475
658
|
outcome.counts.new = 1;
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
659
|
+
let parsed;
|
|
660
|
+
if (session.cursor) {
|
|
661
|
+
parsed = parseCursorComposerSession(session.cursor.raw, session.sessionId);
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
const data = fs.readFileSync(session.path, "utf-8");
|
|
665
|
+
parsed = session.source === "claude_code"
|
|
666
|
+
? parseClaudeCodeSession(session.sessionId, data)
|
|
667
|
+
: parseCodexSession(session.sessionId, data);
|
|
668
|
+
}
|
|
480
669
|
if (!parsed || parsed.content.length < 100) {
|
|
481
|
-
if (
|
|
670
|
+
if (writesAllowed) {
|
|
482
671
|
state.processed[stateKey(session.source, session.sessionId)] = {
|
|
483
672
|
fingerprint,
|
|
484
673
|
idempotency_key: "",
|
|
@@ -489,36 +678,97 @@ async function processSession(session, state, deps) {
|
|
|
489
678
|
outcome.results.push({
|
|
490
679
|
source: session.source,
|
|
491
680
|
sessionId: session.sessionId,
|
|
492
|
-
title: parsed?.title
|
|
681
|
+
title: displayTitle(parsed?.title),
|
|
493
682
|
decision: "discard",
|
|
494
683
|
confidence: 1.0,
|
|
495
684
|
model: "n/a",
|
|
496
685
|
provider: "auto-capture",
|
|
497
686
|
summary: "Too short or empty",
|
|
687
|
+
...cursorResultFields,
|
|
498
688
|
});
|
|
499
689
|
return outcome;
|
|
500
690
|
}
|
|
691
|
+
// Cursor size guard. Cursor has no file to stat (its content lives in a
|
|
692
|
+
// shared `state.vscdb`), so the guard runs on the PARSED transcript — the
|
|
693
|
+
// exact text triage would feed the subprocess — and fires BEFORE any triage
|
|
694
|
+
// or save. Mirrors the codex oversized path: quarantine, never triage. The
|
|
695
|
+
// quarantine record + capture-state sentinel are writes, so they only land
|
|
696
|
+
// when writes are allowed (persist on, non-dry); otherwise it is a pure skip.
|
|
697
|
+
//
|
|
698
|
+
// TERMINAL by design (decided pre-scheduling): unlike Codex, an oversized
|
|
699
|
+
// Cursor session has NO auto-recovery path — `recover-quarantine` globs only
|
|
700
|
+
// `codex_cli_` records, so it never touches the `cursor_composer_` record we
|
|
701
|
+
// write here, and `countParkedOversizedSessions` never raises a "run repair"
|
|
702
|
+
// advisory for it. The record is operator-visible metadata only; the sole
|
|
703
|
+
// remediation is to raise the byte limit or split the session, after which
|
|
704
|
+
// the grown/changed fingerprint re-discovers it on a later run. Cursor
|
|
705
|
+
// chunked-summarize backfill is deferred to the scheduling slice.
|
|
706
|
+
if (session.cursor) {
|
|
707
|
+
const sizeBytes = Buffer.byteLength(parsed.content, "utf8");
|
|
708
|
+
const maxBytes = deps.cursorMaxSessionBytes ?? DEFAULT_CURSOR_MAX_SESSION_BYTES;
|
|
709
|
+
if (sizeBytes > maxBytes) {
|
|
710
|
+
const oversized = empty();
|
|
711
|
+
oversized.counts.quarantined = 1;
|
|
712
|
+
if (writesAllowed) {
|
|
713
|
+
await writeOversizedQuarantineRecord({
|
|
714
|
+
source: session.source,
|
|
715
|
+
dataDir: deps.dataDir,
|
|
716
|
+
sessionId: session.sessionId,
|
|
717
|
+
sessionPath: session.path,
|
|
718
|
+
sizeBytes,
|
|
719
|
+
thresholdBytes: maxBytes,
|
|
720
|
+
fingerprint,
|
|
721
|
+
});
|
|
722
|
+
state.processed[stateKey(session.source, session.sessionId)] = {
|
|
723
|
+
fingerprint,
|
|
724
|
+
idempotency_key: QUARANTINED_OVERSIZED_KEY,
|
|
725
|
+
};
|
|
726
|
+
oversized.stateChanged = true;
|
|
727
|
+
}
|
|
728
|
+
return oversized;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
501
731
|
const triage = await triageConversation(parsed.content);
|
|
502
732
|
const result = {
|
|
503
733
|
source: session.source,
|
|
504
734
|
sessionId: session.sessionId,
|
|
505
|
-
title: parsed.title,
|
|
735
|
+
title: displayTitle(parsed.title),
|
|
506
736
|
decision: triage.decision,
|
|
507
737
|
confidence: triage.confidence,
|
|
508
738
|
model: triage.model,
|
|
509
739
|
provider: triage.provider,
|
|
510
740
|
summary: triage.summary,
|
|
741
|
+
...cursorResultFields,
|
|
511
742
|
};
|
|
512
743
|
outcome.results.push(result);
|
|
513
|
-
if (
|
|
744
|
+
if (!writesAllowed) {
|
|
745
|
+
// Writes are off for one of two reasons, and they are NOT equivalent:
|
|
746
|
+
// - dry-run of a persistable source: a faithful preview of what a real
|
|
747
|
+
// run WOULD persist, so save/review count as saved/review.
|
|
748
|
+
// - a source whose persistence is gated off this slice (Cursor): a real
|
|
749
|
+
// run would not persist it either, so reporting "saved" is a lie. Even
|
|
750
|
+
// a non-dry `rift capture` reaches the health ledger (capture.ts), and
|
|
751
|
+
// `rift status` would then sum those as real saved sessions. Bucket
|
|
752
|
+
// these as `deferred` so neither the ledger nor status ever inflates.
|
|
753
|
+
// Bucketing keys on the SOURCE (writesEnabledForSource), not --dry-run, so
|
|
754
|
+
// a gated source reads `deferred` consistently in dry and non-dry runs.
|
|
755
|
+
const persistable = writesEnabledForSource(session.source, Boolean(deps.cursorConfigEnabled));
|
|
514
756
|
switch (triage.decision) {
|
|
515
757
|
case "save":
|
|
516
|
-
|
|
758
|
+
if (persistable)
|
|
759
|
+
outcome.counts.saved = 1;
|
|
760
|
+
else
|
|
761
|
+
outcome.counts.deferred = 1;
|
|
517
762
|
break;
|
|
518
763
|
case "review":
|
|
519
|
-
|
|
764
|
+
if (persistable)
|
|
765
|
+
outcome.counts.review = 1;
|
|
766
|
+
else
|
|
767
|
+
outcome.counts.deferred = 1;
|
|
520
768
|
break;
|
|
521
769
|
case "discard":
|
|
770
|
+
// A discard persists nothing regardless of the gate, so it is honest
|
|
771
|
+
// in either case and never inflates a "saved" count.
|
|
522
772
|
outcome.counts.discarded = 1;
|
|
523
773
|
break;
|
|
524
774
|
}
|
|
@@ -592,6 +842,7 @@ async function processSession(session, state, deps) {
|
|
|
592
842
|
export async function runAutoCapture(deps) {
|
|
593
843
|
const claudeDir = deps.claudeDir ?? path.join(os.homedir(), ".claude");
|
|
594
844
|
const codexDir = deps.codexDir ?? path.join(os.homedir(), ".codex");
|
|
845
|
+
const cursorDir = deps.cursorDir ?? defaultCursorDir();
|
|
595
846
|
const state = loadState(deps.dataDir);
|
|
596
847
|
// Snapshot the ledger exactly as loaded so the end-of-run merge can overlay
|
|
597
848
|
// ONLY the keys this run actually changed (see saveState). Capturing it here
|
|
@@ -600,8 +851,25 @@ export async function runAutoCapture(deps) {
|
|
|
600
851
|
// run never touched.
|
|
601
852
|
const initialProcessed = { ...state.processed };
|
|
602
853
|
const allowedSources = deps.sources ? new Set(deps.sources) : null;
|
|
603
|
-
|
|
604
|
-
|
|
854
|
+
// Cursor is enabled for this run via EITHER the config opt-in
|
|
855
|
+
// (`cursorConfigEnabled` — cursor_composer in config.capture.sources, the
|
|
856
|
+
// scheduled path) OR the `RIFT_CURSOR_CAPTURE` env flag (the ad-hoc/dogfood
|
|
857
|
+
// path). Config enablement also authorizes persistence (see
|
|
858
|
+
// writesEnabledForSource); the env path still needs CURSOR_CAPTURE_PERSIST.
|
|
859
|
+
const cursorConfigEnabled = Boolean(deps.cursorConfigEnabled);
|
|
860
|
+
const cursorOn = cursorConfigEnabled || cursorCaptureEnabled();
|
|
861
|
+
// When no explicit source filter was given and Cursor is on (config or env),
|
|
862
|
+
// it joins the default run so its sessions show up alongside claude/codex.
|
|
863
|
+
const enabledSources = deps.sources
|
|
864
|
+
? deps.sources
|
|
865
|
+
: cursorOn
|
|
866
|
+
? [...DEFAULT_AUTO_CAPTURE_SOURCES, CURSOR_CAPTURE_SOURCE]
|
|
867
|
+
: DEFAULT_AUTO_CAPTURE_SOURCES;
|
|
868
|
+
const { sessions, liveSources, cursorDiscovery } = discoverAllSessions(allowedSources, claudeDir, codexDir, {
|
|
869
|
+
enabled: cursorOn,
|
|
870
|
+
cursorDir,
|
|
871
|
+
...(deps.cursorReader ? { reader: deps.cursorReader } : {}),
|
|
872
|
+
});
|
|
605
873
|
const report = {
|
|
606
874
|
total_discovered: 0,
|
|
607
875
|
new_conversations: 0,
|
|
@@ -610,10 +878,13 @@ export async function runAutoCapture(deps) {
|
|
|
610
878
|
review: 0,
|
|
611
879
|
quarantined: 0,
|
|
612
880
|
errors: 0,
|
|
881
|
+
deferred: 0,
|
|
882
|
+
bootstrapped: 0,
|
|
613
883
|
preflight_ok: true,
|
|
614
884
|
counts_by_source: {},
|
|
615
885
|
results: [],
|
|
616
886
|
error_details: [],
|
|
887
|
+
...(cursorDiscovery ? { cursor_discovery: cursorDiscovery } : {}),
|
|
617
888
|
};
|
|
618
889
|
for (const source of enabledSources) {
|
|
619
890
|
ensureCounts(report, source);
|
|
@@ -626,18 +897,26 @@ export async function runAutoCapture(deps) {
|
|
|
626
897
|
report,
|
|
627
898
|
stateChanged: false,
|
|
628
899
|
};
|
|
629
|
-
const bootstrappedSessionKeys = applyBootstrap(ctx, sessions, enabledSources, Boolean(deps.dryRun));
|
|
900
|
+
const bootstrappedSessionKeys = applyBootstrap(ctx, sessions, enabledSources, liveSources, cursorConfigEnabled, Boolean(deps.dryRun));
|
|
630
901
|
const sessionsToProcess = sessions.filter((session) => !bootstrappedSessionKeys.has(stateKey(session.source, session.sessionId)));
|
|
631
902
|
if (enabledSources.length > 0) {
|
|
903
|
+
// Stamp the worker that this run's preflight will use (and did use, on
|
|
904
|
+
// failure) BEFORE running it, so the recorded outcome is attributable to the
|
|
905
|
+
// worker that actually ran — never re-inferred later from drifting config or
|
|
906
|
+
// ambiguous error text. `deps.preflightTriage` (test seam) stands in for the
|
|
907
|
+
// configured provider, so the configured name is still the right label.
|
|
908
|
+
report.preflight_worker =
|
|
909
|
+
getConfiguredTriageProviderName() === "claude_code" ? "claude" : "codex";
|
|
632
910
|
try {
|
|
633
911
|
await (deps.preflightTriage ?? ensureAutoCaptureTriageReady)();
|
|
634
912
|
}
|
|
635
913
|
catch (err) {
|
|
636
914
|
const error = err instanceof Error ? err.message : String(err);
|
|
637
|
-
// Preflight is a
|
|
638
|
-
// own field + the preflight_ok health flag,
|
|
639
|
-
// conversation errors — so the "N failed" count
|
|
640
|
-
// sessions. (autoCaptureRunFailed still flags the run
|
|
915
|
+
// Preflight is a triage-worker health probe (Codex or Claude), NOT a
|
|
916
|
+
// conversation. Record it on its own field + the preflight_ok health flag,
|
|
917
|
+
// never as per-source conversation errors — so the "N failed" count
|
|
918
|
+
// reflects only real sessions. (autoCaptureRunFailed still flags the run
|
|
919
|
+
// via preflight_ok.)
|
|
641
920
|
report.preflight_ok = false;
|
|
642
921
|
report.preflight_error = error;
|
|
643
922
|
// A timeout/termination is transient (cold start, brief contention) and
|