@cortexkit/opencode-magic-context 0.46.0 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auto-search-worker.js +115 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/live-run-config.d.ts.map +1 -1
- package/dist/config/migrate-config-location.d.ts.map +1 -1
- package/dist/config/schema/magic-context.d.ts +15 -7
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/{conflict-warning-hook-7fj2b9jw.js → conflict-warning-hook-z44ynfz7.js} +5 -4
- package/dist/{context-8dkpszy5.js → context-wq4ffwyb.js} +164 -37
- package/dist/{derive-budgets-7nj7r1rv.js → derive-budgets-ke12jnnh.js} +1 -1
- package/dist/embedding-worker.js +2 -2
- package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-gates.d.ts.map +1 -1
- package/dist/features/magic-context/overflow-detection.d.ts +2 -0
- package/dist/features/magic-context/overflow-detection.d.ts.map +1 -1
- package/dist/features/magic-context/project-embedding-registry.d.ts +7 -0
- package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
- package/dist/features/magic-context/search.d.ts +2 -0
- package/dist/features/magic-context/search.d.ts.map +1 -1
- package/dist/features/magic-context/session-project-storage.d.ts +8 -2
- package/dist/features/magic-context/session-project-storage.d.ts.map +1 -1
- package/dist/features/magic-context/storage-historian-runs.d.ts +2 -5
- package/dist/features/magic-context/storage-historian-runs.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-persisted.d.ts +12 -0
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
- package/dist/features/magic-context/storage-tags.d.ts +7 -2
- package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
- package/dist/features/magic-context/storage.d.ts +1 -0
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/features/magic-context/transform-decision-log.d.ts +3 -1
- package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
- package/dist/hooks/magic-context/active-anthropic-turn.d.ts +8 -0
- package/dist/hooks/magic-context/active-anthropic-turn.d.ts.map +1 -0
- package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-blocking.fixture.d.ts +2 -0
- package/dist/hooks/magic-context/auto-search-blocking.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-deadline.d.ts +7 -0
- package/dist/hooks/magic-context/auto-search-deadline.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-host-review.fixture.d.ts +3 -0
- package/dist/hooks/magic-context/auto-search-host-review.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-lock-review.fixture.d.ts +2 -0
- package/dist/hooks/magic-context/auto-search-lock-review.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-long-lock-review.fixture.d.ts +2 -0
- package/dist/hooks/magic-context/auto-search-long-lock-review.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-omp-host-review.fixture.d.ts +4 -0
- package/dist/hooks/magic-context/auto-search-omp-host-review.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-omp-loader.fixture.d.ts +3 -0
- package/dist/hooks/magic-context/auto-search-omp-loader.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-runner.d.ts +1 -1
- package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-snapshot.fixture.d.ts +4 -0
- package/dist/hooks/magic-context/auto-search-snapshot.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-worker-client.d.ts +69 -0
- package/dist/hooks/magic-context/auto-search-worker-client.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-worker.d.ts +2 -0
- package/dist/hooks/magic-context/auto-search-worker.d.ts.map +1 -0
- package/dist/hooks/magic-context/caveman-cleanup.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-types.d.ts +3 -5
- package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner.d.ts +2 -1
- package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-trigger.d.ts +3 -1
- package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-handler.d.ts +1 -1
- package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
- package/dist/hooks/magic-context/execute-status.d.ts.map +1 -1
- package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +2 -0
- package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -1
- package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
- package/dist/hooks/magic-context/historian-drain-gate.d.ts +11 -0
- package/dist/hooks/magic-context/historian-drain-gate.d.ts.map +1 -0
- package/dist/hooks/magic-context/hook.d.ts +2 -1
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/latest-assistant-turn.d.ts +25 -0
- package/dist/hooks/magic-context/latest-assistant-turn.d.ts.map +1 -0
- package/dist/hooks/magic-context/latest-thinking-recovery.d.ts +61 -0
- package/dist/hooks/magic-context/latest-thinking-recovery.d.ts.map +1 -0
- package/dist/hooks/magic-context/module-wire.d.ts.map +1 -1
- package/dist/hooks/magic-context/opencode-proven-floor.d.ts +6 -0
- package/dist/hooks/magic-context/opencode-proven-floor.d.ts.map +1 -0
- package/dist/hooks/magic-context/pass-outcome.d.ts.map +1 -1
- package/dist/hooks/magic-context/protected-tail-boundary.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
- package/dist/hooks/magic-context/reasoning-budget-status.d.ts +15 -0
- package/dist/hooks/magic-context/reasoning-budget-status.d.ts.map +1 -0
- package/dist/hooks/magic-context/reasoning-budget.d.ts +40 -0
- package/dist/hooks/magic-context/reasoning-budget.d.ts.map +1 -0
- package/dist/hooks/magic-context/reasoning-removal.d.ts +6 -6
- package/dist/hooks/magic-context/reasoning-removal.d.ts.map +1 -1
- package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/sentinel.d.ts +4 -0
- package/dist/hooks/magic-context/sentinel.d.ts.map +1 -1
- package/dist/hooks/magic-context/strip-content.d.ts +7 -3
- package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
- package/dist/hooks/magic-context/tag-messages.d.ts +13 -0
- package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
- package/dist/hooks/magic-context/tool-drop-target.d.ts +5 -0
- package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts +3 -28
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +28 -6
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +3 -1
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/{index-0d3g3461.js → index-0c82743p.js} +2 -1
- package/dist/{index-e2dtv0ra.js → index-5a2r0p9f.js} +3 -3
- package/dist/{index-p821bjdb.js → index-6d9e1m08.js} +4726 -11050
- package/dist/{index-majxda46.js → index-6wkcswft.js} +1 -1
- package/dist/index-92bp57hg.js +2063 -0
- package/dist/{index-sfnj6h8h.js → index-9pk9exrb.js} +3 -3
- package/dist/{index-7qw742ev.js → index-adwfkpex.js} +5 -24
- package/dist/{index-3h0ybwah.js → index-bb36vgr3.js} +80 -12
- package/dist/{index-k4404na5.js → index-cwf6s3ay.js} +1072 -2422
- package/dist/index-fnn5pjtt.js +6554 -0
- package/dist/{index-j7fvnjye.js → index-gg8j07zz.js} +1 -1
- package/dist/{index-yasgb8ej.js → index-m5fe6jch.js} +9 -3
- package/dist/{v2/server-g0fjrkta.js → index-rrbbspwh.js} +42 -27
- package/dist/index-rrrsepyw.js +3464 -0
- package/dist/{index-qr6r7akd.js → index-w2t4ah5j.js} +950 -502
- package/dist/{v2/server-fg34g6a5.js → index-wx8a6bw3.js} +48 -8
- package/dist/{index-r5hmbx3t.js → index-x3kwrmqk.js} +146 -3571
- package/dist/index-yr3zj7e7.js +23 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +624 -65
- package/dist/{message-time-backfill-68gj6t3e.js → message-time-backfill-r5epkzj3.js} +1 -1
- package/dist/migration-worker.js +7 -5
- package/dist/plugin/dream-timer.d.ts.map +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts +2 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/host-stall-profiler-report.d.ts +67 -0
- package/dist/plugin/host-stall-profiler-report.d.ts.map +1 -0
- package/dist/plugin/host-stall-profiler.d.ts +96 -0
- package/dist/plugin/host-stall-profiler.d.ts.map +1 -0
- package/dist/plugin/messages-transform.d.ts +21 -6
- package/dist/plugin/messages-transform.d.ts.map +1 -1
- package/dist/{read-session-chunk-egz7r68d.js → read-session-chunk-4jey7hpt.js} +4 -4
- package/dist/{recomp-orchestrator-ejq7ex5d.js → recomp-orchestrator-rervg6q4.js} +13 -10
- package/dist/{send-session-notification-dyw42dtr.js → send-session-notification-0m68ms3n.js} +4 -3
- package/dist/shared/auto-search-lifecycle.d.ts +7 -0
- package/dist/shared/auto-search-lifecycle.d.ts.map +1 -0
- package/dist/shared/historian-tool-expansions.d.ts +1 -1
- package/dist/shared/historian-tool-expansions.d.ts.map +1 -1
- package/dist/shared/historian-tool-template.d.ts +2 -2
- package/dist/shared/historian-tool-template.d.ts.map +1 -1
- package/dist/shared/sqlite.d.ts +16 -1
- package/dist/shared/sqlite.d.ts.map +1 -1
- package/dist/shared/status-view.d.ts.map +1 -1
- package/dist/shared/window-geometry.d.ts +4 -0
- package/dist/shared/window-geometry.d.ts.map +1 -1
- package/dist/tools/ctx-expand/render.d.ts.map +1 -1
- package/dist/{transform-postprocess-phase-tz3pat2p.js → transform-postprocess-phase-z5hzpsxv.js} +13 -10
- package/dist/v2/auto-search-worker.js +115 -0
- package/dist/v2/{context-8vgtb6sz.js → context-14v1hr8b.js} +4918 -5760
- package/dist/v2/{derive-budgets-kdgp4s3r.js → derive-budgets-nc9avxz8.js} +2 -2
- package/dist/v2/embedding-worker.js +3 -3
- package/dist/v2/hidden-child-native.d.ts +7 -0
- package/dist/v2/hidden-child-native.d.ts.map +1 -1
- package/dist/v2/hidden-child-record.d.ts +8 -0
- package/dist/v2/hidden-child-record.d.ts.map +1 -1
- package/dist/v2/hidden-completion.d.ts.map +1 -1
- package/dist/v2/hooks/context.d.ts.map +1 -1
- package/dist/v2/hooks/dream-trigger.d.ts +15 -1
- package/dist/v2/hooks/dream-trigger.d.ts.map +1 -1
- package/dist/v2/hooks/hidden-child.d.ts +54 -3
- package/dist/v2/hooks/hidden-child.d.ts.map +1 -1
- package/dist/v2/hooks/types.d.ts +12 -1
- package/dist/v2/hooks/types.d.ts.map +1 -1
- package/dist/v2/{message-time-backfill-r6qkem1x.js → message-time-backfill-1f9tzvcs.js} +1 -1
- package/dist/v2/migration-worker.js +7 -5
- package/dist/v2/{read-session-chunk-8vjp6581.js → read-session-chunk-gyyv7w2r.js} +4 -4
- package/dist/v2/{recomp-orchestrator-5xj0yzyk.js → recomp-orchestrator-42vjba7p.js} +17 -14
- package/dist/v2/{send-session-notification-qs0j78v6.js → send-session-notification-c0wtscea.js} +5 -4
- package/dist/v2/server-3trjk1hx.js +6360 -0
- package/dist/v2/{server-xtp687da.js → server-50hvkg3v.js} +9 -3
- package/dist/v2/{server-y8maxeft.js → server-cpepxy2q.js} +2 -1
- package/dist/v2/server-d0sraw7m.js +3403 -0
- package/dist/v2/server-e0c1b7a7.js +2063 -0
- package/dist/v2/{server-mcfawjgc.js → server-efhwzx05.js} +5 -24
- package/dist/v2/{server-3n4q9xme.js → server-fcdehk1s.js} +1 -1
- package/dist/{index-q4yp4b52.js → v2/server-fq7kq32k.js} +42 -27
- package/dist/v2/{server-vnh7r35f.js → server-nf82q6pt.js} +2459 -8603
- package/dist/v2/{server-frbm99q5.js → server-njt3np1k.js} +99 -3465
- package/dist/v2/{server-qp5hx1ad.js → server-nm9n3aqq.js} +107 -12
- package/dist/v2/{server-p2en78c8.js → server-rz514f14.js} +7 -2
- package/dist/v2/{server-wvjvfhck.js → server-vd4e7174.js} +1 -1
- package/dist/{index-fg34g6a5.js → v2/server-wx8a6bw3.js} +48 -8
- package/dist/v2/{server-4663ejam.js → server-y10vbq6y.js} +1 -1
- package/dist/v2/server-yr3zj7e7.js +23 -0
- package/dist/v2/server.js +5 -5
- package/dist/v2/{wrapup-orchestrator-r5t1pyj8.js → wrapup-orchestrator-nbz854vc.js} +16 -13
- package/dist/wrapup-orchestrator-b2dztrar.js +29 -0
- package/package.json +6 -5
- package/src/config/index.ts +6 -0
- package/src/config/live-run-config.test.ts +37 -1
- package/src/config/live-run-config.ts +28 -0
- package/src/config/migrate-config-location.ts +10 -3
- package/src/config/schema/magic-context.test.ts +4 -3
- package/src/config/schema/magic-context.ts +23 -6
- package/src/shared/auto-search-lifecycle.ts +27 -0
- package/src/shared/historian-tool-defaults.json +6 -6
- package/src/shared/historian-tool-expansions.ts +65 -8
- package/src/shared/historian-tool-preview-defaults.json +13 -0
- package/src/shared/historian-tool-template.ts +48 -14
- package/src/shared/sqlite-640-review-r2.test.ts +136 -0
- package/src/shared/sqlite-640-review.test.ts +183 -0
- package/src/shared/sqlite-acquisition-routing.test.ts +18 -0
- package/src/shared/sqlite.ts +84 -14
- package/src/shared/status-view.test.ts +6 -1
- package/src/shared/status-view.ts +15 -1
- package/src/shared/tag-transcript.ts +6 -0
- package/src/shared/window-geometry.ts +10 -0
- package/dist/wrapup-orchestrator-kw3x3myw.js +0 -26
- /package/dist/{index-en1k8h8m.js → index-5prbx026.js} +0 -0
- /package/dist/v2/{index-en1k8h8m.js → index-5prbx026.js} +0 -0
|
@@ -6,17 +6,14 @@ import {
|
|
|
6
6
|
sessionLog
|
|
7
7
|
} from "./index-hybnrwq0.js";
|
|
8
8
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import {
|
|
9
|
+
getSqliteDatabasePath,
|
|
10
|
+
assertTransformWrite,
|
|
12
11
|
isTransientSqliteError
|
|
13
|
-
} from "./index-
|
|
12
|
+
} from "./index-wx8a6bw3.js";
|
|
14
13
|
import {
|
|
15
14
|
BoundedSessionMap,
|
|
16
15
|
renderUserFacingFailure,
|
|
17
|
-
loadCompartmentChunkEmbeddingsForSearch,
|
|
18
16
|
getMaxM0MutationId,
|
|
19
|
-
getIndexedMessageCorpusSize,
|
|
20
17
|
escalationBands,
|
|
21
18
|
MERGED_REASONING_PARTS_PREFIX,
|
|
22
19
|
readFrozenMergedReasoningParts,
|
|
@@ -62,42 +59,13 @@ import {
|
|
|
62
59
|
setPendingCompactionMarkerState,
|
|
63
60
|
replacePendingCompactionMarkerStateIf,
|
|
64
61
|
clearPendingCompactionMarkerStateIf,
|
|
65
|
-
|
|
62
|
+
registerAutoSearchWork,
|
|
63
|
+
cancelAutoSearchWork,
|
|
66
64
|
updateSessionMeta,
|
|
67
65
|
advanceToolReclaimWatermark,
|
|
68
|
-
getNotes,
|
|
69
|
-
getActivePrimers,
|
|
70
66
|
saveSourceContent,
|
|
71
67
|
replaceSourceContent,
|
|
72
68
|
getSourceContents,
|
|
73
|
-
cosineSimilarity,
|
|
74
|
-
saveCommitEmbedding,
|
|
75
|
-
loadProjectCommitEmbeddings,
|
|
76
|
-
loadUnembeddedCommits,
|
|
77
|
-
countEmbeddedCommits,
|
|
78
|
-
upsertCommits,
|
|
79
|
-
getLatestIndexedCommitSha,
|
|
80
|
-
enforceProjectCap,
|
|
81
|
-
getProjectEmbeddings,
|
|
82
|
-
contentSha256,
|
|
83
|
-
getShadowEmbeddingMeasurementCohort,
|
|
84
|
-
getPrimaryEmbeddingMeasurementCohort,
|
|
85
|
-
embedShadowTextForProject,
|
|
86
|
-
enqueueShadowEmbeddingItems,
|
|
87
|
-
getProjectEmbeddingSnapshot,
|
|
88
|
-
embedTextForProject,
|
|
89
|
-
embedItemsForProject,
|
|
90
|
-
isEmbeddingEnabled,
|
|
91
|
-
embedText,
|
|
92
|
-
ensureMemoryEmbeddings,
|
|
93
|
-
ModuleMemoryAuthorityError,
|
|
94
|
-
getMemoriesByProject,
|
|
95
|
-
getMemoriesByProjects,
|
|
96
|
-
updateMemoryRetrievalCount,
|
|
97
|
-
relaxedFtsQuery,
|
|
98
|
-
sanitizeFtsQuery,
|
|
99
|
-
searchMemoriesFTS,
|
|
100
|
-
searchMemoriesFTSUnion,
|
|
101
69
|
compareOpenCodeMessagesByCanonicalOrder,
|
|
102
70
|
MARKER_SUMMARY_TEXT,
|
|
103
71
|
applyDeferredCompactionMarker,
|
|
@@ -115,7 +83,7 @@ import {
|
|
|
115
83
|
peekNoteNudgeText,
|
|
116
84
|
markNoteNudgeDelivered,
|
|
117
85
|
getActiveCompartmentRun
|
|
118
|
-
} from "./index-
|
|
86
|
+
} from "./index-6d9e1m08.js";
|
|
119
87
|
import {
|
|
120
88
|
protectedToolTagNumbers,
|
|
121
89
|
stripWellFormedLeadingTagPrefix,
|
|
@@ -124,6 +92,7 @@ import {
|
|
|
124
92
|
prependTag,
|
|
125
93
|
isThinkingPart,
|
|
126
94
|
isRecord,
|
|
95
|
+
contentTagOwnerMessageId,
|
|
127
96
|
estimateImageTokensFromDataUrl,
|
|
128
97
|
estimateToolAttachmentImageTokens,
|
|
129
98
|
buildSyntheticTodoPart,
|
|
@@ -146,6 +115,7 @@ import {
|
|
|
146
115
|
AGE_RECLAIM_MIN_TOKENS,
|
|
147
116
|
getOldestActiveUnprotectedToolTags,
|
|
148
117
|
getActiveToolTagsForAgeReclaim,
|
|
118
|
+
getReasoningTokenEstimatesByMessage,
|
|
149
119
|
tagTokenCountIsNull,
|
|
150
120
|
backfillTagTokenCounts,
|
|
151
121
|
insertTag,
|
|
@@ -159,6 +129,7 @@ import {
|
|
|
159
129
|
getMaxTagNumberBySession,
|
|
160
130
|
getTagNumberByMessageId,
|
|
161
131
|
getAssignableTagNumberByMessageId,
|
|
132
|
+
getTagsBySession,
|
|
162
133
|
getActiveTagsBySession,
|
|
163
134
|
getTailHygieneTags,
|
|
164
135
|
getTagsByNumbers,
|
|
@@ -178,9 +149,11 @@ import {
|
|
|
178
149
|
toolPartHasUserAnswer,
|
|
179
150
|
isEditTool,
|
|
180
151
|
modelAcceptsEmptyContent,
|
|
152
|
+
isAnthropicFamilyRoute,
|
|
181
153
|
neutralizeDroppedReasoningPart,
|
|
182
154
|
restoreNeutralizedReasoningPart,
|
|
183
155
|
isNeutralizedReasoningPart,
|
|
156
|
+
neutralizedReasoningSource,
|
|
184
157
|
makeSentinel,
|
|
185
158
|
makeWholeMessageSentinel,
|
|
186
159
|
isSentinel,
|
|
@@ -190,22 +163,21 @@ import {
|
|
|
190
163
|
extractToolCallObservation,
|
|
191
164
|
ToolMutationBatch,
|
|
192
165
|
createToolDropTarget
|
|
193
|
-
} from "./index-
|
|
166
|
+
} from "./index-bb36vgr3.js";
|
|
194
167
|
import {
|
|
195
168
|
textMentionsRecentCommit,
|
|
196
169
|
hasMeaningfulUserText2,
|
|
197
170
|
estimateTokens
|
|
198
171
|
} from "./index-5eqk6z3s.js";
|
|
199
172
|
import {
|
|
200
|
-
isValidLanguageCode
|
|
201
|
-
|
|
173
|
+
isValidLanguageCode,
|
|
174
|
+
resolveModelConfigValue
|
|
175
|
+
} from "./index-m5fe6jch.js";
|
|
202
176
|
import {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
sourceNameForMemory
|
|
208
|
-
} from "./index-r5hmbx3t.js";
|
|
177
|
+
getProjectEmbeddingSnapshot,
|
|
178
|
+
embedTextForProject,
|
|
179
|
+
embedBatchForProject
|
|
180
|
+
} from "./index-fnn5pjtt.js";
|
|
209
181
|
import {
|
|
210
182
|
getTemporalDecisions,
|
|
211
183
|
freezeTemporalDecisions,
|
|
@@ -214,12 +186,15 @@ import {
|
|
|
214
186
|
import {
|
|
215
187
|
normalizeProtectedToolName,
|
|
216
188
|
mergeProtectedTools
|
|
217
|
-
} from "./index-
|
|
189
|
+
} from "./index-rrbbspwh.js";
|
|
218
190
|
import {
|
|
219
191
|
openCodeDbExists,
|
|
220
192
|
withReadOnlySessionDb,
|
|
221
193
|
getMessageTimesFromOpenCodeDb
|
|
222
|
-
} from "./index-
|
|
194
|
+
} from "./index-6wkcswft.js";
|
|
195
|
+
import {
|
|
196
|
+
isEmbeddingHostBusy
|
|
197
|
+
} from "./index-yr3zj7e7.js";
|
|
223
198
|
import {
|
|
224
199
|
getErrorMessage
|
|
225
200
|
} from "./index-y8v6yerm.js";
|
|
@@ -299,6 +274,39 @@ function markDropLeavesReasoning(db, sessionId) {
|
|
|
299
274
|
});
|
|
300
275
|
}
|
|
301
276
|
|
|
277
|
+
// src/hooks/magic-context/active-anthropic-turn.ts
|
|
278
|
+
function isInActiveAnthropicTurn(messages, index, anthropic) {
|
|
279
|
+
if (!anthropic)
|
|
280
|
+
return false;
|
|
281
|
+
for (let user = messages.length - 1;user >= 0; user--) {
|
|
282
|
+
const message = messages[user];
|
|
283
|
+
if (!isRecord(message))
|
|
284
|
+
continue;
|
|
285
|
+
const info = isRecord(message.info) ? message.info : message;
|
|
286
|
+
if (info.role !== "user" || info.synthetic === true || message.synthetic === true)
|
|
287
|
+
continue;
|
|
288
|
+
const id = typeof info.id === "string" ? info.id : "";
|
|
289
|
+
if (id.startsWith("synth-user-"))
|
|
290
|
+
continue;
|
|
291
|
+
const parts = Array.isArray(message.parts) ? message.parts : Array.isArray(message.content) ? message.content : [];
|
|
292
|
+
if (parts.length > 0 && parts.every((part) => isRecord(part) && (part.synthetic === true || part.ignored === true || ["tool_result", "toolResult", "tool-result"].includes(String(part.type)))))
|
|
293
|
+
continue;
|
|
294
|
+
return index > user;
|
|
295
|
+
}
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
function hasAnthropicReasoning(messages) {
|
|
299
|
+
return messages.some((message) => {
|
|
300
|
+
if (!isRecord(message))
|
|
301
|
+
return false;
|
|
302
|
+
const info = isRecord(message.info) ? message.info : message;
|
|
303
|
+
if (isAnthropicFamilyRoute(typeof info.providerID === "string" ? info.providerID : typeof info.provider === "string" ? info.provider : undefined, typeof info.modelID === "string" ? info.modelID : typeof info.model === "string" ? info.model : undefined))
|
|
304
|
+
return true;
|
|
305
|
+
const parts = Array.isArray(message.parts) ? message.parts : [];
|
|
306
|
+
return parts.some((part) => isRecord(part) && isRecord(part.metadata) && isRecord(part.metadata.anthropic));
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
302
310
|
// src/hooks/magic-context/apply-operations.ts
|
|
303
311
|
var RECENT_TOOL_SKELETON_WINDOW = 20;
|
|
304
312
|
function buildReplacementContent(tagId) {
|
|
@@ -440,6 +448,10 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
|
|
|
440
448
|
continue;
|
|
441
449
|
}
|
|
442
450
|
const target = targets.get(pendingOp.tagId);
|
|
451
|
+
if (target?.thinkingDropProtected) {
|
|
452
|
+
reject("active_thinking");
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
443
455
|
const isToolTag = tagTypeById.get(pendingOp.tagId) === "tool";
|
|
444
456
|
if (synthetic) {
|
|
445
457
|
if (!isToolTag) {
|
|
@@ -541,2070 +553,104 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
|
|
|
541
553
|
}
|
|
542
554
|
function applyFlushedStatuses(sessionId, db, targets, preloadedTags) {
|
|
543
555
|
let didMutateMessage = false;
|
|
544
|
-
const tags = preloadedTags ?? getDroppedTagsByNumbers(db, sessionId, [...targets.keys()]);
|
|
545
|
-
for (const tag of tags) {
|
|
546
|
-
if (tag.status === "dropped") {
|
|
547
|
-
const target = targets.get(tag.tagNumber);
|
|
548
|
-
if (tag.type === "tool") {
|
|
549
|
-
if (tag.dropMode === "edit_marker_stripped") {
|
|
550
|
-
const result = target?.editMarkerStripped?.() ?? "absent";
|
|
551
|
-
if (result === "truncated")
|
|
552
|
-
didMutateMessage = true;
|
|
553
|
-
} else if (tag.dropMode === "edit_marker") {
|
|
554
|
-
const markResult = target?.editMarker?.() ?? "absent";
|
|
555
|
-
if (markResult === "truncated") {
|
|
556
|
-
didMutateMessage = true;
|
|
557
|
-
}
|
|
558
|
-
} else if (tag.dropMode === "skeleton_stripped") {
|
|
559
|
-
const result = target?.skeletonStripped?.() ?? "absent";
|
|
560
|
-
if (result === "truncated")
|
|
561
|
-
didMutateMessage = true;
|
|
562
|
-
} else if (tag.dropMode === "skeleton_real") {
|
|
563
|
-
const result = target?.skeletonReal?.() ?? "absent";
|
|
564
|
-
if (result === "truncated") {
|
|
565
|
-
didMutateMessage = true;
|
|
566
|
-
}
|
|
567
|
-
} else if (tag.dropMode === "truncated") {
|
|
568
|
-
const truncResult = target?.truncate?.() ?? "absent";
|
|
569
|
-
if (truncResult === "truncated") {
|
|
570
|
-
didMutateMessage = true;
|
|
571
|
-
}
|
|
572
|
-
} else {
|
|
573
|
-
const dropResult = target?.drop?.() ?? "absent";
|
|
574
|
-
if (dropResult === "removed" || dropResult === "truncated") {
|
|
575
|
-
didMutateMessage = true;
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
} else if (target) {
|
|
579
|
-
const changed = target.setContent(buildReplacementContent(tag.tagNumber));
|
|
580
|
-
if (changed)
|
|
581
|
-
didMutateMessage = true;
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
return didMutateMessage;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
// src/features/magic-context/git-commits/git-log-reader.ts
|
|
589
|
-
import { execFile } from "node:child_process";
|
|
590
|
-
import { promisify } from "node:util";
|
|
591
|
-
var execFileAsync = promisify(execFile);
|
|
592
|
-
var GIT_TIMEOUT_MS = 1e4;
|
|
593
|
-
var DEFAULT_MAX_COMMITS = 5000;
|
|
594
|
-
var RECORD_SEPARATOR = "\x1E";
|
|
595
|
-
var FIELD_SEPARATOR = "\x1F";
|
|
596
|
-
var FULL_OBJECT_NAME = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
597
|
-
function classifyGitLogFailure(message) {
|
|
598
|
-
if (message.includes("not a git repository"))
|
|
599
|
-
return "not_a_repo";
|
|
600
|
-
if (message.includes("unknown revision or path not in the working tree") || message.includes("does not have any commits yet") || message.includes("bad revision")) {
|
|
601
|
-
return "no_head";
|
|
602
|
-
}
|
|
603
|
-
return "transient";
|
|
604
|
-
}
|
|
605
|
-
async function readGitCommitsResult(directory, options = {}) {
|
|
606
|
-
const revision = options.branch ?? "HEAD";
|
|
607
|
-
if (revision.startsWith("-")) {
|
|
608
|
-
throw new Error(`readGitCommits: refusing revision that looks like an option: "${revision}"`);
|
|
609
|
-
}
|
|
610
|
-
const projectLabel = options.projectIdentity ?? "<project>";
|
|
611
|
-
const exclusion = options.excludeReachableFrom && FULL_OBJECT_NAME.test(options.excludeReachableFrom) ? options.excludeReachableFrom : null;
|
|
612
|
-
const args = [
|
|
613
|
-
"log",
|
|
614
|
-
revision,
|
|
615
|
-
...exclusion ? [`^${exclusion}`] : [],
|
|
616
|
-
"--no-merges",
|
|
617
|
-
`--max-count=${options.maxCommits ?? DEFAULT_MAX_COMMITS}`,
|
|
618
|
-
`--format=%H${FIELD_SEPARATOR}%s${FIELD_SEPARATOR}%ae${FIELD_SEPARATOR}%ct${FIELD_SEPARATOR}%b${RECORD_SEPARATOR}`
|
|
619
|
-
];
|
|
620
|
-
if (options.sinceMs !== undefined && options.sinceMs > 0) {
|
|
621
|
-
const iso = new Date(options.sinceMs).toISOString();
|
|
622
|
-
args.push(`--since=${iso}`);
|
|
623
|
-
}
|
|
624
|
-
args.push("--");
|
|
625
|
-
let stdout;
|
|
626
|
-
try {
|
|
627
|
-
const result = await execFileAsync("git", args, {
|
|
628
|
-
windowsHide: true,
|
|
629
|
-
cwd: directory,
|
|
630
|
-
timeout: GIT_TIMEOUT_MS,
|
|
631
|
-
maxBuffer: 32 * 1024 * 1024,
|
|
632
|
-
encoding: "utf8"
|
|
633
|
-
});
|
|
634
|
-
stdout = result.stdout;
|
|
635
|
-
} catch (error) {
|
|
636
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
637
|
-
if (exclusion && message.includes("bad object")) {
|
|
638
|
-
log(`[git-commits] indexed tip ${exclusion.slice(0, 7)} is missing for ${projectLabel}; reading the full window`);
|
|
639
|
-
return readGitCommitsResult(directory, { ...options, excludeReachableFrom: undefined });
|
|
640
|
-
}
|
|
641
|
-
const failure = classifyGitLogFailure(message);
|
|
642
|
-
if (failure === "transient") {
|
|
643
|
-
log(`[git-commits] readGitCommits failed for ${projectLabel}: ${message.slice(0, 500)}`);
|
|
644
|
-
} else {
|
|
645
|
-
log(`[git-commits] ${projectLabel} is not indexable (${failure})`);
|
|
646
|
-
}
|
|
647
|
-
return { commits: [], failure };
|
|
648
|
-
}
|
|
649
|
-
if (stdout.trim().length === 0) {
|
|
650
|
-
log(`[git-commits] readGitCommits returned empty stdout for ${projectLabel} (sinceMs=${options.sinceMs ?? "none"} args=${args.slice(0, 4).join(" ")})`);
|
|
651
|
-
}
|
|
652
|
-
return { commits: parseGitLogOutput(stdout), failure: null };
|
|
653
|
-
}
|
|
654
|
-
function parseGitLogOutput(stdout) {
|
|
655
|
-
const commits = [];
|
|
656
|
-
const records = stdout.split(RECORD_SEPARATOR);
|
|
657
|
-
for (const rawRecord of records) {
|
|
658
|
-
const record = rawRecord.replace(/^\s+/, "");
|
|
659
|
-
if (!record)
|
|
660
|
-
continue;
|
|
661
|
-
const fields = [];
|
|
662
|
-
let remaining = record;
|
|
663
|
-
for (let i = 0;i < 4; i++) {
|
|
664
|
-
const idx = remaining.indexOf(FIELD_SEPARATOR);
|
|
665
|
-
if (idx < 0)
|
|
666
|
-
break;
|
|
667
|
-
fields.push(remaining.slice(0, idx));
|
|
668
|
-
remaining = remaining.slice(idx + FIELD_SEPARATOR.length);
|
|
669
|
-
}
|
|
670
|
-
fields.push(remaining);
|
|
671
|
-
if (fields.length < 5)
|
|
672
|
-
continue;
|
|
673
|
-
const sha = fields[0].trim();
|
|
674
|
-
const subject = fields[1].trim();
|
|
675
|
-
const author = fields[2].trim();
|
|
676
|
-
const timeSec = Number.parseInt(fields[3].trim(), 10);
|
|
677
|
-
const body = fields[4].trim();
|
|
678
|
-
if (!FULL_OBJECT_NAME.test(sha) || !Number.isFinite(timeSec) || timeSec <= 0) {
|
|
679
|
-
continue;
|
|
680
|
-
}
|
|
681
|
-
const message = body.length > 0 ? `${subject}
|
|
682
|
-
|
|
683
|
-
${body}` : subject;
|
|
684
|
-
commits.push({
|
|
685
|
-
sha,
|
|
686
|
-
shortSha: sha.slice(0, 7),
|
|
687
|
-
message,
|
|
688
|
-
author: author.length > 0 ? author : null,
|
|
689
|
-
committedAtMs: timeSec * 1000
|
|
690
|
-
});
|
|
691
|
-
}
|
|
692
|
-
return commits;
|
|
693
|
-
}
|
|
694
|
-
// src/features/magic-context/git-commits/indexer.ts
|
|
695
|
-
var MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
696
|
-
var EMBED_BATCH_SIZE = 16;
|
|
697
|
-
var EMBED_MAX_PER_SWEEP = 500;
|
|
698
|
-
var EMBED_SWEEP_MAX_WALL_CLOCK_MS = 5 * 60 * 1000;
|
|
699
|
-
var indexInProgress = new Set;
|
|
700
|
-
var embedInProgress = new Set;
|
|
701
|
-
async function indexCommitsForProject(db, projectPath, directory, options) {
|
|
702
|
-
const result = {
|
|
703
|
-
scanned: 0,
|
|
704
|
-
inserted: 0,
|
|
705
|
-
updated: 0,
|
|
706
|
-
evicted: 0,
|
|
707
|
-
embedded: 0,
|
|
708
|
-
nonIndexable: false
|
|
709
|
-
};
|
|
710
|
-
if (indexInProgress.has(projectPath)) {
|
|
711
|
-
log(`[git-commits] index already in progress for ${projectPath}, skipping`);
|
|
712
|
-
return result;
|
|
713
|
-
}
|
|
714
|
-
indexInProgress.add(projectPath);
|
|
715
|
-
try {
|
|
716
|
-
const latestIndexed = getLatestIndexedCommitSha(db, projectPath);
|
|
717
|
-
const sinceMs = Date.now() - options.sinceDays * MS_PER_DAY;
|
|
718
|
-
const read = await readGitCommitsResult(directory, {
|
|
719
|
-
sinceMs,
|
|
720
|
-
maxCommits: options.maxCommits,
|
|
721
|
-
projectIdentity: projectPath,
|
|
722
|
-
excludeReachableFrom: latestIndexed ?? undefined
|
|
723
|
-
});
|
|
724
|
-
const commits = read.commits;
|
|
725
|
-
result.scanned = commits.length;
|
|
726
|
-
if (read.failure === "not_a_repo" || read.failure === "no_head") {
|
|
727
|
-
result.nonIndexable = true;
|
|
728
|
-
return result;
|
|
729
|
-
}
|
|
730
|
-
if (commits.length === 0) {
|
|
731
|
-
result.evicted = enforceProjectCap(db, projectPath, options.maxCommits);
|
|
732
|
-
log(`[git-commits] no new commits for ${projectPath} (sinceMs=${sinceMs} latestIndexed=${latestIndexed?.slice(0, 7) ?? "none"} evicted=${result.evicted})`);
|
|
733
|
-
return result;
|
|
734
|
-
}
|
|
735
|
-
log(`[git-commits] read ${commits.length} commits for ${projectPath} (sinceMs=${sinceMs} latestIndexed=${latestIndexed?.slice(0, 7) ?? "none"})`);
|
|
736
|
-
const upsert = upsertCommits(db, projectPath, commits);
|
|
737
|
-
result.inserted = upsert.inserted;
|
|
738
|
-
result.updated = upsert.updated;
|
|
739
|
-
result.evicted = enforceProjectCap(db, projectPath, options.maxCommits);
|
|
740
|
-
const snapshot = getProjectEmbeddingSnapshot(projectPath);
|
|
741
|
-
if (options.skipEmbed || !snapshot?.gitCommitEnabled) {
|
|
742
|
-
log(`[git-commits] indexed ${projectPath}: scanned=${result.scanned} inserted=${result.inserted} updated=${result.updated} evicted=${result.evicted} embedded=0 (embedding skipped: skipEmbed=${options.skipEmbed === true} gitCommitEnabled=${snapshot?.gitCommitEnabled === true})`);
|
|
743
|
-
return result;
|
|
744
|
-
}
|
|
745
|
-
result.embedded = await embedUnembeddedCommits(db, projectPath);
|
|
746
|
-
log(`[git-commits] indexed ${projectPath}: scanned=${result.scanned} inserted=${result.inserted} updated=${result.updated} evicted=${result.evicted} embedded=${result.embedded}`);
|
|
747
|
-
return result;
|
|
748
|
-
} finally {
|
|
749
|
-
indexInProgress.delete(projectPath);
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
async function embedUnembeddedCommits(db, projectPath) {
|
|
753
|
-
if (embedInProgress.has(projectPath)) {
|
|
754
|
-
return 0;
|
|
755
|
-
}
|
|
756
|
-
const snapshot = getProjectEmbeddingSnapshot(projectPath);
|
|
757
|
-
if (!snapshot?.gitCommitEnabled) {
|
|
758
|
-
return 0;
|
|
759
|
-
}
|
|
760
|
-
embedInProgress.add(projectPath);
|
|
761
|
-
const startedAt = Date.now();
|
|
762
|
-
const deadline = startedAt + EMBED_SWEEP_MAX_WALL_CLOCK_MS;
|
|
763
|
-
let total = 0;
|
|
764
|
-
try {
|
|
765
|
-
while (Date.now() < deadline && total < EMBED_MAX_PER_SWEEP) {
|
|
766
|
-
const rows = loadUnembeddedCommits(db, projectPath, snapshot.modelId, EMBED_BATCH_SIZE);
|
|
767
|
-
if (rows.length === 0)
|
|
768
|
-
break;
|
|
769
|
-
let embeddedThisBatch = 0;
|
|
770
|
-
try {
|
|
771
|
-
const result = await embedItemsForProject(projectPath, rows.map((row) => ({
|
|
772
|
-
id: `commit:${row.sha}`,
|
|
773
|
-
text: row.message,
|
|
774
|
-
contentSha256: contentSha256(row.message)
|
|
775
|
-
})), undefined, db, projectPath);
|
|
776
|
-
if (!result)
|
|
777
|
-
break;
|
|
778
|
-
db.transaction(() => {
|
|
779
|
-
for (const row of rows) {
|
|
780
|
-
const embedding = result.vectors.get(`commit:${row.sha}`);
|
|
781
|
-
if (!embedding)
|
|
782
|
-
continue;
|
|
783
|
-
saveCommitEmbedding(db, row.sha, embedding, result.modelId);
|
|
784
|
-
embeddedThisBatch += 1;
|
|
785
|
-
}
|
|
786
|
-
}).immediate();
|
|
787
|
-
enqueueShadowEmbeddingItems(projectPath, "commit", rows.filter((row) => result.vectors.has(`commit:${row.sha}`)).map((row) => row.sha));
|
|
788
|
-
} catch (error) {
|
|
789
|
-
log(`[git-commits] embed batch failed for ${projectPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
790
|
-
break;
|
|
791
|
-
}
|
|
792
|
-
if (embeddedThisBatch === 0)
|
|
793
|
-
break;
|
|
794
|
-
total += embeddedThisBatch;
|
|
795
|
-
if (embeddedThisBatch < rows.length)
|
|
796
|
-
break;
|
|
797
|
-
}
|
|
798
|
-
if (total > 0) {
|
|
799
|
-
const totalEmbedded = countEmbeddedCommits(db, projectPath, snapshot.modelId);
|
|
800
|
-
log(`[git-commits] embedded ${total} commits for ${projectPath} (total embedded: ${totalEmbedded})`);
|
|
801
|
-
}
|
|
802
|
-
return total;
|
|
803
|
-
} finally {
|
|
804
|
-
embedInProgress.delete(projectPath);
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
// src/features/magic-context/git-commits/search-git-commits.ts
|
|
808
|
-
var ftsStatements = new WeakMap;
|
|
809
|
-
var datedFtsStatements = new WeakMap;
|
|
810
|
-
var ftsPlainStatements = new WeakMap;
|
|
811
|
-
var datedFtsPlainStatements = new WeakMap;
|
|
812
|
-
var getBySHAsStatements = new WeakMap;
|
|
813
|
-
var datedGetBySHAsStatements = new WeakMap;
|
|
814
|
-
var embeddingCache = new WeakMap;
|
|
815
|
-
function getSearchEmbeddings(db, projectPath, modelId) {
|
|
816
|
-
if (!isKnownAutocommit(db)) {
|
|
817
|
-
embeddingCache.delete(db);
|
|
818
|
-
return loadProjectCommitEmbeddings(db, projectPath, modelId);
|
|
819
|
-
}
|
|
820
|
-
let cache = embeddingCache.get(db);
|
|
821
|
-
if (!cache) {
|
|
822
|
-
cache = {
|
|
823
|
-
revisionStatement: db.prepare(`SELECT total_changes() AS writes,
|
|
824
|
-
(SELECT data_version FROM pragma_data_version) AS dataVersion,
|
|
825
|
-
(SELECT schema_version FROM pragma_schema_version) AS schemaVersion`),
|
|
826
|
-
revision: "",
|
|
827
|
-
entries: new Map
|
|
828
|
-
};
|
|
829
|
-
embeddingCache.set(db, cache);
|
|
830
|
-
}
|
|
831
|
-
const revision = JSON.stringify(cache.revisionStatement.get());
|
|
832
|
-
if (revision !== cache.revision) {
|
|
833
|
-
cache.entries.clear();
|
|
834
|
-
cache.revision = revision;
|
|
835
|
-
}
|
|
836
|
-
const key = JSON.stringify([projectPath, modelId]);
|
|
837
|
-
const cached = cache.entries.get(key);
|
|
838
|
-
if (cached) {
|
|
839
|
-
cache.entries.delete(key);
|
|
840
|
-
cache.entries.set(key, cached);
|
|
841
|
-
return cached;
|
|
842
|
-
}
|
|
843
|
-
const vectors = loadProjectCommitEmbeddings(db, projectPath, modelId);
|
|
844
|
-
const size = (entries) => [...entries.values()].reduce((sum, vector) => sum + vector.byteLength, 0);
|
|
845
|
-
if (size(vectors) <= 32 * 1024 * 1024)
|
|
846
|
-
cache.entries.set(key, vectors);
|
|
847
|
-
while (cache.entries.size > 16 || [...cache.entries.values()].reduce((sum, entry) => sum + size(entry), 0) > 32 * 1024 * 1024) {
|
|
848
|
-
const oldest = cache.entries.keys().next().value;
|
|
849
|
-
if (oldest === undefined)
|
|
850
|
-
break;
|
|
851
|
-
cache.entries.delete(oldest);
|
|
852
|
-
}
|
|
853
|
-
return vectors;
|
|
854
|
-
}
|
|
855
|
-
function rowToCommit(row) {
|
|
856
|
-
return {
|
|
857
|
-
sha: row.sha,
|
|
858
|
-
shortSha: row.short_sha,
|
|
859
|
-
projectPath: row.project_path,
|
|
860
|
-
message: row.message,
|
|
861
|
-
author: row.author,
|
|
862
|
-
committedAtMs: row.committed_at,
|
|
863
|
-
indexedAtMs: row.indexed_at
|
|
864
|
-
};
|
|
865
|
-
}
|
|
866
|
-
function getFtsStatement(db, dated = false) {
|
|
867
|
-
const statements = dated ? datedFtsStatements : ftsStatements;
|
|
868
|
-
let stmt = statements.get(db);
|
|
869
|
-
if (!stmt) {
|
|
870
|
-
stmt = db.prepare(`SELECT c.sha AS sha, c.project_path AS project_path, c.short_sha AS short_sha,
|
|
871
|
-
c.message AS message, c.author AS author,
|
|
872
|
-
c.committed_at AS committed_at, c.indexed_at AS indexed_at
|
|
873
|
-
FROM git_commits_fts
|
|
874
|
-
INNER JOIN git_commits c ON c.sha = git_commits_fts.sha
|
|
875
|
-
WHERE c.project_path = ?
|
|
876
|
-
${dated ? "AND c.committed_at BETWEEN ? AND ?" : ""}
|
|
877
|
-
AND git_commits_fts MATCH ?
|
|
878
|
-
ORDER BY bm25(git_commits_fts) LIMIT ?`);
|
|
879
|
-
statements.set(db, stmt);
|
|
880
|
-
}
|
|
881
|
-
return stmt;
|
|
882
|
-
}
|
|
883
|
-
function escapeLikePattern(text) {
|
|
884
|
-
return text.replace(/[\\%_]/g, (char) => `\\${char}`);
|
|
885
|
-
}
|
|
886
|
-
function getLikeFallbackStatement(db, dated = false) {
|
|
887
|
-
const statements = dated ? datedFtsPlainStatements : ftsPlainStatements;
|
|
888
|
-
let stmt = statements.get(db);
|
|
889
|
-
if (!stmt) {
|
|
890
|
-
stmt = db.prepare(`SELECT sha, project_path, short_sha, message, author, committed_at, indexed_at
|
|
891
|
-
FROM git_commits
|
|
892
|
-
WHERE project_path = ?
|
|
893
|
-
${dated ? "AND committed_at BETWEEN ? AND ?" : ""}
|
|
894
|
-
AND lower(message) LIKE '%' || lower(?) || '%' ESCAPE '\\'
|
|
895
|
-
ORDER BY committed_at DESC LIMIT ?`);
|
|
896
|
-
statements.set(db, stmt);
|
|
897
|
-
}
|
|
898
|
-
return stmt;
|
|
899
|
-
}
|
|
900
|
-
function getBySHAsStatement(db, dated = false) {
|
|
901
|
-
const statements = dated ? datedGetBySHAsStatements : getBySHAsStatements;
|
|
902
|
-
let stmt = statements.get(db);
|
|
903
|
-
if (!stmt) {
|
|
904
|
-
stmt = db.prepare(`SELECT sha, project_path, short_sha, message, author, committed_at, indexed_at
|
|
905
|
-
FROM git_commits
|
|
906
|
-
WHERE project_path = ?
|
|
907
|
-
${dated ? "AND committed_at BETWEEN ? AND ?" : ""}
|
|
908
|
-
AND sha IN (SELECT value FROM json_each(?))`);
|
|
909
|
-
statements.set(db, stmt);
|
|
910
|
-
}
|
|
911
|
-
return stmt;
|
|
912
|
-
}
|
|
913
|
-
function clamp01(value) {
|
|
914
|
-
if (!Number.isFinite(value))
|
|
915
|
-
return 0;
|
|
916
|
-
return Math.min(1, Math.max(0, value));
|
|
917
|
-
}
|
|
918
|
-
function searchGitCommitsSync(db, projectPath, query, options) {
|
|
919
|
-
const trimmed = query.trim();
|
|
920
|
-
if (trimmed.length === 0 || options.limit <= 0)
|
|
921
|
-
return [];
|
|
922
|
-
const semanticWeight = options.semanticWeight ?? 0.7;
|
|
923
|
-
const ftsWeight = options.ftsWeight ?? 0.3;
|
|
924
|
-
const singleSourcePenalty = options.singleSourcePenalty ?? 0.8;
|
|
925
|
-
const fetchLimit = Math.max(options.limit * 3, 30);
|
|
926
|
-
const dated = options.from !== undefined || options.to !== undefined;
|
|
927
|
-
const from = options.from ?? Number.MIN_SAFE_INTEGER;
|
|
928
|
-
const to = options.to ?? Number.MAX_SAFE_INTEGER;
|
|
929
|
-
const ftsCandidates = [];
|
|
930
|
-
const sanitized = sanitizeFtsQuery(trimmed);
|
|
931
|
-
if (sanitized.length > 0) {
|
|
932
|
-
try {
|
|
933
|
-
for (const row of getFtsStatement(db, dated).all(projectPath, ...dated ? [from, to] : [], sanitized, fetchLimit)) {
|
|
934
|
-
ftsCandidates.push(rowToCommit(row));
|
|
935
|
-
}
|
|
936
|
-
} catch (error) {
|
|
937
|
-
log(`[git-commits] FTS query failed for "${trimmed}": ${error instanceof Error ? error.message : String(error)}`);
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
if (ftsCandidates.length === 0) {
|
|
941
|
-
for (const row of getLikeFallbackStatement(db, dated).all(projectPath, ...dated ? [from, to] : [], escapeLikePattern(trimmed), fetchLimit)) {
|
|
942
|
-
ftsCandidates.push(rowToCommit(row));
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
const ftsScores = new Map;
|
|
946
|
-
ftsCandidates.forEach((commit, rank) => {
|
|
947
|
-
ftsScores.set(commit.sha, 1 / (rank + 1));
|
|
948
|
-
});
|
|
949
|
-
const semanticScores = new Map;
|
|
950
|
-
if (options.queryEmbedding && options.queryModelId && options.queryModelId !== "off") {
|
|
951
|
-
const embeddings = getSearchEmbeddings(db, projectPath, options.queryModelId);
|
|
952
|
-
for (const [sha, embedding] of embeddings.entries()) {
|
|
953
|
-
const similarity = clamp01(cosineSimilarity(options.queryEmbedding, embedding));
|
|
954
|
-
if (similarity > 0) {
|
|
955
|
-
semanticScores.set(sha, similarity);
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
const bySha = new Map;
|
|
960
|
-
for (const commit of ftsCandidates)
|
|
961
|
-
bySha.set(commit.sha, commit);
|
|
962
|
-
const semanticOnlyShas = [...semanticScores.keys()].filter((sha) => !bySha.has(sha));
|
|
963
|
-
if (semanticOnlyShas.length > 0) {
|
|
964
|
-
const rows = getBySHAsStatement(db, dated).all(projectPath, ...dated ? [from, to] : [], JSON.stringify(semanticOnlyShas));
|
|
965
|
-
for (const row of rows)
|
|
966
|
-
bySha.set(row.sha, rowToCommit(row));
|
|
967
|
-
}
|
|
968
|
-
const results = [];
|
|
969
|
-
for (const [sha, commit] of bySha.entries()) {
|
|
970
|
-
const sem = semanticScores.get(sha);
|
|
971
|
-
const fts = ftsScores.get(sha);
|
|
972
|
-
let score = 0;
|
|
973
|
-
let matchType = "fts";
|
|
974
|
-
if (sem !== undefined && fts !== undefined) {
|
|
975
|
-
score = semanticWeight * sem + ftsWeight * fts;
|
|
976
|
-
matchType = "hybrid";
|
|
977
|
-
} else if (sem !== undefined) {
|
|
978
|
-
score = sem * singleSourcePenalty;
|
|
979
|
-
matchType = "semantic";
|
|
980
|
-
} else if (fts !== undefined) {
|
|
981
|
-
score = fts * singleSourcePenalty;
|
|
982
|
-
matchType = "fts";
|
|
983
|
-
}
|
|
984
|
-
if (score <= 0)
|
|
985
|
-
continue;
|
|
986
|
-
results.push({ commit, score, matchType });
|
|
987
|
-
}
|
|
988
|
-
results.sort((left, right) => {
|
|
989
|
-
if (right.score !== left.score)
|
|
990
|
-
return right.score - left.score;
|
|
991
|
-
return right.commit.committedAtMs - left.commit.committedAtMs;
|
|
992
|
-
});
|
|
993
|
-
return results.slice(0, options.limit);
|
|
994
|
-
}
|
|
995
|
-
// src/features/magic-context/literal-probes.ts
|
|
996
|
-
var MAX_PROBES = 5;
|
|
997
|
-
var MIN_PROBE_LENGTH = 3;
|
|
998
|
-
var SLASH_COMMAND_RE = /\/[a-z][a-z0-9]*(?:-[a-z0-9]+)+/gi;
|
|
999
|
-
var KEBAB_SNAKE_RE = /[a-z][a-z0-9]*(?:[-_][a-z0-9]+)+/gi;
|
|
1000
|
-
var DOTTED_RE = /[a-z0-9][a-z0-9_-]*(?:\.[a-z0-9_-]+)+/gi;
|
|
1001
|
-
var CAMEL_RE = /\b[a-zA-Z][a-z0-9]*(?:[A-Z][a-z0-9]*)+\b/g;
|
|
1002
|
-
var SHA_RE = /\b[0-9a-f]{7,40}\b/gi;
|
|
1003
|
-
var ERROR_CODE_RE = /\b(?:TS\d{4,}|ERR_[A-Z][A-Z0-9_]*)\b/g;
|
|
1004
|
-
var QUOTED_RE = /["`]([^"`]{3,80})["`]/g;
|
|
1005
|
-
function looksLikeSha(token) {
|
|
1006
|
-
return /[0-9]/.test(token) && /^[0-9a-f]{7,40}$/i.test(token);
|
|
1007
|
-
}
|
|
1008
|
-
function extractLiteralProbes(query) {
|
|
1009
|
-
const trimmed = query.trim();
|
|
1010
|
-
if (trimmed.length === 0)
|
|
1011
|
-
return [];
|
|
1012
|
-
const ordered = [];
|
|
1013
|
-
const seen = new Set;
|
|
1014
|
-
const add = (raw) => {
|
|
1015
|
-
if (!raw)
|
|
1016
|
-
return;
|
|
1017
|
-
const probe = raw.trim();
|
|
1018
|
-
if (probe.length < MIN_PROBE_LENGTH)
|
|
1019
|
-
return;
|
|
1020
|
-
const key = probe.toLowerCase();
|
|
1021
|
-
if (seen.has(key))
|
|
1022
|
-
return;
|
|
1023
|
-
seen.add(key);
|
|
1024
|
-
ordered.push(probe);
|
|
1025
|
-
};
|
|
1026
|
-
for (const m of trimmed.matchAll(QUOTED_RE))
|
|
1027
|
-
add(m[1]);
|
|
1028
|
-
for (const m of trimmed.matchAll(SLASH_COMMAND_RE))
|
|
1029
|
-
add(m[0]);
|
|
1030
|
-
for (const m of trimmed.matchAll(ERROR_CODE_RE))
|
|
1031
|
-
add(m[0]);
|
|
1032
|
-
for (const m of trimmed.matchAll(DOTTED_RE))
|
|
1033
|
-
add(m[0]);
|
|
1034
|
-
for (const m of trimmed.matchAll(KEBAB_SNAKE_RE))
|
|
1035
|
-
add(m[0]);
|
|
1036
|
-
for (const m of trimmed.matchAll(CAMEL_RE))
|
|
1037
|
-
add(m[0]);
|
|
1038
|
-
for (const m of trimmed.matchAll(SHA_RE)) {
|
|
1039
|
-
if (looksLikeSha(m[0]))
|
|
1040
|
-
add(m[0]);
|
|
1041
|
-
}
|
|
1042
|
-
return ordered.slice(0, MAX_PROBES);
|
|
1043
|
-
}
|
|
1044
|
-
function containsProbeVerbatim(text, probes) {
|
|
1045
|
-
if (probes.length === 0)
|
|
1046
|
-
return false;
|
|
1047
|
-
const haystack = text.toLowerCase();
|
|
1048
|
-
return probes.some((probe) => haystack.includes(probe.toLowerCase()));
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
// src/features/magic-context/message-fts-session-filter.ts
|
|
1052
|
-
var MESSAGE_FTS_SESSION_FILTER_SQL = `+message_history_fts.rowid IN (
|
|
1053
|
-
SELECT fts_rowid FROM message_fts_rowid_map WHERE session_id = ?1
|
|
1054
|
-
) AND `;
|
|
1055
|
-
var coverageCaches = new WeakMap;
|
|
1056
|
-
function getCoverageCache(db) {
|
|
1057
|
-
let cache = coverageCaches.get(db);
|
|
1058
|
-
if (!cache) {
|
|
1059
|
-
cache = {
|
|
1060
|
-
pin: db.prepare("SELECT fts_rowid FROM message_fts_rowid_map LIMIT 1"),
|
|
1061
|
-
stamp: db.prepare("SELECT total_changes() AS changes, data_version AS version FROM pragma_data_version"),
|
|
1062
|
-
missing: db.prepare(`SELECT DISTINCT session_id AS sessionId
|
|
1063
|
-
FROM message_history_fts WHERE rowid IN (
|
|
1064
|
-
SELECT id FROM message_history_fts_docsize
|
|
1065
|
-
EXCEPT SELECT fts_rowid FROM message_fts_rowid_map
|
|
1066
|
-
)`)
|
|
1067
|
-
};
|
|
1068
|
-
coverageCaches.set(db, cache);
|
|
1069
|
-
}
|
|
1070
|
-
return cache;
|
|
1071
|
-
}
|
|
1072
|
-
function withMessageFtsSessionFilter(db, sessionId, read) {
|
|
1073
|
-
let cacheable = false;
|
|
1074
|
-
try {
|
|
1075
|
-
const transaction = db;
|
|
1076
|
-
cacheable = transaction.inTransaction === false || transaction.isTransaction === false;
|
|
1077
|
-
if (!cacheable)
|
|
1078
|
-
coverageCaches.delete(db);
|
|
1079
|
-
} catch {
|
|
1080
|
-
coverageCaches.delete(db);
|
|
1081
|
-
}
|
|
1082
|
-
return db.transaction(() => {
|
|
1083
|
-
let sessionFirst = false;
|
|
1084
|
-
try {
|
|
1085
|
-
const cache = getCoverageCache(db);
|
|
1086
|
-
cache.pin.get();
|
|
1087
|
-
const stamp = cache.stamp.get();
|
|
1088
|
-
let incomplete = cache.incompleteSessions;
|
|
1089
|
-
if (!cacheable || !incomplete || cache.version !== stamp.version || cache.changes !== stamp.changes) {
|
|
1090
|
-
const rows = cache.missing.all();
|
|
1091
|
-
incomplete = new Set(rows.map((row) => row.sessionId));
|
|
1092
|
-
if (cacheable) {
|
|
1093
|
-
cache.version = stamp.version;
|
|
1094
|
-
cache.changes = stamp.changes;
|
|
1095
|
-
cache.incompleteSessions = incomplete;
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
sessionFirst = !incomplete.has(sessionId);
|
|
1099
|
-
} catch {
|
|
1100
|
-
coverageCaches.delete(db);
|
|
1101
|
-
}
|
|
1102
|
-
return read(sessionFirst);
|
|
1103
|
-
}).deferred();
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
// src/features/magic-context/search-measurement.ts
|
|
1107
|
-
import { createHash } from "node:crypto";
|
|
1108
|
-
function resultId(result) {
|
|
1109
|
-
switch (result.source) {
|
|
1110
|
-
case "memory":
|
|
1111
|
-
return `memory:${result.memoryId}`;
|
|
1112
|
-
case "message":
|
|
1113
|
-
return `message:${result.messageId}`;
|
|
1114
|
-
case "compartment":
|
|
1115
|
-
return `chunk:${result.compartmentId}`;
|
|
1116
|
-
case "git_commit":
|
|
1117
|
-
return `commit:${result.sha}`;
|
|
1118
|
-
case "primer":
|
|
1119
|
-
return `primer:${result.primerId}`;
|
|
1120
|
-
case "note":
|
|
1121
|
-
return `note:${result.noteId}`;
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
async function recordShadowMeasurement(args) {
|
|
1125
|
-
try {
|
|
1126
|
-
const shadowCohort = getShadowEmbeddingMeasurementCohort(args.projectPath);
|
|
1127
|
-
if (!shadowCohort)
|
|
1128
|
-
return;
|
|
1129
|
-
const primaryCohort = getPrimaryEmbeddingMeasurementCohort(args.projectPath);
|
|
1130
|
-
const shadowStartedAt = Date.now();
|
|
1131
|
-
let shadowFailed = false;
|
|
1132
|
-
let shadowResults = [];
|
|
1133
|
-
try {
|
|
1134
|
-
const vector = await embedShadowTextForProject(args.projectPath, args.query);
|
|
1135
|
-
if (!vector) {
|
|
1136
|
-
shadowFailed = true;
|
|
1137
|
-
} else {
|
|
1138
|
-
shadowResults = await args.search(args.db, args.sessionId, args.projectPath, args.query, {
|
|
1139
|
-
...args.options,
|
|
1140
|
-
embedQuery: async () => vector,
|
|
1141
|
-
isEmbeddingRuntimeEnabled: () => true,
|
|
1142
|
-
embeddingEnabled: true,
|
|
1143
|
-
embeddingModelIdOverride: shadowCohort.modelId,
|
|
1144
|
-
chunkModelIdOverride: shadowCohort.chunkModelId,
|
|
1145
|
-
measurementDisabled: true,
|
|
1146
|
-
countRetrievals: false
|
|
1147
|
-
});
|
|
1148
|
-
}
|
|
1149
|
-
} catch {
|
|
1150
|
-
shadowFailed = true;
|
|
1151
|
-
}
|
|
1152
|
-
const primaryModelId = args.primaryQuery?.modelId ?? primaryCohort?.modelId ?? "";
|
|
1153
|
-
const primaryFingerprint = primaryCohort?.fingerprint ?? "";
|
|
1154
|
-
const primaryEpoch = primaryCohort?.epoch ?? 0;
|
|
1155
|
-
const cohortKey = JSON.stringify({
|
|
1156
|
-
primaryFingerprint,
|
|
1157
|
-
primaryEpoch,
|
|
1158
|
-
shadowFingerprint: shadowCohort.fingerprint,
|
|
1159
|
-
shadowEpoch: shadowCohort.epoch
|
|
1160
|
-
});
|
|
1161
|
-
const primaryIds = args.primaryResults.map(resultId);
|
|
1162
|
-
const shadowIds = shadowResults.map(resultId);
|
|
1163
|
-
const corpusHash = sha256(JSON.stringify({ query: args.query, primaryIds, shadowIds }));
|
|
1164
|
-
recordEmbeddingMeasurement(args.db, {
|
|
1165
|
-
sessionId: args.sessionId,
|
|
1166
|
-
projectPath: args.projectPath,
|
|
1167
|
-
queryText: args.query,
|
|
1168
|
-
cohortKey,
|
|
1169
|
-
primaryResultIds: primaryIds,
|
|
1170
|
-
shadowResultIds: shadowIds,
|
|
1171
|
-
primaryLatencyMs: args.primaryLatencyMs,
|
|
1172
|
-
shadowLatencyMs: Date.now() - shadowStartedAt,
|
|
1173
|
-
primaryFailed: args.primaryQuery === null,
|
|
1174
|
-
shadowFailed,
|
|
1175
|
-
primaryModelId,
|
|
1176
|
-
shadowModelId: shadowCohort.modelId,
|
|
1177
|
-
primaryFingerprint,
|
|
1178
|
-
shadowFingerprint: shadowCohort.fingerprint,
|
|
1179
|
-
primaryEpoch,
|
|
1180
|
-
shadowEpoch: shadowCohort.epoch,
|
|
1181
|
-
corpusHash,
|
|
1182
|
-
coverage: {
|
|
1183
|
-
primaryResultCount: primaryIds.length,
|
|
1184
|
-
shadowResultCount: shadowIds.length,
|
|
1185
|
-
primaryAnswered: args.primaryQuery !== null,
|
|
1186
|
-
shadowAnswered: !shadowFailed
|
|
1187
|
-
}
|
|
1188
|
-
});
|
|
1189
|
-
} catch (error) {
|
|
1190
|
-
log("[magic-context] shadow embedding measurement failed:", error);
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
function sha256(value) {
|
|
1194
|
-
return createHash("sha256").update(value).digest("hex");
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
|
-
// src/features/magic-context/search.ts
|
|
1198
|
-
var DEFAULT_UNIFIED_SEARCH_LIMIT = 10;
|
|
1199
|
-
var MAX_UNIFIED_SEARCH_LIMIT = 25;
|
|
1200
|
-
var FTS_SEMANTIC_CANDIDATE_LIMIT = 50;
|
|
1201
|
-
var SEMANTIC_WEIGHT = 0.7;
|
|
1202
|
-
var FTS_WEIGHT = 0.3;
|
|
1203
|
-
var SINGLE_SOURCE_PENALTY = 0.8;
|
|
1204
|
-
var RESULT_PREVIEW_LIMIT = 220;
|
|
1205
|
-
var MEMORY_SOURCE_BOOST = 1.3;
|
|
1206
|
-
var MESSAGE_SOURCE_BOOST = 1.275;
|
|
1207
|
-
var GIT_COMMIT_SOURCE_BOOST = 1.2;
|
|
1208
|
-
var PRIMER_SOURCE_BOOST = 1.25;
|
|
1209
|
-
var messageSearchStatements = new WeakMap;
|
|
1210
|
-
var sessionFirstMessageSearchStatements = new WeakMap;
|
|
1211
|
-
var messageSearchStatementsWithCutoff = new WeakMap;
|
|
1212
|
-
var sessionFirstMessageSearchStatementsWithCutoff = new WeakMap;
|
|
1213
|
-
var messageSearchStatementsWithDateRange = new WeakMap;
|
|
1214
|
-
var messageSearchDiagnosticStatements = new WeakMap;
|
|
1215
|
-
var sessionFirstMessageSearchDiagnosticStatements = new WeakMap;
|
|
1216
|
-
var messageSearchDiagnosticStatementsWithDateRange = new WeakMap;
|
|
1217
|
-
var batchedMessageSearchStatements = new WeakMap;
|
|
1218
|
-
var batchedFtsCountStatements = new WeakMap;
|
|
1219
|
-
function createUnifiedSearchDiagnostics() {
|
|
1220
|
-
return {
|
|
1221
|
-
suppressedVisibleMemoryIds: [],
|
|
1222
|
-
suppressedLiveMessageMatches: 0,
|
|
1223
|
-
gitCommitUnavailable: null
|
|
1224
|
-
};
|
|
1225
|
-
}
|
|
1226
|
-
function normalizeLimit(limit) {
|
|
1227
|
-
if (typeof limit !== "number" || !Number.isFinite(limit)) {
|
|
1228
|
-
return DEFAULT_UNIFIED_SEARCH_LIMIT;
|
|
1229
|
-
}
|
|
1230
|
-
return Math.min(MAX_UNIFIED_SEARCH_LIMIT, Math.max(1, Math.floor(limit)));
|
|
1231
|
-
}
|
|
1232
|
-
function normalizeDateRange(from, to) {
|
|
1233
|
-
if (from === undefined && to === undefined)
|
|
1234
|
-
return null;
|
|
1235
|
-
return {
|
|
1236
|
-
from: typeof from === "number" && Number.isSafeInteger(from) ? from : Number.MIN_SAFE_INTEGER,
|
|
1237
|
-
to: typeof to === "number" && Number.isSafeInteger(to) ? to : Number.MAX_SAFE_INTEGER
|
|
1238
|
-
};
|
|
1239
|
-
}
|
|
1240
|
-
function timestampIsInRange(timestamp, range) {
|
|
1241
|
-
return range === null || timestamp >= range.from && timestamp <= range.to;
|
|
1242
|
-
}
|
|
1243
|
-
var ID_SHAPED_QUERY_MAX_TOKENS = 5;
|
|
1244
|
-
var ID_SHAPED_TOKEN = /^#?\d+$/;
|
|
1245
|
-
function parseIdShapedQuery(query) {
|
|
1246
|
-
const trimmed = query.trim();
|
|
1247
|
-
if (trimmed.length === 0) {
|
|
1248
|
-
return null;
|
|
1249
|
-
}
|
|
1250
|
-
const tokens = trimmed.split(/[\s,]+/).filter((token) => token.length > 0);
|
|
1251
|
-
if (tokens.length === 0 || tokens.length > ID_SHAPED_QUERY_MAX_TOKENS) {
|
|
1252
|
-
return null;
|
|
1253
|
-
}
|
|
1254
|
-
const ids = [];
|
|
1255
|
-
for (const token of tokens) {
|
|
1256
|
-
if (!ID_SHAPED_TOKEN.test(token)) {
|
|
1257
|
-
return null;
|
|
1258
|
-
}
|
|
1259
|
-
const parsed = Number.parseInt(token.replace(/^#/, ""), 10);
|
|
1260
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
1261
|
-
return null;
|
|
1262
|
-
}
|
|
1263
|
-
ids.push(parsed);
|
|
1264
|
-
}
|
|
1265
|
-
return ids;
|
|
1266
|
-
}
|
|
1267
|
-
function normalizeCosineScore(score) {
|
|
1268
|
-
if (!Number.isFinite(score)) {
|
|
1269
|
-
return 0;
|
|
1270
|
-
}
|
|
1271
|
-
return Math.min(1, Math.max(0, score));
|
|
1272
|
-
}
|
|
1273
|
-
function previewText(text) {
|
|
1274
|
-
const normalized = text.replace(/\s+/g, " ").trim();
|
|
1275
|
-
if (normalized.length <= RESULT_PREVIEW_LIMIT) {
|
|
1276
|
-
return normalized;
|
|
1277
|
-
}
|
|
1278
|
-
return `${normalized.slice(0, RESULT_PREVIEW_LIMIT - 1).trimEnd()}…`;
|
|
1279
|
-
}
|
|
1280
|
-
function resolveSearchWorkspaceContext(db, projectPath, identitySet) {
|
|
1281
|
-
const resolved = identitySet ?? resolveWorkspaceIdentitySet(db, projectPath);
|
|
1282
|
-
const isWorkspaced = resolved.identities.length > 1;
|
|
1283
|
-
const expanded = expandWorkspaceIdentitySetWithAliases(db, resolved.identities);
|
|
1284
|
-
const expandedIdentities = isWorkspaced ? expanded.expandedIdentities : resolved.identities;
|
|
1285
|
-
const canonicalIdentityByStoredPath = isWorkspaced ? expanded.canonicalIdentityByStoredPath : new Map(resolved.identities.map((identity) => [identity, identity]));
|
|
1286
|
-
const ownIdentities = expandedIdentities.filter((identity) => canonicalIdentityByStoredPath.get(identity) === projectPath);
|
|
1287
|
-
return {
|
|
1288
|
-
identities: resolved.identities,
|
|
1289
|
-
expandedIdentities,
|
|
1290
|
-
ownIdentities,
|
|
1291
|
-
shareCategories: isWorkspaced ? resolveWorkspaceShareCategories(db, projectPath) : null,
|
|
1292
|
-
namesByIdentity: resolved.namesByIdentity,
|
|
1293
|
-
canonicalIdentityByStoredPath,
|
|
1294
|
-
isWorkspaced
|
|
1295
|
-
};
|
|
1296
|
-
}
|
|
1297
|
-
function memoryWorkspaceIdentity(memory, workspace) {
|
|
1298
|
-
return resolveStoredPathWorkspaceIdentity(memory.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
|
|
1299
|
-
}
|
|
1300
|
-
function sourceNamesForSearchMemories(args) {
|
|
1301
|
-
if (!args.workspace.isWorkspaced)
|
|
1302
|
-
return;
|
|
1303
|
-
const sourceNames = new Map;
|
|
1304
|
-
for (const memory of args.memories) {
|
|
1305
|
-
const source = sourceNameForMemory(memory.projectPath, args.projectPath, args.workspace.identities, args.workspace.namesByIdentity, args.workspace.canonicalIdentityByStoredPath);
|
|
1306
|
-
if (source)
|
|
1307
|
-
sourceNames.set(memory.id, source);
|
|
1308
|
-
}
|
|
1309
|
-
return sourceNames.size > 0 ? sourceNames : undefined;
|
|
1310
|
-
}
|
|
1311
|
-
function getMessageSearchStatement(db, sessionFirst) {
|
|
1312
|
-
const statements = sessionFirst ? sessionFirstMessageSearchStatements : messageSearchStatements;
|
|
1313
|
-
let stmt = statements.get(db);
|
|
1314
|
-
if (!stmt) {
|
|
1315
|
-
stmt = db.prepare(`SELECT message_ordinal AS messageOrdinal, message_id AS messageId, role, content FROM message_history_fts WHERE ${sessionFirst ? MESSAGE_FTS_SESSION_FILTER_SQL : ""}session_id = ?1 AND message_history_fts MATCH ? ORDER BY bm25(message_history_fts), CAST(message_ordinal AS INTEGER) ASC LIMIT ?`);
|
|
1316
|
-
statements.set(db, stmt);
|
|
1317
|
-
}
|
|
1318
|
-
return stmt;
|
|
1319
|
-
}
|
|
1320
|
-
function getMessageSearchStatementWithCutoff(db, sessionFirst) {
|
|
1321
|
-
const statements = sessionFirst ? sessionFirstMessageSearchStatementsWithCutoff : messageSearchStatementsWithCutoff;
|
|
1322
|
-
let stmt = statements.get(db);
|
|
1323
|
-
if (!stmt) {
|
|
1324
|
-
stmt = db.prepare(`SELECT message_ordinal AS messageOrdinal, message_id AS messageId, role, content FROM message_history_fts WHERE ${sessionFirst ? MESSAGE_FTS_SESSION_FILTER_SQL : ""}session_id = ?1 AND message_history_fts MATCH ? AND CAST(message_ordinal AS INTEGER) <= ? ORDER BY bm25(message_history_fts), CAST(message_ordinal AS INTEGER) ASC LIMIT ?`);
|
|
1325
|
-
statements.set(db, stmt);
|
|
1326
|
-
}
|
|
1327
|
-
return stmt;
|
|
1328
|
-
}
|
|
1329
|
-
function getMessageSearchStatementWithDateRange(db, withCutoff) {
|
|
1330
|
-
let statements = messageSearchStatementsWithDateRange.get(db);
|
|
1331
|
-
if (!statements) {
|
|
1332
|
-
statements = new Map;
|
|
1333
|
-
messageSearchStatementsWithDateRange.set(db, statements);
|
|
1334
|
-
}
|
|
1335
|
-
const key = withCutoff ? "cutoff" : "all";
|
|
1336
|
-
let statement = statements.get(key);
|
|
1337
|
-
if (!statement) {
|
|
1338
|
-
statement = db.prepare(`SELECT message_history_fts.message_ordinal AS messageOrdinal,
|
|
1339
|
-
message_history_fts.message_id AS messageId,
|
|
1340
|
-
message_history_fts.role AS role,
|
|
1341
|
-
message_history_fts.content AS content
|
|
1342
|
-
FROM message_history_fts
|
|
1343
|
-
JOIN message_fts_rowid_map AS map
|
|
1344
|
-
ON map.session_id = message_history_fts.session_id
|
|
1345
|
-
AND map.fts_rowid = message_history_fts.rowid
|
|
1346
|
-
WHERE ${MESSAGE_FTS_SESSION_FILTER_SQL}message_history_fts.session_id = ?1
|
|
1347
|
-
AND message_history_fts MATCH ?
|
|
1348
|
-
AND map.message_time_ms BETWEEN ? AND ?
|
|
1349
|
-
${withCutoff ? "AND CAST(message_history_fts.message_ordinal AS INTEGER) <= ?" : ""}
|
|
1350
|
-
ORDER BY bm25(message_history_fts),
|
|
1351
|
-
CAST(message_history_fts.message_ordinal AS INTEGER) ASC
|
|
1352
|
-
LIMIT ?`);
|
|
1353
|
-
statements.set(key, statement);
|
|
1354
|
-
}
|
|
1355
|
-
return statement;
|
|
1356
|
-
}
|
|
1357
|
-
function getMessageSearchDiagnosticStatement(db, sessionFirst) {
|
|
1358
|
-
const statements = sessionFirst ? sessionFirstMessageSearchDiagnosticStatements : messageSearchDiagnosticStatements;
|
|
1359
|
-
let stmt = statements.get(db);
|
|
1360
|
-
if (!stmt) {
|
|
1361
|
-
stmt = db.prepare(`
|
|
1362
|
-
WITH matches AS MATERIALIZED (
|
|
1363
|
-
SELECT
|
|
1364
|
-
message_ordinal AS messageOrdinal,
|
|
1365
|
-
message_id AS messageId,
|
|
1366
|
-
role,
|
|
1367
|
-
content,
|
|
1368
|
-
CAST(message_ordinal AS INTEGER) AS ordinalValue,
|
|
1369
|
-
bm25(message_history_fts) AS ftsRank
|
|
1370
|
-
FROM message_history_fts
|
|
1371
|
-
WHERE ${sessionFirst ? MESSAGE_FTS_SESSION_FILTER_SQL : ""}session_id = ?1 AND message_history_fts MATCH ?
|
|
1372
|
-
),
|
|
1373
|
-
eligible AS (
|
|
1374
|
-
SELECT * FROM matches
|
|
1375
|
-
WHERE ordinalValue <= ?
|
|
1376
|
-
ORDER BY ftsRank, ordinalValue ASC
|
|
1377
|
-
LIMIT ?
|
|
1378
|
-
),
|
|
1379
|
-
summary AS (
|
|
1380
|
-
SELECT COUNT(*) AS suppressedCount
|
|
1381
|
-
FROM matches
|
|
1382
|
-
WHERE ordinalValue > ?
|
|
1383
|
-
)
|
|
1384
|
-
SELECT
|
|
1385
|
-
eligible.messageOrdinal,
|
|
1386
|
-
eligible.messageId,
|
|
1387
|
-
eligible.role,
|
|
1388
|
-
eligible.content,
|
|
1389
|
-
eligible.ftsRank,
|
|
1390
|
-
summary.suppressedCount,
|
|
1391
|
-
0 AS summaryOnly
|
|
1392
|
-
FROM eligible CROSS JOIN summary
|
|
1393
|
-
UNION ALL
|
|
1394
|
-
SELECT NULL, NULL, NULL, NULL, NULL, summary.suppressedCount, 1
|
|
1395
|
-
FROM summary
|
|
1396
|
-
WHERE NOT EXISTS (SELECT 1 FROM eligible)
|
|
1397
|
-
ORDER BY summaryOnly ASC, ftsRank ASC, messageOrdinal ASC
|
|
1398
|
-
`);
|
|
1399
|
-
statements.set(db, stmt);
|
|
1400
|
-
}
|
|
1401
|
-
return stmt;
|
|
1402
|
-
}
|
|
1403
|
-
function getMessageSearchDiagnosticStatementWithDateRange(db) {
|
|
1404
|
-
let statement = messageSearchDiagnosticStatementsWithDateRange.get(db);
|
|
1405
|
-
if (!statement) {
|
|
1406
|
-
statement = db.prepare(`
|
|
1407
|
-
WITH matches AS MATERIALIZED (
|
|
1408
|
-
SELECT
|
|
1409
|
-
message_history_fts.message_ordinal AS messageOrdinal,
|
|
1410
|
-
message_history_fts.message_id AS messageId,
|
|
1411
|
-
message_history_fts.role AS role,
|
|
1412
|
-
message_history_fts.content AS content,
|
|
1413
|
-
CAST(message_history_fts.message_ordinal AS INTEGER) AS ordinalValue,
|
|
1414
|
-
bm25(message_history_fts) AS ftsRank
|
|
1415
|
-
FROM message_history_fts
|
|
1416
|
-
JOIN message_fts_rowid_map AS map
|
|
1417
|
-
ON map.session_id = message_history_fts.session_id
|
|
1418
|
-
AND map.fts_rowid = message_history_fts.rowid
|
|
1419
|
-
WHERE ${MESSAGE_FTS_SESSION_FILTER_SQL}message_history_fts.session_id = ?1
|
|
1420
|
-
AND message_history_fts MATCH ?
|
|
1421
|
-
AND map.message_time_ms BETWEEN ? AND ?
|
|
1422
|
-
),
|
|
1423
|
-
eligible AS (
|
|
1424
|
-
SELECT * FROM matches
|
|
1425
|
-
WHERE ordinalValue <= ?
|
|
1426
|
-
ORDER BY ftsRank, ordinalValue ASC
|
|
1427
|
-
LIMIT ?
|
|
1428
|
-
),
|
|
1429
|
-
summary AS (
|
|
1430
|
-
SELECT COUNT(*) AS suppressedCount
|
|
1431
|
-
FROM matches
|
|
1432
|
-
WHERE ordinalValue > ?
|
|
1433
|
-
)
|
|
1434
|
-
SELECT
|
|
1435
|
-
eligible.messageOrdinal,
|
|
1436
|
-
eligible.messageId,
|
|
1437
|
-
eligible.role,
|
|
1438
|
-
eligible.content,
|
|
1439
|
-
eligible.ftsRank,
|
|
1440
|
-
summary.suppressedCount,
|
|
1441
|
-
0 AS summaryOnly
|
|
1442
|
-
FROM eligible CROSS JOIN summary
|
|
1443
|
-
UNION ALL
|
|
1444
|
-
SELECT NULL, NULL, NULL, NULL, NULL, summary.suppressedCount, 1
|
|
1445
|
-
FROM summary
|
|
1446
|
-
WHERE NOT EXISTS (SELECT 1 FROM eligible)
|
|
1447
|
-
ORDER BY summaryOnly ASC, ftsRank ASC, messageOrdinal ASC
|
|
1448
|
-
`);
|
|
1449
|
-
messageSearchDiagnosticStatementsWithDateRange.set(db, statement);
|
|
1450
|
-
}
|
|
1451
|
-
return statement;
|
|
1452
|
-
}
|
|
1453
|
-
function getBatchedFtsCountStatement(db, queryCount, cutoff, dateRange, sessionFirst) {
|
|
1454
|
-
let statements = batchedFtsCountStatements.get(db);
|
|
1455
|
-
if (!statements) {
|
|
1456
|
-
statements = new Map;
|
|
1457
|
-
batchedFtsCountStatements.set(db, statements);
|
|
1458
|
-
}
|
|
1459
|
-
const key = `${queryCount}:${cutoff === null ? "all" : "cutoff"}:${dateRange === null ? "all-dates" : "dated"}:${sessionFirst}`;
|
|
1460
|
-
let statement = statements.get(key);
|
|
1461
|
-
if (!statement) {
|
|
1462
|
-
const cutoffSql = cutoff === null ? "" : " AND CAST(message_history_fts.message_ordinal AS INTEGER) <= ?";
|
|
1463
|
-
const joinSql = dateRange === null ? "" : ` JOIN message_fts_rowid_map AS map
|
|
1464
|
-
ON map.session_id = message_history_fts.session_id
|
|
1465
|
-
AND map.fts_rowid = message_history_fts.rowid`;
|
|
1466
|
-
const dateSql = dateRange === null ? "" : " AND map.message_time_ms BETWEEN ? AND ?";
|
|
1467
|
-
statement = db.prepare(Array.from({ length: queryCount }, (_, index) => `SELECT ${index} AS queryIndex, COUNT(*) AS count
|
|
1468
|
-
FROM message_history_fts${joinSql}
|
|
1469
|
-
WHERE ${sessionFirst || dateRange !== null ? MESSAGE_FTS_SESSION_FILTER_SQL : ""}message_history_fts.session_id = ${index === 0 ? "?1" : "?"}
|
|
1470
|
-
AND message_history_fts MATCH ?${dateSql}${cutoffSql}`).join(`
|
|
1471
|
-
UNION ALL
|
|
1472
|
-
`));
|
|
1473
|
-
statements.set(key, statement);
|
|
1474
|
-
}
|
|
1475
|
-
return statement;
|
|
1476
|
-
}
|
|
1477
|
-
function contentOnlyMessageQuery(ftsQuery) {
|
|
1478
|
-
return ftsQuery.length === 0 ? "" : `content : (${ftsQuery})`;
|
|
1479
|
-
}
|
|
1480
|
-
function countSessionFtsMatchesBatch(db, sessionId, ftsQueries, cutoff, dateRange, sessionFirst) {
|
|
1481
|
-
if (ftsQueries.length === 0)
|
|
1482
|
-
return [];
|
|
1483
|
-
const bindings = [];
|
|
1484
|
-
for (const query of ftsQueries) {
|
|
1485
|
-
bindings.push(sessionId, contentOnlyMessageQuery(query));
|
|
1486
|
-
if (dateRange !== null)
|
|
1487
|
-
bindings.push(dateRange.from, dateRange.to);
|
|
1488
|
-
if (cutoff !== null)
|
|
1489
|
-
bindings.push(cutoff);
|
|
1490
|
-
}
|
|
1491
|
-
try {
|
|
1492
|
-
const rows = getBatchedFtsCountStatement(db, ftsQueries.length, cutoff, dateRange, sessionFirst).all(...bindings);
|
|
1493
|
-
const counts = Array.from({ length: ftsQueries.length }, () => 0);
|
|
1494
|
-
for (const row of rows) {
|
|
1495
|
-
if (typeof row.queryIndex === "number" && row.queryIndex >= 0 && row.queryIndex < counts.length && typeof row.count === "number") {
|
|
1496
|
-
counts[row.queryIndex] = row.count;
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
return counts;
|
|
1500
|
-
} catch {
|
|
1501
|
-
return Array.from({ length: ftsQueries.length }, () => 0);
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
1504
|
-
function getMessageOrdinal(value) {
|
|
1505
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1506
|
-
return value;
|
|
1507
|
-
}
|
|
1508
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
1509
|
-
const parsed = Number.parseInt(value, 10);
|
|
1510
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
1511
|
-
}
|
|
1512
|
-
return null;
|
|
1513
|
-
}
|
|
1514
|
-
async function getSemanticScores(args) {
|
|
1515
|
-
const semanticScores = new Map;
|
|
1516
|
-
if (!args.queryEmbedding || args.memories.length === 0 || !args.queryModelId || args.queryModelId === "off") {
|
|
1517
|
-
return semanticScores;
|
|
1518
|
-
}
|
|
1519
|
-
if (!args.workspace?.isWorkspaced) {
|
|
1520
|
-
const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath, args.queryModelId);
|
|
1521
|
-
const embeddings = await ensureMemoryEmbeddings({
|
|
1522
|
-
db: args.db,
|
|
1523
|
-
projectIdentity: args.projectPath,
|
|
1524
|
-
memories: args.memories,
|
|
1525
|
-
existingEmbeddings: cachedEmbeddings
|
|
1526
|
-
});
|
|
1527
|
-
for (const memory of args.memories) {
|
|
1528
|
-
const memoryEmbedding = embeddings.get(memory.id);
|
|
1529
|
-
if (!memoryEmbedding) {
|
|
1530
|
-
continue;
|
|
1531
|
-
}
|
|
1532
|
-
semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
|
|
1533
|
-
}
|
|
1534
|
-
return semanticScores;
|
|
1535
|
-
}
|
|
1536
|
-
const workspace = args.workspace;
|
|
1537
|
-
const memoriesByIdentity = new Map;
|
|
1538
|
-
for (const memory of args.memories) {
|
|
1539
|
-
const identity = memoryWorkspaceIdentity(memory, workspace);
|
|
1540
|
-
if (!identity)
|
|
1541
|
-
continue;
|
|
1542
|
-
const list = memoriesByIdentity.get(identity) ?? [];
|
|
1543
|
-
list.push(memory);
|
|
1544
|
-
memoriesByIdentity.set(identity, list);
|
|
1545
|
-
}
|
|
1546
|
-
const ownMemories = memoriesByIdentity.get(args.projectPath) ?? [];
|
|
1547
|
-
if (ownMemories.length > 0) {
|
|
1548
|
-
const ownEmbeddings = getProjectEmbeddings(args.db, args.projectPath, args.queryModelId);
|
|
1549
|
-
await ensureMemoryEmbeddings({
|
|
1550
|
-
db: args.db,
|
|
1551
|
-
projectIdentity: args.projectPath,
|
|
1552
|
-
memories: ownMemories,
|
|
1553
|
-
existingEmbeddings: ownEmbeddings
|
|
1554
|
-
});
|
|
1555
|
-
}
|
|
1556
|
-
for (const identity of workspace.identities) {
|
|
1557
|
-
const memberMemories = memoriesByIdentity.get(identity) ?? [];
|
|
1558
|
-
if (memberMemories.length === 0)
|
|
1559
|
-
continue;
|
|
1560
|
-
const cachedEmbeddings = getProjectEmbeddings(args.db, identity, args.queryModelId);
|
|
1561
|
-
for (const memory of memberMemories) {
|
|
1562
|
-
const memoryEmbedding = cachedEmbeddings.get(memory.id);
|
|
1563
|
-
if (!memoryEmbedding || memoryEmbedding.modelId !== args.queryModelId)
|
|
1564
|
-
continue;
|
|
1565
|
-
semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
|
|
1566
|
-
}
|
|
1567
|
-
}
|
|
1568
|
-
return semanticScores;
|
|
1569
|
-
}
|
|
1570
|
-
function getFtsMatches(args) {
|
|
1571
|
-
try {
|
|
1572
|
-
return args.workspace?.isWorkspaced ? searchMemoriesFTSUnion(args.db, args.workspace.expandedIdentities, args.query, args.limit, args.workspace.ownIdentities, args.workspace.shareCategories, args.dateRange) : searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit, args.dateRange);
|
|
1573
|
-
} catch (error) {
|
|
1574
|
-
log(`[search] FTS query failed for "${args.query}": ${error instanceof Error ? error.message : String(error)}`);
|
|
1575
|
-
return [];
|
|
1576
|
-
}
|
|
1577
|
-
}
|
|
1578
|
-
function getFtsScores(matches) {
|
|
1579
|
-
return new Map(matches.map((memory, rank) => [memory.id, 1 / (rank + 1)]));
|
|
1580
|
-
}
|
|
1581
|
-
function selectSemanticCandidates(args) {
|
|
1582
|
-
if (args.ftsMatches.length === 0) {
|
|
1583
|
-
return args.memories;
|
|
1584
|
-
}
|
|
1585
|
-
const candidateIds = new Set(args.ftsMatches.map((memory) => memory.id));
|
|
1586
|
-
if (args.queryModelId && args.queryModelId !== "off") {
|
|
1587
|
-
const embeddingProjects = args.workspace?.isWorkspaced ? args.workspace.identities : [args.projectPath];
|
|
1588
|
-
for (const projectPath of embeddingProjects) {
|
|
1589
|
-
const storedEmbeddings = getProjectEmbeddings(args.db, projectPath, args.queryModelId);
|
|
1590
|
-
for (const memoryId of storedEmbeddings.keys()) {
|
|
1591
|
-
candidateIds.add(memoryId);
|
|
1592
|
-
}
|
|
1593
|
-
}
|
|
1594
|
-
}
|
|
1595
|
-
return args.memories.filter((memory) => candidateIds.has(memory.id));
|
|
1596
|
-
}
|
|
1597
|
-
function mergeMemoryResults(args) {
|
|
1598
|
-
const memoryById = new Map(args.memories.map((memory) => [memory.id, memory]));
|
|
1599
|
-
const candidateIds = new Set([...args.semanticScores.keys(), ...args.ftsScores.keys()]);
|
|
1600
|
-
const results = [];
|
|
1601
|
-
const suppressedVisibleIds = [];
|
|
1602
|
-
for (const id of candidateIds) {
|
|
1603
|
-
const memory = memoryById.get(id);
|
|
1604
|
-
if (!memory) {
|
|
1605
|
-
continue;
|
|
1606
|
-
}
|
|
1607
|
-
const semanticScore = args.semanticScores.get(id);
|
|
1608
|
-
const ftsScore = args.ftsScores.get(id);
|
|
1609
|
-
let score = 0;
|
|
1610
|
-
let matchType = "fts";
|
|
1611
|
-
if (semanticScore !== undefined && ftsScore !== undefined) {
|
|
1612
|
-
score = SEMANTIC_WEIGHT * semanticScore + FTS_WEIGHT * ftsScore;
|
|
1613
|
-
matchType = "hybrid";
|
|
1614
|
-
} else if (semanticScore !== undefined) {
|
|
1615
|
-
score = semanticScore * SINGLE_SOURCE_PENALTY;
|
|
1616
|
-
matchType = "semantic";
|
|
1617
|
-
} else if (ftsScore !== undefined) {
|
|
1618
|
-
score = ftsScore * SINGLE_SOURCE_PENALTY;
|
|
1619
|
-
matchType = "fts";
|
|
1620
|
-
}
|
|
1621
|
-
if (score <= 0) {
|
|
1622
|
-
continue;
|
|
1623
|
-
}
|
|
1624
|
-
if (args.visibleMemoryIds?.has(id)) {
|
|
1625
|
-
suppressedVisibleIds.push(id);
|
|
1626
|
-
continue;
|
|
1627
|
-
}
|
|
1628
|
-
results.push({
|
|
1629
|
-
source: "memory",
|
|
1630
|
-
content: previewText(memory.content),
|
|
1631
|
-
score,
|
|
1632
|
-
memoryId: memory.id,
|
|
1633
|
-
category: memory.category,
|
|
1634
|
-
matchType,
|
|
1635
|
-
sourceName: args.sourceNameByMemoryId?.get(memory.id)
|
|
1636
|
-
});
|
|
1637
|
-
}
|
|
1638
|
-
return {
|
|
1639
|
-
results: results.sort((left, right) => {
|
|
1640
|
-
if (right.score !== left.score) {
|
|
1641
|
-
return right.score - left.score;
|
|
1642
|
-
}
|
|
1643
|
-
return left.memoryId - right.memoryId;
|
|
1644
|
-
}).slice(0, args.limit),
|
|
1645
|
-
suppressedVisibleIds: suppressedVisibleIds.sort((left, right) => left - right)
|
|
1646
|
-
};
|
|
1647
|
-
}
|
|
1648
|
-
async function searchMemories(args) {
|
|
1649
|
-
if (!args.memoryEnabled) {
|
|
1650
|
-
return { results: [], suppressedVisibleIds: [] };
|
|
1651
|
-
}
|
|
1652
|
-
const unfilteredMemories = args.workspace?.isWorkspaced ? getMemoriesByProjects(args.db, args.workspace.expandedIdentities, ["active", "permanent"], Date.now(), args.workspace.ownIdentities, args.workspace.shareCategories) : getMemoriesByProject(args.db, args.projectPath);
|
|
1653
|
-
const memories = unfilteredMemories.filter((memory) => timestampIsInRange(memory.createdAt, args.dateRange));
|
|
1654
|
-
if (memories.length === 0) {
|
|
1655
|
-
return { results: [], suppressedVisibleIds: [] };
|
|
1656
|
-
}
|
|
1657
|
-
const ftsMatches = getFtsMatches({
|
|
1658
|
-
db: args.db,
|
|
1659
|
-
projectPath: args.projectPath,
|
|
1660
|
-
query: args.query,
|
|
1661
|
-
limit: FTS_SEMANTIC_CANDIDATE_LIMIT,
|
|
1662
|
-
workspace: args.workspace,
|
|
1663
|
-
dateRange: args.dateRange
|
|
1664
|
-
});
|
|
1665
|
-
const ftsScores = getFtsScores(ftsMatches);
|
|
1666
|
-
const semanticCandidates = selectSemanticCandidates({
|
|
1667
|
-
db: args.db,
|
|
1668
|
-
memories,
|
|
1669
|
-
projectPath: args.projectPath,
|
|
1670
|
-
ftsMatches,
|
|
1671
|
-
queryModelId: args.queryModelId,
|
|
1672
|
-
workspace: args.workspace
|
|
1673
|
-
});
|
|
1674
|
-
const semanticScores = await getSemanticScores({
|
|
1675
|
-
db: args.db,
|
|
1676
|
-
projectPath: args.projectPath,
|
|
1677
|
-
memories: semanticCandidates,
|
|
1678
|
-
queryEmbedding: args.queryEmbedding,
|
|
1679
|
-
queryModelId: args.queryModelId,
|
|
1680
|
-
workspace: args.workspace
|
|
1681
|
-
});
|
|
1682
|
-
return mergeMemoryResults({
|
|
1683
|
-
memories,
|
|
1684
|
-
semanticScores,
|
|
1685
|
-
ftsScores,
|
|
1686
|
-
limit: args.limit,
|
|
1687
|
-
visibleMemoryIds: args.visibleMemoryIds,
|
|
1688
|
-
sourceNameByMemoryId: sourceNamesForSearchMemories({
|
|
1689
|
-
memories,
|
|
1690
|
-
projectPath: args.projectPath,
|
|
1691
|
-
workspace: args.workspace ?? {
|
|
1692
|
-
identities: [args.projectPath],
|
|
1693
|
-
expandedIdentities: [args.projectPath],
|
|
1694
|
-
namesByIdentity: new Map,
|
|
1695
|
-
canonicalIdentityByStoredPath: new Map([[args.projectPath, args.projectPath]]),
|
|
1696
|
-
ownIdentities: [args.projectPath],
|
|
1697
|
-
shareCategories: null,
|
|
1698
|
-
isWorkspaced: false
|
|
1699
|
-
}
|
|
1700
|
-
})
|
|
1701
|
-
});
|
|
1702
|
-
}
|
|
1703
|
-
function linearDecayScore(rank, total) {
|
|
1704
|
-
if (total <= 0)
|
|
1705
|
-
return 0;
|
|
1706
|
-
return Math.max(0, 1 - rank / total);
|
|
1707
|
-
}
|
|
1708
|
-
function normalizeMessageSearchRow(row, cutoff) {
|
|
1709
|
-
const messageOrdinal = getMessageOrdinal(row.messageOrdinal);
|
|
1710
|
-
if (messageOrdinal === null || typeof row.messageId !== "string" || typeof row.role !== "string" || typeof row.content !== "string") {
|
|
1711
|
-
return null;
|
|
1712
|
-
}
|
|
1713
|
-
if (cutoff !== null && messageOrdinal > cutoff)
|
|
1714
|
-
return null;
|
|
1715
|
-
return {
|
|
1716
|
-
messageOrdinal,
|
|
1717
|
-
messageId: row.messageId,
|
|
1718
|
-
role: row.role,
|
|
1719
|
-
content: row.content
|
|
1720
|
-
};
|
|
1721
|
-
}
|
|
1722
|
-
function runMessageFtsQuery(db, sessionId, ftsQuery, fetchLimit, cutoff, dateRange, sessionFirst) {
|
|
1723
|
-
if (ftsQuery.length === 0)
|
|
1724
|
-
return [];
|
|
1725
|
-
let rawRows;
|
|
1726
|
-
const matchQuery = contentOnlyMessageQuery(ftsQuery);
|
|
1727
|
-
if (dateRange !== null) {
|
|
1728
|
-
const bindings = [sessionId, matchQuery, dateRange.from, dateRange.to];
|
|
1729
|
-
if (cutoff !== null)
|
|
1730
|
-
bindings.push(cutoff);
|
|
1731
|
-
bindings.push(fetchLimit);
|
|
1732
|
-
rawRows = getMessageSearchStatementWithDateRange(db, cutoff !== null).all(...bindings);
|
|
1733
|
-
} else {
|
|
1734
|
-
rawRows = cutoff !== null ? getMessageSearchStatementWithCutoff(db, sessionFirst).all(sessionId, matchQuery, cutoff, fetchLimit) : getMessageSearchStatement(db, sessionFirst).all(sessionId, matchQuery, fetchLimit);
|
|
1735
|
-
}
|
|
1736
|
-
const rows = rawRows.map((row) => row);
|
|
1737
|
-
const result = [];
|
|
1738
|
-
for (const row of rows) {
|
|
1739
|
-
const normalized = normalizeMessageSearchRow(row, cutoff);
|
|
1740
|
-
if (normalized)
|
|
1741
|
-
result.push(normalized);
|
|
1742
|
-
}
|
|
1743
|
-
return result;
|
|
1744
|
-
}
|
|
1745
|
-
function runMessageFtsQueryWithDiagnostics(args) {
|
|
1746
|
-
if (args.ftsQuery.length === 0)
|
|
1747
|
-
return { rows: [], suppressedCount: 0 };
|
|
1748
|
-
const matchQuery = contentOnlyMessageQuery(args.ftsQuery);
|
|
1749
|
-
const rawRows = (args.dateRange === null ? getMessageSearchDiagnosticStatement(args.db, args.sessionFirst).all(args.sessionId, matchQuery, args.cutoff, args.fetchLimit, args.cutoff) : getMessageSearchDiagnosticStatementWithDateRange(args.db).all(args.sessionId, matchQuery, args.dateRange.from, args.dateRange.to, args.cutoff, args.fetchLimit, args.cutoff)).map((row) => row);
|
|
1750
|
-
const suppressedCount = rawRows[0]?.suppressedCount ?? 0;
|
|
1751
|
-
const rows = [];
|
|
1752
|
-
for (const row of rawRows) {
|
|
1753
|
-
if (row.summaryOnly === 1)
|
|
1754
|
-
continue;
|
|
1755
|
-
const normalized = normalizeMessageSearchRow(row, args.cutoff);
|
|
1756
|
-
if (normalized)
|
|
1757
|
-
rows.push(normalized);
|
|
1758
|
-
}
|
|
1759
|
-
return { rows, suppressedCount };
|
|
1760
|
-
}
|
|
1761
|
-
function getBatchedMessageSearchStatement(db, queryCount, cutoff, dateRange, sessionFirst) {
|
|
1762
|
-
let statements = batchedMessageSearchStatements.get(db);
|
|
1763
|
-
if (!statements) {
|
|
1764
|
-
statements = new Map;
|
|
1765
|
-
batchedMessageSearchStatements.set(db, statements);
|
|
1766
|
-
}
|
|
1767
|
-
const key = `${queryCount}:${cutoff === null ? "all" : "cutoff"}:${dateRange === null ? "all-dates" : "dated"}:${sessionFirst}`;
|
|
1768
|
-
let statement = statements.get(key);
|
|
1769
|
-
if (!statement) {
|
|
1770
|
-
const cutoffSql = cutoff === null ? "" : " AND CAST(message_history_fts.message_ordinal AS INTEGER) <= ?";
|
|
1771
|
-
const joinSql = dateRange === null ? "" : ` JOIN message_fts_rowid_map AS map
|
|
1772
|
-
ON map.session_id = message_history_fts.session_id
|
|
1773
|
-
AND map.fts_rowid = message_history_fts.rowid`;
|
|
1774
|
-
const dateSql = dateRange === null ? "" : " AND map.message_time_ms BETWEEN ? AND ?";
|
|
1775
|
-
const branches = Array.from({ length: queryCount }, (_, index) => `SELECT * FROM (
|
|
1776
|
-
SELECT ${index} AS queryIndex,
|
|
1777
|
-
message_history_fts.message_ordinal AS messageOrdinal,
|
|
1778
|
-
message_history_fts.message_id AS messageId,
|
|
1779
|
-
message_history_fts.role AS role,
|
|
1780
|
-
message_history_fts.content AS content,
|
|
1781
|
-
bm25(message_history_fts) AS ftsRank
|
|
1782
|
-
FROM message_history_fts${joinSql}
|
|
1783
|
-
WHERE ${sessionFirst || dateRange !== null ? MESSAGE_FTS_SESSION_FILTER_SQL : ""}message_history_fts.session_id = ${index === 0 ? "?1" : "?"}
|
|
1784
|
-
AND message_history_fts MATCH ?${dateSql}${cutoffSql}
|
|
1785
|
-
ORDER BY ftsRank
|
|
1786
|
-
LIMIT ?
|
|
1787
|
-
)`);
|
|
1788
|
-
statement = db.prepare(`${branches.join(`
|
|
1789
|
-
UNION ALL
|
|
1790
|
-
`)}
|
|
1791
|
-
ORDER BY queryIndex ASC, ftsRank ASC`);
|
|
1792
|
-
statements.set(key, statement);
|
|
1793
|
-
}
|
|
1794
|
-
return statement;
|
|
1795
|
-
}
|
|
1796
|
-
function runMessageFtsQueriesBatch(db, sessionId, ftsQueries, fetchLimit, cutoff, dateRange, sessionFirst) {
|
|
1797
|
-
if (ftsQueries.length === 0)
|
|
1798
|
-
return [];
|
|
1799
|
-
const bindings = [];
|
|
1800
|
-
for (const query of ftsQueries) {
|
|
1801
|
-
bindings.push(sessionId, contentOnlyMessageQuery(query));
|
|
1802
|
-
if (dateRange !== null)
|
|
1803
|
-
bindings.push(dateRange.from, dateRange.to);
|
|
1804
|
-
if (cutoff !== null)
|
|
1805
|
-
bindings.push(cutoff);
|
|
1806
|
-
bindings.push(fetchLimit);
|
|
1807
|
-
}
|
|
1808
|
-
const rows = getBatchedMessageSearchStatement(db, ftsQueries.length, cutoff, dateRange, sessionFirst).all(...bindings);
|
|
1809
|
-
const result = Array.from({ length: ftsQueries.length }, () => []);
|
|
1810
|
-
for (const row of rows) {
|
|
1811
|
-
if (typeof row.queryIndex !== "number" || row.queryIndex < 0 || row.queryIndex >= result.length) {
|
|
1812
|
-
continue;
|
|
1813
|
-
}
|
|
1814
|
-
const normalized = normalizeMessageSearchRow(row, cutoff);
|
|
1815
|
-
if (normalized)
|
|
1816
|
-
result[row.queryIndex].push(normalized);
|
|
1817
|
-
}
|
|
1818
|
-
return result;
|
|
1819
|
-
}
|
|
1820
|
-
var RRF_K = 60;
|
|
1821
|
-
var VERBATIM_RANK_BONUS = 1 / RRF_K;
|
|
1822
|
-
var IDF_FALLOFF = 100;
|
|
1823
|
-
function probeDiscriminationWeight(df, corpusSize) {
|
|
1824
|
-
if (corpusSize <= 0 || df <= 0)
|
|
1825
|
-
return 1;
|
|
1826
|
-
return 1 / (1 + IDF_FALLOFF * df / corpusSize);
|
|
1827
|
-
}
|
|
1828
|
-
function searchMessages(args) {
|
|
1829
|
-
return withMessageFtsSessionFilter(args.db, args.sessionId, (sessionFirst) => searchMessagesInSnapshot(args, sessionFirst));
|
|
1830
|
-
}
|
|
1831
|
-
function searchMessagesInSnapshot(args, sessionFirst) {
|
|
1832
|
-
const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
|
|
1833
|
-
const fetchLimit = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.limit * 3 : args.limit;
|
|
1834
|
-
const baseQuery = sanitizeFtsQuery(args.query.trim());
|
|
1835
|
-
const probes = args.probes ?? [];
|
|
1836
|
-
if (probes.length === 0) {
|
|
1837
|
-
const outcome = args.diagnostics && cutoff !== null ? runMessageFtsQueryWithDiagnostics({
|
|
1838
|
-
db: args.db,
|
|
1839
|
-
sessionId: args.sessionId,
|
|
1840
|
-
ftsQuery: baseQuery,
|
|
1841
|
-
fetchLimit,
|
|
1842
|
-
cutoff,
|
|
1843
|
-
dateRange: args.dateRange,
|
|
1844
|
-
sessionFirst
|
|
1845
|
-
}) : {
|
|
1846
|
-
rows: runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff, args.dateRange, sessionFirst),
|
|
1847
|
-
suppressedCount: 0
|
|
1848
|
-
};
|
|
1849
|
-
if (args.diagnostics) {
|
|
1850
|
-
args.diagnostics.suppressedLiveMessageMatches = outcome.suppressedCount;
|
|
1851
|
-
}
|
|
1852
|
-
const rows = outcome.rows.length > 0 || !args.relaxedRecall ? outcome.rows : runMessageFtsQuery(args.db, args.sessionId, relaxedFtsQuery(args.query), fetchLimit, cutoff, args.dateRange, sessionFirst);
|
|
1853
|
-
const filtered = rows.slice(0, args.limit);
|
|
1854
|
-
return filtered.map((row, rank) => ({
|
|
1855
|
-
source: "message",
|
|
1856
|
-
content: previewText(row.content),
|
|
1857
|
-
score: linearDecayScore(rank, filtered.length),
|
|
1858
|
-
messageOrdinal: row.messageOrdinal,
|
|
1859
|
-
messageId: row.messageId,
|
|
1860
|
-
role: row.role
|
|
1861
|
-
}));
|
|
1862
|
-
}
|
|
1863
|
-
const sanitizedProbes = probes.map((probe) => ({ probe, query: sanitizeFtsQuery(probe) })).filter((entry) => entry.query.length > 0);
|
|
1864
|
-
const corpusSize = getIndexedMessageCorpusSize(args.db, args.sessionId, cutoff);
|
|
1865
|
-
const probeCounts = countSessionFtsMatchesBatch(args.db, args.sessionId, sanitizedProbes.map((entry) => entry.query), cutoff, args.dateRange, sessionFirst);
|
|
1866
|
-
const collectBaseDiagnostics = args.diagnostics !== undefined && cutoff !== null;
|
|
1867
|
-
const baseOutcome = collectBaseDiagnostics && baseQuery.length > 0 ? runMessageFtsQueryWithDiagnostics({
|
|
1868
|
-
db: args.db,
|
|
1869
|
-
sessionId: args.sessionId,
|
|
1870
|
-
ftsQuery: baseQuery,
|
|
1871
|
-
fetchLimit,
|
|
1872
|
-
cutoff,
|
|
1873
|
-
dateRange: args.dateRange,
|
|
1874
|
-
sessionFirst
|
|
1875
|
-
}) : null;
|
|
1876
|
-
if (args.diagnostics) {
|
|
1877
|
-
args.diagnostics.suppressedLiveMessageMatches = baseOutcome?.suppressedCount ?? 0;
|
|
1878
|
-
}
|
|
1879
|
-
const searchQueries = [
|
|
1880
|
-
...!collectBaseDiagnostics && baseQuery.length > 0 ? [baseQuery] : [],
|
|
1881
|
-
...sanitizedProbes.map((entry) => entry.query)
|
|
1882
|
-
];
|
|
1883
|
-
const rowsByQuery = runMessageFtsQueriesBatch(args.db, args.sessionId, searchQueries, fetchLimit, cutoff, args.dateRange, sessionFirst);
|
|
1884
|
-
const queryLists = [];
|
|
1885
|
-
let queryIndex = 0;
|
|
1886
|
-
if (baseQuery.length > 0) {
|
|
1887
|
-
queryLists.push({
|
|
1888
|
-
rows: baseOutcome?.rows ?? rowsByQuery[queryIndex] ?? [],
|
|
1889
|
-
weight: 1
|
|
1890
|
-
});
|
|
1891
|
-
if (!collectBaseDiagnostics)
|
|
1892
|
-
queryIndex += 1;
|
|
1893
|
-
}
|
|
1894
|
-
const probeWeights = new Map;
|
|
1895
|
-
sanitizedProbes.forEach((entry, probeIndex) => {
|
|
1896
|
-
const weight = probeDiscriminationWeight(probeCounts[probeIndex] ?? 0, corpusSize);
|
|
1897
|
-
probeWeights.set(entry.probe, weight);
|
|
1898
|
-
queryLists.push({ rows: rowsByQuery[queryIndex] ?? [], weight });
|
|
1899
|
-
queryIndex += 1;
|
|
1900
|
-
});
|
|
1901
|
-
const fused = new Map;
|
|
1902
|
-
for (const list of queryLists) {
|
|
1903
|
-
list.rows.forEach((row, rank) => {
|
|
1904
|
-
const rrf = list.weight / (RRF_K + rank);
|
|
1905
|
-
const existing = fused.get(row.messageId);
|
|
1906
|
-
if (existing) {
|
|
1907
|
-
existing.score += rrf;
|
|
1908
|
-
} else {
|
|
1909
|
-
fused.set(row.messageId, { row, score: rrf });
|
|
1910
|
-
}
|
|
1911
|
-
});
|
|
1912
|
-
}
|
|
1913
|
-
for (const entry of fused.values()) {
|
|
1914
|
-
let best = 0;
|
|
1915
|
-
for (const probe of probes) {
|
|
1916
|
-
const weight = probeWeights.get(probe) ?? 0;
|
|
1917
|
-
if (weight > best && containsProbeVerbatim(entry.row.content, [probe])) {
|
|
1918
|
-
best = weight;
|
|
1919
|
-
}
|
|
1920
|
-
}
|
|
1921
|
-
if (best > 0) {
|
|
1922
|
-
entry.score += best * VERBATIM_RANK_BONUS;
|
|
1923
|
-
}
|
|
1924
|
-
}
|
|
1925
|
-
const ranked = [...fused.values()].sort((a, b) => b.score !== a.score ? b.score - a.score : a.row.messageOrdinal - b.row.messageOrdinal).slice(0, args.limit);
|
|
1926
|
-
return ranked.map((entry, rank) => ({
|
|
1927
|
-
source: "message",
|
|
1928
|
-
content: previewText(entry.row.content),
|
|
1929
|
-
score: linearDecayScore(rank, ranked.length),
|
|
1930
|
-
messageOrdinal: entry.row.messageOrdinal,
|
|
1931
|
-
messageId: entry.row.messageId,
|
|
1932
|
-
role: entry.row.role
|
|
1933
|
-
}));
|
|
1934
|
-
}
|
|
1935
|
-
var NOTE_SEARCHABLE_STATUSES = ["active", "pending", "ready"];
|
|
1936
|
-
var MAX_NOTE_KEYWORD_SCORE = 3.5;
|
|
1937
|
-
function noteSearchText(note) {
|
|
1938
|
-
const reason = note.readyReason?.trim();
|
|
1939
|
-
return reason ? `${note.content}
|
|
1940
|
-
Reason: ${reason}` : note.content;
|
|
1941
|
-
}
|
|
1942
|
-
function tokenizeKeywordNeedle(text) {
|
|
1943
|
-
const matches = text.toLowerCase().match(/[a-z0-9/._:-]+/g) ?? [];
|
|
1944
|
-
const seen = new Set;
|
|
1945
|
-
const tokens = [];
|
|
1946
|
-
for (const match of matches) {
|
|
1947
|
-
if (match.length <= 1 || !/[a-z0-9]/.test(match) || seen.has(match)) {
|
|
1948
|
-
continue;
|
|
1949
|
-
}
|
|
1950
|
-
seen.add(match);
|
|
1951
|
-
tokens.push(match);
|
|
1952
|
-
}
|
|
1953
|
-
return tokens;
|
|
1954
|
-
}
|
|
1955
|
-
function normalizeNoteKeywordScore(score) {
|
|
1956
|
-
return normalizeCosineScore(score / MAX_NOTE_KEYWORD_SCORE) * SINGLE_SOURCE_PENALTY;
|
|
1957
|
-
}
|
|
1958
|
-
function rankNotesForNeedle(notes, needle) {
|
|
1959
|
-
const normalizedNeedle = needle.trim().toLowerCase();
|
|
1960
|
-
if (normalizedNeedle.length === 0) {
|
|
1961
|
-
return [];
|
|
1962
|
-
}
|
|
1963
|
-
const needleTokens = tokenizeKeywordNeedle(normalizedNeedle);
|
|
1964
|
-
const ranked = [];
|
|
1965
|
-
for (const note of notes) {
|
|
1966
|
-
const text = noteSearchText(note);
|
|
1967
|
-
const normalizedText = text.toLowerCase();
|
|
1968
|
-
const noteTokens = new Set(tokenizeKeywordNeedle(normalizedText));
|
|
1969
|
-
const exact = normalizedText.includes(normalizedNeedle);
|
|
1970
|
-
const matchedTokens = needleTokens.filter((token) => noteTokens.has(token)).length;
|
|
1971
|
-
if (!exact && matchedTokens === 0) {
|
|
1972
|
-
continue;
|
|
1973
|
-
}
|
|
1974
|
-
const matchedUniqueTokens = new Set(needleTokens.filter((token) => noteTokens.has(token))).size;
|
|
1975
|
-
const coverage = needleTokens.length > 0 ? matchedTokens / needleTokens.length : 0;
|
|
1976
|
-
const density = noteTokens.size > 0 ? matchedUniqueTokens / noteTokens.size : 0;
|
|
1977
|
-
const exactPhrase = exact && (needleTokens.length > 1 || normalizedText.trim() === normalizedNeedle);
|
|
1978
|
-
const allTokens = needleTokens.length > 1 && matchedTokens === needleTokens.length;
|
|
1979
|
-
const score = (exactPhrase ? 2 : 0) + coverage * density + (allTokens ? 0.5 * density : 0);
|
|
1980
|
-
ranked.push({ note, score, text });
|
|
1981
|
-
}
|
|
1982
|
-
return ranked.sort((left, right) => {
|
|
1983
|
-
if (right.score !== left.score) {
|
|
1984
|
-
return right.score - left.score;
|
|
1985
|
-
}
|
|
1986
|
-
if (right.note.createdAt !== left.note.createdAt) {
|
|
1987
|
-
return right.note.createdAt - left.note.createdAt;
|
|
1988
|
-
}
|
|
1989
|
-
return left.note.id - right.note.id;
|
|
1990
|
-
});
|
|
1991
|
-
}
|
|
1992
|
-
function searchNotes(args) {
|
|
1993
|
-
if (args.limit <= 0) {
|
|
1994
|
-
return [];
|
|
1995
|
-
}
|
|
1996
|
-
const notes = [
|
|
1997
|
-
...getNotes(args.db, {
|
|
1998
|
-
sessionId: args.sessionId,
|
|
1999
|
-
type: "session",
|
|
2000
|
-
status: NOTE_SEARCHABLE_STATUSES
|
|
2001
|
-
}),
|
|
2002
|
-
...getNotes(args.db, {
|
|
2003
|
-
projectPath: args.projectPath,
|
|
2004
|
-
type: "smart",
|
|
2005
|
-
status: NOTE_SEARCHABLE_STATUSES
|
|
2006
|
-
})
|
|
2007
|
-
].filter((note) => timestampIsInRange(note.createdAt, args.dateRange));
|
|
2008
|
-
if (notes.length === 0) {
|
|
2009
|
-
return [];
|
|
2010
|
-
}
|
|
2011
|
-
const baseList = rankNotesForNeedle(notes, args.query);
|
|
2012
|
-
const probes = args.probes ?? [];
|
|
2013
|
-
if (probes.length === 0) {
|
|
2014
|
-
const ranked = baseList.slice(0, args.limit);
|
|
2015
|
-
return ranked.map((entry) => ({
|
|
2016
|
-
source: "note",
|
|
2017
|
-
content: previewText(entry.text),
|
|
2018
|
-
score: normalizeNoteKeywordScore(entry.score),
|
|
2019
|
-
noteId: entry.note.id,
|
|
2020
|
-
status: entry.note.status,
|
|
2021
|
-
createdAt: entry.note.createdAt,
|
|
2022
|
-
anchorOrdinal: entry.note.anchorOrdinal,
|
|
2023
|
-
sourceSessionId: entry.note.sessionId
|
|
2024
|
-
}));
|
|
2025
|
-
}
|
|
2026
|
-
const queryLists = [];
|
|
2027
|
-
if (baseList.length > 0) {
|
|
2028
|
-
queryLists.push({ rows: baseList, weight: 1 });
|
|
2029
|
-
}
|
|
2030
|
-
for (const probe of probes) {
|
|
2031
|
-
const rows = rankNotesForNeedle(notes, probe);
|
|
2032
|
-
if (rows.length === 0) {
|
|
2033
|
-
continue;
|
|
2034
|
-
}
|
|
2035
|
-
const weight = probeDiscriminationWeight(rows.length, notes.length);
|
|
2036
|
-
queryLists.push({ rows, weight });
|
|
2037
|
-
}
|
|
2038
|
-
const fused = new Map;
|
|
2039
|
-
for (const list of queryLists) {
|
|
2040
|
-
for (const row of list.rows) {
|
|
2041
|
-
const relevance = row.score * list.weight;
|
|
2042
|
-
const existing = fused.get(row.note.id);
|
|
2043
|
-
if (existing) {
|
|
2044
|
-
if (relevance > existing.score) {
|
|
2045
|
-
existing.entry = row;
|
|
2046
|
-
existing.score = relevance;
|
|
2047
|
-
}
|
|
2048
|
-
} else {
|
|
2049
|
-
fused.set(row.note.id, { entry: row, score: relevance });
|
|
2050
|
-
}
|
|
2051
|
-
}
|
|
2052
|
-
}
|
|
2053
|
-
const ranked = [...fused.values()].sort((left, right) => {
|
|
2054
|
-
if (right.score !== left.score) {
|
|
2055
|
-
return right.score - left.score;
|
|
2056
|
-
}
|
|
2057
|
-
if (right.entry.note.createdAt !== left.entry.note.createdAt) {
|
|
2058
|
-
return right.entry.note.createdAt - left.entry.note.createdAt;
|
|
2059
|
-
}
|
|
2060
|
-
return left.entry.note.id - right.entry.note.id;
|
|
2061
|
-
}).slice(0, args.limit);
|
|
2062
|
-
return ranked.map((entry) => ({
|
|
2063
|
-
source: "note",
|
|
2064
|
-
content: previewText(entry.entry.text),
|
|
2065
|
-
score: normalizeNoteKeywordScore(entry.score),
|
|
2066
|
-
noteId: entry.entry.note.id,
|
|
2067
|
-
status: entry.entry.note.status,
|
|
2068
|
-
createdAt: entry.entry.note.createdAt,
|
|
2069
|
-
anchorOrdinal: entry.entry.note.anchorOrdinal,
|
|
2070
|
-
sourceSessionId: entry.entry.note.sessionId
|
|
2071
|
-
}));
|
|
2072
|
-
}
|
|
2073
|
-
function searchCompartmentChunks(args) {
|
|
2074
|
-
if (!args.queryEmbedding || args.limit <= 0 || !args.modelId || args.modelId === "off")
|
|
2075
|
-
return [];
|
|
2076
|
-
const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
|
|
2077
|
-
const rows = loadCompartmentChunkEmbeddingsForSearch(args.db, args.sessionId, args.projectPath, args.modelId, args.dateRange);
|
|
2078
|
-
if (rows.length === 0)
|
|
2079
|
-
return [];
|
|
2080
|
-
const byCompartment = new Map;
|
|
2081
|
-
for (const row of rows) {
|
|
2082
|
-
if (cutoff !== null && row.endOrdinal > cutoff) {
|
|
2083
|
-
continue;
|
|
2084
|
-
}
|
|
2085
|
-
const score = normalizeCosineScore(cosineSimilarity(args.queryEmbedding, row.vector));
|
|
2086
|
-
if (score <= 0)
|
|
2087
|
-
continue;
|
|
2088
|
-
const existing = byCompartment.get(row.compartmentId);
|
|
2089
|
-
if (!existing || score > existing.score) {
|
|
2090
|
-
byCompartment.set(row.compartmentId, { row, score });
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
return [...byCompartment.values()].sort((left, right) => right.score !== left.score ? right.score - left.score : left.row.startOrdinal - right.row.startOrdinal).slice(0, args.limit).map(({ row, score }) => ({
|
|
2094
|
-
source: "compartment",
|
|
2095
|
-
content: previewText(row.title),
|
|
2096
|
-
score: score * SINGLE_SOURCE_PENALTY,
|
|
2097
|
-
compartmentId: row.compartmentId,
|
|
2098
|
-
sessionId: row.sessionId,
|
|
2099
|
-
title: row.title,
|
|
2100
|
-
startOrdinal: row.startOrdinal,
|
|
2101
|
-
endOrdinal: row.endOrdinal,
|
|
2102
|
-
matchType: "semantic"
|
|
2103
|
-
}));
|
|
2104
|
-
}
|
|
2105
|
-
function mergeMessageAndCompartmentResults(args) {
|
|
2106
|
-
if (args.compartments.length === 0)
|
|
2107
|
-
return args.messages;
|
|
2108
|
-
if (args.messages.length === 0)
|
|
2109
|
-
return args.compartments;
|
|
2110
|
-
const fused = new Map;
|
|
2111
|
-
const add = (key, result, score, tieOrdinal) => {
|
|
2112
|
-
const existing = fused.get(key);
|
|
2113
|
-
if (existing) {
|
|
2114
|
-
existing.score += score;
|
|
2115
|
-
return existing;
|
|
2116
|
-
}
|
|
2117
|
-
const entry = { result, score, tieOrdinal, snippetScore: -1 };
|
|
2118
|
-
fused.set(key, entry);
|
|
2119
|
-
return entry;
|
|
2120
|
-
};
|
|
2121
|
-
args.compartments.forEach((compartment, rank) => {
|
|
2122
|
-
add(`compartment:${compartment.compartmentId}`, compartment, 1 / (RRF_K + rank), compartment.startOrdinal);
|
|
2123
|
-
});
|
|
2124
|
-
for (const [rank, message] of args.messages.entries()) {
|
|
2125
|
-
const containing = args.compartments.find((compartment) => message.messageOrdinal >= compartment.startOrdinal && message.messageOrdinal <= compartment.endOrdinal);
|
|
2126
|
-
const contribution = 1 / (RRF_K + rank);
|
|
2127
|
-
if (!containing) {
|
|
2128
|
-
add(`message:${message.messageId}`, message, contribution, message.messageOrdinal);
|
|
2129
|
-
continue;
|
|
2130
|
-
}
|
|
2131
|
-
const entry = add(`compartment:${containing.compartmentId}`, containing, contribution, containing.startOrdinal);
|
|
2132
|
-
if (message.score > entry.snippetScore && entry.result.source === "compartment") {
|
|
2133
|
-
entry.snippetScore = message.score;
|
|
2134
|
-
entry.result = {
|
|
2135
|
-
...entry.result,
|
|
2136
|
-
matchType: "hybrid",
|
|
2137
|
-
snippet: message.content
|
|
2138
|
-
};
|
|
2139
|
-
}
|
|
2140
|
-
}
|
|
2141
|
-
const ranked = [...fused.values()].sort((left, right) => right.score !== left.score ? right.score - left.score : left.tieOrdinal - right.tieOrdinal).slice(0, args.limit);
|
|
2142
|
-
return ranked.map((entry, rank) => ({
|
|
2143
|
-
...entry.result,
|
|
2144
|
-
score: linearDecayScore(rank, ranked.length)
|
|
2145
|
-
}));
|
|
2146
|
-
}
|
|
2147
|
-
function getSourceBoost(result) {
|
|
2148
|
-
switch (result.source) {
|
|
2149
|
-
case "memory":
|
|
2150
|
-
return MEMORY_SOURCE_BOOST;
|
|
2151
|
-
case "message":
|
|
2152
|
-
case "compartment":
|
|
2153
|
-
return MESSAGE_SOURCE_BOOST;
|
|
2154
|
-
case "git_commit":
|
|
2155
|
-
return GIT_COMMIT_SOURCE_BOOST;
|
|
2156
|
-
case "primer":
|
|
2157
|
-
return PRIMER_SOURCE_BOOST;
|
|
2158
|
-
case "note":
|
|
2159
|
-
return 1;
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
function compareUnifiedResults(left, right) {
|
|
2163
|
-
const leftEffective = left.score * getSourceBoost(left);
|
|
2164
|
-
const rightEffective = right.score * getSourceBoost(right);
|
|
2165
|
-
if (rightEffective !== leftEffective) {
|
|
2166
|
-
return rightEffective - leftEffective;
|
|
2167
|
-
}
|
|
2168
|
-
if (left.source === "memory" && right.source === "memory") {
|
|
2169
|
-
return left.memoryId - right.memoryId;
|
|
2170
|
-
}
|
|
2171
|
-
if (left.source === "message" && right.source === "message") {
|
|
2172
|
-
return left.messageOrdinal - right.messageOrdinal;
|
|
2173
|
-
}
|
|
2174
|
-
if (left.source === "compartment" && right.source === "compartment") {
|
|
2175
|
-
return left.startOrdinal - right.startOrdinal;
|
|
2176
|
-
}
|
|
2177
|
-
if (left.source === "git_commit" && right.source === "git_commit") {
|
|
2178
|
-
return right.committedAtMs - left.committedAtMs;
|
|
2179
|
-
}
|
|
2180
|
-
if (left.source === "primer" && right.source === "primer") {
|
|
2181
|
-
return right.support - left.support || left.primerId - right.primerId;
|
|
2182
|
-
}
|
|
2183
|
-
if (left.source === "note" && right.source === "note") {
|
|
2184
|
-
return right.createdAt - left.createdAt || left.noteId - right.noteId;
|
|
2185
|
-
}
|
|
2186
|
-
return 0;
|
|
2187
|
-
}
|
|
2188
|
-
function toGitCommitResult(hit) {
|
|
2189
|
-
return {
|
|
2190
|
-
source: "git_commit",
|
|
2191
|
-
content: previewText(hit.commit.message),
|
|
2192
|
-
score: hit.score,
|
|
2193
|
-
sha: hit.commit.sha,
|
|
2194
|
-
shortSha: hit.commit.shortSha,
|
|
2195
|
-
author: hit.commit.author,
|
|
2196
|
-
committedAtMs: hit.commit.committedAtMs,
|
|
2197
|
-
matchType: hit.matchType
|
|
2198
|
-
};
|
|
2199
|
-
}
|
|
2200
|
-
function searchGitCommits(args) {
|
|
2201
|
-
if (args.limit <= 0)
|
|
2202
|
-
return [];
|
|
2203
|
-
const hits = searchGitCommitsSync(args.db, args.projectPath, args.query, {
|
|
2204
|
-
limit: args.limit,
|
|
2205
|
-
queryEmbedding: args.queryEmbedding,
|
|
2206
|
-
queryModelId: args.queryModelId,
|
|
2207
|
-
from: args.dateRange?.from,
|
|
2208
|
-
to: args.dateRange?.to
|
|
2209
|
-
});
|
|
2210
|
-
return hits.map(toGitCommitResult);
|
|
2211
|
-
}
|
|
2212
|
-
function primerText(primer) {
|
|
2213
|
-
const answer = primer.answer.trim();
|
|
2214
|
-
return answer ? `Q: ${primer.question}
|
|
2215
|
-
A: ${answer}` : `Q: ${primer.question}`;
|
|
2216
|
-
}
|
|
2217
|
-
function searchPrimers(args) {
|
|
2218
|
-
const primers = getActivePrimers(args.db, args.projectPath).filter((primer) => timestampIsInRange(primer.createdAt, args.dateRange));
|
|
2219
|
-
if (primers.length === 0 || args.limit <= 0)
|
|
2220
|
-
return [];
|
|
2221
|
-
const ftsQuery = sanitizeFtsQuery(args.query);
|
|
2222
|
-
const ftsRanks = new Map;
|
|
2223
|
-
if (ftsQuery) {
|
|
2224
|
-
const rows = args.db.prepare(`SELECT p.id AS id, bm25(primers_fts) AS rank
|
|
2225
|
-
FROM primers_fts
|
|
2226
|
-
JOIN primers p ON p.id = primers_fts.rowid
|
|
2227
|
-
WHERE primers_fts MATCH ? AND p.project_path = ? AND p.status = 'active'
|
|
2228
|
-
${args.dateRange === null ? "" : "AND p.created_at BETWEEN ? AND ?"}
|
|
2229
|
-
ORDER BY rank ASC
|
|
2230
|
-
LIMIT ?`).all(ftsQuery, args.projectPath, ...args.dateRange === null ? [] : [args.dateRange.from, args.dateRange.to], args.limit * 3);
|
|
2231
|
-
rows.forEach((row, index) => {
|
|
2232
|
-
ftsRanks.set(row.id, linearDecayScore(index, rows.length));
|
|
2233
|
-
});
|
|
2234
|
-
}
|
|
2235
|
-
const scored = primers.map((primer) => {
|
|
2236
|
-
const semantic = args.queryEmbedding && primer.questionEmbedding && primer.questionEmbeddingModelId === args.queryModelId ? normalizeCosineScore(cosineSimilarity(args.queryEmbedding, primer.questionEmbedding)) : 0;
|
|
2237
|
-
const fts = ftsRanks.get(primer.id) ?? 0;
|
|
2238
|
-
if (semantic <= 0 && fts <= 0)
|
|
2239
|
-
return null;
|
|
2240
|
-
const score = semantic > 0 && fts > 0 ? semantic * SEMANTIC_WEIGHT + fts * FTS_WEIGHT : Math.max(semantic, fts);
|
|
2241
|
-
return {
|
|
2242
|
-
source: "primer",
|
|
2243
|
-
content: previewText(primerText(primer)),
|
|
2244
|
-
score,
|
|
2245
|
-
primerId: primer.id,
|
|
2246
|
-
question: primer.question,
|
|
2247
|
-
support: primer.totalSupport,
|
|
2248
|
-
lastObservedAt: primer.lastObservedAt,
|
|
2249
|
-
matchType: semantic > 0 && fts > 0 ? "hybrid" : semantic > 0 ? "semantic" : "fts"
|
|
2250
|
-
};
|
|
2251
|
-
}).filter((result) => result !== null).sort((a, b) => b.score - a.score || b.support - a.support || a.primerId - b.primerId).slice(0, args.limit);
|
|
2252
|
-
return scored;
|
|
2253
|
-
}
|
|
2254
|
-
function resolveSources(sources) {
|
|
2255
|
-
if (sources === undefined) {
|
|
2256
|
-
return new Set(["memory", "message", "git_commit", "primer", "note"]);
|
|
2257
|
-
}
|
|
2258
|
-
const set = new Set;
|
|
2259
|
-
for (const source of sources) {
|
|
2260
|
-
if (source === "memory" || source === "message" || source === "git_commit" || source === "primer" || source === "note") {
|
|
2261
|
-
set.add(source);
|
|
2262
|
-
}
|
|
2263
|
-
}
|
|
2264
|
-
return set;
|
|
2265
|
-
}
|
|
2266
|
-
function memoriesToIdLookupResults(args) {
|
|
2267
|
-
const ordered = args.memories.slice(0, args.limit);
|
|
2268
|
-
return ordered.map((memory, rank) => ({
|
|
2269
|
-
source: "memory",
|
|
2270
|
-
content: previewText(memory.content),
|
|
2271
|
-
score: 1 - rank * 0.01,
|
|
2272
|
-
memoryId: memory.id,
|
|
2273
|
-
category: memory.category,
|
|
2274
|
-
matchType: "fts",
|
|
2275
|
-
sourceName: args.sourceNameByMemoryId?.get(memory.id)
|
|
2276
|
-
}));
|
|
2277
|
-
}
|
|
2278
|
-
function resolveMemoriesByIdsForSearch(args) {
|
|
2279
|
-
if (args.diagnostics) {
|
|
2280
|
-
args.diagnostics.suppressedVisibleMemoryIds = [];
|
|
2281
|
-
}
|
|
2282
|
-
if (args.ids.length === 0) {
|
|
2283
|
-
return null;
|
|
2284
|
-
}
|
|
2285
|
-
const workspace = resolveSearchWorkspaceContext(args.db, args.projectPath);
|
|
2286
|
-
const fetched = workspace.isWorkspaced ? getMemoriesByProjects(args.db, workspace.expandedIdentities, ["active", "permanent", "archived"], Date.now(), workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(args.db, args.projectPath, ["active", "permanent", "archived"]);
|
|
2287
|
-
if (fetched.length === 0) {
|
|
2288
|
-
return null;
|
|
2289
|
-
}
|
|
2290
|
-
const dateRange = normalizeDateRange(args.from, args.to);
|
|
2291
|
-
const memoriesById = new Map(fetched.filter((memory) => timestampIsInRange(memory.createdAt, dateRange)).map((memory) => [memory.id, memory]));
|
|
2292
|
-
const ordered = [];
|
|
2293
|
-
const suppressedVisibleIds = new Set;
|
|
2294
|
-
for (const id of args.ids) {
|
|
2295
|
-
const memory = memoriesById.get(id);
|
|
2296
|
-
if (!memory)
|
|
2297
|
-
continue;
|
|
2298
|
-
if (args.visibleMemoryIds?.has(id)) {
|
|
2299
|
-
suppressedVisibleIds.add(id);
|
|
2300
|
-
continue;
|
|
2301
|
-
}
|
|
2302
|
-
ordered.push(memory);
|
|
2303
|
-
if (ordered.length >= args.limit)
|
|
2304
|
-
break;
|
|
2305
|
-
}
|
|
2306
|
-
if (args.diagnostics) {
|
|
2307
|
-
args.diagnostics.suppressedVisibleMemoryIds = [...suppressedVisibleIds].sort((left, right) => left - right);
|
|
2308
|
-
}
|
|
2309
|
-
if (ordered.length === 0) {
|
|
2310
|
-
return null;
|
|
2311
|
-
}
|
|
2312
|
-
return memoriesToIdLookupResults({
|
|
2313
|
-
memories: ordered,
|
|
2314
|
-
limit: args.limit,
|
|
2315
|
-
sourceNameByMemoryId: sourceNamesForSearchMemories({
|
|
2316
|
-
memories: ordered,
|
|
2317
|
-
projectPath: args.projectPath,
|
|
2318
|
-
workspace
|
|
2319
|
-
})
|
|
2320
|
-
});
|
|
2321
|
-
}
|
|
2322
|
-
async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
|
|
2323
|
-
const trimmedQuery = query.trim();
|
|
2324
|
-
const measurementStartedAt = Date.now();
|
|
2325
|
-
if (trimmedQuery.length === 0 || options.signal?.aborted) {
|
|
2326
|
-
return [];
|
|
2327
|
-
}
|
|
2328
|
-
const limit = normalizeLimit(options.limit);
|
|
2329
|
-
const dateRange = normalizeDateRange(options.from, options.to);
|
|
2330
|
-
const tierLimit = Math.max(limit * 3, DEFAULT_UNIFIED_SEARCH_LIMIT);
|
|
2331
|
-
if (options.diagnostics) {
|
|
2332
|
-
options.diagnostics.suppressedVisibleMemoryIds = [];
|
|
2333
|
-
options.diagnostics.suppressedLiveMessageMatches = 0;
|
|
2334
|
-
options.diagnostics.gitCommitUnavailable = null;
|
|
2335
|
-
}
|
|
2336
|
-
const embeddingEnabled = options.embeddingEnabled ?? true;
|
|
2337
|
-
const embedQuery = options.embedQuery ?? embedText;
|
|
2338
|
-
const isEmbeddingRuntimeEnabled = options.isEmbeddingRuntimeEnabled ?? isEmbeddingEnabled;
|
|
2339
|
-
const gitCommitsEnabled = options.gitCommitsEnabled ?? false;
|
|
2340
|
-
const activeSources = resolveSources(options.sources);
|
|
2341
|
-
const memoryFeatureEnabled = options.memoryEnabled ?? true;
|
|
2342
|
-
const runMemory = activeSources.has("memory") && memoryFeatureEnabled;
|
|
2343
|
-
const runMessages = activeSources.has("message");
|
|
2344
|
-
const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
|
|
2345
|
-
if (options.diagnostics && activeSources.has("git_commit") && options.gitRepositoryAvailable === false) {
|
|
2346
|
-
options.diagnostics.gitCommitUnavailable = "no_git_repository";
|
|
2347
|
-
}
|
|
2348
|
-
const runPrimers = activeSources.has("primer") && memoryFeatureEnabled;
|
|
2349
|
-
const runNotes = activeSources.has("note");
|
|
2350
|
-
const runCompartmentChunks = runMessages && embeddingEnabled;
|
|
2351
|
-
const needsEmbedding = (runMemory || runGitCommits || runCompartmentChunks || runPrimers) && embeddingEnabled && isEmbeddingRuntimeEnabled();
|
|
2352
|
-
const queryEmbeddingPromise = needsEmbedding ? embedQuery(trimmedQuery, options.signal).catch((error) => {
|
|
2353
|
-
log(`[search] query embedding failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2354
|
-
return null;
|
|
2355
|
-
}) : Promise.resolve(null);
|
|
2356
|
-
await Promise.resolve();
|
|
2357
|
-
if (options.signal?.aborted)
|
|
2358
|
-
return [];
|
|
2359
|
-
const messageProbes = options.explicitSearch ? extractLiteralProbes(trimmedQuery) : [];
|
|
2360
|
-
const messageResults = runMessages ? searchMessages({
|
|
2361
|
-
db,
|
|
2362
|
-
sessionId,
|
|
2363
|
-
query: trimmedQuery,
|
|
2364
|
-
limit: tierLimit,
|
|
2365
|
-
maxOrdinal: options.maxMessageOrdinal,
|
|
2366
|
-
probes: messageProbes,
|
|
2367
|
-
relaxedRecall: options.explicitSearch,
|
|
2368
|
-
diagnostics: options.diagnostics,
|
|
2369
|
-
dateRange
|
|
2370
|
-
}) : [];
|
|
2371
|
-
const capturedQuery = await queryEmbeddingPromise;
|
|
2372
|
-
if (options.signal?.aborted)
|
|
2373
|
-
return [];
|
|
2374
|
-
const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
|
|
2375
|
-
const queryContract = capturedQuery instanceof Float32Array || capturedQuery === null ? null : capturedQuery;
|
|
2376
|
-
const generationIsCurrent = queryContract === null || embeddingSnapshot !== null && embeddingSnapshot.generation === queryContract.generation;
|
|
2377
|
-
const queryEmbedding = generationIsCurrent ? queryContract?.vector ?? (capturedQuery instanceof Float32Array ? capturedQuery : null) : null;
|
|
2378
|
-
const workspace = resolveSearchWorkspaceContext(db, projectPath);
|
|
2379
|
-
const embeddingModelId = queryContract?.modelId ?? options.embeddingModelIdOverride ?? embeddingSnapshot?.modelId;
|
|
2380
|
-
const chunkModelId = queryContract?.chunkModelId ?? options.chunkModelIdOverride ?? embeddingSnapshot?.chunkModelId;
|
|
2381
|
-
const compartmentResults = runCompartmentChunks ? searchCompartmentChunks({
|
|
2382
|
-
db,
|
|
2383
|
-
sessionId,
|
|
2384
|
-
projectPath,
|
|
2385
|
-
queryEmbedding,
|
|
2386
|
-
limit: tierLimit,
|
|
2387
|
-
maxOrdinal: options.maxMessageOrdinal,
|
|
2388
|
-
modelId: chunkModelId && chunkModelId !== "off" ? chunkModelId : null,
|
|
2389
|
-
dateRange
|
|
2390
|
-
}) : [];
|
|
2391
|
-
const messageLikeResults = mergeMessageAndCompartmentResults({
|
|
2392
|
-
messages: messageResults,
|
|
2393
|
-
compartments: compartmentResults,
|
|
2394
|
-
limit: tierLimit
|
|
2395
|
-
});
|
|
2396
|
-
const [memoryOutcome, gitCommitResults, primerResults, noteResults] = await Promise.all([
|
|
2397
|
-
runMemory ? searchMemories({
|
|
2398
|
-
db,
|
|
2399
|
-
projectPath,
|
|
2400
|
-
query: trimmedQuery,
|
|
2401
|
-
limit: tierLimit,
|
|
2402
|
-
memoryEnabled: true,
|
|
2403
|
-
queryEmbedding,
|
|
2404
|
-
queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null,
|
|
2405
|
-
workspace,
|
|
2406
|
-
visibleMemoryIds: options.visibleMemoryIds,
|
|
2407
|
-
dateRange
|
|
2408
|
-
}) : Promise.resolve({
|
|
2409
|
-
results: [],
|
|
2410
|
-
suppressedVisibleIds: []
|
|
2411
|
-
}),
|
|
2412
|
-
runGitCommits ? Promise.resolve(searchGitCommits({
|
|
2413
|
-
db,
|
|
2414
|
-
projectPath,
|
|
2415
|
-
query: trimmedQuery,
|
|
2416
|
-
limit: tierLimit,
|
|
2417
|
-
queryEmbedding,
|
|
2418
|
-
queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null,
|
|
2419
|
-
dateRange
|
|
2420
|
-
})) : Promise.resolve([]),
|
|
2421
|
-
runPrimers ? Promise.resolve(searchPrimers({
|
|
2422
|
-
db,
|
|
2423
|
-
projectPath,
|
|
2424
|
-
query: trimmedQuery,
|
|
2425
|
-
limit: tierLimit,
|
|
2426
|
-
queryEmbedding,
|
|
2427
|
-
queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null,
|
|
2428
|
-
dateRange
|
|
2429
|
-
})) : Promise.resolve([]),
|
|
2430
|
-
runNotes ? Promise.resolve(searchNotes({
|
|
2431
|
-
db,
|
|
2432
|
-
sessionId,
|
|
2433
|
-
projectPath,
|
|
2434
|
-
query: trimmedQuery,
|
|
2435
|
-
limit: tierLimit,
|
|
2436
|
-
probes: messageProbes,
|
|
2437
|
-
dateRange
|
|
2438
|
-
})) : Promise.resolve([])
|
|
2439
|
-
]);
|
|
2440
|
-
if (options.diagnostics) {
|
|
2441
|
-
options.diagnostics.suppressedVisibleMemoryIds = memoryOutcome.suppressedVisibleIds;
|
|
2442
|
-
}
|
|
2443
|
-
const results = [
|
|
2444
|
-
...memoryOutcome.results,
|
|
2445
|
-
...primerResults,
|
|
2446
|
-
...messageLikeResults,
|
|
2447
|
-
...gitCommitResults,
|
|
2448
|
-
...noteResults
|
|
2449
|
-
].sort(compareUnifiedResults).slice(0, limit);
|
|
2450
|
-
if (!options.measurementDisabled) {
|
|
2451
|
-
recordShadowMeasurement({
|
|
2452
|
-
db,
|
|
2453
|
-
sessionId,
|
|
2454
|
-
projectPath,
|
|
2455
|
-
query: trimmedQuery,
|
|
2456
|
-
options,
|
|
2457
|
-
primaryResults: results,
|
|
2458
|
-
primaryQuery: queryContract,
|
|
2459
|
-
primaryLatencyMs: Date.now() - measurementStartedAt,
|
|
2460
|
-
search: unifiedSearch
|
|
2461
|
-
});
|
|
2462
|
-
}
|
|
2463
|
-
const countRetrievals = options.countRetrievals ?? true;
|
|
2464
|
-
if (countRetrievals) {
|
|
2465
|
-
const memoryIds = results.filter((result) => result.source === "memory").map((result) => result.memoryId);
|
|
2466
|
-
if (memoryIds.length > 0) {
|
|
2467
|
-
db.transaction(() => {
|
|
2468
|
-
for (const memoryId of memoryIds) {
|
|
2469
|
-
try {
|
|
2470
|
-
updateMemoryRetrievalCount(db, memoryId);
|
|
2471
|
-
} catch (error) {
|
|
2472
|
-
if (error instanceof ModuleMemoryAuthorityError)
|
|
2473
|
-
continue;
|
|
2474
|
-
throw error;
|
|
2475
|
-
}
|
|
2476
|
-
}
|
|
2477
|
-
}).immediate();
|
|
2478
|
-
}
|
|
2479
|
-
}
|
|
2480
|
-
return results;
|
|
2481
|
-
}
|
|
2482
|
-
var SEARCH_NOTE_EXPAND_HINT = "Use ctx_expand(start=N-10, end=N) around any note @msg anchor above to read the surrounding conversation context.";
|
|
2483
|
-
function formatSearchAge(timestampMs) {
|
|
2484
|
-
const ageMs = Date.now() - timestampMs;
|
|
2485
|
-
if (ageMs < 0)
|
|
2486
|
-
return "future";
|
|
2487
|
-
const days = Math.floor(ageMs / (24 * 60 * 60 * 1000));
|
|
2488
|
-
if (days <= 0)
|
|
2489
|
-
return "today";
|
|
2490
|
-
if (days === 1)
|
|
2491
|
-
return "1d ago";
|
|
2492
|
-
if (days < 30)
|
|
2493
|
-
return `${days}d ago`;
|
|
2494
|
-
const months = Math.floor(days / 30);
|
|
2495
|
-
if (months === 1)
|
|
2496
|
-
return "1mo ago";
|
|
2497
|
-
if (months < 12)
|
|
2498
|
-
return `${months}mo ago`;
|
|
2499
|
-
const years = Math.floor(days / 365);
|
|
2500
|
-
return years === 1 ? "1y ago" : `${years}y ago`;
|
|
2501
|
-
}
|
|
2502
|
-
function formatUnifiedSearchResult(result, index, currentSessionId) {
|
|
2503
|
-
if (result.source === "memory") {
|
|
2504
|
-
const source = result.sourceName ? ` source=${result.sourceName}` : "";
|
|
2505
|
-
return [
|
|
2506
|
-
`[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category}${source} match=${result.matchType}`,
|
|
2507
|
-
result.content
|
|
2508
|
-
].join(`
|
|
2509
|
-
`);
|
|
2510
|
-
}
|
|
2511
|
-
if (result.source === "git_commit") {
|
|
2512
|
-
return [
|
|
2513
|
-
`[${index}] [git_commit] score=${result.score.toFixed(2)} sha=${result.shortSha} ${formatSearchAge(result.committedAtMs)} match=${result.matchType}`,
|
|
2514
|
-
result.content
|
|
2515
|
-
].join(`
|
|
2516
|
-
`);
|
|
2517
|
-
}
|
|
2518
|
-
if (result.source === "primer") {
|
|
2519
|
-
return [
|
|
2520
|
-
`[${index}] [primer] score=${result.score.toFixed(2)} id=${result.primerId} support=${result.support} match=${result.matchType}`,
|
|
2521
|
-
result.content
|
|
2522
|
-
].join(`
|
|
2523
|
-
`);
|
|
2524
|
-
}
|
|
2525
|
-
if (result.source === "note") {
|
|
2526
|
-
const anchor = result.anchorOrdinal !== null && result.sourceSessionId === currentSessionId ? ` @msg ${result.anchorOrdinal}` : "";
|
|
2527
|
-
return [
|
|
2528
|
-
`[${index}] [note] score=${result.score.toFixed(2)} id=#${result.noteId} status=${result.status} ${formatSearchAge(result.createdAt)}${anchor}`,
|
|
2529
|
-
result.content
|
|
2530
|
-
].join(`
|
|
2531
|
-
`);
|
|
2532
|
-
}
|
|
2533
|
-
if (result.source === "compartment") {
|
|
2534
|
-
return [
|
|
2535
|
-
`[${index}] [message] score=${result.score.toFixed(2)} compartment_id=${result.compartmentId} range=${result.startOrdinal}-${result.endOrdinal} match=${result.matchType} title=${result.title}`,
|
|
2536
|
-
result.snippet ? `Snippet: ${result.snippet}` : result.content
|
|
2537
|
-
].join(`
|
|
2538
|
-
`);
|
|
2539
|
-
}
|
|
2540
|
-
const expandStart = Math.max(1, result.messageOrdinal - 3);
|
|
2541
|
-
const expandEnd = result.messageOrdinal + 3;
|
|
2542
|
-
return [
|
|
2543
|
-
`[${index}] [message] score=${result.score.toFixed(2)} ordinal=${result.messageOrdinal} range=${expandStart}-${expandEnd} role=${result.role}`,
|
|
2544
|
-
result.content
|
|
2545
|
-
].join(`
|
|
2546
|
-
`);
|
|
2547
|
-
}
|
|
2548
|
-
function formatSearchDiagnosticLines(results, diagnostics) {
|
|
2549
|
-
if (!diagnostics)
|
|
2550
|
-
return [];
|
|
2551
|
-
const lines = [];
|
|
2552
|
-
const visibleIds = [...diagnostics.suppressedVisibleMemoryIds].sort((left, right) => left - right);
|
|
2553
|
-
if (visibleIds.length > 0) {
|
|
2554
|
-
const count = visibleIds.length;
|
|
2555
|
-
const noun = count === 1 ? "match" : "matches";
|
|
2556
|
-
const ids = visibleIds.join(", ");
|
|
2557
|
-
if (results.some((result) => result.source === "memory")) {
|
|
2558
|
-
lines.push(`Memories: ${count} additional ${noun} suppressed because ${count === 1 ? "it is" : "they are"} already visible in your project-memory block (ids ${ids}).`);
|
|
2559
|
-
} else {
|
|
2560
|
-
lines.push(`Memories: ${count} ${noun} found, all already visible in your project-memory block (ids ${ids}).`);
|
|
2561
|
-
}
|
|
2562
|
-
}
|
|
2563
|
-
if (diagnostics.suppressedLiveMessageMatches > 0) {
|
|
2564
|
-
const count = diagnostics.suppressedLiveMessageMatches;
|
|
2565
|
-
lines.push(`Message history: ${count} raw-message ${count === 1 ? "match is" : "matches are"} newer than the last compartment boundary (already in your context).`);
|
|
2566
|
-
}
|
|
2567
|
-
if (diagnostics.gitCommitUnavailable === "no_git_repository") {
|
|
2568
|
-
lines.push("Git commits: no git repository — commit search unavailable for this project.");
|
|
2569
|
-
}
|
|
2570
|
-
return lines;
|
|
2571
|
-
}
|
|
2572
|
-
function formatSearchResults(query, results, currentSessionId, diagnostics) {
|
|
2573
|
-
const diagnosticLines = formatSearchDiagnosticLines(results, diagnostics);
|
|
2574
|
-
if (results.length === 0) {
|
|
2575
|
-
if (diagnosticLines.length > 0) {
|
|
2576
|
-
return `No hidden results found for "${query}".
|
|
2577
|
-
|
|
2578
|
-
${diagnosticLines.join(`
|
|
2579
|
-
`)}`;
|
|
556
|
+
const tags = preloadedTags ?? getDroppedTagsByNumbers(db, sessionId, [...targets.keys()]);
|
|
557
|
+
for (const tag of tags) {
|
|
558
|
+
if (tag.status === "dropped") {
|
|
559
|
+
const target = targets.get(tag.tagNumber);
|
|
560
|
+
if (tag.type === "tool") {
|
|
561
|
+
if (tag.dropMode === "edit_marker_stripped") {
|
|
562
|
+
const result = target?.editMarkerStripped?.() ?? "absent";
|
|
563
|
+
if (result === "truncated")
|
|
564
|
+
didMutateMessage = true;
|
|
565
|
+
} else if (tag.dropMode === "edit_marker") {
|
|
566
|
+
const markResult = target?.editMarker?.() ?? "absent";
|
|
567
|
+
if (markResult === "truncated") {
|
|
568
|
+
didMutateMessage = true;
|
|
569
|
+
}
|
|
570
|
+
} else if (tag.dropMode === "skeleton_stripped") {
|
|
571
|
+
const result = target?.skeletonStripped?.() ?? "absent";
|
|
572
|
+
if (result === "truncated")
|
|
573
|
+
didMutateMessage = true;
|
|
574
|
+
} else if (tag.dropMode === "skeleton_real") {
|
|
575
|
+
const result = target?.skeletonReal?.() ?? "absent";
|
|
576
|
+
if (result === "truncated") {
|
|
577
|
+
didMutateMessage = true;
|
|
578
|
+
}
|
|
579
|
+
} else if (tag.dropMode === "truncated") {
|
|
580
|
+
const truncResult = target?.truncate?.() ?? "absent";
|
|
581
|
+
if (truncResult === "truncated") {
|
|
582
|
+
didMutateMessage = true;
|
|
583
|
+
}
|
|
584
|
+
} else {
|
|
585
|
+
const dropResult = target?.drop?.() ?? "absent";
|
|
586
|
+
if (dropResult === "removed" || dropResult === "truncated") {
|
|
587
|
+
didMutateMessage = true;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
} else if (target) {
|
|
591
|
+
const changed = target.setContent(buildReplacementContent(tag.tagNumber));
|
|
592
|
+
if (changed)
|
|
593
|
+
didMutateMessage = true;
|
|
594
|
+
}
|
|
2580
595
|
}
|
|
2581
|
-
return `No results found for "${query}" across notes, memories, primers, git commits, or message history.`;
|
|
2582
|
-
}
|
|
2583
|
-
const bodyParts = results.map((result, index) => formatUnifiedSearchResult(result, index + 1, currentSessionId));
|
|
2584
|
-
if (diagnosticLines.length > 0)
|
|
2585
|
-
bodyParts.push(diagnosticLines.join(`
|
|
2586
|
-
`));
|
|
2587
|
-
if (results.some((result) => result.source === "message" || result.source === "compartment")) {
|
|
2588
|
-
bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
|
|
2589
596
|
}
|
|
2590
|
-
|
|
2591
|
-
bodyParts.push(SEARCH_NOTE_EXPAND_HINT);
|
|
2592
|
-
}
|
|
2593
|
-
return `Found ${results.length} result${results.length === 1 ? "" : "s"} for "${query}":
|
|
2594
|
-
|
|
2595
|
-
${bodyParts.join(`
|
|
2596
|
-
|
|
2597
|
-
`)}`;
|
|
597
|
+
return didMutateMessage;
|
|
2598
598
|
}
|
|
2599
599
|
|
|
2600
600
|
// src/hooks/magic-context/auto-search-deadline.ts
|
|
2601
601
|
var AUTO_SEARCH_TIMEOUT_MS = 3000;
|
|
602
|
+
var operationDeadlines = new WeakMap;
|
|
603
|
+
function autoSearchDeadlineUnixMs(signal) {
|
|
604
|
+
const deadline = signal && operationDeadlines.get(signal);
|
|
605
|
+
return Date.now() + (deadline === undefined ? AUTO_SEARCH_TIMEOUT_MS : Math.max(0, deadline - performance.now()));
|
|
606
|
+
}
|
|
607
|
+
var skippedTurns = new Map;
|
|
608
|
+
function wasAutoSearchSkipped(db, sessionId, messageId) {
|
|
609
|
+
return skippedTurns.get(sessionId)?.get(db)?.has(messageId) ?? false;
|
|
610
|
+
}
|
|
611
|
+
function clearAutoSearchTimeoutForSession(sessionId) {
|
|
612
|
+
cancelAutoSearchWork(sessionId);
|
|
613
|
+
if (sessionId === undefined)
|
|
614
|
+
skippedTurns.clear();
|
|
615
|
+
else
|
|
616
|
+
skippedTurns.delete(sessionId);
|
|
617
|
+
}
|
|
618
|
+
function persistAutoSearchSkip(db, sessionId, messageId, reason = "timeout") {
|
|
619
|
+
assertTransformWrite();
|
|
620
|
+
let turns = skippedTurns.get(sessionId);
|
|
621
|
+
if (!turns) {
|
|
622
|
+
turns = new WeakMap;
|
|
623
|
+
skippedTurns.set(sessionId, turns);
|
|
624
|
+
}
|
|
625
|
+
let messages = turns.get(db);
|
|
626
|
+
if (!messages) {
|
|
627
|
+
messages = new Map;
|
|
628
|
+
turns.set(db, messages);
|
|
629
|
+
}
|
|
630
|
+
const prior = messages.get(messageId);
|
|
631
|
+
if (prior)
|
|
632
|
+
return prior;
|
|
633
|
+
let ok = false;
|
|
634
|
+
try {
|
|
635
|
+
const outcome = appendAutoSearchHintDecision(db, sessionId, {
|
|
636
|
+
messageId,
|
|
637
|
+
decision: "no-hint",
|
|
638
|
+
reason
|
|
639
|
+
});
|
|
640
|
+
ok = outcome.ok && outcome.decision.decision === "no-hint";
|
|
641
|
+
} catch {}
|
|
642
|
+
assertTransformWrite();
|
|
643
|
+
const persistence = Promise.resolve(ok);
|
|
644
|
+
messages.set(messageId, persistence);
|
|
645
|
+
return persistence;
|
|
646
|
+
}
|
|
2602
647
|
async function withAutoSearchDeadline(operation, startedAt = performance.now()) {
|
|
2603
648
|
const deadline = startedAt + AUTO_SEARCH_TIMEOUT_MS;
|
|
2604
649
|
const remaining = deadline - performance.now();
|
|
2605
650
|
if (remaining <= 0)
|
|
2606
651
|
return null;
|
|
2607
652
|
const controller = new AbortController;
|
|
653
|
+
operationDeadlines.set(controller.signal, deadline);
|
|
2608
654
|
const checkDeadline = () => {
|
|
2609
655
|
if (!controller.signal.aborted && performance.now() >= deadline)
|
|
2610
656
|
controller.abort();
|
|
@@ -3080,7 +1126,7 @@ function cavemanCompressAsciiV1(text, level) {
|
|
|
3080
1126
|
var MAX_FRAGMENTS = 3;
|
|
3081
1127
|
var FRAGMENT_CHAR_CAP = 80;
|
|
3082
1128
|
var MAX_HINT_CHARS = 800;
|
|
3083
|
-
var
|
|
1129
|
+
var MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
3084
1130
|
function truncate(text, limit) {
|
|
3085
1131
|
const normalized = text.replace(/\s+/g, " ").trim();
|
|
3086
1132
|
if (normalized.length <= limit)
|
|
@@ -3091,7 +1137,7 @@ function formatAge(committedAtMs) {
|
|
|
3091
1137
|
const delta = Date.now() - committedAtMs;
|
|
3092
1138
|
if (delta < 0)
|
|
3093
1139
|
return "future";
|
|
3094
|
-
const days = Math.floor(delta /
|
|
1140
|
+
const days = Math.floor(delta / MS_PER_DAY);
|
|
3095
1141
|
if (days <= 0)
|
|
3096
1142
|
return "today";
|
|
3097
1143
|
if (days === 1)
|
|
@@ -3166,6 +1212,205 @@ ${trimmedBody}…
|
|
|
3166
1212
|
return wrapped;
|
|
3167
1213
|
}
|
|
3168
1214
|
|
|
1215
|
+
// src/hooks/magic-context/auto-search-worker-client.ts
|
|
1216
|
+
import { Worker } from "node:worker_threads";
|
|
1217
|
+
var pendingRegistrations = new WeakMap;
|
|
1218
|
+
var pendingTurns = new WeakMap;
|
|
1219
|
+
async function coalesceAutoSearchTurn(db, sessionId, messageId, operation, replay) {
|
|
1220
|
+
let sessions = pendingTurns.get(db);
|
|
1221
|
+
if (!sessions) {
|
|
1222
|
+
sessions = new Map;
|
|
1223
|
+
pendingTurns.set(db, sessions);
|
|
1224
|
+
}
|
|
1225
|
+
let turns = sessions.get(sessionId);
|
|
1226
|
+
if (!turns) {
|
|
1227
|
+
turns = new Map;
|
|
1228
|
+
sessions.set(sessionId, turns);
|
|
1229
|
+
}
|
|
1230
|
+
const pending = turns.get(messageId);
|
|
1231
|
+
if (pending && !pending.signal.aborted) {
|
|
1232
|
+
const result = await pending.promise;
|
|
1233
|
+
if (!pending.signal.aborted)
|
|
1234
|
+
replay();
|
|
1235
|
+
return result;
|
|
1236
|
+
}
|
|
1237
|
+
const lifecycle = registerAutoSearchWork(sessionId);
|
|
1238
|
+
const work = Promise.resolve().then(() => operation(lifecycle.signal));
|
|
1239
|
+
const turn = { promise: work, signal: lifecycle.signal };
|
|
1240
|
+
turns.set(messageId, turn);
|
|
1241
|
+
try {
|
|
1242
|
+
return await work;
|
|
1243
|
+
} finally {
|
|
1244
|
+
lifecycle.done();
|
|
1245
|
+
if (turns.get(messageId) === turn)
|
|
1246
|
+
turns.delete(messageId);
|
|
1247
|
+
if (turns.size === 0)
|
|
1248
|
+
sessions.delete(sessionId);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
function queueAutoSearchRegistration(db, projectPath, register) {
|
|
1252
|
+
if (!register)
|
|
1253
|
+
return;
|
|
1254
|
+
let pending = pendingRegistrations.get(db);
|
|
1255
|
+
if (!pending) {
|
|
1256
|
+
pending = new Set;
|
|
1257
|
+
pendingRegistrations.set(db, pending);
|
|
1258
|
+
}
|
|
1259
|
+
if (pending.has(projectPath))
|
|
1260
|
+
return;
|
|
1261
|
+
pending.add(projectPath);
|
|
1262
|
+
const timer = setTimeout(() => {
|
|
1263
|
+
if (getProjectEmbeddingSnapshot(projectPath)) {
|
|
1264
|
+
pending.delete(projectPath);
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
Promise.resolve().then(register).catch((error) => log(`[auto-search] background registration failed: ${error instanceof Error ? error.message : String(error)}`)).finally(() => pending.delete(projectPath));
|
|
1268
|
+
}, 0);
|
|
1269
|
+
timer.unref?.();
|
|
1270
|
+
}
|
|
1271
|
+
function persistAutoSearchDecision(db, sessionId, decision, startedAt) {
|
|
1272
|
+
if (performance.now() - startedAt >= AUTO_SEARCH_TIMEOUT_MS)
|
|
1273
|
+
return null;
|
|
1274
|
+
return appendAutoSearchHintDecision(db, sessionId, decision);
|
|
1275
|
+
}
|
|
1276
|
+
async function searchAutoHint(db, sessionId, projectPath, query, options, entry = new URL(new URL(import.meta.url).pathname.endsWith(".ts") ? "./auto-search-worker.ts" : "./auto-search-worker.js", import.meta.url)) {
|
|
1277
|
+
return executeAutoSearchWorker(db, sessionId, projectPath, query, options, entry, "search");
|
|
1278
|
+
}
|
|
1279
|
+
var pendingBackfills = new WeakMap;
|
|
1280
|
+
function queueAutoSearchBackfill(db, sessionId, projectPath, query) {
|
|
1281
|
+
if (isEmbeddingHostBusy())
|
|
1282
|
+
return Promise.resolve();
|
|
1283
|
+
const key = `${sessionId}\x00${projectPath}`;
|
|
1284
|
+
const pending = pendingBackfills.get(db) ?? new Map;
|
|
1285
|
+
pendingBackfills.set(db, pending);
|
|
1286
|
+
const prior = pending.get(key);
|
|
1287
|
+
if (prior)
|
|
1288
|
+
return prior;
|
|
1289
|
+
const lifecycle = registerAutoSearchWork(sessionId);
|
|
1290
|
+
const work = new Promise((resolve) => {
|
|
1291
|
+
const done = () => {
|
|
1292
|
+
lifecycle.done();
|
|
1293
|
+
pending.delete(key);
|
|
1294
|
+
resolve();
|
|
1295
|
+
};
|
|
1296
|
+
const queued = setTimeout(() => {
|
|
1297
|
+
lifecycle.signal.removeEventListener("abort", cancel);
|
|
1298
|
+
if (lifecycle.signal.aborted)
|
|
1299
|
+
return done();
|
|
1300
|
+
const controller = new AbortController;
|
|
1301
|
+
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
1302
|
+
timeout.unref?.();
|
|
1303
|
+
executeAutoSearchWorker(db, sessionId, projectPath, query, { signal: AbortSignal.any([controller.signal, lifecycle.signal]) }, new URL(new URL(import.meta.url).pathname.endsWith(".ts") ? "./auto-search-worker.ts" : "./auto-search-worker.js", import.meta.url), "backfill").catch((error) => log("[auto-search] background backfill failed:", error)).finally(() => {
|
|
1304
|
+
clearTimeout(timeout);
|
|
1305
|
+
done();
|
|
1306
|
+
});
|
|
1307
|
+
}, 0);
|
|
1308
|
+
const cancel = () => {
|
|
1309
|
+
clearTimeout(queued);
|
|
1310
|
+
done();
|
|
1311
|
+
};
|
|
1312
|
+
lifecycle.signal.addEventListener("abort", cancel, { once: true });
|
|
1313
|
+
queued.unref?.();
|
|
1314
|
+
});
|
|
1315
|
+
pending.set(key, work);
|
|
1316
|
+
return work;
|
|
1317
|
+
}
|
|
1318
|
+
async function executeAutoSearchWorker(db, sessionId, projectPath, query, options, entry, job) {
|
|
1319
|
+
if (options.signal?.aborted)
|
|
1320
|
+
return [];
|
|
1321
|
+
const path = getSqliteDatabasePath(db);
|
|
1322
|
+
if (!path)
|
|
1323
|
+
throw new Error("auto-search requires a file-backed database for off-thread search");
|
|
1324
|
+
const {
|
|
1325
|
+
embedQuery,
|
|
1326
|
+
isEmbeddingRuntimeEnabled,
|
|
1327
|
+
signal: deadlineSignal,
|
|
1328
|
+
readMessages: _readMessages,
|
|
1329
|
+
...serializable
|
|
1330
|
+
} = options;
|
|
1331
|
+
const lifecycle = registerAutoSearchWork(sessionId);
|
|
1332
|
+
const signal = deadlineSignal ? AbortSignal.any([deadlineSignal, lifecycle.signal]) : lifecycle.signal;
|
|
1333
|
+
const snapshot = getProjectEmbeddingSnapshot(projectPath);
|
|
1334
|
+
const input = {
|
|
1335
|
+
job,
|
|
1336
|
+
path,
|
|
1337
|
+
harness: getHarness(),
|
|
1338
|
+
sessionId,
|
|
1339
|
+
projectPath,
|
|
1340
|
+
query,
|
|
1341
|
+
options: {
|
|
1342
|
+
...serializable,
|
|
1343
|
+
backfillMemoryEmbeddings: false,
|
|
1344
|
+
countRetrievals: false,
|
|
1345
|
+
measurementDisabled: true
|
|
1346
|
+
},
|
|
1347
|
+
embeddingRuntimeEnabled: isEmbeddingRuntimeEnabled?.() ?? false,
|
|
1348
|
+
embeddingHostBusy: isEmbeddingHostBusy(),
|
|
1349
|
+
snapshot,
|
|
1350
|
+
deadlineUnixMs: autoSearchDeadlineUnixMs(deadlineSignal)
|
|
1351
|
+
};
|
|
1352
|
+
return new Promise((resolve, reject) => {
|
|
1353
|
+
const worker = new Worker(entry, { workerData: input });
|
|
1354
|
+
let settled = false;
|
|
1355
|
+
const finish = (results, error) => {
|
|
1356
|
+
if (settled)
|
|
1357
|
+
return;
|
|
1358
|
+
settled = true;
|
|
1359
|
+
signal?.removeEventListener("abort", abort);
|
|
1360
|
+
lifecycle.signal.removeEventListener("abort", abort);
|
|
1361
|
+
lifecycle.done();
|
|
1362
|
+
worker.terminate();
|
|
1363
|
+
worker.unref();
|
|
1364
|
+
if (error)
|
|
1365
|
+
reject(error);
|
|
1366
|
+
else
|
|
1367
|
+
resolve(results);
|
|
1368
|
+
};
|
|
1369
|
+
const abort = () => finish([]);
|
|
1370
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
1371
|
+
lifecycle.signal.addEventListener("abort", abort, { once: true });
|
|
1372
|
+
worker.on("error", (error) => finish([], error));
|
|
1373
|
+
worker.on("exit", (code) => finish([], new Error(`auto-search worker exited (${code})`)));
|
|
1374
|
+
worker.on("message", async (reply) => {
|
|
1375
|
+
if (settled)
|
|
1376
|
+
return;
|
|
1377
|
+
if (reply.kind === "result") {
|
|
1378
|
+
if (snapshot && getProjectEmbeddingSnapshot(projectPath)?.generation !== snapshot.generation)
|
|
1379
|
+
finish([]);
|
|
1380
|
+
else
|
|
1381
|
+
finish(reply.results);
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
if (reply.kind === "error") {
|
|
1385
|
+
finish([], new Error(reply.error));
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
const response = { id: reply.id };
|
|
1389
|
+
try {
|
|
1390
|
+
if (reply.kind === "query")
|
|
1391
|
+
response.result = await (job === "backfill" ? embedTextForProject(projectPath, reply.text, signal, "query") : embedQuery?.(reply.text, signal)) ?? null;
|
|
1392
|
+
else {
|
|
1393
|
+
const passage = await embedBatchForProject(projectPath, reply.texts, signal, reply.purpose);
|
|
1394
|
+
const current = getProjectEmbeddingSnapshot(projectPath);
|
|
1395
|
+
response.passage = passage && current ? {
|
|
1396
|
+
...passage,
|
|
1397
|
+
providerIdentity: current.providerIdentity,
|
|
1398
|
+
runtimeFingerprint: current.runtimeFingerprint,
|
|
1399
|
+
dimensions: passage.vectors.find((vector) => vector !== null)?.length ?? null
|
|
1400
|
+
} : null;
|
|
1401
|
+
}
|
|
1402
|
+
} catch (error) {
|
|
1403
|
+
response.error = error instanceof Error ? error.message : String(error);
|
|
1404
|
+
}
|
|
1405
|
+
response.embeddingHostBusy = isEmbeddingHostBusy();
|
|
1406
|
+
if (!settled && !signal?.aborted)
|
|
1407
|
+
worker.postMessage(response);
|
|
1408
|
+
});
|
|
1409
|
+
if (signal?.aborted)
|
|
1410
|
+
abort();
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
|
|
3169
1414
|
// src/hooks/magic-context/transform-message-helpers.ts
|
|
3170
1415
|
function isMeaningfulUserMessage(msg) {
|
|
3171
1416
|
return msg.info.role === "user" && hasMeaningfulUserText2(msg.parts);
|
|
@@ -3340,109 +1585,142 @@ async function runAutoSearchHint(args) {
|
|
|
3340
1585
|
if (!userMsg || typeof userMsg.info.id !== "string")
|
|
3341
1586
|
return AUTO_SEARCH_OK;
|
|
3342
1587
|
const userMsgId = userMsg.info.id;
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
if (
|
|
3347
|
-
|
|
1588
|
+
return coalesceAutoSearchTurn(db, sessionId, userMsgId, async (lifecycleSignal) => {
|
|
1589
|
+
if (lifecycleSignal.aborted)
|
|
1590
|
+
return AUTO_SEARCH_OK;
|
|
1591
|
+
if (wasAutoSearchSkipped(db, sessionId, userMsgId))
|
|
1592
|
+
return AUTO_SEARCH_OK;
|
|
1593
|
+
const existing = getAutoSearchHintDecisions(db, sessionId);
|
|
1594
|
+
const existingForMessage = existing.find((decision) => decision.messageId === userMsgId);
|
|
1595
|
+
if (existingForMessage) {
|
|
1596
|
+
if (existingForMessage.decision === "hint") {
|
|
1597
|
+
appendReminderToUserMessageById(messages, userMsgId, existingForMessage.text);
|
|
1598
|
+
}
|
|
1599
|
+
return AUTO_SEARCH_OK;
|
|
1600
|
+
}
|
|
1601
|
+
if (messages.length === 0 || messages[messages.length - 1].info.id !== userMsgId) {
|
|
1602
|
+
return AUTO_SEARCH_OK;
|
|
1603
|
+
}
|
|
1604
|
+
const embeddingSnapshot = getProjectEmbeddingSnapshot(options.projectPath);
|
|
1605
|
+
if (!embeddingSnapshot) {
|
|
1606
|
+
queueAutoSearchRegistration(db, options.projectPath, options.directory && options.ensureProjectRegistered ? () => options.ensureProjectRegistered?.(options.directory, db) ?? Promise.resolve() : undefined);
|
|
1607
|
+
persistAutoSearchSkip(db, sessionId, userMsgId, "empty");
|
|
1608
|
+
return AUTO_SEARCH_OK;
|
|
1609
|
+
}
|
|
1610
|
+
const writeNoHintAndReconcile = async (reason) => {
|
|
1611
|
+
const outcome = persistAutoSearchDecision(db, sessionId, {
|
|
1612
|
+
messageId: userMsgId,
|
|
1613
|
+
decision: "no-hint",
|
|
1614
|
+
reason
|
|
1615
|
+
}, startedAt);
|
|
1616
|
+
if (outcome === null) {
|
|
1617
|
+
persistAutoSearchSkip(db, sessionId, userMsgId);
|
|
1618
|
+
return { ok: false, kind: "timeout" };
|
|
1619
|
+
}
|
|
1620
|
+
if (!outcome.ok)
|
|
1621
|
+
return { ok: false, kind: "cas-exhaustion" };
|
|
1622
|
+
if (outcome.kind === "already-present" && outcome.decision.decision === "hint") {
|
|
1623
|
+
appendReminderToUserMessageById(messages, userMsgId, outcome.decision.text);
|
|
1624
|
+
}
|
|
1625
|
+
return AUTO_SEARCH_OK;
|
|
1626
|
+
};
|
|
1627
|
+
const rawPartsText = collectUserPromptParts(userMsg);
|
|
1628
|
+
if (hasStackedAugmentation(rawPartsText)) {
|
|
1629
|
+
sessionLog(sessionId, "auto-search: skipping — user message already carries augmentation/hint");
|
|
1630
|
+
return writeNoHintAndReconcile("stacked");
|
|
3348
1631
|
}
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
return AUTO_SEARCH_OK;
|
|
3353
|
-
}
|
|
3354
|
-
const writeNoHintAndReconcile = (reason) => {
|
|
3355
|
-
const outcome = appendAutoSearchHintDecision(db, sessionId, {
|
|
3356
|
-
messageId: userMsgId,
|
|
3357
|
-
decision: "no-hint",
|
|
3358
|
-
reason
|
|
3359
|
-
});
|
|
3360
|
-
if (!outcome.ok)
|
|
3361
|
-
return { ok: false, kind: "cas-exhaustion" };
|
|
3362
|
-
if (outcome.kind === "already-present" && outcome.decision.decision === "hint") {
|
|
3363
|
-
appendReminderToUserMessageById(messages, userMsgId, outcome.decision.text);
|
|
1632
|
+
const rawPrompt = extractUserPromptText(userMsg);
|
|
1633
|
+
if (rawPrompt.length < options.minPromptChars) {
|
|
1634
|
+
return writeNoHintAndReconcile("too-short");
|
|
3364
1635
|
}
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
1636
|
+
let results;
|
|
1637
|
+
try {
|
|
1638
|
+
results = await withAutoSearchDeadline(async (signal, checkDeadline) => {
|
|
1639
|
+
if (checkDeadline())
|
|
1640
|
+
return null;
|
|
1641
|
+
const memoryEnabled = embeddingSnapshot?.features.memoryEnabled ?? options.memoryEnabled;
|
|
1642
|
+
const embeddingEnabled = embeddingSnapshot ? embeddingSnapshot.historyEnabled : options.embeddingEnabled;
|
|
1643
|
+
const gitCommitsEnabled = embeddingSnapshot?.gitCommitEnabled ?? options.gitCommitsEnabled ?? false;
|
|
1644
|
+
const searchOptions = {
|
|
1645
|
+
limit: 10,
|
|
1646
|
+
memoryEnabled,
|
|
1647
|
+
embeddingEnabled,
|
|
1648
|
+
gitCommitsEnabled,
|
|
1649
|
+
embedQuery: async (text, signal) => {
|
|
1650
|
+
const result = await embedTextForProject(options.projectPath, text, signal, "query");
|
|
1651
|
+
checkDeadline();
|
|
1652
|
+
return result;
|
|
1653
|
+
},
|
|
1654
|
+
isEmbeddingRuntimeEnabled: () => embeddingEnabled === true,
|
|
1655
|
+
visibleMemoryIds: options.visibleMemoryIds ?? null,
|
|
1656
|
+
sources: ["memory", "message", "git_commit"]
|
|
1657
|
+
};
|
|
1658
|
+
return searchAutoHint(db, sessionId, options.projectPath, rawPrompt, {
|
|
1659
|
+
...searchOptions,
|
|
1660
|
+
signal,
|
|
1661
|
+
countRetrievals: false
|
|
1662
|
+
});
|
|
1663
|
+
}, startedAt);
|
|
1664
|
+
} catch (error) {
|
|
1665
|
+
log(`[auto-search] unified search failed for session ${sessionId} (will retry next pass): ${error instanceof Error ? error.message : String(error)}`);
|
|
1666
|
+
return { ok: false, kind: "search-failure" };
|
|
1667
|
+
}
|
|
1668
|
+
if (lifecycleSignal.aborted)
|
|
1669
|
+
return AUTO_SEARCH_OK;
|
|
1670
|
+
try {
|
|
1671
|
+
if (results === null) {
|
|
1672
|
+
sessionLog(sessionId, `auto-search: timed out after ${AUTO_SEARCH_TIMEOUT_MS}ms, skipping hint for this turn`);
|
|
1673
|
+
persistAutoSearchSkip(db, sessionId, userMsgId);
|
|
1674
|
+
sessionLog(sessionId, "auto-search: skip frozen; retries for this turn are replay-only");
|
|
1675
|
+
return { ok: false, kind: "timeout" };
|
|
3381
1676
|
}
|
|
3382
|
-
if (
|
|
3383
|
-
return
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
const result = await embedTextForProject(options.projectPath, text, signal, "query");
|
|
3395
|
-
checkDeadline();
|
|
3396
|
-
return result;
|
|
3397
|
-
},
|
|
3398
|
-
isEmbeddingRuntimeEnabled: () => embeddingEnabled === true,
|
|
3399
|
-
visibleMemoryIds: options.visibleMemoryIds ?? null,
|
|
3400
|
-
sources: ["memory", "message", "git_commit"]
|
|
3401
|
-
};
|
|
3402
|
-
return unifiedSearch(db, sessionId, options.projectPath, rawPrompt, {
|
|
3403
|
-
...searchOptions,
|
|
3404
|
-
signal,
|
|
3405
|
-
countRetrievals: false
|
|
3406
|
-
});
|
|
3407
|
-
}, startedAt);
|
|
3408
|
-
} catch (error) {
|
|
3409
|
-
log(`[auto-search] unified search failed for session ${sessionId} (will retry next pass): ${error instanceof Error ? error.message : String(error)}`);
|
|
3410
|
-
return { ok: false, kind: "search-failure" };
|
|
3411
|
-
}
|
|
3412
|
-
if (results === null) {
|
|
3413
|
-
sessionLog(sessionId, `auto-search: timed out after ${AUTO_SEARCH_TIMEOUT_MS}ms, skipping hint for this turn (will retry)`);
|
|
3414
|
-
return { ok: false, kind: "timeout" };
|
|
3415
|
-
}
|
|
3416
|
-
if (results.length === 0) {
|
|
3417
|
-
return writeNoHintAndReconcile("empty");
|
|
3418
|
-
}
|
|
3419
|
-
if (results[0].score < options.scoreThreshold) {
|
|
3420
|
-
sessionLog(sessionId, `auto-search: top score ${results[0].score.toFixed(3)} below threshold ${options.scoreThreshold}`);
|
|
3421
|
-
return writeNoHintAndReconcile("below-threshold");
|
|
3422
|
-
}
|
|
3423
|
-
const hintText = buildAutoSearchHint(results, { wordRules: options.wordRules });
|
|
3424
|
-
if (!hintText) {
|
|
3425
|
-
return writeNoHintAndReconcile("empty");
|
|
3426
|
-
}
|
|
3427
|
-
const payload = `
|
|
1677
|
+
if (results.length === 0) {
|
|
1678
|
+
return writeNoHintAndReconcile("empty");
|
|
1679
|
+
}
|
|
1680
|
+
if (results[0].score < options.scoreThreshold) {
|
|
1681
|
+
sessionLog(sessionId, `auto-search: top score ${results[0].score.toFixed(3)} below threshold ${options.scoreThreshold}`);
|
|
1682
|
+
return writeNoHintAndReconcile("below-threshold");
|
|
1683
|
+
}
|
|
1684
|
+
const hintText = buildAutoSearchHint(results, { wordRules: options.wordRules });
|
|
1685
|
+
if (!hintText) {
|
|
1686
|
+
return writeNoHintAndReconcile("empty");
|
|
1687
|
+
}
|
|
1688
|
+
const payload = `
|
|
3428
1689
|
|
|
3429
1690
|
${hintText}`;
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
1691
|
+
const outcome = persistAutoSearchDecision(db, sessionId, {
|
|
1692
|
+
messageId: userMsgId,
|
|
1693
|
+
decision: "hint",
|
|
1694
|
+
text: payload
|
|
1695
|
+
}, startedAt);
|
|
1696
|
+
if (outcome === null) {
|
|
1697
|
+
persistAutoSearchSkip(db, sessionId, userMsgId);
|
|
1698
|
+
return { ok: false, kind: "timeout" };
|
|
1699
|
+
}
|
|
1700
|
+
if (!outcome.ok) {
|
|
1701
|
+
sessionLog(sessionId, `auto-search: CAS exhausted for ${userMsgId}; skipping wire append`);
|
|
1702
|
+
return { ok: false, kind: "cas-exhaustion" };
|
|
1703
|
+
}
|
|
1704
|
+
if (outcome.decision.decision === "hint") {
|
|
1705
|
+
appendReminderToUserMessageById(messages, userMsgId, outcome.decision.text);
|
|
1706
|
+
}
|
|
1707
|
+
sessionLog(sessionId, `auto-search: attached hint to ${userMsgId} (${results.length} fragments, top score ${results[0].score.toFixed(3)})`);
|
|
1708
|
+
return AUTO_SEARCH_OK;
|
|
1709
|
+
} finally {
|
|
1710
|
+
if (!lifecycleSignal.aborted && results !== null)
|
|
1711
|
+
queueAutoSearchBackfill(db, sessionId, options.projectPath, rawPrompt);
|
|
1712
|
+
}
|
|
1713
|
+
}, () => {
|
|
1714
|
+
if (wasAutoSearchSkipped(db, sessionId, userMsgId))
|
|
1715
|
+
return;
|
|
1716
|
+
const decision = getAutoSearchHintDecisions(db, sessionId).find((entry) => entry.messageId === userMsgId);
|
|
1717
|
+
if (decision?.decision === "hint")
|
|
1718
|
+
appendReminderToUserMessageById(messages, userMsgId, decision.text);
|
|
3434
1719
|
});
|
|
3435
|
-
if (!outcome.ok) {
|
|
3436
|
-
sessionLog(sessionId, `auto-search: CAS exhausted for ${userMsgId}; skipping wire append`);
|
|
3437
|
-
return { ok: false, kind: "cas-exhaustion" };
|
|
3438
|
-
}
|
|
3439
|
-
if (outcome.decision.decision === "hint") {
|
|
3440
|
-
appendReminderToUserMessageById(messages, userMsgId, outcome.decision.text);
|
|
3441
|
-
}
|
|
3442
|
-
sessionLog(sessionId, `auto-search: attached hint to ${userMsgId} (${results.length} fragments, top score ${results[0].score.toFixed(3)})`);
|
|
3443
|
-
return AUTO_SEARCH_OK;
|
|
3444
1720
|
}
|
|
3445
|
-
function clearAutoSearchForSession(
|
|
1721
|
+
function clearAutoSearchForSession(sessionId) {
|
|
1722
|
+
clearAutoSearchTimeoutForSession(sessionId);
|
|
1723
|
+
}
|
|
3446
1724
|
|
|
3447
1725
|
// src/hooks/magic-context/cache-busting-signals.ts
|
|
3448
1726
|
function canConsumeDeferredOnThisPass(args) {
|
|
@@ -4078,7 +2356,7 @@ function applyCavemanCleanup(sessionId, db, targets, tags, config) {
|
|
|
4078
2356
|
const eligible = tags.filter((tag) => tag.type === "message" && tag.status === "active" && (protectedCutoff === null || tag.tagNumber < protectedCutoff) && tag.byteSize >= config.minChars).sort((a, b) => a.tagNumber - b.tagNumber);
|
|
4079
2357
|
const tagsNeedingCompression = eligible.filter((tag, index) => {
|
|
4080
2358
|
const target = targets.get(tag.tagNumber);
|
|
4081
|
-
if (!target?.getContent || !target.setContent)
|
|
2359
|
+
if (!target?.getContent || !target.setContent || target.thinkingRewriteProtected)
|
|
4082
2360
|
return false;
|
|
4083
2361
|
const targetDepth = computeTargetDepth(index, eligible.length);
|
|
4084
2362
|
return targetDepth > tag.cavemanDepth;
|
|
@@ -4087,7 +2365,7 @@ function applyCavemanCleanup(sessionId, db, targets, tags, config) {
|
|
|
4087
2365
|
let state = getCavemanReplayState(db, sessionId);
|
|
4088
2366
|
if (!state.currentRules || state.englishWordRules !== englishWordRules) {
|
|
4089
2367
|
const compressedBefore = tags.filter((tag) => tag.type === "message" && tag.status === "active" && tag.cavemanDepth > 0);
|
|
4090
|
-
const switched = recordCavemanCurrentRules(db, sessionId, englishWordRules, compressedBefore.map((tag) => tag.tagNumber));
|
|
2368
|
+
const switched = compressedBefore.some((tag) => targets.get(tag.tagNumber)?.thinkingRewriteProtected) ? null : recordCavemanCurrentRules(db, sessionId, englishWordRules, compressedBefore.map((tag) => tag.tagNumber));
|
|
4091
2369
|
if (switched) {
|
|
4092
2370
|
state = switched;
|
|
4093
2371
|
const deepened = new Set(tagsNeedingCompression.map((tag) => tag.tagNumber));
|
|
@@ -4309,6 +2587,8 @@ function applyHeuristicCleanup(sessionId, db, targets, messageTagNumbers, config
|
|
|
4309
2587
|
continue;
|
|
4310
2588
|
const strippedSource = stripTagPrefix(stripped);
|
|
4311
2589
|
if (strippedSource.trim().length === 0) {
|
|
2590
|
+
if (target.thinkingDropProtected)
|
|
2591
|
+
continue;
|
|
4312
2592
|
const dropResult = target.drop?.() ?? "absent";
|
|
4313
2593
|
const replacement = `[dropped §${tag.tagNumber}§]`;
|
|
4314
2594
|
const didReplace = dropResult === "absent" ? target.setContent(replacement) : false;
|
|
@@ -4429,147 +2709,41 @@ function extractToolInfo(part) {
|
|
|
4429
2709
|
if (part.type === "tool_use" && typeof part.name === "string" && DEDUP_SAFE_TOOLS.has(part.name)) {
|
|
4430
2710
|
return { toolName: part.name, args: part.input ?? {} };
|
|
4431
2711
|
}
|
|
4432
|
-
return null;
|
|
4433
|
-
}
|
|
4434
|
-
function buildToolFingerprints(messages) {
|
|
4435
|
-
const fingerprints = new Map;
|
|
4436
|
-
for (const message of messages) {
|
|
4437
|
-
if (message.info.role !== "assistant")
|
|
4438
|
-
continue;
|
|
4439
|
-
const ownerMsgId = typeof message.info.id === "string" ? message.info.id : null;
|
|
4440
|
-
if (!ownerMsgId)
|
|
4441
|
-
continue;
|
|
4442
|
-
for (const part of message.parts) {
|
|
4443
|
-
const record = part;
|
|
4444
|
-
const info = extractToolInfo(record);
|
|
4445
|
-
if (!info)
|
|
4446
|
-
continue;
|
|
4447
|
-
const callId = extractCallId(record);
|
|
4448
|
-
if (!callId)
|
|
4449
|
-
continue;
|
|
4450
|
-
try {
|
|
4451
|
-
const fingerprint = `${ownerMsgId}:${info.toolName}:${JSON.stringify(info.args)}`;
|
|
4452
|
-
const compositeKey = `${ownerMsgId}\x00${callId}`;
|
|
4453
|
-
fingerprints.set(compositeKey, fingerprint);
|
|
4454
|
-
} catch {}
|
|
4455
|
-
}
|
|
4456
|
-
}
|
|
4457
|
-
return fingerprints;
|
|
4458
|
-
}
|
|
4459
|
-
function extractCallId(part) {
|
|
4460
|
-
if (part.type === "tool" && typeof part.callID === "string")
|
|
4461
|
-
return part.callID;
|
|
4462
|
-
if (part.type === "tool-invocation" && typeof part.callID === "string")
|
|
4463
|
-
return part.callID;
|
|
4464
|
-
if (part.type === "tool_use" && typeof part.id === "string")
|
|
4465
|
-
return part.id;
|
|
4466
|
-
return null;
|
|
4467
|
-
}
|
|
4468
|
-
|
|
4469
|
-
// src/hooks/magic-context/note-visibility.ts
|
|
4470
|
-
var NOTE_TOOL_NAMES = new Set(["ctx_note"]);
|
|
4471
|
-
var READ_ACTION = "read";
|
|
4472
|
-
function hasVisibleNoteReadCall(messages) {
|
|
4473
|
-
for (let i = messages.length - 1;i >= 0; i -= 1) {
|
|
4474
|
-
const parts = messages[i]?.parts;
|
|
4475
|
-
if (!Array.isArray(parts))
|
|
4476
|
-
continue;
|
|
4477
|
-
for (const part of parts) {
|
|
4478
|
-
if (isSentinel(part))
|
|
4479
|
-
continue;
|
|
4480
|
-
if (isVisibleNoteReadPart(part))
|
|
4481
|
-
return true;
|
|
4482
|
-
}
|
|
4483
|
-
}
|
|
4484
|
-
return false;
|
|
4485
|
-
}
|
|
4486
|
-
function isVisibleNoteReadPart(part) {
|
|
4487
|
-
if (!isRecord(part))
|
|
4488
|
-
return false;
|
|
4489
|
-
if (part.type === "tool" && typeof part.tool === "string" && NOTE_TOOL_NAMES.has(part.tool)) {
|
|
4490
|
-
const state = part.state;
|
|
4491
|
-
if (isRecord(state) && isRecord(state.input)) {
|
|
4492
|
-
return state.input.action === READ_ACTION;
|
|
4493
|
-
}
|
|
4494
|
-
return false;
|
|
4495
|
-
}
|
|
4496
|
-
if (part.type === "tool_use" && typeof part.name === "string" && NOTE_TOOL_NAMES.has(part.name)) {
|
|
4497
|
-
if (isRecord(part.input)) {
|
|
4498
|
-
return part.input.action === READ_ACTION;
|
|
4499
|
-
}
|
|
4500
|
-
return false;
|
|
4501
|
-
}
|
|
4502
|
-
if (part.type === "tool-invocation" && typeof part.toolName === "string" && NOTE_TOOL_NAMES.has(part.toolName)) {
|
|
4503
|
-
const argsCandidate = part.args ?? part.input;
|
|
4504
|
-
if (isRecord(argsCandidate)) {
|
|
4505
|
-
return argsCandidate.action === READ_ACTION;
|
|
4506
|
-
}
|
|
4507
|
-
return false;
|
|
4508
|
-
}
|
|
4509
|
-
return false;
|
|
4510
|
-
}
|
|
4511
|
-
|
|
4512
|
-
// src/hooks/magic-context/postprocess-read-cache.ts
|
|
4513
|
-
var connections = new WeakMap;
|
|
4514
|
-
function snapshot(db, sessionId) {
|
|
4515
|
-
const transaction = db;
|
|
4516
|
-
try {
|
|
4517
|
-
if (transaction.inTransaction || transaction.isTransaction) {
|
|
4518
|
-
connections.delete(db);
|
|
4519
|
-
return { version: -1, changes: -1 };
|
|
4520
|
-
}
|
|
4521
|
-
} catch {
|
|
4522
|
-
connections.delete(db);
|
|
4523
|
-
return { version: -1, changes: -1 };
|
|
4524
|
-
}
|
|
4525
|
-
let connection = connections.get(db);
|
|
4526
|
-
if (!connection) {
|
|
4527
|
-
connection = {
|
|
4528
|
-
stamp: db.prepare("SELECT total_changes() AS changes, data_version AS version FROM pragma_data_version"),
|
|
4529
|
-
sessions: new BoundedSessionMap(100)
|
|
4530
|
-
};
|
|
4531
|
-
connections.set(db, connection);
|
|
4532
|
-
}
|
|
4533
|
-
const stamp = connection.stamp.get();
|
|
4534
|
-
const previous = connection.sessions.get(sessionId);
|
|
4535
|
-
if (previous?.version === stamp.version && previous.changes === stamp.changes) {
|
|
4536
|
-
return previous;
|
|
4537
|
-
}
|
|
4538
|
-
const value = { ...stamp };
|
|
4539
|
-
connection.sessions.set(sessionId, value);
|
|
4540
|
-
return value;
|
|
4541
|
-
}
|
|
4542
|
-
function postprocessTailTags(db, sessionId) {
|
|
4543
|
-
const state = snapshot(db, sessionId);
|
|
4544
|
-
return state.tags ??= getTailHygieneTags(db, sessionId);
|
|
2712
|
+
return null;
|
|
4545
2713
|
}
|
|
4546
|
-
function
|
|
4547
|
-
const
|
|
4548
|
-
const
|
|
4549
|
-
|
|
4550
|
-
|
|
2714
|
+
function buildToolFingerprints(messages) {
|
|
2715
|
+
const fingerprints = new Map;
|
|
2716
|
+
for (const message of messages) {
|
|
2717
|
+
if (message.info.role !== "assistant")
|
|
2718
|
+
continue;
|
|
2719
|
+
const ownerMsgId = typeof message.info.id === "string" ? message.info.id : null;
|
|
2720
|
+
if (!ownerMsgId)
|
|
2721
|
+
continue;
|
|
2722
|
+
for (const part of message.parts) {
|
|
2723
|
+
const record = part;
|
|
2724
|
+
const info = extractToolInfo(record);
|
|
2725
|
+
if (!info)
|
|
2726
|
+
continue;
|
|
2727
|
+
const callId = extractCallId(record);
|
|
2728
|
+
if (!callId)
|
|
2729
|
+
continue;
|
|
2730
|
+
try {
|
|
2731
|
+
const fingerprint = `${ownerMsgId}:${info.toolName}:${JSON.stringify(info.args)}`;
|
|
2732
|
+
const compositeKey = `${ownerMsgId}\x00${callId}`;
|
|
2733
|
+
fingerprints.set(compositeKey, fingerprint);
|
|
2734
|
+
} catch {}
|
|
2735
|
+
}
|
|
4551
2736
|
}
|
|
4552
|
-
|
|
4553
|
-
state.hints = { protectedNumbers: new Set(protectedNumbers), value };
|
|
4554
|
-
return value;
|
|
2737
|
+
return fingerprints;
|
|
4555
2738
|
}
|
|
4556
|
-
function
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
hiddenSeamPlaceholderIds: new Set(replay.hiddenSeamPlaceholderIds),
|
|
4565
|
-
mergedReasoningStrippedIds: new Set(replay.mergedReasoningStrippedIds),
|
|
4566
|
-
trailingBlankDecisions: new Map(replay.trailingBlankDecisions),
|
|
4567
|
-
noteNudgeAnchors: structuredClone(replay.noteNudgeAnchors),
|
|
4568
|
-
autoSearchHintDecisions: structuredClone(replay.autoSearchHintDecisions),
|
|
4569
|
-
todoSyntheticAnchor: structuredClone(replay.todoSyntheticAnchor),
|
|
4570
|
-
pendingCompactionMarker: structuredClone(replay.pendingCompactionMarker),
|
|
4571
|
-
compactionMarker: structuredClone(replay.compactionMarker)
|
|
4572
|
-
};
|
|
2739
|
+
function extractCallId(part) {
|
|
2740
|
+
if (part.type === "tool" && typeof part.callID === "string")
|
|
2741
|
+
return part.callID;
|
|
2742
|
+
if (part.type === "tool-invocation" && typeof part.callID === "string")
|
|
2743
|
+
return part.callID;
|
|
2744
|
+
if (part.type === "tool_use" && typeof part.id === "string")
|
|
2745
|
+
return part.id;
|
|
2746
|
+
return null;
|
|
4573
2747
|
}
|
|
4574
2748
|
|
|
4575
2749
|
// src/hooks/magic-context/strip-content.ts
|
|
@@ -4752,11 +2926,8 @@ function replayStrippedInlineThinking(messages, messageTagNumbers, persistedWate
|
|
|
4752
2926
|
}
|
|
4753
2927
|
return stripped;
|
|
4754
2928
|
}
|
|
4755
|
-
function clearOldReasoning(messages, reasoningByMessage, messageTagNumbers,
|
|
4756
|
-
const
|
|
4757
|
-
if (maxTag === 0)
|
|
4758
|
-
return 0;
|
|
4759
|
-
const ageCutoff = maxTag - clearReasoningAge;
|
|
2929
|
+
function clearOldReasoning(messages, reasoningByMessage, messageTagNumbers, cutoff) {
|
|
2930
|
+
const ageCutoff = cutoff;
|
|
4760
2931
|
let cleared = 0;
|
|
4761
2932
|
for (const message of messages) {
|
|
4762
2933
|
const msgTag = messageTagNumbers.get(message) ?? 0;
|
|
@@ -4780,14 +2951,6 @@ function clearOldReasoning(messages, reasoningByMessage, messageTagNumbers, clea
|
|
|
4780
2951
|
}
|
|
4781
2952
|
return cleared;
|
|
4782
2953
|
}
|
|
4783
|
-
function findMaxTag(messageTagNumbers) {
|
|
4784
|
-
let max = 0;
|
|
4785
|
-
for (const tag of messageTagNumbers.values()) {
|
|
4786
|
-
if (tag > max)
|
|
4787
|
-
max = tag;
|
|
4788
|
-
}
|
|
4789
|
-
return max;
|
|
4790
|
-
}
|
|
4791
2954
|
var CLEARED_REASONING_TYPES = new Set(["thinking", "reasoning"]);
|
|
4792
2955
|
function stripClearedReasoning(messages) {
|
|
4793
2956
|
let stripped = 0;
|
|
@@ -4815,11 +2978,8 @@ function stripClearedReasoning(messages) {
|
|
|
4815
2978
|
return stripped;
|
|
4816
2979
|
}
|
|
4817
2980
|
var INLINE_THINKING_PATTERN = /<(?:thinking|think)>[\s\S]*?<\/(?:thinking|think)>\s*/g;
|
|
4818
|
-
function stripInlineThinking(messages, messageTagNumbers,
|
|
4819
|
-
const
|
|
4820
|
-
if (maxTag === 0)
|
|
4821
|
-
return 0;
|
|
4822
|
-
const ageCutoff = maxTag - clearReasoningAge;
|
|
2981
|
+
function stripInlineThinking(messages, messageTagNumbers, cutoff) {
|
|
2982
|
+
const ageCutoff = cutoff;
|
|
4823
2983
|
let stripped = 0;
|
|
4824
2984
|
for (const message of messages) {
|
|
4825
2985
|
if (message.info.role !== "assistant")
|
|
@@ -4882,7 +3042,7 @@ function findLatestAssistantReasoningMutationExemptMessage(messages) {
|
|
|
4882
3042
|
}
|
|
4883
3043
|
return;
|
|
4884
3044
|
}
|
|
4885
|
-
function planMergedAssistantReasoningStrip(messages, mutationExemptMessage, frozenMessageIds) {
|
|
3045
|
+
function planMergedAssistantReasoningStrip(messages, mutationExemptMessage, frozenMessageIds, protectedMessages) {
|
|
4886
3046
|
const plan = [];
|
|
4887
3047
|
let prevRole;
|
|
4888
3048
|
let keptReasoningInRun = false;
|
|
@@ -4896,7 +3056,7 @@ function planMergedAssistantReasoningStrip(messages, mutationExemptMessage, froz
|
|
|
4896
3056
|
const firstInRun = prevRole !== "assistant";
|
|
4897
3057
|
if (firstInRun)
|
|
4898
3058
|
keptReasoningInRun = false;
|
|
4899
|
-
if (message === mutationExemptMessage || frozenMessageIds?.has(message.info.id ?? "")) {
|
|
3059
|
+
if (protectedMessages?.has(message) || message === mutationExemptMessage || frozenMessageIds?.has(message.info.id ?? "")) {
|
|
4900
3060
|
prevRole = role;
|
|
4901
3061
|
continue;
|
|
4902
3062
|
}
|
|
@@ -5058,12 +3218,14 @@ function applyFrozenTrailingBlankDecisions(messages, frozenDecisions) {
|
|
|
5058
3218
|
}
|
|
5059
3219
|
return mutations;
|
|
5060
3220
|
}
|
|
5061
|
-
function stripReasoningFromAssistantIds(messages, providerID, messageIds) {
|
|
3221
|
+
function stripReasoningFromAssistantIds(messages, providerID, messageIds, protectedMessages) {
|
|
5062
3222
|
if (messageIds.size === 0)
|
|
5063
3223
|
return 0;
|
|
5064
3224
|
const emptySentinels = modelAcceptsEmptyContent(providerID);
|
|
5065
3225
|
let stripped = 0;
|
|
5066
3226
|
for (const message of messages) {
|
|
3227
|
+
if (protectedMessages?.has(message))
|
|
3228
|
+
continue;
|
|
5067
3229
|
const id = message.info.id;
|
|
5068
3230
|
if (typeof id !== "string" || !messageIds.has(id))
|
|
5069
3231
|
continue;
|
|
@@ -5089,7 +3251,7 @@ function findMergedReasoningStripDecisions(messages, providerID, frozenIds, opti
|
|
|
5089
3251
|
return [];
|
|
5090
3252
|
const frozenParts = readFrozenMergedReasoningParts(frozenIds);
|
|
5091
3253
|
const decisions = [];
|
|
5092
|
-
for (const entry of planMergedAssistantReasoningStrip(messages, options?.mutationExemptMessage, new Set(frozenParts.keys()))) {
|
|
3254
|
+
for (const entry of planMergedAssistantReasoningStrip(messages, options?.mutationExemptMessage, new Set(frozenParts.keys()), options?.protectedMessages)) {
|
|
5093
3255
|
const id = entry.message.info.id;
|
|
5094
3256
|
if (typeof id !== "string" || id.length === 0 || frozenParts.has(id))
|
|
5095
3257
|
continue;
|
|
@@ -5107,7 +3269,7 @@ function stripReasoningFromMergedAssistants(messages, providerID, options) {
|
|
|
5107
3269
|
let stripped = 0;
|
|
5108
3270
|
const frozenParts = readFrozenMergedReasoningParts(options?.frozenMessageIds ?? new Set);
|
|
5109
3271
|
for (const message of messages) {
|
|
5110
|
-
if (message
|
|
3272
|
+
if (message.info.role !== "assistant" || options?.restoreMessages?.has(message))
|
|
5111
3273
|
continue;
|
|
5112
3274
|
const parts = frozenParts.get(message.info.id ?? "");
|
|
5113
3275
|
if (!parts)
|
|
@@ -5122,7 +3284,7 @@ function stripReasoningFromMergedAssistants(messages, providerID, options) {
|
|
|
5122
3284
|
stripped++;
|
|
5123
3285
|
}
|
|
5124
3286
|
}
|
|
5125
|
-
for (const entry of planMergedAssistantReasoningStrip(messages, options?.mutationExemptMessage, new Set(frozenParts.keys()))) {
|
|
3287
|
+
for (const entry of planMergedAssistantReasoningStrip(messages, options?.mutationExemptMessage, new Set(frozenParts.keys()), options?.frozenMessageIds ? options.restoreMessages : options?.protectedMessages)) {
|
|
5126
3288
|
if (options?.frozenMessageIds) {
|
|
5127
3289
|
const id = entry.message.info.id;
|
|
5128
3290
|
if (typeof id !== "string" || !options.frozenMessageIds.has(id))
|
|
@@ -5240,12 +3402,8 @@ function stripOpenRouterReasoningDetails(message) {
|
|
|
5240
3402
|
return stripped;
|
|
5241
3403
|
}
|
|
5242
3404
|
function selectReasoningRemovals(args) {
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
if (tag > maxTag)
|
|
5246
|
-
maxTag = tag;
|
|
5247
|
-
const cutoff = maxTag - args.clearReasoningAge;
|
|
5248
|
-
if (maxTag === 0 || cutoff <= 0)
|
|
3405
|
+
const cutoff = args.cutoff;
|
|
3406
|
+
if (cutoff <= 0)
|
|
5249
3407
|
return [];
|
|
5250
3408
|
const newest = newestAssistant(args.messages);
|
|
5251
3409
|
const exempt = findLatestAssistantReasoningMutationExemptMessage(args.messages);
|
|
@@ -5259,7 +3417,7 @@ function selectReasoningRemovals(args) {
|
|
|
5259
3417
|
if (id !== undefined && (args.alreadyRemoved.has(id) || args.prefixBound && args.alsoGone?.has(id)))
|
|
5260
3418
|
continue;
|
|
5261
3419
|
const tag = args.messageTagNumbers.get(message) ?? 0;
|
|
5262
|
-
const removable = id !== undefined && message !== newest && message !== exempt && tag > 0 && tag <= cutoff && hasWireContentBesideReasoning(message) && reasoningPayloadLeavesWithParts(message);
|
|
3420
|
+
const removable = !args.protectedMessages?.has(message) && id !== undefined && message !== newest && message !== exempt && tag > 0 && tag <= cutoff && hasWireContentBesideReasoning(message) && reasoningPayloadLeavesWithParts(message);
|
|
5263
3421
|
if (removable && id !== undefined)
|
|
5264
3422
|
selected.push(id);
|
|
5265
3423
|
else if (args.prefixBound)
|
|
@@ -5270,10 +3428,9 @@ function selectReasoningRemovals(args) {
|
|
|
5270
3428
|
function removeReasoningParts(messages, ids, providerID) {
|
|
5271
3429
|
if (ids.size === 0)
|
|
5272
3430
|
return 0;
|
|
5273
|
-
const exempt = findLatestAssistantReasoningMutationExemptMessage(messages);
|
|
5274
3431
|
let removed = 0;
|
|
5275
3432
|
for (const message of messages) {
|
|
5276
|
-
if (message.info.role !== "assistant"
|
|
3433
|
+
if (message.info.role !== "assistant")
|
|
5277
3434
|
continue;
|
|
5278
3435
|
const id = message.info.id;
|
|
5279
3436
|
if (typeof id !== "string" || !ids.has(id))
|
|
@@ -5309,6 +3466,441 @@ function settleDroppedReasoningParts(messages, mode) {
|
|
|
5309
3466
|
return settled;
|
|
5310
3467
|
}
|
|
5311
3468
|
|
|
3469
|
+
// src/hooks/magic-context/latest-assistant-turn.ts
|
|
3470
|
+
var THINKING_TYPES = new Set(["reasoning", "thinking", "redacted_thinking"]);
|
|
3471
|
+
function role(message) {
|
|
3472
|
+
if (!isRecord(message))
|
|
3473
|
+
return;
|
|
3474
|
+
return isRecord(message.info) ? message.info.role : message.role;
|
|
3475
|
+
}
|
|
3476
|
+
function content(message) {
|
|
3477
|
+
if (!isRecord(message))
|
|
3478
|
+
return;
|
|
3479
|
+
return Array.isArray(message.parts) ? message.parts : message.content;
|
|
3480
|
+
}
|
|
3481
|
+
function latestAssistantTurnStart(messages) {
|
|
3482
|
+
let lo = 0;
|
|
3483
|
+
let hi = messages.length;
|
|
3484
|
+
while (lo < hi) {
|
|
3485
|
+
const mid = lo + hi >>> 1;
|
|
3486
|
+
if (isInActiveAnthropicTurn(messages, mid, true))
|
|
3487
|
+
hi = mid;
|
|
3488
|
+
else
|
|
3489
|
+
lo = mid + 1;
|
|
3490
|
+
}
|
|
3491
|
+
return lo;
|
|
3492
|
+
}
|
|
3493
|
+
function latestAssistantTurnMessages(messages) {
|
|
3494
|
+
return new Set(messages.slice(latestAssistantTurnStart(messages)));
|
|
3495
|
+
}
|
|
3496
|
+
function hasActiveAnthropicThinkingTurn(messages, provider, model) {
|
|
3497
|
+
const familyRoute = isAnthropicFamilyRoute(provider, model);
|
|
3498
|
+
for (const message of messages.slice(latestAssistantTurnStart(messages))) {
|
|
3499
|
+
if (role(message) !== "assistant")
|
|
3500
|
+
continue;
|
|
3501
|
+
const parts = content(message);
|
|
3502
|
+
if (!Array.isArray(parts))
|
|
3503
|
+
continue;
|
|
3504
|
+
if (parts.some((part) => isRecord(part) && THINKING_TYPES.has(String(part.type ?? part.kind)) && (familyRoute || isRecord(part.metadata) && isRecord(part.metadata.anthropic))))
|
|
3505
|
+
return true;
|
|
3506
|
+
}
|
|
3507
|
+
return false;
|
|
3508
|
+
}
|
|
3509
|
+
function protectNewTagMutations(messages, targets, protectedParts, prefixBound) {
|
|
3510
|
+
if (protectedParts.size === 0)
|
|
3511
|
+
return targets;
|
|
3512
|
+
const positions = new Map;
|
|
3513
|
+
let ordinal = 0;
|
|
3514
|
+
for (const message of messages)
|
|
3515
|
+
for (const part of message.parts)
|
|
3516
|
+
positions.set(part, ordinal++);
|
|
3517
|
+
let lastProtected = -1;
|
|
3518
|
+
for (const part of protectedParts)
|
|
3519
|
+
lastProtected = Math.max(lastProtected, positions.get(part) ?? -1);
|
|
3520
|
+
const result = new Map;
|
|
3521
|
+
for (const [tag, target] of targets) {
|
|
3522
|
+
const coordinates = target.mutationParts ?? (target.message ? target.message.parts.map((part) => ({ message: target.message, part })) : []);
|
|
3523
|
+
const prefixEdit = prefixBound && coordinates.some(({ part }) => (positions.get(part) ?? Infinity) < lastProtected);
|
|
3524
|
+
const dropsThinking = target.dropReasoningParts?.some((part) => protectedParts.has(part)) === true;
|
|
3525
|
+
if (!prefixEdit && !dropsThinking) {
|
|
3526
|
+
result.set(tag, target);
|
|
3527
|
+
continue;
|
|
3528
|
+
}
|
|
3529
|
+
result.set(tag, {
|
|
3530
|
+
...target,
|
|
3531
|
+
thinkingDropProtected: true,
|
|
3532
|
+
thinkingRewriteProtected: prefixEdit,
|
|
3533
|
+
canDrop: () => false,
|
|
3534
|
+
drop: () => "incomplete",
|
|
3535
|
+
truncate: () => "incomplete",
|
|
3536
|
+
skeletonReal: () => "incomplete",
|
|
3537
|
+
skeletonStripped: () => "incomplete",
|
|
3538
|
+
editMarker: () => "incomplete",
|
|
3539
|
+
editMarkerStripped: () => "incomplete",
|
|
3540
|
+
setContent: (content, options) => prefixEdit || options?.keepReasoning !== true ? false : target.setContent(content, options)
|
|
3541
|
+
});
|
|
3542
|
+
}
|
|
3543
|
+
return result;
|
|
3544
|
+
}
|
|
3545
|
+
function retainedActiveThinkingParts(args) {
|
|
3546
|
+
if (!hasActiveAnthropicThinkingTurn(args.messages, args.providerID, args.modelID))
|
|
3547
|
+
return new Set;
|
|
3548
|
+
const copies = args.messages.map((message) => ({
|
|
3549
|
+
info: message.info,
|
|
3550
|
+
parts: message.parts.map((part) => {
|
|
3551
|
+
if (!isRecord(part) || THINKING_TYPES.has(String(part.type)) || !isRecord(part.metadata))
|
|
3552
|
+
return part;
|
|
3553
|
+
return {
|
|
3554
|
+
...part,
|
|
3555
|
+
metadata: {
|
|
3556
|
+
...part.metadata,
|
|
3557
|
+
...isRecord(part.metadata.openrouter) ? { openrouter: { ...part.metadata.openrouter } } : {}
|
|
3558
|
+
}
|
|
3559
|
+
};
|
|
3560
|
+
})
|
|
3561
|
+
}));
|
|
3562
|
+
stripClearedReasoning(copies);
|
|
3563
|
+
stripReasoningFromAssistantIds(copies, args.providerID, args.bindingIds);
|
|
3564
|
+
stripReasoningFromMergedAssistants(copies, args.providerID, {
|
|
3565
|
+
frozenMessageIds: args.mergedIds
|
|
3566
|
+
});
|
|
3567
|
+
if (args.removedIds?.size)
|
|
3568
|
+
removeReasoningParts(copies, args.removedIds, args.providerID);
|
|
3569
|
+
const retained = new Set;
|
|
3570
|
+
for (let i = latestAssistantTurnStart(args.messages);i < copies.length; i++) {
|
|
3571
|
+
for (const part of copies[i].parts) {
|
|
3572
|
+
if (isRecord(part) && THINKING_TYPES.has(String(part.type)))
|
|
3573
|
+
retained.add(part);
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
return retained;
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3579
|
+
// src/hooks/magic-context/note-visibility.ts
|
|
3580
|
+
var NOTE_TOOL_NAMES = new Set(["ctx_note"]);
|
|
3581
|
+
var READ_ACTION = "read";
|
|
3582
|
+
function hasVisibleNoteReadCall(messages) {
|
|
3583
|
+
for (let i = messages.length - 1;i >= 0; i -= 1) {
|
|
3584
|
+
const parts = messages[i]?.parts;
|
|
3585
|
+
if (!Array.isArray(parts))
|
|
3586
|
+
continue;
|
|
3587
|
+
for (const part of parts) {
|
|
3588
|
+
if (isSentinel(part))
|
|
3589
|
+
continue;
|
|
3590
|
+
if (isVisibleNoteReadPart(part))
|
|
3591
|
+
return true;
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
return false;
|
|
3595
|
+
}
|
|
3596
|
+
function isVisibleNoteReadPart(part) {
|
|
3597
|
+
if (!isRecord(part))
|
|
3598
|
+
return false;
|
|
3599
|
+
if (part.type === "tool" && typeof part.tool === "string" && NOTE_TOOL_NAMES.has(part.tool)) {
|
|
3600
|
+
const state = part.state;
|
|
3601
|
+
if (isRecord(state) && isRecord(state.input)) {
|
|
3602
|
+
return state.input.action === READ_ACTION;
|
|
3603
|
+
}
|
|
3604
|
+
return false;
|
|
3605
|
+
}
|
|
3606
|
+
if (part.type === "tool_use" && typeof part.name === "string" && NOTE_TOOL_NAMES.has(part.name)) {
|
|
3607
|
+
if (isRecord(part.input)) {
|
|
3608
|
+
return part.input.action === READ_ACTION;
|
|
3609
|
+
}
|
|
3610
|
+
return false;
|
|
3611
|
+
}
|
|
3612
|
+
if (part.type === "tool-invocation" && typeof part.toolName === "string" && NOTE_TOOL_NAMES.has(part.toolName)) {
|
|
3613
|
+
const argsCandidate = part.args ?? part.input;
|
|
3614
|
+
if (isRecord(argsCandidate)) {
|
|
3615
|
+
return argsCandidate.action === READ_ACTION;
|
|
3616
|
+
}
|
|
3617
|
+
return false;
|
|
3618
|
+
}
|
|
3619
|
+
return false;
|
|
3620
|
+
}
|
|
3621
|
+
|
|
3622
|
+
// src/hooks/magic-context/postprocess-read-cache.ts
|
|
3623
|
+
var connections = new WeakMap;
|
|
3624
|
+
function snapshot(db, sessionId) {
|
|
3625
|
+
const transaction = db;
|
|
3626
|
+
try {
|
|
3627
|
+
if (transaction.inTransaction || transaction.isTransaction) {
|
|
3628
|
+
connections.delete(db);
|
|
3629
|
+
return { version: -1, changes: -1 };
|
|
3630
|
+
}
|
|
3631
|
+
} catch {
|
|
3632
|
+
connections.delete(db);
|
|
3633
|
+
return { version: -1, changes: -1 };
|
|
3634
|
+
}
|
|
3635
|
+
let connection = connections.get(db);
|
|
3636
|
+
if (!connection) {
|
|
3637
|
+
connection = {
|
|
3638
|
+
stamp: db.prepare("SELECT total_changes() AS changes, data_version AS version FROM pragma_data_version"),
|
|
3639
|
+
sessions: new BoundedSessionMap(100)
|
|
3640
|
+
};
|
|
3641
|
+
connections.set(db, connection);
|
|
3642
|
+
}
|
|
3643
|
+
const stamp = connection.stamp.get();
|
|
3644
|
+
const previous = connection.sessions.get(sessionId);
|
|
3645
|
+
if (previous?.version === stamp.version && previous.changes === stamp.changes) {
|
|
3646
|
+
return previous;
|
|
3647
|
+
}
|
|
3648
|
+
const value = { ...stamp };
|
|
3649
|
+
connection.sessions.set(sessionId, value);
|
|
3650
|
+
return value;
|
|
3651
|
+
}
|
|
3652
|
+
function postprocessTailTags(db, sessionId) {
|
|
3653
|
+
const state = snapshot(db, sessionId);
|
|
3654
|
+
return state.tags ??= getTailHygieneTags(db, sessionId);
|
|
3655
|
+
}
|
|
3656
|
+
function postprocessOldestTags(db, sessionId, protectedNumbers) {
|
|
3657
|
+
const state = snapshot(db, sessionId);
|
|
3658
|
+
const hints = state.hints;
|
|
3659
|
+
if (hints && hints.protectedNumbers.size === protectedNumbers.size && [...protectedNumbers].every((number) => hints.protectedNumbers.has(number))) {
|
|
3660
|
+
return hints.value;
|
|
3661
|
+
}
|
|
3662
|
+
const value = getOldestActiveUnprotectedToolTags(db, sessionId, protectedNumbers);
|
|
3663
|
+
state.hints = { protectedNumbers: new Set(protectedNumbers), value };
|
|
3664
|
+
return value;
|
|
3665
|
+
}
|
|
3666
|
+
function postprocessReplaySnapshot(db, sessionId) {
|
|
3667
|
+
const state = snapshot(db, sessionId);
|
|
3668
|
+
const replay = state.replay ??= loadPostprocessReplaySnapshot(db, sessionId);
|
|
3669
|
+
return {
|
|
3670
|
+
...replay,
|
|
3671
|
+
staleReduceStrippedIds: new Set(replay.staleReduceStrippedIds),
|
|
3672
|
+
processedImageStrippedIds: new Set(replay.processedImageStrippedIds),
|
|
3673
|
+
strippedPlaceholderIds: new Set(replay.strippedPlaceholderIds),
|
|
3674
|
+
hiddenSeamPlaceholderIds: new Set(replay.hiddenSeamPlaceholderIds),
|
|
3675
|
+
mergedReasoningStrippedIds: new Set(replay.mergedReasoningStrippedIds),
|
|
3676
|
+
trailingBlankDecisions: new Map(replay.trailingBlankDecisions),
|
|
3677
|
+
noteNudgeAnchors: structuredClone(replay.noteNudgeAnchors),
|
|
3678
|
+
autoSearchHintDecisions: structuredClone(replay.autoSearchHintDecisions),
|
|
3679
|
+
todoSyntheticAnchor: structuredClone(replay.todoSyntheticAnchor),
|
|
3680
|
+
pendingCompactionMarker: structuredClone(replay.pendingCompactionMarker),
|
|
3681
|
+
compactionMarker: structuredClone(replay.compactionMarker)
|
|
3682
|
+
};
|
|
3683
|
+
}
|
|
3684
|
+
|
|
3685
|
+
// src/hooks/magic-context/reasoning-budget.ts
|
|
3686
|
+
function projectOpencodeReasoningBudgetCutoff(db, sessionId, messages, budget, watermark, proseRatio) {
|
|
3687
|
+
const maxTags = new Map;
|
|
3688
|
+
for (const tag of getTagsBySession(db, sessionId)) {
|
|
3689
|
+
const id = tag.type === "tool" ? tag.toolOwnerMessageId : contentTagOwnerMessageId(tag.messageId);
|
|
3690
|
+
if (id)
|
|
3691
|
+
maxTags.set(id, Math.max(maxTags.get(id) ?? 0, tag.tagNumber));
|
|
3692
|
+
}
|
|
3693
|
+
const messageTags = new Map;
|
|
3694
|
+
const gone = new Set(getReasoningRemovalState(db, sessionId).messageIds);
|
|
3695
|
+
for (const message of messages) {
|
|
3696
|
+
if (typeof message.info.id !== "string")
|
|
3697
|
+
continue;
|
|
3698
|
+
const tag = maxTags.get(message.info.id) ?? 0;
|
|
3699
|
+
messageTags.set(message, tag);
|
|
3700
|
+
if (tag > 0 && tag <= watermark)
|
|
3701
|
+
gone.add(message.info.id);
|
|
3702
|
+
}
|
|
3703
|
+
return opencodeReasoningBudgetCutoff({
|
|
3704
|
+
messages,
|
|
3705
|
+
messageTagNumbers: messageTags,
|
|
3706
|
+
budget,
|
|
3707
|
+
alreadyRemoved: gone,
|
|
3708
|
+
textEstimateByMessageId: getReasoningTokenEstimatesByMessage(db, sessionId, proseRatio),
|
|
3709
|
+
proseRatio,
|
|
3710
|
+
frozenMergedIds: getMergedReasoningStrippedIds(db, sessionId),
|
|
3711
|
+
alsoGone: new Set([...getMergedReasoningStrippedIds(db, sessionId)].filter((id) => id.startsWith(THINKING_BINDING_RECOVERY_FROZEN_PREFIX)).map((id) => id.slice(THINKING_BINDING_RECOVERY_FROZEN_PREFIX.length)))
|
|
3712
|
+
});
|
|
3713
|
+
}
|
|
3714
|
+
var DEFAULT_KEEP_REASONING_TOKENS = 1e4;
|
|
3715
|
+
var UNKNOWN_REASONING_STEP_TOKENS = 1000;
|
|
3716
|
+
function resolveKeepReasoningTokens(config, modelKey) {
|
|
3717
|
+
const values = config && typeof config === "object" ? Object.assign(Object.create(null), config) : undefined;
|
|
3718
|
+
return typeof config === "number" ? config : resolveModelConfigValue(values, modelKey)?.value ?? values?.default ?? DEFAULT_KEEP_REASONING_TOKENS;
|
|
3719
|
+
}
|
|
3720
|
+
function reasoningStepCost(reported, textEstimate, opaque) {
|
|
3721
|
+
if (reported !== undefined && Number.isFinite(reported) && reported > 0)
|
|
3722
|
+
return reported;
|
|
3723
|
+
if (textEstimate > 0)
|
|
3724
|
+
return Math.ceil(textEstimate);
|
|
3725
|
+
return opaque ? UNKNOWN_REASONING_STEP_TOKENS : 0;
|
|
3726
|
+
}
|
|
3727
|
+
function reasoningBudgetCutoff(steps, budget) {
|
|
3728
|
+
const cost = (step) => step.alreadyRemoved ? 0 : typeof step.cost === "function" ? step.cost() : step.cost;
|
|
3729
|
+
let kept = 0;
|
|
3730
|
+
let upperBound = Number.POSITIVE_INFINITY;
|
|
3731
|
+
for (const step of steps) {
|
|
3732
|
+
if (!step.exempt)
|
|
3733
|
+
continue;
|
|
3734
|
+
kept += cost(step);
|
|
3735
|
+
if (step.tag > 0)
|
|
3736
|
+
upperBound = Math.min(upperBound, step.tag - 1);
|
|
3737
|
+
}
|
|
3738
|
+
for (let index = steps.length - 1;index >= 0; index--) {
|
|
3739
|
+
const step = steps[index];
|
|
3740
|
+
if (step.exempt || step.alreadyRemoved)
|
|
3741
|
+
continue;
|
|
3742
|
+
const tokens = cost(step);
|
|
3743
|
+
if (kept + tokens <= budget) {
|
|
3744
|
+
kept += tokens;
|
|
3745
|
+
if (step.tag > 0)
|
|
3746
|
+
upperBound = Math.min(upperBound, step.tag - 1);
|
|
3747
|
+
continue;
|
|
3748
|
+
}
|
|
3749
|
+
let tag = step.tag;
|
|
3750
|
+
for (let older = index - 1;tag <= 0 && older >= 0; older--)
|
|
3751
|
+
tag = steps[older].tag;
|
|
3752
|
+
return Math.max(0, Math.min(tag, upperBound));
|
|
3753
|
+
}
|
|
3754
|
+
return 0;
|
|
3755
|
+
}
|
|
3756
|
+
function reasoningTextAndOpaque(parts) {
|
|
3757
|
+
let text = "";
|
|
3758
|
+
let inlineText = "";
|
|
3759
|
+
let opaque = false;
|
|
3760
|
+
let hasReasoning = false;
|
|
3761
|
+
const detailText = new Set;
|
|
3762
|
+
for (const part of parts) {
|
|
3763
|
+
if (!isRecord(part))
|
|
3764
|
+
continue;
|
|
3765
|
+
if (part.ignored === true)
|
|
3766
|
+
continue;
|
|
3767
|
+
if (isRecord(part.metadata)) {
|
|
3768
|
+
const router = part.metadata.openrouter;
|
|
3769
|
+
if (isRecord(router) && Array.isArray(router.reasoning_details) && router.reasoning_details.length > 0) {
|
|
3770
|
+
hasReasoning = true;
|
|
3771
|
+
opaque = true;
|
|
3772
|
+
for (const detail of router.reasoning_details) {
|
|
3773
|
+
if (!isRecord(detail))
|
|
3774
|
+
continue;
|
|
3775
|
+
if (detail.type === "reasoning.text" && typeof detail.text === "string")
|
|
3776
|
+
detailText.add(detail.text);
|
|
3777
|
+
if (detail.type === "reasoning.summary" && typeof detail.summary === "string")
|
|
3778
|
+
detailText.add(detail.summary);
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
3781
|
+
}
|
|
3782
|
+
if (["reasoning", "thinking", "redacted_thinking"].includes(String(part.type))) {
|
|
3783
|
+
hasReasoning = true;
|
|
3784
|
+
const visible = part.type === "redacted_thinking" || part.redacted === true ? "" : typeof part.thinking === "string" ? part.thinking : typeof part.text === "string" ? part.text : "";
|
|
3785
|
+
if (visible !== "[cleared]")
|
|
3786
|
+
text += visible;
|
|
3787
|
+
opaque ||= part.type === "redacted_thinking" || part.redacted === true || visible === "" && (part.metadata !== undefined || part.signature !== undefined || part.thinkingSignature !== undefined);
|
|
3788
|
+
} else if (part.type === "text" && typeof part.text === "string") {
|
|
3789
|
+
for (const match of part.text.matchAll(/<(?:thinking|think)>([\s\S]*?)<\/(?:thinking|think)>/gi))
|
|
3790
|
+
inlineText += match[1];
|
|
3791
|
+
}
|
|
3792
|
+
}
|
|
3793
|
+
return { text: text || [...detailText].join(""), inlineText, opaque, hasReasoning };
|
|
3794
|
+
}
|
|
3795
|
+
function opencodeReasoningBudgetCutoff(args) {
|
|
3796
|
+
const assistants = args.messages.filter((message) => message.info.role === "assistant");
|
|
3797
|
+
const newest = assistants.at(-1);
|
|
3798
|
+
const exempt = findLatestAssistantReasoningMutationExemptMessage(args.messages);
|
|
3799
|
+
return reasoningBudgetCutoff(assistants.map((message) => ({
|
|
3800
|
+
tag: args.messageTagNumbers.get(message) ?? 0,
|
|
3801
|
+
exempt: message === newest || message === exempt || isInActiveAnthropicTurn(args.messages, args.messages.indexOf(message), args.anthropic ?? hasAnthropicReasoning(args.messages)),
|
|
3802
|
+
cost: () => {
|
|
3803
|
+
const costMessage = { ...message, parts: [...message.parts] };
|
|
3804
|
+
if (args.frozenMergedIds)
|
|
3805
|
+
stripReasoningFromMergedAssistants([costMessage], "anthropic", {
|
|
3806
|
+
frozenMessageIds: args.frozenMergedIds
|
|
3807
|
+
});
|
|
3808
|
+
const visible = reasoningTextAndOpaque(args.countNeutralized ? costMessage.parts.map(neutralizedReasoningSource) : costMessage.parts);
|
|
3809
|
+
if (!visible.hasReasoning && !visible.inlineText)
|
|
3810
|
+
return 0;
|
|
3811
|
+
const info = message.info;
|
|
3812
|
+
const tokens = isRecord(info.tokens) ? info.tokens.reasoning : undefined;
|
|
3813
|
+
const typedGone = typeof message.info.id === "string" && (args.alreadyRemoved?.has(message.info.id) === true || args.alsoGone?.has(message.info.id) === true);
|
|
3814
|
+
return (visible.hasReasoning && !typedGone ? reasoningStepCost(typeof tokens === "number" ? tokens : undefined, estimateTokens(visible.text) * (args.proseRatio ?? 1), visible.opaque) : 0) + Math.ceil(estimateTokens(visible.inlineText) * (args.proseRatio ?? 1));
|
|
3815
|
+
}
|
|
3816
|
+
})), args.budget);
|
|
3817
|
+
}
|
|
3818
|
+
|
|
3819
|
+
// src/hooks/magic-context/reasoning-budget-status.ts
|
|
3820
|
+
var sources = new Map;
|
|
3821
|
+
function captureOpencodeReasoningBudgetStatus(sessionId, messages, budget, prefixBound, proseRatio = 1) {
|
|
3822
|
+
if (messages.some((message) => !isRecord(message) || !isRecord(message.info) || typeof message.info.role !== "string" || !Array.isArray(message.parts))) {
|
|
3823
|
+
sources.delete(sessionId);
|
|
3824
|
+
return;
|
|
3825
|
+
}
|
|
3826
|
+
const compact = messages.filter((message) => message.info.role === "assistant").map((message) => {
|
|
3827
|
+
const info = message.info;
|
|
3828
|
+
const visible = reasoningTextAndOpaque(message.parts);
|
|
3829
|
+
const parts = message.parts.filter(isRecord).map((part) => ({
|
|
3830
|
+
type: typeof part.type === "string" ? part.type : "",
|
|
3831
|
+
text: typeof part.text === "string" && part.type !== "redacted_thinking" && part.redacted !== true ? part.text : undefined,
|
|
3832
|
+
thinking: typeof part.thinking === "string" && part.redacted !== true ? part.thinking : undefined,
|
|
3833
|
+
ignored: part.ignored === true,
|
|
3834
|
+
redacted: part.redacted === true,
|
|
3835
|
+
metadata: part.metadata === undefined ? undefined : {},
|
|
3836
|
+
signature: part.signature === undefined ? undefined : true,
|
|
3837
|
+
thinkingSignature: part.thinkingSignature === undefined ? undefined : true
|
|
3838
|
+
}));
|
|
3839
|
+
if (visible.hasReasoning && !reasoningTextAndOpaque(parts).hasReasoning)
|
|
3840
|
+
parts.push({
|
|
3841
|
+
type: "reasoning",
|
|
3842
|
+
text: visible.text,
|
|
3843
|
+
thinking: undefined,
|
|
3844
|
+
ignored: false,
|
|
3845
|
+
redacted: false,
|
|
3846
|
+
metadata: visible.opaque ? {} : undefined,
|
|
3847
|
+
signature: undefined,
|
|
3848
|
+
thinkingSignature: undefined
|
|
3849
|
+
});
|
|
3850
|
+
return {
|
|
3851
|
+
info: {
|
|
3852
|
+
role: info.role,
|
|
3853
|
+
tokens: isRecord(info.tokens) && typeof info.tokens.reasoning === "number" ? { reasoning: info.tokens.reasoning } : undefined
|
|
3854
|
+
},
|
|
3855
|
+
parts
|
|
3856
|
+
};
|
|
3857
|
+
});
|
|
3858
|
+
sources.delete(sessionId);
|
|
3859
|
+
sources.set(sessionId, { messages: compact, budget, prefixBound, proseRatio });
|
|
3860
|
+
if (sources.size > 32)
|
|
3861
|
+
sources.delete(sources.keys().next().value);
|
|
3862
|
+
}
|
|
3863
|
+
function reasoningBudgetStatusLine(sessionId) {
|
|
3864
|
+
try {
|
|
3865
|
+
const source = sources.get(sessionId);
|
|
3866
|
+
if (!source)
|
|
3867
|
+
return;
|
|
3868
|
+
source.status ??= opencodeReasoningBudgetStatus(source.messages, source.budget, source.prefixBound, source.proseRatio);
|
|
3869
|
+
return formatReasoningBudgetStatus(source.status);
|
|
3870
|
+
} catch {
|
|
3871
|
+
return;
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
function formatReasoningBudgetStatus(status) {
|
|
3875
|
+
const count = (tokens) => tokens >= 1000 ? `${Number((tokens / 1000).toFixed(1))}k` : String(Math.round(tokens));
|
|
3876
|
+
return `Reasoning kept: ${count(status.kept)} of ${count(status.budget)} (${status.estimated ? "estimated" : "reported"})${status.overrun ? ` (over budget: ${status.overrun})` : ""}`;
|
|
3877
|
+
}
|
|
3878
|
+
function opencodeReasoningBudgetStatus(messages, budget, prefixBound, proseRatio = 1) {
|
|
3879
|
+
const assistants = messages.filter((message) => message.info.role === "assistant");
|
|
3880
|
+
const newest = assistants.at(-1);
|
|
3881
|
+
const exempt = findLatestAssistantReasoningMutationExemptMessage(messages);
|
|
3882
|
+
let kept = 0;
|
|
3883
|
+
let exemptCost = 0;
|
|
3884
|
+
let estimated = false;
|
|
3885
|
+
for (const message of assistants) {
|
|
3886
|
+
const visible = reasoningTextAndOpaque(message.parts);
|
|
3887
|
+
const info = message.info;
|
|
3888
|
+
const reported = isRecord(info.tokens) && typeof info.tokens.reasoning === "number" ? info.tokens.reasoning : undefined;
|
|
3889
|
+
const cost = (visible.hasReasoning ? reported !== undefined && Number.isFinite(reported) && reported > 0 ? reported : reasoningStepCost(reported, estimateTokens(visible.text) * proseRatio, visible.opaque) : 0) + Math.ceil(estimateTokens(visible.inlineText) * proseRatio);
|
|
3890
|
+
kept += cost;
|
|
3891
|
+
if (message === newest || message === exempt)
|
|
3892
|
+
exemptCost += cost;
|
|
3893
|
+
if (cost > 0 && (!(reported && Number.isFinite(reported) && reported > 0) || visible.inlineText !== ""))
|
|
3894
|
+
estimated = true;
|
|
3895
|
+
}
|
|
3896
|
+
return {
|
|
3897
|
+
kept,
|
|
3898
|
+
budget,
|
|
3899
|
+
estimated,
|
|
3900
|
+
overrun: kept > budget ? exemptCost > budget ? "newest step" : prefixBound ? "signed prefix" : "awaiting rebuild" : undefined
|
|
3901
|
+
};
|
|
3902
|
+
}
|
|
3903
|
+
|
|
5312
3904
|
// src/hooks/magic-context/supersession-reclaim.ts
|
|
5313
3905
|
var SUPERSESSION_RECENT_MESSAGE_WINDOW = 20;
|
|
5314
3906
|
function recentSupersessionOwnerMessageIds(db, sessionId) {
|
|
@@ -6073,6 +4665,7 @@ function hasRecentAssistantCommit(messages) {
|
|
|
6073
4665
|
return false;
|
|
6074
4666
|
}
|
|
6075
4667
|
function tagMessages(sessionId, messages, tagger, db, options = {}) {
|
|
4668
|
+
const protectedThinkingParts = new Set([...options.protectedThinkingMessages ?? []].flatMap((message) => message.parts));
|
|
6076
4669
|
const skipPrefixInjection = options.skipPrefixInjection === true;
|
|
6077
4670
|
const onToolOwnerFallbackLookup = options.onToolOwnerFallbackLookup;
|
|
6078
4671
|
const targets = new Map;
|
|
@@ -6280,6 +4873,8 @@ function tagMessages(sessionId, messages, tagger, db, options = {}) {
|
|
|
6280
4873
|
}
|
|
6281
4874
|
targets.set(tagId, {
|
|
6282
4875
|
message,
|
|
4876
|
+
dropReasoningParts: thinkingParts,
|
|
4877
|
+
mutationParts: [{ message, part: textPart }],
|
|
6283
4878
|
textPrefix: skipPrefixInjection ? "" : prependTag(tagId, ""),
|
|
6284
4879
|
setContent: (content, options) => {
|
|
6285
4880
|
if (textPart.text === content)
|
|
@@ -6291,6 +4886,8 @@ function tagMessages(sessionId, messages, tagger, db, options = {}) {
|
|
|
6291
4886
|
if (options?.keepReasoning === true)
|
|
6292
4887
|
return true;
|
|
6293
4888
|
for (const tp of thinkingParts) {
|
|
4889
|
+
if (protectedThinkingParts.has(tp))
|
|
4890
|
+
continue;
|
|
6294
4891
|
if (partialEnd !== undefined && message.parts.indexOf(tp) > partialEnd)
|
|
6295
4892
|
continue;
|
|
6296
4893
|
neutralizeDroppedReasoningPart(tp);
|
|
@@ -6360,6 +4957,7 @@ function tagMessages(sessionId, messages, tagger, db, options = {}) {
|
|
|
6360
4957
|
messageTagNumbers.set(message, Math.max(messageTagNumbers.get(message) ?? 0, tagId));
|
|
6361
4958
|
targets.set(tagId, {
|
|
6362
4959
|
message,
|
|
4960
|
+
mutationParts: [{ message, part: filePart }],
|
|
6363
4961
|
setContent: (content) => {
|
|
6364
4962
|
const prev = messageParts[partIndex];
|
|
6365
4963
|
const prevText = typeof prev === "object" && prev !== null && "text" in prev ? prev.text : "";
|
|
@@ -6403,7 +5001,7 @@ function tagMessages(sessionId, messages, tagger, db, options = {}) {
|
|
|
6403
5001
|
if (touchesUncoveredPart)
|
|
6404
5002
|
continue;
|
|
6405
5003
|
const thinkingParts = toolThinkingByCallId.get(compositeKey) ?? [];
|
|
6406
|
-
targets.set(tagId, createToolDropTarget(compositeKey, thinkingParts, toolCallIndex, batch, tagId));
|
|
5004
|
+
targets.set(tagId, createToolDropTarget(compositeKey, thinkingParts.filter((part) => !protectedThinkingParts.has(part)), toolCallIndex, batch, tagId));
|
|
6407
5005
|
}
|
|
6408
5006
|
const hasRecentReduceCall = lastReduceMessageIndex >= 0 && messages.length - lastReduceMessageIndex <= RECENT_REDUCE_LOOKBACK;
|
|
6409
5007
|
return {
|
|
@@ -6546,7 +5144,8 @@ async function applyTodoSynthesis2(args) {
|
|
|
6546
5144
|
return 0;
|
|
6547
5145
|
}
|
|
6548
5146
|
function freezeAllReasoningForBindingRecovery(args) {
|
|
6549
|
-
const
|
|
5147
|
+
const protectedMessages = args.protectedMessages ?? new Set;
|
|
5148
|
+
const messageIds = findReasoningBearingAssistantIds(args.messages.filter((message) => !protectedMessages.has(message)));
|
|
6550
5149
|
const newIds = messageIds.filter((id) => !args.recoveredMessageIds.has(id));
|
|
6551
5150
|
if (newIds.length > 0 && !addMergedReasoningStrippedIds(args.db, args.sessionId, newIds.map(thinkingBindingRecoveryFrozenId))) {
|
|
6552
5151
|
return null;
|
|
@@ -6559,7 +5158,8 @@ function countAutoSearchHints(db, sessionId) {
|
|
|
6559
5158
|
return getAutoSearchHintDecisions(db, sessionId).filter((decision) => decision.decision === "hint").length;
|
|
6560
5159
|
}
|
|
6561
5160
|
function freezeReasoningOnBustingPass2(args) {
|
|
6562
|
-
const
|
|
5161
|
+
const protectedMessages = args.protectedMessages ?? new Set;
|
|
5162
|
+
const messageIds = findReasoningBearingAssistantIds(args.messages.filter((message) => !protectedMessages.has(message))).filter((id) => !args.alreadyFrozen.has(id));
|
|
6563
5163
|
if (messageIds.length === 0)
|
|
6564
5164
|
return { strip: null, persistenceFailed: false };
|
|
6565
5165
|
let persisted = false;
|
|
@@ -6646,7 +5246,7 @@ function replayRustModeBindingMismatchStrips2(args) {
|
|
|
6646
5246
|
if (messageId.length > 0)
|
|
6647
5247
|
recoveryMessageIds.add(messageId);
|
|
6648
5248
|
}
|
|
6649
|
-
stripReasoningFromAssistantIds(args.messages, args.resolvedProviderID, recoveryMessageIds);
|
|
5249
|
+
stripReasoningFromAssistantIds(args.messages, args.resolvedProviderID, recoveryMessageIds, args.protectedThinkingMessages);
|
|
6650
5250
|
}
|
|
6651
5251
|
var RUST_MARKER_LOCK_SKIP_LOG2 = "rust compaction-marker: pending target write skipped on lock contention; next cache-busting pass retries";
|
|
6652
5252
|
function isSqliteLockContentionError(error) {
|
|
@@ -6759,13 +5359,14 @@ function applyRustModeThinkingStrips(args, hostEditBeforeNewestThinking) {
|
|
|
6759
5359
|
if (messageId.length > 0)
|
|
6760
5360
|
recoveryMessageIds.add(messageId);
|
|
6761
5361
|
}
|
|
6762
|
-
const flagTarget = args.thinkingBindingRecoveryEnabledForModel ? getThinkingBindingRecoveryTarget(args.db, args.sessionId) : null;
|
|
5362
|
+
const flagTarget = !args.activeThinkingTurn && args.thinkingBindingRecoveryEnabledForModel ? getThinkingBindingRecoveryTarget(args.db, args.sessionId) : null;
|
|
6763
5363
|
if (flagTarget) {
|
|
6764
5364
|
thinkingBindingRecovery = freezeAllReasoningForBindingRecovery({
|
|
6765
5365
|
db: args.db,
|
|
6766
5366
|
sessionId: args.sessionId,
|
|
6767
5367
|
messages: args.messages,
|
|
6768
5368
|
flagTarget,
|
|
5369
|
+
protectedMessages: args.protectedThinkingMessages,
|
|
6769
5370
|
recoveredMessageIds: recoveryMessageIds
|
|
6770
5371
|
});
|
|
6771
5372
|
if (!thinkingBindingRecovery) {
|
|
@@ -6775,24 +5376,26 @@ function applyRustModeThinkingStrips(args, hostEditBeforeNewestThinking) {
|
|
|
6775
5376
|
} catch (error) {
|
|
6776
5377
|
sessionLog(args.sessionId, "rust thinking binding recovery failed:", error);
|
|
6777
5378
|
}
|
|
6778
|
-
stripReasoningFromAssistantIds(args.messages, args.resolvedProviderID, recoveryMessageIds);
|
|
6779
|
-
const moduleBustStrips = args.cacheBustingPass === true && (args.moduleReasoningTrimOnly !== true || hostEditBeforeNewestThinking);
|
|
5379
|
+
stripReasoningFromAssistantIds(args.messages, args.resolvedProviderID, recoveryMessageIds, args.protectedThinkingMessages);
|
|
5380
|
+
const moduleBustStrips = !args.activeThinkingTurn && args.cacheBustingPass === true && (args.moduleReasoningTrimOnly !== true || hostEditBeforeNewestThinking);
|
|
6780
5381
|
const stripFrom = args.thinkingBindingRecoveryEnabledForModel === true ? proactiveStripStartIndex({ ...args, cacheBustingPass: moduleBustStrips }) : null;
|
|
6781
5382
|
if (stripFrom !== null) {
|
|
6782
5383
|
const outcome = freezeReasoningOnBustingPass2({
|
|
6783
5384
|
db: args.db,
|
|
6784
5385
|
sessionId: args.sessionId,
|
|
6785
5386
|
messages: args.messages.slice(stripFrom),
|
|
5387
|
+
protectedMessages: args.protectedThinkingMessages,
|
|
6786
5388
|
alreadyFrozen: recoveryMessageIds
|
|
6787
5389
|
});
|
|
6788
5390
|
if (outcome.strip) {
|
|
6789
5391
|
proactiveThinkingStrip = outcome.strip;
|
|
6790
5392
|
for (const id of outcome.strip.messageIds)
|
|
6791
5393
|
recoveryMessageIds.add(id);
|
|
6792
|
-
stripReasoningFromAssistantIds(args.messages, args.resolvedProviderID, new Set(outcome.strip.messageIds));
|
|
5394
|
+
stripReasoningFromAssistantIds(args.messages, args.resolvedProviderID, new Set(outcome.strip.messageIds), args.protectedThinkingMessages);
|
|
6793
5395
|
}
|
|
6794
5396
|
}
|
|
6795
5397
|
}
|
|
5398
|
+
args.restoreLatestTurnOriginals?.();
|
|
6796
5399
|
return { thinkingBindingRecovery, proactiveThinkingStrip };
|
|
6797
5400
|
}
|
|
6798
5401
|
function runRustModePostprocess2(args) {
|
|
@@ -7042,6 +5645,8 @@ function evaluateEmergencyFailClosed2(input) {
|
|
|
7042
5645
|
};
|
|
7043
5646
|
}
|
|
7044
5647
|
function finalizeMessageRepresentation2(messages, resolvedProviderID, options) {
|
|
5648
|
+
const protectedMessages = options?.protectedThinkingMessages ?? new Set;
|
|
5649
|
+
const restoredMessages = new Set(messages.filter((message) => protectedMessages.has(message) && typeof message.info.id === "string" && options?.restoreThinkingMessageIds?.has(message.info.id)));
|
|
7045
5650
|
let clearedParts = 0;
|
|
7046
5651
|
if (modelAcceptsEmptyContent(resolvedProviderID)) {
|
|
7047
5652
|
const prependedMessageCount = Math.min(messages.length, Math.max(0, options?.prependedMessageCount ?? 0));
|
|
@@ -7056,13 +5661,15 @@ function finalizeMessageRepresentation2(messages, resolvedProviderID, options) {
|
|
|
7056
5661
|
}
|
|
7057
5662
|
}
|
|
7058
5663
|
if (targetedMessages.length > 0) {
|
|
7059
|
-
clearedParts = stripClearedReasoning(targetedMessages);
|
|
5664
|
+
clearedParts = stripClearedReasoning(targetedMessages.filter((message) => !restoredMessages.has(message)));
|
|
7060
5665
|
}
|
|
7061
5666
|
}
|
|
7062
|
-
const bindingRecoveryParts = options?.skipMergedReasoningStrip ? 0 : stripReasoningFromAssistantIds(messages, resolvedProviderID, options?.
|
|
5667
|
+
const bindingRecoveryParts = options?.skipMergedReasoningStrip ? 0 : stripReasoningFromAssistantIds(messages, resolvedProviderID, options?.frozenThinkingBindingMessageIds ?? new Set, restoredMessages);
|
|
7063
5668
|
const mergedReasoningParts = bindingRecoveryParts + (options?.skipMergedReasoningStrip ? 0 : stripReasoningFromMergedAssistants(messages, resolvedProviderID, {
|
|
7064
5669
|
mutationExemptMessage: options?.reasoningMutationExemptMessage,
|
|
7065
|
-
frozenMessageIds: options?.mergedReasoningStrippedIds
|
|
5670
|
+
frozenMessageIds: options?.mergedReasoningStrippedIds,
|
|
5671
|
+
protectedMessages,
|
|
5672
|
+
restoreMessages: restoredMessages
|
|
7066
5673
|
}));
|
|
7067
5674
|
if (!options?.skipTrailingWhitespaceStrip && modelAcceptsEmptyContent(resolvedProviderID)) {
|
|
7068
5675
|
applyFrozenTrailingBlankDecisions(messages, options?.trailingBlankDecisions ?? new Map);
|
|
@@ -7111,7 +5718,29 @@ async function runPostTransformPhase2(args) {
|
|
|
7111
5718
|
const deferredMaterialize = args.canConsumeDeferredLate && deferredMaterializationWasPending;
|
|
7112
5719
|
const materializationRequested = isExplicitFlush || deferredMaterialize;
|
|
7113
5720
|
const m0M1EnabledForFold = args.m0M1 !== undefined && (!!args.m0M1.projectPath || !!args.m0M1.projectDirectory) && (args.fullFeatureMode || compactionOff);
|
|
7114
|
-
const
|
|
5721
|
+
const frozenThinking = new Set(args.activeThinkingTurn ? getMergedReasoningStrippedIds(args.db, args.sessionId) : []);
|
|
5722
|
+
const bindingThinking = new Set([...frozenThinking].filter((id) => id.startsWith(THINKING_BINDING_RECOVERY_FROZEN_PREFIX)).map((id) => id.slice(THINKING_BINDING_RECOVERY_FROZEN_PREFIX.length)));
|
|
5723
|
+
const retainedThinking = args.activeThinkingTurn ? retainedActiveThinkingParts({
|
|
5724
|
+
messages: args.messages,
|
|
5725
|
+
providerID: args.resolvedProviderID,
|
|
5726
|
+
modelID: args.resolvedModelID,
|
|
5727
|
+
mergedIds: frozenThinking,
|
|
5728
|
+
bindingIds: bindingThinking,
|
|
5729
|
+
removedIds: getReasoningRemovalState(args.db, args.sessionId).messageIds
|
|
5730
|
+
}) : new Set;
|
|
5731
|
+
const activeThinkingTurn = retainedThinking.size > 0;
|
|
5732
|
+
const protectedThinkingMessages = new Set(args.messages.filter((message) => message.parts.some((part) => retainedThinking.has(part))));
|
|
5733
|
+
const newTargets = protectNewTagMutations(args.messages, args.targets, retainedThinking, args.thinkingBindingRecoveryEnabledForModel === true);
|
|
5734
|
+
let maxReasoningTag = 0;
|
|
5735
|
+
for (const tag of args.messageTagNumbers.values())
|
|
5736
|
+
maxReasoningTag = Math.max(maxReasoningTag, tag);
|
|
5737
|
+
let safeReasoningCutoff = maxReasoningTag;
|
|
5738
|
+
for (const message of protectedThinkingMessages) {
|
|
5739
|
+
const tag = args.messageTagNumbers.get(message);
|
|
5740
|
+
if (tag && tag > 0)
|
|
5741
|
+
safeReasoningCutoff = Math.min(safeReasoningCutoff, tag - 1);
|
|
5742
|
+
}
|
|
5743
|
+
const freezeM0M1 = args.freezeM0M1 === true || activeThinkingTurn && args.thinkingBindingRecoveryEnabledForModel === true;
|
|
7115
5744
|
const foldDueDecision = m0M1EnabledForFold && args.m0M1 && !freezeM0M1 ? mustMaterialize({
|
|
7116
5745
|
db: args.db,
|
|
7117
5746
|
sessionId: args.sessionId,
|
|
@@ -7228,6 +5857,8 @@ async function runPostTransformPhase2(args) {
|
|
|
7228
5857
|
const shouldReadPendingOps = !compactionOff && (materializationRequested || publishedM1RefreshedThisPass || args.schedulerDecision === "execute" || forceMaterialization || foldExecutedThisPass || firstRenderBust || compartmentRunning);
|
|
7229
5858
|
const pendingOps = shouldReadPendingOps ? getPendingOps(args.db, args.sessionId) : [];
|
|
7230
5859
|
const hasPendingUserOps = pendingOps.length > 0;
|
|
5860
|
+
if (args.schedulerDecision === "execute" && pendingOps.some((op) => newTargets.get(op.tagId)?.thinkingDropProtected))
|
|
5861
|
+
args.pendingMaterializationSessions.add(args.sessionId);
|
|
7231
5862
|
const formatPendingOpsDepth = () => {
|
|
7232
5863
|
const depth = getPendingOpsCount(args.db, args.sessionId);
|
|
7233
5864
|
return depth === null ? "not loaded (deferred pass)" : String(depth);
|
|
@@ -7345,7 +5976,7 @@ async function runPostTransformPhase2(args) {
|
|
|
7345
5976
|
const applyReason = isExplicitFlush ? "explicit_flush" : deferredMaterialize ? "deferred_materialization" : foldBustsServedPrefixThisPass && args.schedulerDecision !== "execute" ? `m0_hard_fold (drain folded into executed m[0] bust, scheduler=${args.schedulerDecision})` : `${reclaimRideLabel(rideSignals)} (scheduler=${args.schedulerDecision})`;
|
|
7346
5977
|
sessionLog(args.sessionId, `pending ops WILL APPLY — reason=${applyReason}, pendingOps=${formatPendingOpsDepth()}, context=${args.contextUsage.percentage.toFixed(1)}%`);
|
|
7347
5978
|
const tApply = performance.now();
|
|
7348
|
-
pendingOpsDidMutate = applyPendingOperations(args.sessionId, args.db,
|
|
5979
|
+
pendingOpsDidMutate = applyPendingOperations(args.sessionId, args.db, newTargets, new Set([
|
|
7349
5980
|
...args.contextUsage.percentage >= 95 ? [] : args.protectedTagIds,
|
|
7350
5981
|
...protectedToolTags
|
|
7351
5982
|
]), undefined, pendingOps, [], new Set, (reduction) => droppedTokenReductions.push(reduction));
|
|
@@ -7374,7 +6005,7 @@ async function runPostTransformPhase2(args) {
|
|
|
7374
6005
|
clearEmergencyDropSample(args.db, args.sessionId);
|
|
7375
6006
|
}
|
|
7376
6007
|
let routineCleanupApplied = (emergencyDropEligible ? args.contextUsage.percentage >= 95 || getEmergencyInputSample(args.db, args.sessionId) === 0 : !args.fullFeatureMode || !routinePressureAlreadyApplied) || materializationRequested || emergencyRewriteAlreadyPriced;
|
|
7377
|
-
let cleanup = applyHeuristicCleanup(args.sessionId, args.db,
|
|
6008
|
+
let cleanup = applyHeuristicCleanup(args.sessionId, args.db, newTargets, args.messageTagNumbers, {
|
|
7378
6009
|
protectedTagNumbers: args.protectedTagNumbers,
|
|
7379
6010
|
protectedCutoff: args.protectedCutoff,
|
|
7380
6011
|
protectedToolTags,
|
|
@@ -7388,7 +6019,7 @@ async function runPostTransformPhase2(args) {
|
|
|
7388
6019
|
caveman: cavemanConfig
|
|
7389
6020
|
}, heuristicTags);
|
|
7390
6021
|
if (!routineCleanupApplied && cleanup.emergencyDroppedTools > 0) {
|
|
7391
|
-
const ridingCleanup = applyHeuristicCleanup(args.sessionId, args.db,
|
|
6022
|
+
const ridingCleanup = applyHeuristicCleanup(args.sessionId, args.db, newTargets, args.messageTagNumbers, {
|
|
7392
6023
|
protectedTagNumbers: args.protectedTagNumbers,
|
|
7393
6024
|
protectedCutoff: args.protectedCutoff,
|
|
7394
6025
|
protectedToolTags,
|
|
@@ -7421,34 +6052,45 @@ async function runPostTransformPhase2(args) {
|
|
|
7421
6052
|
droppedTokenReductions.push(...cleanup.droppedTokenReductions);
|
|
7422
6053
|
const t7 = performance.now();
|
|
7423
6054
|
const ageLaneAllowed = !prefixBoundModel;
|
|
7424
|
-
const
|
|
6055
|
+
const bindingStrippedIds = new Set;
|
|
6056
|
+
if (routineCleanupApplied && prefixBoundModel) {
|
|
6057
|
+
for (const id of getMergedReasoningStrippedIds(args.db, args.sessionId)) {
|
|
6058
|
+
if (id.startsWith(THINKING_BINDING_RECOVERY_FROZEN_PREFIX)) {
|
|
6059
|
+
bindingStrippedIds.add(id.slice(THINKING_BINDING_RECOVERY_FROZEN_PREFIX.length));
|
|
6060
|
+
}
|
|
6061
|
+
}
|
|
6062
|
+
}
|
|
6063
|
+
const budgetCutoff = routineCleanupApplied ? Math.min(safeReasoningCutoff, opencodeReasoningBudgetCutoff({
|
|
6064
|
+
messages: args.messages,
|
|
6065
|
+
messageTagNumbers: args.messageTagNumbers,
|
|
6066
|
+
budget: args.keepReasoningTokens ?? DEFAULT_KEEP_REASONING_TOKENS,
|
|
6067
|
+
alreadyRemoved: removedReasoningIds,
|
|
6068
|
+
alsoGone: bindingStrippedIds,
|
|
6069
|
+
countNeutralized: reasoningRemovalSelectable,
|
|
6070
|
+
proseRatio: sessionDecisionCalibration(args.db, args.sessionId).proseRatio,
|
|
6071
|
+
frozenMergedIds: getMergedReasoningStrippedIds(args.db, args.sessionId),
|
|
6072
|
+
anthropic: canUseEmptySentinels || prefixBoundModel || hasAnthropicReasoning(args.messages),
|
|
6073
|
+
textEstimateByMessageId: getReasoningTokenEstimatesByMessage(args.db, args.sessionId, sessionDecisionCalibration(args.db, args.sessionId).proseRatio)
|
|
6074
|
+
})) : 0;
|
|
6075
|
+
const clearedReasoning = routineCleanupApplied && canUseEmptySentinels && ageLaneAllowed ? clearOldReasoning(args.messages, args.reasoningByMessage, args.messageTagNumbers, budgetCutoff) : 0;
|
|
7425
6076
|
if (routineCleanupApplied && canUseEmptySentinels) {
|
|
7426
6077
|
stripClearedReasoning(args.messages);
|
|
7427
6078
|
}
|
|
7428
|
-
const strippedInline = routineCleanupApplied && ageLaneAllowed ? stripInlineThinking(args.messages, args.messageTagNumbers,
|
|
6079
|
+
const strippedInline = routineCleanupApplied && ageLaneAllowed ? stripInlineThinking(args.messages, args.messageTagNumbers, budgetCutoff) : 0;
|
|
7429
6080
|
const persistedInlineWatermark = args.sessionMeta?.clearedReasoningThroughTag ?? 0;
|
|
7430
6081
|
if (routineCleanupApplied && persistedInlineWatermark > 0 && !compactionOff) {
|
|
7431
6082
|
replayStrippedInlineThinking(args.messages, args.messageTagNumbers, persistedInlineWatermark);
|
|
7432
6083
|
}
|
|
7433
6084
|
let removedReasoningMessages = 0;
|
|
7434
6085
|
if (routineCleanupApplied && reasoningRemovalSelectable && reasoningRemovalReadable) {
|
|
7435
|
-
const bindingStrippedIds = new Set;
|
|
7436
|
-
if (prefixBoundModel) {
|
|
7437
|
-
for (const id of getMergedReasoningStrippedIds(args.db, args.sessionId)) {
|
|
7438
|
-
if (!id.startsWith(THINKING_BINDING_RECOVERY_FROZEN_PREFIX))
|
|
7439
|
-
continue;
|
|
7440
|
-
const messageId = id.slice(THINKING_BINDING_RECOVERY_FROZEN_PREFIX.length);
|
|
7441
|
-
if (messageId.length > 0)
|
|
7442
|
-
bindingStrippedIds.add(messageId);
|
|
7443
|
-
}
|
|
7444
|
-
}
|
|
7445
6086
|
const newIds = selectReasoningRemovals({
|
|
7446
6087
|
messages: args.messages,
|
|
7447
6088
|
messageTagNumbers: args.messageTagNumbers,
|
|
7448
|
-
|
|
6089
|
+
cutoff: budgetCutoff,
|
|
7449
6090
|
alreadyRemoved: removedReasoningIds,
|
|
7450
6091
|
prefixBound: prefixBoundModel,
|
|
7451
|
-
alsoGone: bindingStrippedIds
|
|
6092
|
+
alsoGone: bindingStrippedIds,
|
|
6093
|
+
protectedMessages: protectedThinkingMessages
|
|
7452
6094
|
});
|
|
7453
6095
|
if (newIds.length > 0) {
|
|
7454
6096
|
let persisted = false;
|
|
@@ -7478,12 +6120,7 @@ async function runPostTransformPhase2(args) {
|
|
|
7478
6120
|
}
|
|
7479
6121
|
}
|
|
7480
6122
|
if (clearedReasoning > 0 || strippedInline > 0) {
|
|
7481
|
-
|
|
7482
|
-
for (const tag of args.messageTagNumbers.values()) {
|
|
7483
|
-
if (tag > maxTag)
|
|
7484
|
-
maxTag = tag;
|
|
7485
|
-
}
|
|
7486
|
-
const newWatermark = maxTag - args.clearReasoningAge;
|
|
6123
|
+
const newWatermark = budgetCutoff;
|
|
7487
6124
|
const currentWatermark = args.sessionMeta?.clearedReasoningThroughTag ?? 0;
|
|
7488
6125
|
if (newWatermark > currentWatermark) {
|
|
7489
6126
|
updateSessionMeta(args.db, args.sessionId, {
|
|
@@ -7499,7 +6136,7 @@ async function runPostTransformPhase2(args) {
|
|
|
7499
6136
|
heuristicOrReasoningDidMutate = heuristicMutationCount + clearedReasoning + strippedInline + removedReasoningMessages > 0;
|
|
7500
6137
|
heuristicOrReasoningEditBesidesTrim = heuristicMutationCount + clearedReasoning + strippedInline > 0;
|
|
7501
6138
|
droppedCount += clearedReasoning + strippedInline + removedReasoningMessages;
|
|
7502
|
-
if (pendingMaterializationAtPassStart && !freezeM0M1) {
|
|
6139
|
+
if (pendingMaterializationAtPassStart && !freezeM0M1 && !pendingOps.some((op) => newTargets.get(op.tagId)?.thinkingDropProtected)) {
|
|
7503
6140
|
args.pendingMaterializationSessions.delete(args.sessionId);
|
|
7504
6141
|
}
|
|
7505
6142
|
if (args.currentTurnId) {
|
|
@@ -7517,7 +6154,7 @@ async function runPostTransformPhase2(args) {
|
|
|
7517
6154
|
const syntheticPendingOps = buildSyntheticToolReclaimOps({
|
|
7518
6155
|
db: args.db,
|
|
7519
6156
|
sessionId: args.sessionId,
|
|
7520
|
-
targets:
|
|
6157
|
+
targets: newTargets,
|
|
7521
6158
|
watermark: args.sessionMeta.toolReclaimWatermark ?? 0,
|
|
7522
6159
|
protectedToolTags,
|
|
7523
6160
|
pendingOps
|
|
@@ -7529,7 +6166,7 @@ async function runPostTransformPhase2(args) {
|
|
|
7529
6166
|
const supersessionOps = buildSupersessionReclaimOps({
|
|
7530
6167
|
db: args.db,
|
|
7531
6168
|
sessionId: args.sessionId,
|
|
7532
|
-
targets:
|
|
6169
|
+
targets: newTargets,
|
|
7533
6170
|
pendingOps,
|
|
7534
6171
|
recentMessageIds,
|
|
7535
6172
|
protectedTagNumbers: args.protectedTagNumbers,
|
|
@@ -7544,7 +6181,7 @@ async function runPostTransformPhase2(args) {
|
|
|
7544
6181
|
const editReclaim = buildEditSupersessionReclaim({
|
|
7545
6182
|
db: args.db,
|
|
7546
6183
|
sessionId: args.sessionId,
|
|
7547
|
-
targets:
|
|
6184
|
+
targets: newTargets,
|
|
7548
6185
|
pendingOps,
|
|
7549
6186
|
recentMessageIds,
|
|
7550
6187
|
protectedTagNumbers: args.protectedTagNumbers,
|
|
@@ -7913,6 +6550,7 @@ async function runPostTransformPhase2(args) {
|
|
|
7913
6550
|
}
|
|
7914
6551
|
let autoSearchHintsBefore = 0;
|
|
7915
6552
|
let autoSearchHintAppendedThisPass = false;
|
|
6553
|
+
let autoSearchCompletedThisPass = false;
|
|
7916
6554
|
if (args.fullFeatureMode && args.autoSearch?.enabled && args.projectPath) {
|
|
7917
6555
|
const tAutoSearch = performance.now();
|
|
7918
6556
|
const visibleMemoryIds = getVisibleMemoryIds(args.db, args.sessionId) ?? undefined;
|
|
@@ -7933,6 +6571,7 @@ async function runPostTransformPhase2(args) {
|
|
|
7933
6571
|
wordRules: args.autoSearch.wordRules
|
|
7934
6572
|
}
|
|
7935
6573
|
});
|
|
6574
|
+
autoSearchCompletedThisPass = autoSearchOutcome.ok;
|
|
7936
6575
|
if (!autoSearchOutcome.ok) {
|
|
7937
6576
|
args.passOutcome?.record(`auto-search-${autoSearchOutcome.kind}`);
|
|
7938
6577
|
}
|
|
@@ -7940,7 +6579,7 @@ async function runPostTransformPhase2(args) {
|
|
|
7940
6579
|
args.passOutcome?.record("auto-search-internal-failure");
|
|
7941
6580
|
sessionLog(args.sessionId, "auto-search runner failed:", error);
|
|
7942
6581
|
}
|
|
7943
|
-
autoSearchHintAppendedThisPass = countAutoSearchHints(args.db, args.sessionId) !== autoSearchHintsBefore;
|
|
6582
|
+
autoSearchHintAppendedThisPass = autoSearchCompletedThisPass && countAutoSearchHints(args.db, args.sessionId) !== autoSearchHintsBefore;
|
|
7944
6583
|
logTransformTiming(args.sessionId, "pp.autoSearchHint", tAutoSearch);
|
|
7945
6584
|
}
|
|
7946
6585
|
if (args.fullFeatureMode && isCacheBustingPass) {
|
|
@@ -7977,7 +6616,7 @@ async function runPostTransformPhase2(args) {
|
|
|
7977
6616
|
}
|
|
7978
6617
|
}
|
|
7979
6618
|
const mergedReasoningStrippedIds = new Set(replaySnapshot?.mergedReasoningStrippedIds ?? []);
|
|
7980
|
-
const
|
|
6619
|
+
const frozenThinkingBindingMessageIds = new Set;
|
|
7981
6620
|
let thinkingBindingRecovery = null;
|
|
7982
6621
|
if (!compactionOff) {
|
|
7983
6622
|
try {
|
|
@@ -7985,17 +6624,18 @@ async function runPostTransformPhase2(args) {
|
|
|
7985
6624
|
if (id.startsWith(THINKING_BINDING_RECOVERY_FROZEN_PREFIX)) {
|
|
7986
6625
|
const messageId = id.slice(THINKING_BINDING_RECOVERY_FROZEN_PREFIX.length);
|
|
7987
6626
|
if (messageId.length > 0)
|
|
7988
|
-
|
|
6627
|
+
frozenThinkingBindingMessageIds.add(messageId);
|
|
7989
6628
|
}
|
|
7990
6629
|
}
|
|
7991
|
-
const flagTarget = args.thinkingBindingRecoveryEnabledForModel ? replaySnapshot?.thinkingBindingRecoveryTarget ?? null : null;
|
|
6630
|
+
const flagTarget = !activeThinkingTurn && args.thinkingBindingRecoveryEnabledForModel ? replaySnapshot?.thinkingBindingRecoveryTarget ?? null : null;
|
|
7992
6631
|
if (flagTarget) {
|
|
7993
6632
|
thinkingBindingRecovery = freezeAllReasoningForBindingRecovery({
|
|
7994
6633
|
db: args.db,
|
|
7995
6634
|
sessionId: args.sessionId,
|
|
7996
6635
|
messages: args.messages,
|
|
7997
6636
|
flagTarget,
|
|
7998
|
-
|
|
6637
|
+
protectedMessages: args.protectedThinkingMessages,
|
|
6638
|
+
recoveredMessageIds: frozenThinkingBindingMessageIds
|
|
7999
6639
|
});
|
|
8000
6640
|
if (thinkingBindingRecovery) {
|
|
8001
6641
|
for (const messageId of thinkingBindingRecovery.messageIds) {
|
|
@@ -8010,7 +6650,10 @@ async function runPostTransformPhase2(args) {
|
|
|
8010
6650
|
}
|
|
8011
6651
|
}
|
|
8012
6652
|
if (canUseEmptySentinels && isCacheBustingPass) {
|
|
8013
|
-
const candidates = findMergedReasoningStripDecisions(args.messages, args.resolvedProviderID, mergedReasoningStrippedIds, {
|
|
6653
|
+
const candidates = findMergedReasoningStripDecisions(args.messages, args.resolvedProviderID, mergedReasoningStrippedIds, {
|
|
6654
|
+
mutationExemptMessage: reasoningMutationExemptMessage,
|
|
6655
|
+
protectedMessages: protectedThinkingMessages
|
|
6656
|
+
});
|
|
8014
6657
|
const newlyDetectedIds = candidates.filter((id) => !mergedReasoningStrippedIds.has(id));
|
|
8015
6658
|
if (newlyDetectedIds.length > 0) {
|
|
8016
6659
|
const persisted = addMergedReasoningStrippedIds(args.db, args.sessionId, newlyDetectedIds);
|
|
@@ -8116,11 +6759,14 @@ async function runPostTransformPhase2(args) {
|
|
|
8116
6759
|
logTransformTiming(args.sessionId, "pp.frozenDecisions", tFrozenDecisions);
|
|
8117
6760
|
bustedThisPass ||= firstApplicationEdits.any;
|
|
8118
6761
|
const finalizeOptions = {
|
|
6762
|
+
modelID: args.resolvedModelID,
|
|
6763
|
+
protectedThinkingMessages: args.protectedThinkingMessages,
|
|
8119
6764
|
prependedMessageCount,
|
|
8120
6765
|
reasoningMutatedMessages,
|
|
8121
6766
|
reasoningMutationExemptMessage,
|
|
8122
6767
|
mergedReasoningStrippedIds,
|
|
8123
|
-
|
|
6768
|
+
frozenThinkingBindingMessageIds,
|
|
6769
|
+
restoreThinkingMessageIds: args.restoreThinkingMessageIds,
|
|
8124
6770
|
trailingBlankDecisions,
|
|
8125
6771
|
skipMergedReasoningStrip: compactionOff,
|
|
8126
6772
|
skipTrailingWhitespaceStrip: compactionOff
|
|
@@ -8132,7 +6778,7 @@ async function runPostTransformPhase2(args) {
|
|
|
8132
6778
|
parts: [...message.parts]
|
|
8133
6779
|
}));
|
|
8134
6780
|
stripClearedReasoning(remaining);
|
|
8135
|
-
stripReasoningFromAssistantIds(remaining, args.resolvedProviderID,
|
|
6781
|
+
stripReasoningFromAssistantIds(remaining, args.resolvedProviderID, frozenThinkingBindingMessageIds);
|
|
8136
6782
|
stripReasoningFromMergedAssistants(remaining, args.resolvedProviderID, {
|
|
8137
6783
|
frozenMessageIds: mergedReasoningStrippedIds,
|
|
8138
6784
|
mutationExemptMessage: remaining[args.messages.indexOf(reasoningMutationExemptMessage)]
|
|
@@ -8159,12 +6805,13 @@ async function runPostTransformPhase2(args) {
|
|
|
8159
6805
|
db: args.db,
|
|
8160
6806
|
sessionId: args.sessionId,
|
|
8161
6807
|
messages: args.messages,
|
|
8162
|
-
|
|
6808
|
+
protectedMessages: protectedThinkingMessages,
|
|
6809
|
+
alreadyFrozen: frozenThinkingBindingMessageIds
|
|
8163
6810
|
});
|
|
8164
6811
|
if (outcome.strip) {
|
|
8165
6812
|
proactiveThinkingStrip = outcome.strip;
|
|
8166
6813
|
for (const messageId of outcome.strip.messageIds) {
|
|
8167
|
-
|
|
6814
|
+
frozenThinkingBindingMessageIds.add(messageId);
|
|
8168
6815
|
mergedReasoningStrippedIds.add(thinkingBindingRecoveryFrozenId(messageId));
|
|
8169
6816
|
}
|
|
8170
6817
|
bustedThisPass = true;
|
|
@@ -8196,6 +6843,9 @@ async function runPostTransformPhase2(args) {
|
|
|
8196
6843
|
injectTemporalMarkers(args.messages, temporalDecisions);
|
|
8197
6844
|
const finalRepresentation = finalizeMessageRepresentation2(args.messages, args.resolvedProviderID, finalizeOptions);
|
|
8198
6845
|
const removedReasoningParts = reasoningRemovalEnabled ? removeReasoningParts(args.messages, removedReasoningIds, args.resolvedProviderID) : 0;
|
|
6846
|
+
const reasoningStatusRatio = sessionDecisionCalibration(args.db, args.sessionId).proseRatio;
|
|
6847
|
+
captureOpencodeReasoningBudgetStatus(args.sessionId, args.messages, args.keepReasoningTokens ?? DEFAULT_KEEP_REASONING_TOKENS, prefixBoundModel, reasoningStatusRatio);
|
|
6848
|
+
args.restoreLatestTurnOriginals?.();
|
|
8199
6849
|
sessionLog(args.sessionId, `final representation: clearedParts=${finalRepresentation.clearedParts} mergedReasoningParts=${finalRepresentation.mergedReasoningParts} removedReasoningParts=${removedReasoningParts} settledDroppedReasoning=${settledDroppedReasoning}`);
|
|
8200
6850
|
logTransformTiming(args.sessionId, "finalizeMessageRepresentation", tFinalRepresentation, `clearedParts=${finalRepresentation.clearedParts} mergedReasoningParts=${finalRepresentation.mergedReasoningParts}`);
|
|
8201
6851
|
const tTailBaseline = performance.now();
|
|
@@ -8327,4 +6977,4 @@ function checkM0MutationDriftAndSignal2(args) {
|
|
|
8327
6977
|
return false;
|
|
8328
6978
|
}
|
|
8329
6979
|
|
|
8330
|
-
export {
|
|
6980
|
+
export { createTagger, cavemanWordRulesForLanguage, findSessionId, findLastUserMessageId, clearAutoSearchForSession, setCtxReduceRegisteredGlobally, resolveCtxReduceAvailabilityFromMessages, resolveCtxReduceAvailability, primeCtxReduceSpawnPermission, ctxReduceSpawnPermissionReadNeeded, spawnAgentFromMessages, spawnAgentFromOpenCodeDb, todowritePermissionDenied, cachedToolPermissionDenied, clearToolPermissionDenied, resolveTodowriteAvailabilityFromMessages, resolveTodowriteAvailability, canConsumeDeferredOnThisPass, hasReclaimRide, reclaimRideLabel, replayClearedReasoning, replayStrippedInlineThinking, stripClearedReasoning, snapshotTrailingBlankSourceDecisions, projectOpencodeReasoningBudgetCutoff, resolveKeepReasoningTokens, reasoningBudgetCutoff, captureOpencodeReasoningBudgetStatus, reasoningBudgetStatusLine, ENGINE_RECONNECTING_USER_MESSAGE, contextRefusalError, protectedToolRefusal, outgoingContextRefusal, EmergencyFailClosedError, latestAssistantTurnStart, latestAssistantTurnMessages, hasActiveAnthropicThinkingTurn, replayCavemanCompression, DegradedPassRefusalError, degradedPassError, applyFlushedStatuses, stripStructuralNoise, logTransformTiming, hasRecentAssistantCommit, tagMessages, resetDegradedCacheCount2, isSyntheticHeadMessage2, applyTodoSynthesis2, freezeReasoningOnBustingPass2, firstServedDivergenceIndex2, rustModeServedKeyAfterPersistedStrips2, replayRustModeBindingMismatchStrips2, RUST_MARKER_LOCK_SKIP_LOG2, applyRustModeDeferredCompactionMarker2, clearRustModeBoundaryRecord2, runRustModePostprocess2, reconcileMarkerRepresentation2, clearPendingCompactionMarkerAfterSuccessfulDrain2, defaultCompactionMarkerStrategy2, abortSessionFailClosed2, evaluateEmergencyFailClosed2, finalizeMessageRepresentation2, runPostTransformPhase2, checkM0MutationDriftAndSignal2 };
|