@cortexkit/opencode-magic-context 0.45.0 → 0.46.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/README.md +26 -0
- package/dist/{announcement-f75t58v9.js → announcement-y35wzg2r.js} +1 -1
- 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 +10 -4
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/{conflict-warning-hook-5yrz2r24.js → conflict-warning-hook-7fj2b9jw.js} +6 -5
- package/dist/context-8dkpszy5.js +4483 -0
- package/dist/{derive-budgets-a63zsm5c.js → derive-budgets-7nj7r1rv.js} +2 -2
- package/dist/embedding-worker.js +4 -4
- package/dist/features/magic-context/checkout-claim-fake-subc.test-support.d.ts +45 -0
- package/dist/features/magic-context/checkout-claim-fake-subc.test-support.d.ts.map +1 -0
- package/dist/features/magic-context/checkout-claim.d.ts +155 -0
- package/dist/features/magic-context/checkout-claim.d.ts.map +1 -0
- package/dist/features/magic-context/compaction-marker.d.ts +20 -3
- package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
- package/dist/features/magic-context/compartment-storage.d.ts +23 -2
- package/dist/features/magic-context/compartment-storage.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/classify.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/evaluate-smart-notes.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/provider-output-failure.d.ts +6 -2
- package/dist/features/magic-context/dreamer/provider-output-failure.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/provider-output-failure.test-support.d.ts +30 -0
- package/dist/features/magic-context/dreamer/provider-output-failure.test-support.d.ts.map +1 -0
- package/dist/features/magic-context/dreamer/storage-dream-runs.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-executor.d.ts +9 -0
- package/dist/features/magic-context/dreamer/task-executor.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-gates.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-scheduler.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify-diff.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify-gate.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
- package/dist/features/magic-context/git-commits/search-git-commits.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
- package/dist/features/magic-context/memory/project-identity-cache.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory-verifications.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory.d.ts +3 -0
- package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
- package/dist/features/magic-context/memory/verification-paths.d.ts +2 -2
- package/dist/features/magic-context/memory/verification-paths.d.ts.map +1 -1
- package/dist/features/magic-context/merged-reasoning-decisions.d.ts.map +1 -1
- package/dist/features/magic-context/message-index-async.d.ts +3 -3
- package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
- package/dist/features/magic-context/message-index.d.ts +2 -0
- package/dist/features/magic-context/message-index.d.ts.map +1 -1
- package/dist/features/magic-context/migration-v95-perf-indexes.d.ts +12 -0
- package/dist/features/magic-context/migration-v95-perf-indexes.d.ts.map +1 -0
- package/dist/features/magic-context/migration-worker-client.d.ts +4 -4
- package/dist/features/magic-context/migration-worker-client.d.ts.map +1 -1
- package/dist/features/magic-context/migrations.d.ts +1 -0
- package/dist/features/magic-context/migrations.d.ts.map +1 -1
- package/dist/features/magic-context/mural/render-trigger.d.ts.map +1 -1
- package/dist/features/magic-context/mural/resolve-mural.d.ts +15 -4
- package/dist/features/magic-context/mural/resolve-mural.d.ts.map +1 -1
- package/dist/features/magic-context/mural/source-revision.d.ts +7 -0
- package/dist/features/magic-context/mural/source-revision.d.ts.map +1 -0
- package/dist/features/magic-context/mural/storage-mural-cues.d.ts.map +1 -1
- package/dist/features/magic-context/reclaim-protection.d.ts +15 -10
- package/dist/features/magic-context/reclaim-protection.d.ts.map +1 -1
- package/dist/features/magic-context/session-cache-ttl.d.ts +4 -2
- package/dist/features/magic-context/session-cache-ttl.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/capabilities.d.ts +3 -0
- package/dist/features/magic-context/smart-notes/capabilities.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/compiler.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/github-token.d.ts +5 -0
- package/dist/features/magic-context/smart-notes/github-token.d.ts.map +1 -0
- package/dist/features/magic-context/smart-notes/runner.d.ts +1 -0
- package/dist/features/magic-context/smart-notes/runner.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts +12 -0
- package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/ssrf-guard.d.ts +6 -0
- package/dist/features/magic-context/smart-notes/ssrf-guard.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/storage.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/types.d.ts +2 -0
- package/dist/features/magic-context/smart-notes/types.d.ts.map +1 -1
- package/dist/features/magic-context/storage-clone.d.ts.map +1 -1
- package/dist/features/magic-context/storage-db.d.ts +6 -9
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-shared.d.ts +1 -1
- package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
- package/dist/features/magic-context/storage-ops.d.ts +8 -1
- package/dist/features/magic-context/storage-ops.d.ts.map +1 -1
- package/dist/features/magic-context/storage-replay-document.d.ts +7 -0
- package/dist/features/magic-context/storage-replay-document.d.ts.map +1 -1
- package/dist/features/magic-context/storage-schema-helpers.d.ts +2 -0
- package/dist/features/magic-context/storage-schema-helpers.d.ts.map +1 -1
- package/dist/features/magic-context/storage-session-tables.d.ts.map +1 -1
- package/dist/features/magic-context/storage-source.d.ts.map +1 -1
- package/dist/features/magic-context/storage-tags.d.ts +11 -3
- package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
- package/dist/features/magic-context/storage.d.ts +2 -2
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/features/magic-context/temporal-decisions.d.ts +10 -0
- package/dist/features/magic-context/temporal-decisions.d.ts.map +1 -0
- package/dist/features/magic-context/tool-definition-tokens.d.ts +2 -0
- package/dist/features/magic-context/tool-definition-tokens.d.ts.map +1 -1
- package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
- package/dist/features/magic-context/types.d.ts +7 -6
- package/dist/features/magic-context/types.d.ts.map +1 -1
- package/dist/features/magic-context/window-report-ledger.d.ts.map +1 -1
- package/dist/hooks/auto-update-checker/index.d.ts.map +1 -1
- package/dist/hooks/magic-context/apply-operations.d.ts +8 -0
- package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
- package/dist/hooks/magic-context/cache-busting-signals.d.ts +6 -3
- package/dist/hooks/magic-context/cache-busting-signals.d.ts.map +1 -1
- package/dist/hooks/magic-context/channel2-cycle.d.ts +2 -0
- package/dist/hooks/magic-context/channel2-cycle.d.ts.map +1 -1
- package/dist/hooks/magic-context/compaction-marker-manager.d.ts +7 -0
- package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-prompt.d.ts +2 -2
- package/dist/hooks/magic-context/compartment-prompt.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-render-epoch.d.ts +8 -0
- package/dist/hooks/magic-context/compartment-render-epoch.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-drop-queue.d.ts +18 -4
- package/dist/hooks/magic-context/compartment-runner-drop-queue.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/ctx-reduce-availability.d.ts +12 -0
- package/dist/hooks/magic-context/ctx-reduce-availability.d.ts.map +1 -1
- package/dist/hooks/magic-context/decision-calibration.d.ts +3 -0
- package/dist/hooks/magic-context/decision-calibration.d.ts.map +1 -1
- package/dist/hooks/magic-context/default-protection-pressure.test-support.d.ts +37 -0
- package/dist/hooks/magic-context/default-protection-pressure.test-support.d.ts.map +1 -0
- package/dist/hooks/magic-context/drop-stale-reduce-calls.d.ts +4 -0
- package/dist/hooks/magic-context/drop-stale-reduce-calls.d.ts.map +1 -1
- package/dist/hooks/magic-context/edit-marker.d.ts.map +1 -1
- package/dist/hooks/magic-context/emergency-drop.d.ts +2 -0
- package/dist/hooks/magic-context/emergency-drop.d.ts.map +1 -1
- package/dist/hooks/magic-context/emergency-fail-closed.d.ts +21 -1
- package/dist/hooks/magic-context/emergency-fail-closed.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-handler.d.ts +5 -0
- package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +30 -1
- package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -1
- package/dist/hooks/magic-context/heuristic-cleanup.d.ts +2 -0
- package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
- package/dist/hooks/magic-context/historian-prompt-fit.d.ts +4 -2
- package/dist/hooks/magic-context/historian-prompt-fit.d.ts.map +1 -1
- 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/inject-compartments.d.ts +9 -10
- package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-measured-request.d.ts +69 -0
- package/dist/hooks/magic-context/lkg-measured-request.d.ts.map +1 -0
- package/dist/hooks/magic-context/lkg-persist.d.ts +2 -0
- package/dist/hooks/magic-context/lkg-persist.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-replay-fit.d.ts +14 -0
- package/dist/hooks/magic-context/lkg-replay-fit.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-replay.d.ts +10 -1
- package/dist/hooks/magic-context/lkg-replay.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-slot.d.ts +4 -0
- package/dist/hooks/magic-context/lkg-slot.d.ts.map +1 -1
- package/dist/hooks/magic-context/module-state-sync.d.ts +2 -0
- package/dist/hooks/magic-context/module-state-sync.d.ts.map +1 -1
- package/dist/hooks/magic-context/module-transport.d.ts.map +1 -1
- package/dist/hooks/magic-context/module-wire.d.ts.map +1 -1
- package/dist/hooks/magic-context/note-nudger.d.ts +17 -5
- package/dist/hooks/magic-context/note-nudger.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-chunk.d.ts +1 -1
- package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-db.d.ts +1 -0
- package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-formatting.d.ts +7 -0
- package/dist/hooks/magic-context/read-session-formatting.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-raw.d.ts +4 -0
- package/dist/hooks/magic-context/read-session-raw.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/recomp-orchestrator.d.ts +2 -0
- package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
- package/dist/hooks/magic-context/reference-retrieval.d.ts +27 -17
- package/dist/hooks/magic-context/reference-retrieval.d.ts.map +1 -1
- package/dist/hooks/magic-context/request-hook-order.d.ts +56 -0
- package/dist/hooks/magic-context/request-hook-order.d.ts.map +1 -0
- package/dist/hooks/magic-context/rust-mode-transform.d.ts +22 -3
- package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/strip-content.d.ts +4 -2
- package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
- package/dist/hooks/magic-context/supersession-reclaim.d.ts +4 -0
- package/dist/hooks/magic-context/supersession-reclaim.d.ts.map +1 -1
- package/dist/hooks/magic-context/system-prompt-hash.d.ts +7 -0
- package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
- package/dist/hooks/magic-context/tail-hygiene-walk.d.ts +12 -3
- package/dist/hooks/magic-context/tail-hygiene-walk.d.ts.map +1 -1
- package/dist/hooks/magic-context/temporal-awareness.d.ts +6 -3
- package/dist/hooks/magic-context/temporal-awareness.d.ts.map +1 -1
- package/dist/hooks/magic-context/temporal-served-projection.d.ts +4 -0
- package/dist/hooks/magic-context/temporal-served-projection.d.ts.map +1 -0
- package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
- package/dist/hooks/magic-context/tool-reclaim.d.ts +2 -0
- package/dist/hooks/magic-context/tool-reclaim.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-message-helpers.d.ts +5 -0
- package/dist/hooks/magic-context/transform-message-helpers.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +41 -17
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +12 -0
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/wrapup-orchestrator.d.ts.map +1 -1
- package/dist/{index-vvtfyw1z.js → index-0d3g3461.js} +5 -13
- package/dist/index-1bc61ans.js +154 -0
- package/dist/{index-n96ag4rk.js → index-1zx87sdm.js} +5 -1
- package/dist/{index-4krbw8z9.js → index-3h0ybwah.js} +608 -102
- package/dist/{index-gd5qx1j9.js → index-5eqk6z3s.js} +62 -1
- package/dist/index-5gq3b2ya.js +494 -0
- package/dist/{index-75zjgwga.js → index-7qw742ev.js} +1 -1
- package/dist/{index-f85teqck.js → index-8da2zjhz.js} +3 -256
- package/dist/{index-w9jwmntv.js → index-b35pxds2.js} +9 -1
- package/dist/{index-8fgkmxmy.js → index-e2dtv0ra.js} +4 -3
- package/dist/{v2/server-a4m2b1yv.js → index-fg34g6a5.js} +55 -1
- package/dist/{index-xgt9k6c6.js → index-j7fvnjye.js} +2 -1
- package/dist/{index-2aq21j41.js → index-k4404na5.js} +418 -127
- package/dist/{index-2ntg5sd4.js → index-k5bdx1bx.js} +9 -5
- package/dist/{index-bmzf7zxv.js → index-majxda46.js} +23 -5
- package/dist/{index-16ny736w.js → index-p821bjdb.js} +14406 -13726
- package/dist/{index-remp7e2g.js → index-q4yp4b52.js} +16 -1
- package/dist/index-qr6r7akd.js +28505 -0
- package/dist/{index-fr2z4qy8.js → index-r5hmbx3t.js} +132 -169
- package/dist/index-sfnj6h8h.js +259 -0
- package/dist/index-sj29b3z2.js +30 -0
- package/dist/{index-q7yesyk9.js → index-yasgb8ej.js} +23 -24
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1983 -33118
- package/dist/{message-time-backfill-an311pmk.js → message-time-backfill-68gj6t3e.js} +2 -2
- package/dist/migration-worker.js +12 -7
- package/dist/plugin/dream-timer.d.ts +25 -1
- 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/messages-transform.d.ts +13 -0
- package/dist/plugin/messages-transform.d.ts.map +1 -1
- package/dist/plugin/rpc-handlers.d.ts +4 -0
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/plugin/subagent-tool-policy.d.ts +14 -0
- package/dist/plugin/subagent-tool-policy.d.ts.map +1 -0
- package/dist/plugin/tool-registry.d.ts +1 -0
- package/dist/plugin/tool-registry.d.ts.map +1 -1
- package/dist/{read-session-chunk-8rxs0tc9.js → read-session-chunk-egz7r68d.js} +8 -8
- package/dist/{recomp-orchestrator-nzamtw29.js → recomp-orchestrator-ejq7ex5d.js} +15 -15
- package/dist/{send-session-notification-nqpv7cne.js → send-session-notification-dyw42dtr.js} +4 -4
- package/dist/shared/announcement.d.ts.map +1 -1
- package/dist/shared/cache-ttl-display.d.ts +6 -4
- package/dist/shared/cache-ttl-display.d.ts.map +1 -1
- package/dist/shared/model-cache-ttl.d.ts +1 -1
- package/dist/shared/model-cache-ttl.d.ts.map +1 -1
- package/dist/shared/model-suggestion-retry.d.ts +2 -0
- package/dist/shared/model-suggestion-retry.d.ts.map +1 -1
- package/dist/shared/models-dev-cache.d.ts.map +1 -1
- package/dist/shared/protected-tools-policy.d.ts +6 -0
- package/dist/shared/protected-tools-policy.d.ts.map +1 -0
- package/dist/shared/quota-diagnostic.d.ts +4 -0
- package/dist/shared/quota-diagnostic.d.ts.map +1 -0
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/shared/sqlite-context-pragmas.d.ts +12 -0
- package/dist/shared/sqlite-context-pragmas.d.ts.map +1 -0
- package/dist/shared/sqlite-helpers.d.ts +9 -0
- package/dist/shared/sqlite-helpers.d.ts.map +1 -1
- package/dist/shared/sqlite.d.ts +10 -0
- package/dist/shared/sqlite.d.ts.map +1 -1
- package/dist/shared/status-summary.d.ts +1 -0
- package/dist/shared/status-summary.d.ts.map +1 -1
- package/dist/shared/status-view-check.d.ts.map +1 -1
- package/dist/shared/status-view.d.ts +4 -0
- package/dist/shared/status-view.d.ts.map +1 -1
- package/dist/shared/storage-permissions.d.ts +24 -0
- package/dist/shared/storage-permissions.d.ts.map +1 -1
- package/dist/shared/temporal-upgrade-fixture.d.ts +19 -0
- package/dist/shared/temporal-upgrade-fixture.d.ts.map +1 -0
- package/dist/shared/user-facing-codes.d.ts +5 -0
- package/dist/shared/user-facing-codes.d.ts.map +1 -1
- package/dist/shared/write-transaction-timing.d.ts +8 -1
- package/dist/shared/write-transaction-timing.d.ts.map +1 -1
- package/dist/tools/ctx-expand/render.d.ts.map +1 -1
- package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
- package/dist/tools/ctx-reduce/constants.d.ts +2 -0
- package/dist/tools/ctx-reduce/constants.d.ts.map +1 -1
- package/dist/tools/ctx-reduce/tools.d.ts +4 -4
- package/dist/tools/ctx-reduce/tools.d.ts.map +1 -1
- package/dist/{transform-postprocess-phase-414phndq.js → transform-postprocess-phase-tz3pat2p.js} +15 -15
- package/dist/v2/context-8vgtb6sz.js +52166 -0
- package/dist/v2/{derive-budgets-v0krf3t7.js → derive-budgets-kdgp4s3r.js} +4 -3
- package/dist/v2/embedding-worker.js +6 -5
- package/dist/v2/fold/boundary.d.ts +2 -0
- package/dist/v2/fold/boundary.d.ts.map +1 -1
- package/dist/v2/fold/owner.d.ts.map +1 -1
- package/dist/v2/hooks/channel2.d.ts +2 -0
- package/dist/v2/hooks/channel2.d.ts.map +1 -1
- package/dist/v2/hooks/context.d.ts +2 -2
- package/dist/v2/hooks/context.d.ts.map +1 -1
- package/dist/v2/hooks/dream-timer.d.ts +39 -0
- package/dist/v2/hooks/dream-timer.d.ts.map +1 -0
- package/dist/v2/hooks/payload.d.ts.map +1 -1
- package/dist/v2/hooks/store.d.ts.map +1 -1
- package/dist/v2/hooks/tools.d.ts +1 -1
- package/dist/v2/hooks/tools.d.ts.map +1 -1
- package/dist/v2/hooks/update-check.d.ts.map +1 -1
- package/dist/v2/{message-time-backfill-0n93vrd7.js → message-time-backfill-r6qkem1x.js} +3 -3
- package/dist/v2/migration-worker.js +13 -8
- package/dist/v2/{read-session-chunk-hjza38hr.js → read-session-chunk-8vjp6581.js} +9 -9
- package/dist/v2/recomp-orchestrator-5xj0yzyk.js +39 -0
- package/dist/v2/{safe-notification-target-wtj0bbc2.js → safe-notification-target-eb1n1vfb.js} +2 -2
- package/dist/v2/{send-session-notification-mq9frrtm.js → send-session-notification-qs0j78v6.js} +6 -6
- package/dist/v2/server-14vq4kpm.js +30 -0
- package/dist/v2/server-1bc61ans.js +154 -0
- package/dist/v2/{server-n96ag4rk.js → server-1zx87sdm.js} +5 -1
- package/dist/v2/server-3n4q9xme.js +79 -0
- package/dist/v2/server-4663ejam.js +3351 -0
- package/dist/v2/{server-rhdgxp1t.js → server-51dv1pr5.js} +63 -2
- package/dist/v2/{server-5317ykaf.js → server-6r257mf5.js} +1 -1
- package/dist/v2/server-84w6rvvf.js +14 -0
- package/dist/v2/{server-qpg6wf72.js → server-ajzeteqw.js} +9 -1
- package/dist/{index-a4m2b1yv.js → v2/server-fg34g6a5.js} +55 -1
- package/dist/v2/{server-e36nf2p1.js → server-frbm99q5.js} +1595 -1599
- package/dist/v2/{server-remp7e2g.js → server-g0fjrkta.js} +16 -1
- package/dist/v2/{server-z19x7eqz.js → server-mcfawjgc.js} +4 -4
- package/dist/v2/server-p2en78c8.js +3892 -0
- package/dist/v2/{server-hfjfbg59.js → server-qp5hx1ad.js} +608 -105
- package/dist/v2/{server-c4g2y1z8.js → server-vnh7r35f.js} +2778 -5383
- package/dist/v2/server-vs5gpnd4.js +493 -0
- package/dist/v2/{server-w99cyt6a.js → server-wvjvfhck.js} +14 -6
- package/dist/v2/{server-v5xgft5v.js → server-xtp687da.js} +186 -3533
- package/dist/v2/{server-0jqw05sa.js → server-y8maxeft.js} +6 -84
- package/dist/v2/server.d.ts.map +1 -1
- package/dist/v2/server.js +104 -49288
- package/dist/v2/store-reader.d.ts +39 -1
- package/dist/v2/store-reader.d.ts.map +1 -1
- package/dist/v2/tool-result.d.ts +8 -0
- package/dist/v2/tool-result.d.ts.map +1 -0
- package/dist/v2/tui/index.d.ts.map +1 -1
- package/dist/v2/{wrapup-orchestrator-tbyx78dz.js → wrapup-orchestrator-r5t1pyj8.js} +20 -16
- package/dist/wrapup-orchestrator-kw3x3myw.js +26 -0
- package/package.json +22 -7
- package/src/config/index.ts +21 -4
- package/src/config/latch-permanence-guard.test.ts +6 -1
- package/src/config/live-run-config.ts +1 -0
- package/src/config/migrate-config-location.test.ts +31 -1
- package/src/config/migrate-config-location.ts +8 -41
- package/src/config/protected-tools.test.ts +70 -0
- package/src/config/raw-loader.test.ts +15 -0
- package/src/config/raw-loader.ts +1 -1
- package/src/config/removed-agent-source-fence.test.ts +10 -1
- package/src/config/schema/magic-context.test.ts +20 -1
- package/src/config/schema/magic-context.ts +21 -7
- package/src/shared/announcement.ts +6 -5
- package/src/shared/cache-ttl-display.test.ts +1 -1
- package/src/shared/cache-ttl-display.ts +17 -16
- package/src/shared/fold-render-lock-fence.test.ts +84 -0
- package/src/shared/fold-render-lock-fixtures.test.ts +276 -0
- package/src/shared/model-cache-ttl.test.ts +2 -2
- package/src/shared/model-cache-ttl.ts +8 -4
- package/src/shared/model-suggestion-retry.test.ts +104 -1
- package/src/shared/model-suggestion-retry.ts +63 -6
- package/src/shared/models-dev-cache.ts +3 -11
- package/src/shared/protected-tools-policy.ts +23 -0
- package/src/shared/quota-diagnostic.ts +9 -0
- package/src/shared/rpc-server.ts +6 -54
- package/src/shared/sqlite-context-pragmas.test.ts +105 -0
- package/src/shared/sqlite-context-pragmas.ts +24 -0
- package/src/shared/sqlite-helpers.ts +17 -0
- package/src/shared/sqlite.ts +75 -0
- package/src/shared/status-detail-text.test.ts +18 -1
- package/src/shared/status-summary.ts +11 -0
- package/src/shared/status-view-check.ts +5 -0
- package/src/shared/status-view.test.ts +11 -2
- package/src/shared/status-view.ts +12 -1
- package/src/shared/storage-permissions.test.ts +204 -0
- package/src/shared/storage-permissions.ts +192 -0
- package/src/shared/temporal-upgrade-fixture.ts +50 -0
- package/src/shared/user-facing-codes.ts +6 -0
- package/src/shared/write-transaction-timing.test.ts +39 -0
- package/src/shared/write-transaction-timing.ts +25 -1
- package/src/tui/dialogs/status-dialog-render.test.ts +1 -1
- package/src/tui/index.tsx +3 -1
- package/src/tui/startup-order.test.ts +16 -0
- package/src/tui-compiled/index.tsx +3 -1
- package/src/v2/tui/index.ts +9 -2
- package/dist/index-anq4nn6f.js +0 -131
- package/dist/index-e44zzy84.js +0 -10
- package/dist/index-x23gah8c.js +0 -16
- package/dist/v2/recomp-orchestrator-1aq7h44c.js +0 -36
- package/dist/v2/server-e44zzy84.js +0 -10
- package/dist/v2/server-fgmxwrgy.js +0 -16
- package/dist/v2/server-r7cmye3k.js +0 -131
- package/dist/wrapup-orchestrator-h28bqsy6.js +0 -26
- package/dist/v2/{server-ckpc8pxy.js → server-1qbnzc80.js} +8 -8
|
@@ -0,0 +1,4483 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getMagicContextBuiltinCommands,
|
|
3
|
+
isDreamerRunnable,
|
|
4
|
+
isCompactionEnabled,
|
|
5
|
+
getProtectedTokensTierOverrides,
|
|
6
|
+
loadPluginConfigDetailed,
|
|
7
|
+
BoundedSessionMap,
|
|
8
|
+
historianRunConfig,
|
|
9
|
+
dreamerRunConfig,
|
|
10
|
+
pluginConfigReader,
|
|
11
|
+
withLiveDreamerOutputCap,
|
|
12
|
+
renderUserFacingFailure,
|
|
13
|
+
userFacingFailureCode,
|
|
14
|
+
advanceSessionActivity,
|
|
15
|
+
backfillSessionActivity,
|
|
16
|
+
invalidateAutoEmbedSession,
|
|
17
|
+
deleteChunkEmbedBackoffForSession,
|
|
18
|
+
acquireCompartmentLease,
|
|
19
|
+
releaseCompartmentLeaseBestEffort,
|
|
20
|
+
isCompartmentLeaseHeld,
|
|
21
|
+
queueM0Mutation,
|
|
22
|
+
clearCachedM0M1,
|
|
23
|
+
getCompartments,
|
|
24
|
+
getUncoveredCompartmentEndsThrough,
|
|
25
|
+
getProtectionWindowForSession,
|
|
26
|
+
getChannel2NudgeState,
|
|
27
|
+
claimChannel2NudgeState,
|
|
28
|
+
casChannel2NudgeClaim,
|
|
29
|
+
getOverflowState,
|
|
30
|
+
recordOverflowDetected,
|
|
31
|
+
recordDetectedContextLimit,
|
|
32
|
+
getPersistedCompactionMarkerState,
|
|
33
|
+
setPersistedCompactionMarkerState,
|
|
34
|
+
getObservedEpochFloor,
|
|
35
|
+
providerResponseFailed,
|
|
36
|
+
isSuccessfulProviderCompletion,
|
|
37
|
+
lkgProviderInputTotal,
|
|
38
|
+
beginV2LkgRequest,
|
|
39
|
+
noteLkgProviderResponse,
|
|
40
|
+
getSlot,
|
|
41
|
+
copySessionStateForClone,
|
|
42
|
+
gaDatabasePath,
|
|
43
|
+
RAW_MESSAGE_TYPES,
|
|
44
|
+
V2StoreReaderPool,
|
|
45
|
+
V2StoreReader,
|
|
46
|
+
getOrCreateSessionMeta,
|
|
47
|
+
updateSessionMeta,
|
|
48
|
+
markSessionCleanupPending,
|
|
49
|
+
clearSession,
|
|
50
|
+
HiddenCompletionRefusal,
|
|
51
|
+
HIDDEN_HISTORIAN_AGENT,
|
|
52
|
+
HIDDEN_DREAMER_AGENT,
|
|
53
|
+
HIDDEN_CURATE_AGENT,
|
|
54
|
+
hiddenAgentFor,
|
|
55
|
+
hiddenToolLoop,
|
|
56
|
+
registerHiddenChildAgents,
|
|
57
|
+
HiddenAgentStepLimit,
|
|
58
|
+
HiddenChildHook,
|
|
59
|
+
parseProviderModel,
|
|
60
|
+
toModelEntry,
|
|
61
|
+
ACTIVE_TOOL_IDS,
|
|
62
|
+
createPromptSurfaceRuntime,
|
|
63
|
+
runTokenLog,
|
|
64
|
+
rebaseSessionCoordinatesAsync,
|
|
65
|
+
clearInjectionCache,
|
|
66
|
+
materializeM0,
|
|
67
|
+
resetPrefixTrimFallbackState,
|
|
68
|
+
resolveOpenCodeProtectedTailBoundary,
|
|
69
|
+
getActiveCompartmentRun
|
|
70
|
+
} from "./index-p821bjdb.js";
|
|
71
|
+
import"./index-sd3egckz.js";
|
|
72
|
+
import {
|
|
73
|
+
getHarness,
|
|
74
|
+
getDataDir,
|
|
75
|
+
getMagicContextStorageDir
|
|
76
|
+
} from "./index-q6hef7fh.js";
|
|
77
|
+
import {
|
|
78
|
+
log,
|
|
79
|
+
sessionLog
|
|
80
|
+
} from "./index-hybnrwq0.js";
|
|
81
|
+
import {
|
|
82
|
+
isSaneLimit,
|
|
83
|
+
setOutputReserveConfig,
|
|
84
|
+
resolveLimit,
|
|
85
|
+
refreshModelLimitsFromApi,
|
|
86
|
+
getModelsDevCacheState,
|
|
87
|
+
deriveHistorianChunkTokens2,
|
|
88
|
+
resolveKnownHistorianContextLimit2,
|
|
89
|
+
resolveHistorianContextLimit2
|
|
90
|
+
} from "./index-0d3g3461.js";
|
|
91
|
+
import"./index-yasgb8ej.js";
|
|
92
|
+
import"./index-q4yp4b52.js";
|
|
93
|
+
import {
|
|
94
|
+
CANONICAL_DREAM_TASKS,
|
|
95
|
+
DREAM_TASK_CAPABILITIES,
|
|
96
|
+
formatUnsupportedDreamTasks,
|
|
97
|
+
isCanonicalDreamTask,
|
|
98
|
+
buildDreamTaskRuntimeConfigs2,
|
|
99
|
+
userMemoryCollectionEnabled2
|
|
100
|
+
} from "./index-mc22ayvf.js";
|
|
101
|
+
import {
|
|
102
|
+
resolveHistorianModel2
|
|
103
|
+
} from "./index-sfpzqvdz.js";
|
|
104
|
+
import {
|
|
105
|
+
formatFailClosedBlockingMessage,
|
|
106
|
+
formatFailClosedBlockingSummary,
|
|
107
|
+
createFailClosedBlockingError,
|
|
108
|
+
isFailClosedBlockingError,
|
|
109
|
+
setHomeProjectPermission,
|
|
110
|
+
resolveProjectIdentityForSession,
|
|
111
|
+
scheduleAfterBootQuiet,
|
|
112
|
+
startBootDeadline,
|
|
113
|
+
BOOT_SQLITE_BUSY_TIMEOUT_MS,
|
|
114
|
+
openCurrentDatabase,
|
|
115
|
+
openDatabaseAsync,
|
|
116
|
+
isDatabasePersisted,
|
|
117
|
+
getDatabasePersistenceError
|
|
118
|
+
} from "./index-r5hmbx3t.js";
|
|
119
|
+
import {
|
|
120
|
+
isDroppedToolOutput,
|
|
121
|
+
evaluateChannel2,
|
|
122
|
+
reclaimableToolOutputCount,
|
|
123
|
+
buildChannel2Reminder,
|
|
124
|
+
markHostUnservedRow,
|
|
125
|
+
DreamTokenBudgetExceeded,
|
|
126
|
+
createDreamTokenBudget,
|
|
127
|
+
recordToolParameters,
|
|
128
|
+
createDroppedInputGuard,
|
|
129
|
+
assertExecutableToolInput,
|
|
130
|
+
detectOverflow,
|
|
131
|
+
hasRawMessageProvider2,
|
|
132
|
+
setBoundedRawMessageProvider2,
|
|
133
|
+
cleanUserText2,
|
|
134
|
+
readRawSessionMessageIdOrdinalsForRange2,
|
|
135
|
+
readRawSessionMessageOrdinalById2
|
|
136
|
+
} from "./index-3h0ybwah.js";
|
|
137
|
+
import {
|
|
138
|
+
hasMeaningfulUserText2,
|
|
139
|
+
preloadTokenizer,
|
|
140
|
+
estimateTokens
|
|
141
|
+
} from "./index-5eqk6z3s.js";
|
|
142
|
+
import {
|
|
143
|
+
withSqliteTransformPass,
|
|
144
|
+
withoutSqliteTransformPass,
|
|
145
|
+
isTransientSqliteError,
|
|
146
|
+
withAsyncPrivilegedWriter
|
|
147
|
+
} from "./index-fg34g6a5.js";
|
|
148
|
+
import {
|
|
149
|
+
getCurrentToolSetHash,
|
|
150
|
+
recordToolDefinition
|
|
151
|
+
} from "./index-5gq3b2ya.js";
|
|
152
|
+
import"./index-1zx87sdm.js";
|
|
153
|
+
import"./index-b35pxds2.js";
|
|
154
|
+
import"./index-majxda46.js";
|
|
155
|
+
import {
|
|
156
|
+
createSubcCheckoutClaimGate,
|
|
157
|
+
runSessionProjectBackfill,
|
|
158
|
+
createLiveSessionState,
|
|
159
|
+
getDefaultSubcConnectionFile,
|
|
160
|
+
RETROSPECTIVE_MAX_USER_MESSAGE_CHARS,
|
|
161
|
+
RETROSPECTIVE_MESSAGE_TRUNCATION_MARKER,
|
|
162
|
+
createDreamTaskExecutor,
|
|
163
|
+
runManualDream,
|
|
164
|
+
runDueTasksForProject,
|
|
165
|
+
startDreamScheduleTimer,
|
|
166
|
+
ensureProjectRegisteredFromOpenCodeDirectory,
|
|
167
|
+
resolveContextLimit,
|
|
168
|
+
resolveModelKey,
|
|
169
|
+
createScheduler,
|
|
170
|
+
describeStorageUnavailability,
|
|
171
|
+
getSessionErrorInfo,
|
|
172
|
+
summarizeManualDream,
|
|
173
|
+
createChatMessageHook,
|
|
174
|
+
createToolExecuteAfterHook,
|
|
175
|
+
recordHiddenVariantWarning,
|
|
176
|
+
registerRpcHandlers,
|
|
177
|
+
STORAGE_BUSY_MESSAGE,
|
|
178
|
+
StorageBusyRefusalError,
|
|
179
|
+
UnmanagedOverWindowError,
|
|
180
|
+
UnresolvedHistoryBoundaryError,
|
|
181
|
+
createTransform,
|
|
182
|
+
createSubcModuleClient,
|
|
183
|
+
createModuleToolBackends,
|
|
184
|
+
createRustRefusalRecovery,
|
|
185
|
+
createSystemPromptHashHandler,
|
|
186
|
+
createMessagesTransformHandler,
|
|
187
|
+
subagentToolRefusal,
|
|
188
|
+
hideSubagentTools,
|
|
189
|
+
createCtxExpandTools,
|
|
190
|
+
createCtxMemoryTools,
|
|
191
|
+
createCtxMemoryListTools,
|
|
192
|
+
createCtxNoteTools,
|
|
193
|
+
createCtxReduceTools,
|
|
194
|
+
createCtxSearchTools,
|
|
195
|
+
applyJsonSchemaParameterDescriptions,
|
|
196
|
+
MagicContextRpcServer
|
|
197
|
+
} from "./index-qr6r7akd.js";
|
|
198
|
+
import {
|
|
199
|
+
createTagger,
|
|
200
|
+
setCtxReduceRegisteredGlobally,
|
|
201
|
+
resolveCtxReduceAvailabilityFromMessages,
|
|
202
|
+
DegradedPassRefusalError,
|
|
203
|
+
EmergencyFailClosedError,
|
|
204
|
+
isSyntheticHeadMessage2
|
|
205
|
+
} from "./index-k4404na5.js";
|
|
206
|
+
import"./index-7qw742ev.js";
|
|
207
|
+
import {
|
|
208
|
+
pushNotification2,
|
|
209
|
+
isTuiConnected2
|
|
210
|
+
} from "./index-x8a7jmkh.js";
|
|
211
|
+
import"./index-fzbvakh0.js";
|
|
212
|
+
import"./index-j7fvnjye.js";
|
|
213
|
+
import"./index-k5bdx1bx.js";
|
|
214
|
+
import {
|
|
215
|
+
detectConflicts
|
|
216
|
+
} from "./index-8da2zjhz.js";
|
|
217
|
+
import"./index-fmsr7v69.js";
|
|
218
|
+
import {
|
|
219
|
+
getErrorMessage
|
|
220
|
+
} from "./index-y8v6yerm.js";
|
|
221
|
+
import"./index-sj29b3z2.js";
|
|
222
|
+
|
|
223
|
+
// src/hooks/magic-context/history-boundary-repair.ts
|
|
224
|
+
function readBaseline(db, sessionId) {
|
|
225
|
+
const row = db.prepare("SELECT cached_m0_bytes IS NOT NULL AS hasM0, cached_m0_last_baseline_end_message_id AS boundary FROM session_meta WHERE session_id = ?").get(sessionId);
|
|
226
|
+
return {
|
|
227
|
+
hasCachedM0: row?.hasM0 === 1,
|
|
228
|
+
boundary: row?.boundary && row.boundary.length > 0 ? row.boundary : null
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function findAnchor(compartments, latestIndex, isInHostStore) {
|
|
232
|
+
for (let index = latestIndex - 1;index >= 0; index -= 1) {
|
|
233
|
+
const candidate = compartments[index];
|
|
234
|
+
const endId = candidate?.endMessageId;
|
|
235
|
+
if (!candidate || typeof endId !== "string" || endId.length === 0)
|
|
236
|
+
continue;
|
|
237
|
+
const present = isInHostStore(endId);
|
|
238
|
+
if (present === null)
|
|
239
|
+
return "unknown";
|
|
240
|
+
if (present)
|
|
241
|
+
return candidate;
|
|
242
|
+
}
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
function forgetCachedPrefix(sessionId) {
|
|
246
|
+
clearInjectionCache(sessionId);
|
|
247
|
+
resetPrefixTrimFallbackState(sessionId);
|
|
248
|
+
}
|
|
249
|
+
function dropCompartmentsAfter(db, sessionId, anchor, holderId) {
|
|
250
|
+
db.exec("BEGIN IMMEDIATE");
|
|
251
|
+
let finished = false;
|
|
252
|
+
try {
|
|
253
|
+
if (!isCompartmentLeaseHeld(db, sessionId, holderId)) {
|
|
254
|
+
db.exec("ROLLBACK");
|
|
255
|
+
finished = true;
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
const dropped = db.prepare("SELECT id, sequence FROM compartments WHERE session_id = ? AND sequence > ? ORDER BY sequence").all(sessionId, anchor.sequence);
|
|
259
|
+
const deleteEmbeddings = db.prepare("DELETE FROM compartment_chunk_embeddings WHERE compartment_id = ?");
|
|
260
|
+
for (const row of dropped)
|
|
261
|
+
deleteEmbeddings.run(row.id);
|
|
262
|
+
db.prepare("DELETE FROM compartments WHERE session_id = ? AND sequence > ?").run(sessionId, anchor.sequence);
|
|
263
|
+
db.prepare("DELETE FROM compression_depth WHERE session_id = ? AND message_ordinal > ?").run(sessionId, anchor.endMessage);
|
|
264
|
+
deleteChunkEmbedBackoffForSession(db, sessionId);
|
|
265
|
+
queueM0Mutation(db, { sessionId, mutationType: "recomp_boundary_change" });
|
|
266
|
+
clearCachedM0M1(db, sessionId);
|
|
267
|
+
db.exec("COMMIT");
|
|
268
|
+
finished = true;
|
|
269
|
+
return dropped.map((row) => row.sequence);
|
|
270
|
+
} finally {
|
|
271
|
+
if (!finished) {
|
|
272
|
+
try {
|
|
273
|
+
db.exec("ROLLBACK");
|
|
274
|
+
} catch {}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function repairMissingHistoryBoundary(args) {
|
|
279
|
+
const { db, sessionId, isInHostStore } = args;
|
|
280
|
+
const compartments = getCompartments(db, sessionId);
|
|
281
|
+
let latestIndex = compartments.length - 1;
|
|
282
|
+
while (latestIndex >= 0 && !compartments[latestIndex]?.endMessageId)
|
|
283
|
+
latestIndex -= 1;
|
|
284
|
+
if (latestIndex < 0)
|
|
285
|
+
return { kind: "intact" };
|
|
286
|
+
const latestEnd = compartments[latestIndex].endMessageId;
|
|
287
|
+
const latestPresent = isInHostStore(latestEnd);
|
|
288
|
+
if (latestPresent === null)
|
|
289
|
+
return { kind: "unknown" };
|
|
290
|
+
if (!latestPresent) {
|
|
291
|
+
const missing = latestEnd;
|
|
292
|
+
const problem = `newest compartment end ${latestEnd} is not in the host store`;
|
|
293
|
+
const anchor = findAnchor(compartments, latestIndex, isInHostStore);
|
|
294
|
+
if (anchor === "unknown")
|
|
295
|
+
return { kind: "unknown" };
|
|
296
|
+
if (anchor === null) {
|
|
297
|
+
sessionLog(sessionId, `history boundary unresolved: ${problem} and no older compartment end is in the host store; nothing to re-anchor on`);
|
|
298
|
+
return { kind: "unresolved", reason: "no-anchor", missingEndMessageId: missing };
|
|
299
|
+
}
|
|
300
|
+
if (getActiveCompartmentRun(sessionId) !== undefined) {
|
|
301
|
+
return { kind: "unresolved", reason: "busy", missingEndMessageId: missing };
|
|
302
|
+
}
|
|
303
|
+
const holderId = crypto.randomUUID();
|
|
304
|
+
if (!acquireCompartmentLease(db, sessionId, holderId)) {
|
|
305
|
+
return { kind: "unresolved", reason: "busy", missingEndMessageId: missing };
|
|
306
|
+
}
|
|
307
|
+
let dropped;
|
|
308
|
+
try {
|
|
309
|
+
dropped = dropCompartmentsAfter(db, sessionId, anchor, holderId);
|
|
310
|
+
} finally {
|
|
311
|
+
releaseCompartmentLeaseBestEffort(db, sessionId, holderId);
|
|
312
|
+
}
|
|
313
|
+
if (dropped === null) {
|
|
314
|
+
return { kind: "unresolved", reason: "busy", missingEndMessageId: missing };
|
|
315
|
+
}
|
|
316
|
+
invalidateAutoEmbedSession(sessionId);
|
|
317
|
+
forgetCachedPrefix(sessionId);
|
|
318
|
+
const range = dropped.length === 0 ? "none" : `${dropped[0]}-${dropped[dropped.length - 1]} (${dropped.length})`;
|
|
319
|
+
sessionLog(sessionId, `history boundary repair: ${problem}; re-anchored on compartment ${anchor.sequence} ending at ${anchor.endMessageId} (ordinal ${anchor.endMessage}); removed compartments ${range} for the historian to rebuild; the cached prefix is rebuilt on this pass`);
|
|
320
|
+
return {
|
|
321
|
+
kind: "repaired",
|
|
322
|
+
missingEndMessageId: missing,
|
|
323
|
+
anchorEndMessageId: anchor.endMessageId,
|
|
324
|
+
droppedSequences: dropped
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
const baseline = readBaseline(db, sessionId);
|
|
328
|
+
if (!baseline.hasCachedM0 || baseline.boundary === null || baseline.boundary === latestEnd) {
|
|
329
|
+
return { kind: "intact" };
|
|
330
|
+
}
|
|
331
|
+
if (isInHostStore(baseline.boundary) !== false)
|
|
332
|
+
return { kind: "intact" };
|
|
333
|
+
clearCachedM0M1(db, sessionId);
|
|
334
|
+
forgetCachedPrefix(sessionId);
|
|
335
|
+
sessionLog(sessionId, `history boundary repair: the cached prefix boundary ${baseline.boundary} is not in the host store; rebuilding it on this pass against compartment end ${latestEnd}`);
|
|
336
|
+
return {
|
|
337
|
+
kind: "baseline-reset",
|
|
338
|
+
anchorEndMessageId: latestEnd,
|
|
339
|
+
staleBaselineId: baseline.boundary
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// src/hooks/magic-context/rust-served-m0.ts
|
|
344
|
+
function servedModuleM0Text(sessionId, readSlot = getSlot) {
|
|
345
|
+
const slot = readSlot(sessionId);
|
|
346
|
+
if (!slot)
|
|
347
|
+
return null;
|
|
348
|
+
let prefix;
|
|
349
|
+
try {
|
|
350
|
+
prefix = JSON.parse(slot.jsonPrefix);
|
|
351
|
+
} catch {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
if (!Array.isArray(prefix) || prefix.length === 0)
|
|
355
|
+
return null;
|
|
356
|
+
const head = prefix[0];
|
|
357
|
+
if (!head || typeof head !== "object" || !head.info || !Array.isArray(head.parts))
|
|
358
|
+
return null;
|
|
359
|
+
if (!isSyntheticHeadMessage2(head))
|
|
360
|
+
return null;
|
|
361
|
+
const text = head.parts.find((part) => part.type === "text");
|
|
362
|
+
return typeof text?.text === "string" ? text.text : null;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/v2/fold/markers.ts
|
|
366
|
+
var v2CompactionMarkerStrategy = {
|
|
367
|
+
setPending: () => {},
|
|
368
|
+
publish: () => false,
|
|
369
|
+
applyDeferred: () => ({ kind: "already-current" }),
|
|
370
|
+
reconcile: () => false
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
// src/v2/fold/boundary.ts
|
|
374
|
+
function createV2RustCompactionMarkerStrategy(findBoundaryUserMessage) {
|
|
375
|
+
return {
|
|
376
|
+
...v2CompactionMarkerStrategy,
|
|
377
|
+
applyDeferred: (db, sessionId, pending) => {
|
|
378
|
+
const existing = getPersistedCompactionMarkerState(db, sessionId);
|
|
379
|
+
if (existing && existing.boundaryOrdinal >= pending.ordinal) {
|
|
380
|
+
return { kind: "already-current" };
|
|
381
|
+
}
|
|
382
|
+
let boundary = null;
|
|
383
|
+
try {
|
|
384
|
+
boundary = findBoundaryUserMessage(sessionId, pending.endMessageId);
|
|
385
|
+
} catch (error) {
|
|
386
|
+
return {
|
|
387
|
+
kind: "retryable-failure",
|
|
388
|
+
cut: "definitely-no-cut",
|
|
389
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
if (!boundary) {
|
|
393
|
+
return {
|
|
394
|
+
kind: "retryable-failure",
|
|
395
|
+
cut: "definitely-no-cut",
|
|
396
|
+
error: new Error(`no user boundary found at or before endMessageId ${pending.endMessageId} (ordinal ${pending.ordinal}); preserving existing boundary`)
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
const state = {
|
|
400
|
+
boundaryMessageId: boundary.id,
|
|
401
|
+
summaryMessageId: "",
|
|
402
|
+
compactionPartId: "",
|
|
403
|
+
summaryPartId: "",
|
|
404
|
+
boundaryOrdinal: pending.ordinal,
|
|
405
|
+
targetEndMessageId: pending.endMessageId
|
|
406
|
+
};
|
|
407
|
+
setPersistedCompactionMarkerState(db, sessionId, state);
|
|
408
|
+
sessionLog(sessionId, `v2 boundary recorded at ordinal ${pending.ordinal}, boundary message ${boundary.id}`);
|
|
409
|
+
return { kind: "applied", markerOrdinal: pending.ordinal };
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
function trimToRecordedBoundary(db, sessionId, messages) {
|
|
414
|
+
const marker = getPersistedCompactionMarkerState(db, sessionId);
|
|
415
|
+
const boundaryId = marker?.boundaryMessageId;
|
|
416
|
+
if (!boundaryId)
|
|
417
|
+
return 0;
|
|
418
|
+
const start = messages.findIndex((message) => message.id === boundaryId);
|
|
419
|
+
if (start <= 0)
|
|
420
|
+
return 0;
|
|
421
|
+
const uncovered = new Set(getUncoveredCompartmentEndsThrough(db, sessionId).map((end) => end.endMessageId));
|
|
422
|
+
let partialIndex = messages.findIndex((message) => message.id !== undefined && uncovered.has(message.id));
|
|
423
|
+
const ranges = db.prepare("SELECT start_message AS start, end_message AS end FROM compartments WHERE session_id=? ORDER BY start_message, end_message").all(sessionId);
|
|
424
|
+
const coveredRanges = [];
|
|
425
|
+
for (const range of ranges) {
|
|
426
|
+
if (!Number.isSafeInteger(range.start) || !Number.isSafeInteger(range.end) || range.start < 0 || range.end < range.start)
|
|
427
|
+
continue;
|
|
428
|
+
const previous = coveredRanges.at(-1);
|
|
429
|
+
if (previous && range.start <= previous.end + 1)
|
|
430
|
+
previous.end = Math.max(previous.end, range.end);
|
|
431
|
+
else
|
|
432
|
+
coveredRanges.push({ ...range });
|
|
433
|
+
}
|
|
434
|
+
const covers = (ordinal) => {
|
|
435
|
+
if (ordinal === undefined || !Number.isSafeInteger(ordinal))
|
|
436
|
+
return false;
|
|
437
|
+
let low = 0, high = coveredRanges.length - 1;
|
|
438
|
+
while (low <= high) {
|
|
439
|
+
const middle = low + high >>> 1;
|
|
440
|
+
const range = coveredRanges[middle];
|
|
441
|
+
if (!range)
|
|
442
|
+
return false;
|
|
443
|
+
if (ordinal < range.start)
|
|
444
|
+
high = middle - 1;
|
|
445
|
+
else if (ordinal > range.end)
|
|
446
|
+
low = middle + 1;
|
|
447
|
+
else
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
450
|
+
return false;
|
|
451
|
+
};
|
|
452
|
+
let ordinals;
|
|
453
|
+
const prefix = messages.slice(0, start);
|
|
454
|
+
if (!prefix.every((message) => Number.isSafeInteger(message.ordinal) && (message.ordinal ?? -1) >= 1)) {
|
|
455
|
+
if (!hasRawMessageProvider2(sessionId))
|
|
456
|
+
return 0;
|
|
457
|
+
try {
|
|
458
|
+
const cutOrdinal = readRawSessionMessageOrdinalById2(sessionId, boundaryId);
|
|
459
|
+
if (cutOrdinal === null || cutOrdinal <= 1)
|
|
460
|
+
return 0;
|
|
461
|
+
ordinals = readRawSessionMessageIdOrdinalsForRange2(sessionId, 1, cutOrdinal - 1);
|
|
462
|
+
} catch {
|
|
463
|
+
return 0;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
for (let index = 0;index < start; index++) {
|
|
467
|
+
const message = messages[index];
|
|
468
|
+
if (!message)
|
|
469
|
+
return 0;
|
|
470
|
+
const ordinal = ordinals ? message.id ? ordinals.get(message.id) : undefined : message.ordinal;
|
|
471
|
+
const covered = covers(ordinal);
|
|
472
|
+
if (!covered) {
|
|
473
|
+
partialIndex = partialIndex >= 0 ? Math.min(partialIndex, index) : index;
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
let safeStart = partialIndex >= 0 ? Math.min(start, partialIndex) : start;
|
|
478
|
+
if (partialIndex >= 0 && partialIndex < start) {
|
|
479
|
+
while (safeStart > 0 && messages[safeStart]?.role !== "user")
|
|
480
|
+
safeStart--;
|
|
481
|
+
if (messages[safeStart]?.role !== "user")
|
|
482
|
+
return 0;
|
|
483
|
+
}
|
|
484
|
+
if (safeStart <= 0)
|
|
485
|
+
return 0;
|
|
486
|
+
messages.splice(0, safeStart);
|
|
487
|
+
return safeStart;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// src/v2/fold/host-media.ts
|
|
491
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
492
|
+
function isClassInstance(value) {
|
|
493
|
+
if (!isRecord(value))
|
|
494
|
+
return false;
|
|
495
|
+
const prototype = Object.getPrototypeOf(value);
|
|
496
|
+
return prototype !== Object.prototype && prototype !== null;
|
|
497
|
+
}
|
|
498
|
+
var rememberedAssetClass;
|
|
499
|
+
var rememberedMessageClass;
|
|
500
|
+
var schemaUsesAssets = new WeakMap;
|
|
501
|
+
function rememberHostMedia(messages) {
|
|
502
|
+
for (const message of messages) {
|
|
503
|
+
if (!isClassInstance(message))
|
|
504
|
+
continue;
|
|
505
|
+
rememberedMessageClass ??= Object.getPrototypeOf(message).constructor;
|
|
506
|
+
if (rememberedAssetClass)
|
|
507
|
+
return;
|
|
508
|
+
const content = message.content;
|
|
509
|
+
if (!Array.isArray(content))
|
|
510
|
+
continue;
|
|
511
|
+
for (const part of content) {
|
|
512
|
+
if (!isRecord(part) || part.type !== "media")
|
|
513
|
+
continue;
|
|
514
|
+
const media = part.media;
|
|
515
|
+
if (isClassInstance(media) && isRecord(media.source)) {
|
|
516
|
+
rememberedAssetClass = Object.getPrototypeOf(media).constructor;
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
function hostUsesMediaAssets() {
|
|
523
|
+
const messageClass = rememberedMessageClass;
|
|
524
|
+
if (!messageClass)
|
|
525
|
+
return rememberedAssetClass !== undefined;
|
|
526
|
+
const cached = schemaUsesAssets.get(messageClass);
|
|
527
|
+
if (cached !== undefined)
|
|
528
|
+
return cached;
|
|
529
|
+
let uses = rememberedAssetClass !== undefined;
|
|
530
|
+
if (!uses) {
|
|
531
|
+
try {
|
|
532
|
+
uses = JSON.stringify(messageClass.ast).includes('"Media.Asset"');
|
|
533
|
+
} catch {
|
|
534
|
+
uses = false;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
schemaUsesAssets.set(messageClass, uses);
|
|
538
|
+
return uses;
|
|
539
|
+
}
|
|
540
|
+
function decodeThroughHostSchema(source) {
|
|
541
|
+
const messageClass = rememberedMessageClass;
|
|
542
|
+
if (!messageClass)
|
|
543
|
+
return "no host message class seen";
|
|
544
|
+
const fields = messageClass.fields;
|
|
545
|
+
const union = fields?.content?.value;
|
|
546
|
+
const members = union?.members;
|
|
547
|
+
if (!Array.isArray(members))
|
|
548
|
+
return "host content schema has no union members";
|
|
549
|
+
const mediaPart = members.find((member) => {
|
|
550
|
+
const typeField = member?.fields;
|
|
551
|
+
const literal = typeField?.type?.ast?.literal;
|
|
552
|
+
return literal === "media";
|
|
553
|
+
});
|
|
554
|
+
const assetSchema = mediaPart?.fields?.media;
|
|
555
|
+
const encoding = assetSchema?.ast?.encoding;
|
|
556
|
+
const decode = Array.isArray(encoding) ? encoding[0]?.transformation?.decode : undefined;
|
|
557
|
+
const run = decode?.run;
|
|
558
|
+
if (typeof run !== "function")
|
|
559
|
+
return "host media schema has no decode transformation";
|
|
560
|
+
const result = run.call(decode, { _tag: "Some", value: { source } }, {});
|
|
561
|
+
const option = result?._tag === "Success" ? result.value : undefined;
|
|
562
|
+
const asset = option?._tag === "Some" ? option.value : undefined;
|
|
563
|
+
if (!isClassInstance(asset) || asset.source !== source)
|
|
564
|
+
return "host media schema decode did not return an asset";
|
|
565
|
+
return asset;
|
|
566
|
+
}
|
|
567
|
+
function hostMediaAsset(data, mediaType) {
|
|
568
|
+
const source = { type: "base64", data, mediaType };
|
|
569
|
+
let first = "no Media.Asset seen in this process";
|
|
570
|
+
if (rememberedAssetClass) {
|
|
571
|
+
try {
|
|
572
|
+
return new rememberedAssetClass({ source });
|
|
573
|
+
} catch (error) {
|
|
574
|
+
first = `remembered asset class threw: ${String(error)}`;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
let second;
|
|
578
|
+
try {
|
|
579
|
+
const decoded = decodeThroughHostSchema(source);
|
|
580
|
+
if (typeof decoded !== "string")
|
|
581
|
+
return decoded;
|
|
582
|
+
second = decoded;
|
|
583
|
+
} catch (error) {
|
|
584
|
+
second = `host media schema decode threw: ${String(error)}`;
|
|
585
|
+
}
|
|
586
|
+
return `${first}; ${second}`;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// src/v2/fold/owner.ts
|
|
590
|
+
import { createHash } from "node:crypto";
|
|
591
|
+
var foldDigest = (value) => createHash("sha256").update(value).digest("hex");
|
|
592
|
+
|
|
593
|
+
class FoldOwner {
|
|
594
|
+
storage;
|
|
595
|
+
pending = new Map;
|
|
596
|
+
constructor(storage) {
|
|
597
|
+
this.storage = storage;
|
|
598
|
+
}
|
|
599
|
+
async serial(sessionID, operation) {
|
|
600
|
+
const previous = this.pending.get(sessionID) ?? Promise.resolve();
|
|
601
|
+
const next = previous.catch(() => {}).then(operation);
|
|
602
|
+
this.pending.set(sessionID, next);
|
|
603
|
+
try {
|
|
604
|
+
return await next;
|
|
605
|
+
} finally {
|
|
606
|
+
if (this.pending.get(sessionID) === next)
|
|
607
|
+
this.pending.delete(sessionID);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
key(sessionID) {
|
|
611
|
+
return `fold/v1/${sessionID}`;
|
|
612
|
+
}
|
|
613
|
+
async read(sessionID) {
|
|
614
|
+
return await this.storage.get(this.key(sessionID));
|
|
615
|
+
}
|
|
616
|
+
async supply(args) {
|
|
617
|
+
return this.serial(args.sessionID, async () => {
|
|
618
|
+
const previous = await this.read(args.sessionID);
|
|
619
|
+
const same = previous && (args.runningCut !== undefined ? previous.cutSeq === args.runningCut : previous.cutSeq === undefined && previous.watermark === args.watermark);
|
|
620
|
+
if (same)
|
|
621
|
+
return previous;
|
|
622
|
+
const submitted = args.materialize();
|
|
623
|
+
const next = {
|
|
624
|
+
watermark: args.watermark,
|
|
625
|
+
cutSeq: args.runningCut,
|
|
626
|
+
submitted,
|
|
627
|
+
submittedSha: foldDigest(submitted)
|
|
628
|
+
};
|
|
629
|
+
await this.storage.set(this.key(args.sessionID), next);
|
|
630
|
+
return next;
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
async observe(args) {
|
|
634
|
+
return this.serial(args.sessionID, async () => {
|
|
635
|
+
let state = await this.read(args.sessionID);
|
|
636
|
+
let changed = !state;
|
|
637
|
+
const renderedSha = foldDigest(JSON.stringify(args.rendered));
|
|
638
|
+
if (!state) {
|
|
639
|
+
args.onHard("boot_recovery");
|
|
640
|
+
state = {
|
|
641
|
+
watermark: args.cutSeq,
|
|
642
|
+
submitted: args.summary,
|
|
643
|
+
submittedSha: foldDigest(args.summary),
|
|
644
|
+
reason: "boot_recovery"
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
const reason = args.cutSeq < state.watermark ? "host_cut_before_watermark" : state.submittedSha !== foldDigest(args.summary) || state.renderedSha && state.renderedSha !== renderedSha ? "host_rerender" : undefined;
|
|
648
|
+
if (reason && state.rejectedSha !== renderedSha) {
|
|
649
|
+
args.onHard(reason);
|
|
650
|
+
state.reason = reason;
|
|
651
|
+
state.rejectedSha = renderedSha;
|
|
652
|
+
changed = true;
|
|
653
|
+
}
|
|
654
|
+
if (state.cutSeq === undefined && args.cutSeq >= state.watermark) {
|
|
655
|
+
state.cutSeq = args.cutSeq;
|
|
656
|
+
changed = true;
|
|
657
|
+
}
|
|
658
|
+
if (!state.rendered && reason !== "host_cut_before_watermark") {
|
|
659
|
+
state.rendered = structuredClone(args.rendered);
|
|
660
|
+
state.renderedSha = renderedSha;
|
|
661
|
+
state.renderedSummary = args.summary;
|
|
662
|
+
changed = true;
|
|
663
|
+
}
|
|
664
|
+
if (changed)
|
|
665
|
+
await this.storage.set(this.key(args.sessionID), state);
|
|
666
|
+
return state;
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// src/v2/fold/restore.ts
|
|
672
|
+
import { fileURLToPath } from "node:url";
|
|
673
|
+
|
|
674
|
+
// src/v2/hooks/storage-notice.ts
|
|
675
|
+
var STORAGE_NOTICE_PREFIX = "[Magic Context storage notice] ";
|
|
676
|
+
function formatStorageRefusalNotice(reason) {
|
|
677
|
+
return `${STORAGE_NOTICE_PREFIX}This turn was not sent to the model. ${formatFailClosedBlockingSummary(reason)}`;
|
|
678
|
+
}
|
|
679
|
+
function formatStorageRecoveryNotice(hiddenWorkRecovered) {
|
|
680
|
+
return hiddenWorkRecovered ? `${STORAGE_NOTICE_PREFIX}Magic Context storage is available again and turns are sent to the model. The historian and dreamer are running again. Restart OpenCode to enable Magic Context's tools.` : `${STORAGE_NOTICE_PREFIX}Magic Context storage is available again and turns are sent to the model. Restart OpenCode to enable Magic Context's tools, historian and dreamer.`;
|
|
681
|
+
}
|
|
682
|
+
function isStorageNoticeText(text) {
|
|
683
|
+
return typeof text === "string" && text.startsWith(STORAGE_NOTICE_PREFIX);
|
|
684
|
+
}
|
|
685
|
+
function hasStorageNoticeShape(message) {
|
|
686
|
+
if (message.role !== "user" || typeof message.id !== "string")
|
|
687
|
+
return false;
|
|
688
|
+
if (message.content.length !== 1)
|
|
689
|
+
return false;
|
|
690
|
+
const part = message.content[0];
|
|
691
|
+
return part?.type === "text" && isStorageNoticeText(part.text);
|
|
692
|
+
}
|
|
693
|
+
async function dropStorageNotices(messages, isAdmittedSynthetic) {
|
|
694
|
+
const drop = new Set;
|
|
695
|
+
for (const message of messages) {
|
|
696
|
+
if (!hasStorageNoticeShape(message))
|
|
697
|
+
continue;
|
|
698
|
+
if (await isAdmittedSynthetic(message.id))
|
|
699
|
+
drop.add(message);
|
|
700
|
+
}
|
|
701
|
+
if (drop.size === 0)
|
|
702
|
+
return 0;
|
|
703
|
+
const kept = messages.filter((message) => !drop.has(message));
|
|
704
|
+
messages.splice(0, messages.length, ...kept);
|
|
705
|
+
return drop.size;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// src/v2/fold/restore.ts
|
|
709
|
+
function unavailableAttachmentNote(name, mediaType) {
|
|
710
|
+
return `[Attachment ${name ? `"${name}" ` : ""}(${mediaType}) is not available in this restored history]`;
|
|
711
|
+
}
|
|
712
|
+
var imageMimes = new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
|
|
713
|
+
function attachmentParts(files, rowID, media) {
|
|
714
|
+
const seen = new Map;
|
|
715
|
+
return files.flatMap((file) => {
|
|
716
|
+
if (imageMimes.has(file.mime) && file.source.type === "inline" && file.mention?.text) {
|
|
717
|
+
const key = JSON.stringify([
|
|
718
|
+
file.mime,
|
|
719
|
+
file.name ?? null,
|
|
720
|
+
file.description ?? null,
|
|
721
|
+
file.mention.text
|
|
722
|
+
]);
|
|
723
|
+
const payloads = seen.get(key) ?? new Set;
|
|
724
|
+
if (payloads.has(file.data))
|
|
725
|
+
return [];
|
|
726
|
+
payloads.add(file.data);
|
|
727
|
+
seen.set(key, payloads);
|
|
728
|
+
}
|
|
729
|
+
const uri = file.source.type === "uri" ? file.source.uri : undefined;
|
|
730
|
+
const location = uri?.startsWith("file:") ? fileURLToPath(uri) : undefined;
|
|
731
|
+
if (file.mime === "text/plain" || file.mime === "application/x-directory") {
|
|
732
|
+
const directory = file.mime === "application/x-directory";
|
|
733
|
+
return [
|
|
734
|
+
{
|
|
735
|
+
type: "text",
|
|
736
|
+
text: `
|
|
737
|
+
|
|
738
|
+
${[
|
|
739
|
+
directory ? `Attached directory: ${location ?? file.name ?? uri ?? "directory"}` : `Attached file: ${file.name ?? uri ?? "inline attachment"}`,
|
|
740
|
+
file.description === undefined ? undefined : `Description: ${file.description}`,
|
|
741
|
+
directory && !file.data.length ? undefined : "",
|
|
742
|
+
directory && !file.data.length ? undefined : Buffer.from(file.data, "base64").toString("utf8")
|
|
743
|
+
].filter((line) => line !== undefined).join(`
|
|
744
|
+
`)}`,
|
|
745
|
+
metadata: {
|
|
746
|
+
attachment: {
|
|
747
|
+
source: file.source,
|
|
748
|
+
name: file.name,
|
|
749
|
+
description: file.description
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
];
|
|
754
|
+
}
|
|
755
|
+
if (!imageMimes.has(file.mime) && file.mime !== "application/pdf")
|
|
756
|
+
return [];
|
|
757
|
+
const metadata = file.description === undefined ? undefined : { description: file.description };
|
|
758
|
+
const located = location ? [{ type: "text", text: `Attached file: ${location}` }] : [];
|
|
759
|
+
if (!media)
|
|
760
|
+
return [
|
|
761
|
+
...located,
|
|
762
|
+
{
|
|
763
|
+
type: "media",
|
|
764
|
+
mediaType: file.mime,
|
|
765
|
+
data: file.data,
|
|
766
|
+
filename: file.name,
|
|
767
|
+
metadata
|
|
768
|
+
}
|
|
769
|
+
];
|
|
770
|
+
const asset = media.asset(file.data, file.mime);
|
|
771
|
+
if (typeof asset === "string") {
|
|
772
|
+
media.unavailable({ rowID, name: file.name, mediaType: file.mime, reason: asset });
|
|
773
|
+
return [
|
|
774
|
+
...located,
|
|
775
|
+
{ type: "text", text: unavailableAttachmentNote(file.name, file.mime) }
|
|
776
|
+
];
|
|
777
|
+
}
|
|
778
|
+
return [...located, { type: "media", media: asset, filename: file.name, metadata }];
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
function restoreRow(row, model, media) {
|
|
782
|
+
const data = row.data;
|
|
783
|
+
const make = (role, content, metadata = data.metadata) => ({
|
|
784
|
+
id: row.id,
|
|
785
|
+
role,
|
|
786
|
+
content,
|
|
787
|
+
...metadata === undefined ? {} : { metadata }
|
|
788
|
+
});
|
|
789
|
+
if (row.type === "user") {
|
|
790
|
+
const skills = data.skills ?? [];
|
|
791
|
+
const content = [
|
|
792
|
+
...skills.flatMap((skill) => skill.text === undefined ? [] : [{ type: "text", text: skill.text }]),
|
|
793
|
+
...data.text ? [{ type: "text", text: data.text }] : [],
|
|
794
|
+
...attachmentParts(data.files ?? [], row.id, media)
|
|
795
|
+
];
|
|
796
|
+
return content.length ? [
|
|
797
|
+
make("user", content, {
|
|
798
|
+
...data.metadata ?? {},
|
|
799
|
+
...data.agents?.length ? { agents: data.agents } : {}
|
|
800
|
+
})
|
|
801
|
+
] : [];
|
|
802
|
+
}
|
|
803
|
+
if (row.type === "system")
|
|
804
|
+
return [{ role: "system", content: [{ type: "text", text: data.text ?? "" }] }];
|
|
805
|
+
if (row.type === "synthetic" && isStorageNoticeText(data.text))
|
|
806
|
+
return [];
|
|
807
|
+
if (["synthetic", "skill"].includes(row.type))
|
|
808
|
+
return [make("user", [{ type: "text", text: data.text ?? "" }])];
|
|
809
|
+
if (row.type === "location-switched")
|
|
810
|
+
return [
|
|
811
|
+
make("user", [
|
|
812
|
+
{
|
|
813
|
+
type: "text",
|
|
814
|
+
text: `The working directory has been changed to ${data.location.directory}.`
|
|
815
|
+
}
|
|
816
|
+
])
|
|
817
|
+
];
|
|
818
|
+
if (row.type === "shell")
|
|
819
|
+
return data.metadata?.background === true ? [] : [
|
|
820
|
+
make("user", [
|
|
821
|
+
{
|
|
822
|
+
type: "text",
|
|
823
|
+
text: `The following shell command was executed by the user:
|
|
824
|
+
|
|
825
|
+
Command:
|
|
826
|
+
${data.command}
|
|
827
|
+
|
|
828
|
+
Output:
|
|
829
|
+
${data.output?.output ?? ""}`
|
|
830
|
+
}
|
|
831
|
+
])
|
|
832
|
+
];
|
|
833
|
+
if (row.type !== "assistant")
|
|
834
|
+
return [];
|
|
835
|
+
const previousModel = data.model;
|
|
836
|
+
const sameProvider = previousModel?.providerID === model.providerID;
|
|
837
|
+
const sameModel = sameProvider && previousModel?.id === model.id;
|
|
838
|
+
const reuse = sameModel && data.error === undefined;
|
|
839
|
+
const metadata = (state) => state === undefined ? undefined : { [model.providerID]: state };
|
|
840
|
+
const content = [];
|
|
841
|
+
const results = [];
|
|
842
|
+
for (const part of data.content ?? []) {
|
|
843
|
+
if (part.type === "text") {
|
|
844
|
+
if (part.text !== "")
|
|
845
|
+
content.push({
|
|
846
|
+
type: "text",
|
|
847
|
+
text: part.text,
|
|
848
|
+
providerMetadata: reuse ? metadata(part.state) : undefined
|
|
849
|
+
});
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
if (part.type === "reasoning") {
|
|
853
|
+
if (part.text !== "" || reuse && part.state !== undefined)
|
|
854
|
+
content.push({
|
|
855
|
+
type: reuse || data.error === undefined ? "reasoning" : "text",
|
|
856
|
+
text: part.text,
|
|
857
|
+
providerMetadata: reuse ? metadata(part.state) : undefined
|
|
858
|
+
});
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
const state = part.state;
|
|
862
|
+
const completed = state.status === "completed" || state.status === "error";
|
|
863
|
+
const toolReuse = reuse || sameModel && part.executed === true && completed;
|
|
864
|
+
let input = state.input;
|
|
865
|
+
if (state.status === "streaming" && typeof input === "string") {
|
|
866
|
+
try {
|
|
867
|
+
input = JSON.parse(input);
|
|
868
|
+
} catch {}
|
|
869
|
+
}
|
|
870
|
+
content.push({
|
|
871
|
+
type: "tool-call",
|
|
872
|
+
id: part.id,
|
|
873
|
+
name: part.name,
|
|
874
|
+
input,
|
|
875
|
+
providerExecuted: part.executed,
|
|
876
|
+
providerMetadata: toolReuse ? metadata(part.providerState) : undefined
|
|
877
|
+
});
|
|
878
|
+
if (!completed)
|
|
879
|
+
continue;
|
|
880
|
+
const toolContent = state.content ?? [];
|
|
881
|
+
const single = toolContent.length === 1 ? toolContent[0] : undefined;
|
|
882
|
+
const result = {
|
|
883
|
+
type: "tool-result",
|
|
884
|
+
id: part.id,
|
|
885
|
+
name: part.name,
|
|
886
|
+
providerExecuted: part.executed,
|
|
887
|
+
providerMetadata: toolReuse ? metadata(part.providerResultState ?? part.providerState) : sameProvider ? metadata(part.providerResultState) : undefined,
|
|
888
|
+
result: state.status === "error" ? { error: state.error, content: toolContent } : single?.type === "text" ? { type: "text", value: single.text } : { type: "content", value: toolContent },
|
|
889
|
+
...state.status === "error" ? { resultType: "error" } : {}
|
|
890
|
+
};
|
|
891
|
+
if (part.executed === true)
|
|
892
|
+
content.push(result);
|
|
893
|
+
else
|
|
894
|
+
results.push({ role: "tool", content: [result] });
|
|
895
|
+
}
|
|
896
|
+
return [...content.length ? [make("assistant", content)] : [], ...results];
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// src/v2/fork-inheritance.ts
|
|
900
|
+
var RAW_TYPES = new Set(RAW_MESSAGE_TYPES);
|
|
901
|
+
function forkBoundarySeq(origin, sequenceForId) {
|
|
902
|
+
const seq = sequenceForId(origin.parentSessionID, origin.boundary.messageID);
|
|
903
|
+
if (seq === undefined)
|
|
904
|
+
return;
|
|
905
|
+
return origin.boundary.type === "before" ? seq - 1 : seq;
|
|
906
|
+
}
|
|
907
|
+
function buildForkIdMap(parentRows, forkRows, throughSeq) {
|
|
908
|
+
const parentBySeq = new Map;
|
|
909
|
+
for (const row of parentRows)
|
|
910
|
+
if (row.seq <= throughSeq)
|
|
911
|
+
parentBySeq.set(row.seq, row);
|
|
912
|
+
const parentToFork = new Map;
|
|
913
|
+
const forkOrdinals = new Map;
|
|
914
|
+
let ordinal = 0;
|
|
915
|
+
for (const row of forkRows) {
|
|
916
|
+
if (row.seq > throughSeq)
|
|
917
|
+
break;
|
|
918
|
+
if (RAW_TYPES.has(row.type)) {
|
|
919
|
+
ordinal += 1;
|
|
920
|
+
forkOrdinals.set(row.id, ordinal);
|
|
921
|
+
}
|
|
922
|
+
const parent = parentBySeq.get(row.seq);
|
|
923
|
+
if (parent && parent.type === row.type && parent.time_created === row.time_created && row.id !== parent.id && row.id.endsWith(`_${row.seq}`)) {
|
|
924
|
+
parentToFork.set(parent.id, row.id);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
const parentOrdinals = new Map;
|
|
928
|
+
let parentOrdinal = 0;
|
|
929
|
+
for (const row of parentRows) {
|
|
930
|
+
if (row.seq > throughSeq)
|
|
931
|
+
break;
|
|
932
|
+
if (!RAW_TYPES.has(row.type))
|
|
933
|
+
continue;
|
|
934
|
+
parentOrdinal += 1;
|
|
935
|
+
parentOrdinals.set(parentOrdinal, row.id);
|
|
936
|
+
}
|
|
937
|
+
return { parentToFork, forkOrdinals, parentOrdinals, throughSeq };
|
|
938
|
+
}
|
|
939
|
+
var CONTENT_ID_SUFFIX = /(:(?:p|file)\d+)$/;
|
|
940
|
+
var BLOCK_ID_SUFFIX = /(#\d+)$/;
|
|
941
|
+
function splitMessageScopedId(id) {
|
|
942
|
+
const match = id.match(CONTENT_ID_SUFFIX) ?? id.match(BLOCK_ID_SUFFIX);
|
|
943
|
+
if (!match?.[1])
|
|
944
|
+
return { messageId: id, suffix: "" };
|
|
945
|
+
return { messageId: id.slice(0, -match[1].length), suffix: match[1] };
|
|
946
|
+
}
|
|
947
|
+
function forkCloneFilter(idMap, copiedToolCallIds) {
|
|
948
|
+
const mapMessageScoped = (id) => {
|
|
949
|
+
const direct = idMap.parentToFork.get(id);
|
|
950
|
+
if (direct)
|
|
951
|
+
return direct;
|
|
952
|
+
const { messageId, suffix } = splitMessageScopedId(id);
|
|
953
|
+
if (!suffix)
|
|
954
|
+
return;
|
|
955
|
+
const mapped = idMap.parentToFork.get(messageId);
|
|
956
|
+
return mapped ? `${mapped}${suffix}` : undefined;
|
|
957
|
+
};
|
|
958
|
+
return {
|
|
959
|
+
resolveBoundaryOrdinal: (messageId) => {
|
|
960
|
+
const forkId = idMap.parentToFork.get(messageId);
|
|
961
|
+
return forkId ? idMap.forkOrdinals.get(forkId) : undefined;
|
|
962
|
+
},
|
|
963
|
+
includeMessageId: (messageId) => mapMessageScoped(messageId) !== undefined || copiedToolCallIds.has(messageId),
|
|
964
|
+
mapMessageId: (messageId) => mapMessageScoped(messageId) ?? messageId,
|
|
965
|
+
includeTag: (tag) => {
|
|
966
|
+
if (tag.type === "tool") {
|
|
967
|
+
return tag.toolOwnerMessageId !== null && idMap.parentToFork.has(tag.toolOwnerMessageId);
|
|
968
|
+
}
|
|
969
|
+
return mapMessageScoped(tag.messageId) !== undefined;
|
|
970
|
+
},
|
|
971
|
+
mapOrdinal: (sourceOrdinal) => {
|
|
972
|
+
const parentId = idMap.parentOrdinals.get(sourceOrdinal);
|
|
973
|
+
const forkId = parentId ? idMap.parentToFork.get(parentId) : undefined;
|
|
974
|
+
return forkId ? idMap.forkOrdinals.get(forkId) : undefined;
|
|
975
|
+
},
|
|
976
|
+
copySessionNotesAndFacts: true,
|
|
977
|
+
selectPendingPiMarker: () => null
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
function countSessionRows(db, table, sessionId) {
|
|
981
|
+
const row = db.prepare(`SELECT COUNT(*) AS count FROM ${table} WHERE session_id = ?`).get(sessionId);
|
|
982
|
+
return typeof row?.count === "number" ? row.count : 0;
|
|
983
|
+
}
|
|
984
|
+
function sessionHasMagicContextState(db, sessionId) {
|
|
985
|
+
return countSessionRows(db, "compartments", sessionId) > 0 || countSessionRows(db, "tags", sessionId) > 0;
|
|
986
|
+
}
|
|
987
|
+
function seedV2ForkFromParent(args) {
|
|
988
|
+
const { db, store, sessionId } = args;
|
|
989
|
+
if (sessionHasMagicContextState(db, sessionId))
|
|
990
|
+
return { kind: "has-state" };
|
|
991
|
+
const origin = store.forkOrigin(sessionId);
|
|
992
|
+
if (!origin)
|
|
993
|
+
return { kind: "not-a-fork" };
|
|
994
|
+
const parentSessionID = origin.parentSessionID;
|
|
995
|
+
if (parentSessionID === sessionId)
|
|
996
|
+
return { kind: "not-a-fork" };
|
|
997
|
+
if (!sessionHasMagicContextState(db, parentSessionID))
|
|
998
|
+
return { kind: "parent-missing", parentSessionID, reason: "no Magic Context state" };
|
|
999
|
+
if (!store.sessionExists(parentSessionID))
|
|
1000
|
+
return { kind: "parent-missing", parentSessionID, reason: "no host session row" };
|
|
1001
|
+
const throughSeq = forkBoundarySeq(origin, (session, id) => store.sequenceForId(session, id));
|
|
1002
|
+
if (throughSeq === undefined)
|
|
1003
|
+
return { kind: "parent-missing", parentSessionID, reason: "boundary row not in the store" };
|
|
1004
|
+
const idMap = buildForkIdMap(store.rowStampsThrough(parentSessionID, throughSeq), store.rowStampsThrough(sessionId, throughSeq), throughSeq);
|
|
1005
|
+
if (idMap.parentToFork.size === 0)
|
|
1006
|
+
return { kind: "parent-missing", parentSessionID, reason: "no copied rows pair up" };
|
|
1007
|
+
const toolTags = db.prepare("SELECT message_id, tool_owner_message_id FROM tags WHERE session_id = ? AND type = 'tool'").all(parentSessionID);
|
|
1008
|
+
const copiedToolCallIds = new Set(toolTags.filter((row) => row.tool_owner_message_id !== null && idMap.parentToFork.has(row.tool_owner_message_id)).map((row) => row.message_id));
|
|
1009
|
+
const result = copySessionStateForClone(db, parentSessionID, sessionId, forkCloneFilter(idMap, copiedToolCallIds));
|
|
1010
|
+
if (result.kind === "destination-not-empty")
|
|
1011
|
+
return { kind: "destination-not-empty", parentSessionID };
|
|
1012
|
+
return { kind: "seeded", parentSessionID, pairedRows: idMap.parentToFork.size, result };
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// src/v2/hidden-child-record.ts
|
|
1016
|
+
function keptUnderRetention(keepSubagents, child) {
|
|
1017
|
+
return keepSubagents && (child.ever_settled === true || child.role === "historian");
|
|
1018
|
+
}
|
|
1019
|
+
function roleTitle(role) {
|
|
1020
|
+
return role === "historian" ? "Magic Context historian" : "Magic Context dreamer";
|
|
1021
|
+
}
|
|
1022
|
+
function roleAgent(role) {
|
|
1023
|
+
if (role === "historian")
|
|
1024
|
+
return HIDDEN_HISTORIAN_AGENT;
|
|
1025
|
+
return role === "dreamer-curate" ? HIDDEN_CURATE_AGENT : HIDDEN_DREAMER_AGENT;
|
|
1026
|
+
}
|
|
1027
|
+
function childCreateInput(identity, role, model) {
|
|
1028
|
+
return {
|
|
1029
|
+
title: roleTitle(role),
|
|
1030
|
+
agent: hiddenToolLoop(identity) ? hiddenAgentFor(identity) : roleAgent(role),
|
|
1031
|
+
model: {
|
|
1032
|
+
providerID: model.providerID,
|
|
1033
|
+
id: model.modelID,
|
|
1034
|
+
...model.variant ? { variant: model.variant } : {}
|
|
1035
|
+
},
|
|
1036
|
+
location: { directory: identity.directory },
|
|
1037
|
+
metadata: { magic_context: "hidden-run", role }
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
function assistantOutcome(row) {
|
|
1041
|
+
const outcome = row?.data.outcome;
|
|
1042
|
+
return outcome === "succeeded" || outcome === "failed" || outcome === "interrupted" ? outcome : undefined;
|
|
1043
|
+
}
|
|
1044
|
+
function withReader(openReader, read) {
|
|
1045
|
+
const reader = openReader();
|
|
1046
|
+
try {
|
|
1047
|
+
return read(reader);
|
|
1048
|
+
} finally {
|
|
1049
|
+
reader.close?.();
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
function errorText(value) {
|
|
1053
|
+
if (value instanceof Error)
|
|
1054
|
+
return value.message;
|
|
1055
|
+
if (typeof value === "string")
|
|
1056
|
+
return value;
|
|
1057
|
+
try {
|
|
1058
|
+
return JSON.stringify(value);
|
|
1059
|
+
} catch {
|
|
1060
|
+
return String(value);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// src/v2/hidden-child-native.ts
|
|
1065
|
+
function nativeSessionRemove(session) {
|
|
1066
|
+
const remove = session.remove;
|
|
1067
|
+
if (typeof remove !== "function")
|
|
1068
|
+
return;
|
|
1069
|
+
return (input) => Promise.resolve(remove.call(session, { sessionID: input.sessionID }));
|
|
1070
|
+
}
|
|
1071
|
+
function isSessionNotFound(error) {
|
|
1072
|
+
if (!error || typeof error !== "object")
|
|
1073
|
+
return false;
|
|
1074
|
+
const fields = error;
|
|
1075
|
+
if (fields.status === 404)
|
|
1076
|
+
return true;
|
|
1077
|
+
const label = [fields.name, fields._tag, fields.message].filter((value) => typeof value === "string").join(" ");
|
|
1078
|
+
return /not\s*found/i.test(label);
|
|
1079
|
+
}
|
|
1080
|
+
var parentDroppedNoted = false;
|
|
1081
|
+
function createNativeHiddenChildren(host, removeSession, options) {
|
|
1082
|
+
const removing = new Map;
|
|
1083
|
+
const remove = (child) => {
|
|
1084
|
+
const inFlight = removing.get(child.id);
|
|
1085
|
+
if (inFlight)
|
|
1086
|
+
return inFlight;
|
|
1087
|
+
const done = (async () => {
|
|
1088
|
+
try {
|
|
1089
|
+
let timer;
|
|
1090
|
+
try {
|
|
1091
|
+
await Promise.race([
|
|
1092
|
+
removeSession({ sessionID: child.id }),
|
|
1093
|
+
new Promise((_, reject) => {
|
|
1094
|
+
timer = setTimeout(() => reject(new Error("session.remove timed out")), options.removalTimeoutMs ?? 5000);
|
|
1095
|
+
})
|
|
1096
|
+
]);
|
|
1097
|
+
} finally {
|
|
1098
|
+
if (timer)
|
|
1099
|
+
clearTimeout(timer);
|
|
1100
|
+
}
|
|
1101
|
+
} catch (error) {
|
|
1102
|
+
if (isSessionNotFound(error))
|
|
1103
|
+
return;
|
|
1104
|
+
options.log(`[magic-context] hidden child ${child.id} could not be removed; it goes when its parent session is deleted: ${errorText(error)}`);
|
|
1105
|
+
}
|
|
1106
|
+
})().finally(() => {
|
|
1107
|
+
removing.delete(child.id);
|
|
1108
|
+
});
|
|
1109
|
+
removing.set(child.id, done);
|
|
1110
|
+
return done;
|
|
1111
|
+
};
|
|
1112
|
+
const confirmParent = async (sessionID, parentID) => {
|
|
1113
|
+
let stored;
|
|
1114
|
+
try {
|
|
1115
|
+
stored = (await host.get({ sessionID })).parentID;
|
|
1116
|
+
} catch (error) {
|
|
1117
|
+
options.log(`[magic-context] could not read back hidden child ${sessionID} to confirm its parent: ${errorText(error)}`);
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
if (stored === parentID || parentDroppedNoted)
|
|
1121
|
+
return;
|
|
1122
|
+
parentDroppedNoted = true;
|
|
1123
|
+
options.log(`[magic-context] this OpenCode 2 host does not keep the parent of hidden-run sessions (asked for ${parentID}, read back ${stored ?? "none"}); they are created as separate sessions and still removed when each run ends`);
|
|
1124
|
+
};
|
|
1125
|
+
const create = async (identity, role, model) => {
|
|
1126
|
+
const parentID = identity.parentSessionId;
|
|
1127
|
+
const input = childCreateInput(identity, role, model);
|
|
1128
|
+
const { location, ...parented } = input;
|
|
1129
|
+
const created = await host.create(parentID ? { ...parented, parentID } : { ...parented, location });
|
|
1130
|
+
if (!created.id)
|
|
1131
|
+
throw new Error("OpenCode 2 did not return a child session id");
|
|
1132
|
+
options.hook.registerChild(created.id);
|
|
1133
|
+
if (parentID)
|
|
1134
|
+
await confirmParent(created.id, parentID);
|
|
1135
|
+
return {
|
|
1136
|
+
id: created.id,
|
|
1137
|
+
role,
|
|
1138
|
+
generation: options.generation,
|
|
1139
|
+
title: roleTitle(role),
|
|
1140
|
+
model,
|
|
1141
|
+
created_at: Date.now(),
|
|
1142
|
+
directory: identity.directory
|
|
1143
|
+
};
|
|
1144
|
+
};
|
|
1145
|
+
const kept = (child) => keptUnderRetention(options.keepSubagents, child);
|
|
1146
|
+
return {
|
|
1147
|
+
open: create,
|
|
1148
|
+
create,
|
|
1149
|
+
retire(child) {
|
|
1150
|
+
if (!kept(child))
|
|
1151
|
+
remove(child);
|
|
1152
|
+
},
|
|
1153
|
+
async finish(child, retired) {
|
|
1154
|
+
if (retired) {
|
|
1155
|
+
await removing.get(child.id);
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
if (!kept(child))
|
|
1159
|
+
await remove(child);
|
|
1160
|
+
},
|
|
1161
|
+
updateModel: (child, model) => ({ ...child, model }),
|
|
1162
|
+
markEverSettled: (child) => ({ ...child, ever_settled: true })
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// src/v2/hidden-child-cleanup.ts
|
|
1167
|
+
var PREFIX = "opencode2_hidden_children:";
|
|
1168
|
+
var COMPLETE = "opencode2_hidden_children_cleanup_complete";
|
|
1169
|
+
async function cleanupLegacyHiddenChildren(db, remove, note, options = {}) {
|
|
1170
|
+
if (db.prepare("SELECT 1 FROM schema_migrations_meta WHERE key = ?").get(COMPLETE))
|
|
1171
|
+
return;
|
|
1172
|
+
const rows = db.prepare("SELECT key, value FROM schema_migrations_meta WHERE key GLOB ? ORDER BY key LIMIT 25").all(`${PREFIX}*`);
|
|
1173
|
+
let remaining = options.limit ?? 25;
|
|
1174
|
+
const now = options.now ?? (() => performance.now());
|
|
1175
|
+
const deadline = now() + (options.budgetMs ?? 2000);
|
|
1176
|
+
for (const row of rows) {
|
|
1177
|
+
let record;
|
|
1178
|
+
try {
|
|
1179
|
+
record = JSON.parse(row.value);
|
|
1180
|
+
if (!record || typeof record !== "object" || record.active !== undefined && (!record.active || typeof record.active !== "object" || Array.isArray(record.active)) || record.retired_children !== undefined && !Array.isArray(record.retired_children))
|
|
1181
|
+
throw new Error("invalid record");
|
|
1182
|
+
} catch {
|
|
1183
|
+
note(`[magic-context] legacy hidden-child cleanup cannot read ${row.key}`);
|
|
1184
|
+
continue;
|
|
1185
|
+
}
|
|
1186
|
+
const children = [
|
|
1187
|
+
...Object.values(record.active ?? {}),
|
|
1188
|
+
...record.retired_children ?? []
|
|
1189
|
+
];
|
|
1190
|
+
for (const child of children) {
|
|
1191
|
+
if (remaining-- <= 0 || now() >= deadline)
|
|
1192
|
+
return;
|
|
1193
|
+
if (typeof child?.id !== "string") {
|
|
1194
|
+
note(`[magic-context] legacy hidden-child cleanup has an invalid child in ${row.key}`);
|
|
1195
|
+
continue;
|
|
1196
|
+
}
|
|
1197
|
+
let timer;
|
|
1198
|
+
try {
|
|
1199
|
+
try {
|
|
1200
|
+
await Promise.race([
|
|
1201
|
+
remove({ sessionID: child.id }),
|
|
1202
|
+
new Promise((_, reject) => {
|
|
1203
|
+
timer = setTimeout(() => reject(new Error("session.remove timed out")), Math.min(options.timeoutMs ?? 1000, Math.max(1, deadline - now())));
|
|
1204
|
+
})
|
|
1205
|
+
]);
|
|
1206
|
+
} catch (error) {
|
|
1207
|
+
if (!isSessionNotFound(error))
|
|
1208
|
+
throw error;
|
|
1209
|
+
}
|
|
1210
|
+
record.active = Object.fromEntries(Object.entries(record.active ?? {}).filter(([, entry]) => entry.id !== child.id));
|
|
1211
|
+
record.retired_children = (record.retired_children ?? []).filter((entry) => entry.id !== child.id);
|
|
1212
|
+
db.prepare("UPDATE schema_migrations_meta SET value = ? WHERE key = ?").run(JSON.stringify(record), row.key);
|
|
1213
|
+
} catch (error) {
|
|
1214
|
+
note(`[magic-context] legacy hidden child ${child.id} cleanup deferred: ${String(error)}`);
|
|
1215
|
+
} finally {
|
|
1216
|
+
if (timer)
|
|
1217
|
+
clearTimeout(timer);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
if (Object.keys(record.active ?? {}).length === 0 && (record.retired_children ?? []).length === 0)
|
|
1221
|
+
db.prepare("DELETE FROM schema_migrations_meta WHERE key = ?").run(row.key);
|
|
1222
|
+
}
|
|
1223
|
+
if (!db.prepare("SELECT 1 FROM schema_migrations_meta WHERE key GLOB ? LIMIT 1").get(`${PREFIX}*`))
|
|
1224
|
+
db.prepare("INSERT OR IGNORE INTO schema_migrations_meta (key, value) VALUES (?, 'true')").run(COMPLETE);
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// src/v2/hidden-completion.ts
|
|
1228
|
+
var POLL_INTERVAL_MS = 200;
|
|
1229
|
+
function modelKey(model) {
|
|
1230
|
+
return `${model.providerID}/${model.modelID}`;
|
|
1231
|
+
}
|
|
1232
|
+
function sameModel(left, right) {
|
|
1233
|
+
return modelKey(left) === modelKey(right) && left.variant === right.variant;
|
|
1234
|
+
}
|
|
1235
|
+
function configuredHead(identity) {
|
|
1236
|
+
const candidates = [identity.model, ...identity.configuredModels ?? []];
|
|
1237
|
+
for (const candidate of candidates) {
|
|
1238
|
+
const entry = toModelEntry(candidate);
|
|
1239
|
+
const parsed = entry ? parseProviderModel(entry.model) : null;
|
|
1240
|
+
if (parsed)
|
|
1241
|
+
return { ...parsed, ...entry?.qualifier ? { variant: entry.qualifier } : {} };
|
|
1242
|
+
}
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
function roleFor(identity) {
|
|
1246
|
+
if (identity.kind !== "dreamer-task")
|
|
1247
|
+
return "historian";
|
|
1248
|
+
return identity.agent === HIDDEN_CURATE_AGENT ? "dreamer-curate" : "dreamer";
|
|
1249
|
+
}
|
|
1250
|
+
function promptText(request) {
|
|
1251
|
+
const parts = request.body.parts;
|
|
1252
|
+
return Array.isArray(parts) ? parts.flatMap((part) => part && typeof part === "object" && typeof part.text === "string" ? [part.text] : []).join(`
|
|
1253
|
+
`) : "";
|
|
1254
|
+
}
|
|
1255
|
+
function meter(system, prompt, text) {
|
|
1256
|
+
return {
|
|
1257
|
+
input: estimateTokens(system) + estimateTokens(prompt),
|
|
1258
|
+
output: estimateTokens(text),
|
|
1259
|
+
cacheRead: 0,
|
|
1260
|
+
cacheWrite: 0
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
function toolResultText(result) {
|
|
1264
|
+
if (typeof result !== "object" || result === null)
|
|
1265
|
+
return;
|
|
1266
|
+
const value = result.value;
|
|
1267
|
+
if (typeof value === "string")
|
|
1268
|
+
return value;
|
|
1269
|
+
if (!Array.isArray(value))
|
|
1270
|
+
return;
|
|
1271
|
+
const text = value.map((part) => typeof part === "object" && part !== null && typeof part.text === "string" ? part.text : "").filter((part) => part.length > 0).join(`
|
|
1272
|
+
`);
|
|
1273
|
+
return text.length > 0 ? text : undefined;
|
|
1274
|
+
}
|
|
1275
|
+
function toolLoopMessages(attempt) {
|
|
1276
|
+
const messages = attempt.observedMessages ?? [];
|
|
1277
|
+
const results = new Map;
|
|
1278
|
+
for (const message of messages) {
|
|
1279
|
+
if (message.role !== "tool")
|
|
1280
|
+
continue;
|
|
1281
|
+
for (const part of message.content) {
|
|
1282
|
+
if (part.type !== "tool-result" || typeof part.id !== "string")
|
|
1283
|
+
continue;
|
|
1284
|
+
const result = part.result;
|
|
1285
|
+
const failed = result?.type === "error" || part.resultType === "error" || typeof result === "object" && result !== null && "error" in result;
|
|
1286
|
+
const output = failed ? undefined : toolResultText(result);
|
|
1287
|
+
results.set(part.id, {
|
|
1288
|
+
status: failed ? "error" : "completed",
|
|
1289
|
+
...output === undefined ? {} : { output }
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
return messages.flatMap((message) => {
|
|
1294
|
+
if (message.role !== "assistant")
|
|
1295
|
+
return [];
|
|
1296
|
+
const parts = message.content.flatMap((part) => {
|
|
1297
|
+
if (part.type !== "tool-call" || typeof part.id !== "string" || typeof part.name !== "string")
|
|
1298
|
+
return [];
|
|
1299
|
+
const result = results.get(part.id);
|
|
1300
|
+
return [
|
|
1301
|
+
{
|
|
1302
|
+
type: "tool",
|
|
1303
|
+
tool: part.name,
|
|
1304
|
+
state: {
|
|
1305
|
+
status: result?.status ?? "pending",
|
|
1306
|
+
input: part.input,
|
|
1307
|
+
...result?.output === undefined ? {} : { output: result.output }
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
];
|
|
1311
|
+
});
|
|
1312
|
+
return parts.length ? [{ info: { role: "assistant" }, parts }] : [];
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
function assistantReasoning(row) {
|
|
1316
|
+
const reasoning = (row.data.content ?? []).flatMap((part) => part.type === "reasoning" && typeof part.text === "string" ? [part.text] : []).join(`
|
|
1317
|
+
`);
|
|
1318
|
+
return reasoning.length > 0 ? reasoning : null;
|
|
1319
|
+
}
|
|
1320
|
+
function assistantText(row) {
|
|
1321
|
+
const text = (row.data.content ?? []).flatMap((part) => part.type === "text" && typeof part.text === "string" ? [part.text] : []).join("");
|
|
1322
|
+
return text.length > 0 ? text : null;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
class HiddenProviderError extends Error {
|
|
1326
|
+
settled;
|
|
1327
|
+
constructor(detail, options = {}) {
|
|
1328
|
+
super(`Hidden completion provider error: ${detail}`);
|
|
1329
|
+
this.name = "HiddenProviderError";
|
|
1330
|
+
this.settled = options.settled ?? true;
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
function requestModel(request, current) {
|
|
1334
|
+
const requested = request.body.model;
|
|
1335
|
+
if (requested && typeof requested.providerID === "string" && typeof requested.modelID === "string") {
|
|
1336
|
+
return {
|
|
1337
|
+
providerID: requested.providerID,
|
|
1338
|
+
modelID: requested.modelID,
|
|
1339
|
+
...typeof request.body.variant === "string" ? { variant: request.body.variant } : {}
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
return current;
|
|
1343
|
+
}
|
|
1344
|
+
async function sleepUntilPoll(signal, deadline) {
|
|
1345
|
+
if (signal?.aborted)
|
|
1346
|
+
throw new Error("Hidden completion prompt aborted");
|
|
1347
|
+
const delay = Math.min(POLL_INTERVAL_MS, Math.max(0, deadline - Date.now()));
|
|
1348
|
+
if (delay <= 0)
|
|
1349
|
+
return;
|
|
1350
|
+
await new Promise((resolve, reject) => {
|
|
1351
|
+
const timer = setTimeout(resolve, delay);
|
|
1352
|
+
const abort = () => {
|
|
1353
|
+
clearTimeout(timer);
|
|
1354
|
+
reject(new Error("Hidden completion prompt aborted"));
|
|
1355
|
+
};
|
|
1356
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
1357
|
+
if (signal?.aborted)
|
|
1358
|
+
abort();
|
|
1359
|
+
else
|
|
1360
|
+
setTimeout(() => {
|
|
1361
|
+
signal?.removeEventListener("abort", abort);
|
|
1362
|
+
}, delay);
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
function isProviderFailure(error) {
|
|
1366
|
+
if (!error || typeof error !== "object")
|
|
1367
|
+
return false;
|
|
1368
|
+
const type = error.type;
|
|
1369
|
+
return typeof type === "string" && (type.startsWith("provider.") || type.toLowerCase().includes("provider"));
|
|
1370
|
+
}
|
|
1371
|
+
async function awaitAssistantRow(openReader, readSessionError, localFailure, childID, afterSeq, deadline, signal) {
|
|
1372
|
+
for (;; ) {
|
|
1373
|
+
const { assistant, idle } = withReader(openReader, (reader) => ({
|
|
1374
|
+
assistant: reader.latestAssistant(childID),
|
|
1375
|
+
idle: reader.latestIdle(childID)
|
|
1376
|
+
}));
|
|
1377
|
+
const newAssistant = assistant && assistant.seq > afterSeq ? assistant : undefined;
|
|
1378
|
+
const newIdle = idle && idle.seq > afterSeq ? idle : undefined;
|
|
1379
|
+
const refused = localFailure();
|
|
1380
|
+
if (refused)
|
|
1381
|
+
throw refused;
|
|
1382
|
+
if (newIdle && (!newAssistant || newIdle.seq > newAssistant.seq)) {
|
|
1383
|
+
const outcome = newIdle.data.outcome;
|
|
1384
|
+
if (outcome === "failed" || outcome === "interrupted") {
|
|
1385
|
+
const sessionError = await readSessionError();
|
|
1386
|
+
const details = [
|
|
1387
|
+
`outcome=${outcome}`,
|
|
1388
|
+
`terminal_row=${errorText(newIdle)}`,
|
|
1389
|
+
`session_error=${sessionError === undefined ? "unavailable" : errorText(sessionError)}`
|
|
1390
|
+
];
|
|
1391
|
+
throw sessionError === undefined || !isProviderFailure(sessionError) ? new Error(`Hidden completion failed: ${details.join("; ")}`) : new HiddenProviderError(details.join("; "));
|
|
1392
|
+
}
|
|
1393
|
+
if (outcome === "succeeded" && newAssistant)
|
|
1394
|
+
return newAssistant;
|
|
1395
|
+
}
|
|
1396
|
+
if (newAssistant) {
|
|
1397
|
+
const outcome = assistantOutcome(newAssistant);
|
|
1398
|
+
if (outcome === "failed" || outcome === "interrupted") {
|
|
1399
|
+
const sessionError = await readSessionError();
|
|
1400
|
+
const details = [
|
|
1401
|
+
`outcome=${outcome}`,
|
|
1402
|
+
`terminal_row=${errorText(newAssistant)}`,
|
|
1403
|
+
`session_error=${sessionError === undefined ? "unavailable" : errorText(sessionError)}`
|
|
1404
|
+
];
|
|
1405
|
+
throw sessionError === undefined || !isProviderFailure(sessionError) ? new Error(`Hidden completion failed: ${details.join("; ")}`) : new HiddenProviderError(details.join("; "));
|
|
1406
|
+
}
|
|
1407
|
+
if (newAssistant.data.error !== undefined) {
|
|
1408
|
+
throw new HiddenProviderError(errorText(newAssistant.data.error), {
|
|
1409
|
+
settled: typeof newAssistant.data.finish === "string"
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
if (typeof newAssistant.data.finish === "string" || outcome === "succeeded") {
|
|
1413
|
+
return newAssistant;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
if (Date.now() >= deadline) {
|
|
1417
|
+
throw new Error("Hidden completion timed out waiting for a persisted assistant row");
|
|
1418
|
+
}
|
|
1419
|
+
await sleepUntilPoll(signal, deadline);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
var V2_HIDDEN_EXECUTOR_CAPABILITIES = {
|
|
1423
|
+
tools: true,
|
|
1424
|
+
harness: "opencode2"
|
|
1425
|
+
};
|
|
1426
|
+
function createLateHiddenExecutor(current) {
|
|
1427
|
+
const wired = () => {
|
|
1428
|
+
const executor = current();
|
|
1429
|
+
if (executor)
|
|
1430
|
+
return executor;
|
|
1431
|
+
throw new Error("Magic Context hidden work is unavailable until the context database opens.");
|
|
1432
|
+
};
|
|
1433
|
+
return {
|
|
1434
|
+
get capabilities() {
|
|
1435
|
+
return current()?.capabilities ?? V2_HIDDEN_EXECUTOR_CAPABILITIES;
|
|
1436
|
+
},
|
|
1437
|
+
open: (run) => wired().open(run),
|
|
1438
|
+
attempt: (handle, request) => wired().attempt(handle, request),
|
|
1439
|
+
collect: (handle, limit) => wired().collect(handle, limit),
|
|
1440
|
+
close: (handle, settlement) => wired().close(handle, settlement)
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
async function createV2HiddenCompletionExecutor(host, options) {
|
|
1444
|
+
const runs = new WeakMap;
|
|
1445
|
+
const generation = options.generation ?? "opencode2";
|
|
1446
|
+
const roleTails = new Map;
|
|
1447
|
+
const note = options.log ?? log;
|
|
1448
|
+
const removeSession = host.removeSession;
|
|
1449
|
+
if (!removeSession)
|
|
1450
|
+
throw new Error("Magic Context requires OpenCode 2.0.22 session.remove");
|
|
1451
|
+
const lifecycle = createNativeHiddenChildren(host, (input) => removeSession.call(host, input), {
|
|
1452
|
+
hook: options.hook,
|
|
1453
|
+
generation,
|
|
1454
|
+
keepSubagents: options.keepSubagents === true,
|
|
1455
|
+
log: note,
|
|
1456
|
+
removalTimeoutMs: options.removalTimeoutMs
|
|
1457
|
+
});
|
|
1458
|
+
const acquireRole = async (role) => {
|
|
1459
|
+
const previous = roleTails.get(role) ?? Promise.resolve();
|
|
1460
|
+
let release;
|
|
1461
|
+
const gate = new Promise((resolve) => {
|
|
1462
|
+
release = resolve;
|
|
1463
|
+
});
|
|
1464
|
+
const tail = previous.then(() => gate);
|
|
1465
|
+
roleTails.set(role, tail);
|
|
1466
|
+
await previous;
|
|
1467
|
+
return () => {
|
|
1468
|
+
release();
|
|
1469
|
+
if (roleTails.get(role) === tail)
|
|
1470
|
+
roleTails.delete(role);
|
|
1471
|
+
};
|
|
1472
|
+
};
|
|
1473
|
+
const warnedVariants = new Set;
|
|
1474
|
+
const validateVariant = async (model) => {
|
|
1475
|
+
if (!model.variant || !options.modelCatalog)
|
|
1476
|
+
return model;
|
|
1477
|
+
try {
|
|
1478
|
+
const listed = await options.modelCatalog();
|
|
1479
|
+
const rows = Array.isArray(listed) ? listed : listed && typeof listed === "object" && Array.isArray(listed.data) ? listed.data : [];
|
|
1480
|
+
const entry = rows.find((row) => row && typeof row === "object" && row.providerID === model.providerID && row.id === model.modelID);
|
|
1481
|
+
if (!entry)
|
|
1482
|
+
return model;
|
|
1483
|
+
if (entry.variants && typeof entry.variants === "object" && Object.hasOwn(entry.variants, model.variant))
|
|
1484
|
+
return model;
|
|
1485
|
+
const key = `${model.providerID}/${model.modelID}:${model.variant}`;
|
|
1486
|
+
if (!warnedVariants.has(key)) {
|
|
1487
|
+
warnedVariants.add(key);
|
|
1488
|
+
note(`[magic-context] ${recordHiddenVariantWarning(model.providerID, model.modelID, model.variant)}`);
|
|
1489
|
+
}
|
|
1490
|
+
return { providerID: model.providerID, modelID: model.modelID };
|
|
1491
|
+
} catch {
|
|
1492
|
+
return model;
|
|
1493
|
+
}
|
|
1494
|
+
};
|
|
1495
|
+
const resolveHead = async (identity) => {
|
|
1496
|
+
const configured = configuredHead(identity);
|
|
1497
|
+
if (configured)
|
|
1498
|
+
return validateVariant(configured);
|
|
1499
|
+
if (!identity.parentSessionId) {
|
|
1500
|
+
throw new HiddenCompletionRefusal("hidden_model_unsupported", "Hidden completion requires a configured model or an existing parent session model", true);
|
|
1501
|
+
}
|
|
1502
|
+
const parent = await host.get({ sessionID: identity.parentSessionId });
|
|
1503
|
+
if (!parent.model) {
|
|
1504
|
+
throw new HiddenCompletionRefusal("hidden_model_unsupported", "Hidden completion could not resolve the parent session model", true);
|
|
1505
|
+
}
|
|
1506
|
+
return {
|
|
1507
|
+
providerID: parent.model.providerID,
|
|
1508
|
+
modelID: parent.model.id,
|
|
1509
|
+
...parent.model.variant ? { variant: parent.model.variant } : {}
|
|
1510
|
+
};
|
|
1511
|
+
};
|
|
1512
|
+
const switchChildModel = async (run, requested) => {
|
|
1513
|
+
if (sameModel(run.child.model, requested))
|
|
1514
|
+
return;
|
|
1515
|
+
await host.switchModel({
|
|
1516
|
+
sessionID: run.child.id,
|
|
1517
|
+
model: {
|
|
1518
|
+
providerID: requested.providerID,
|
|
1519
|
+
id: requested.modelID,
|
|
1520
|
+
...requested.variant ? { variant: requested.variant } : {}
|
|
1521
|
+
}
|
|
1522
|
+
});
|
|
1523
|
+
run.child = lifecycle.updateModel(run.child, requested);
|
|
1524
|
+
};
|
|
1525
|
+
const retire = (run, reason) => {
|
|
1526
|
+
if (run.retired)
|
|
1527
|
+
return;
|
|
1528
|
+
lifecycle.retire(run.child, reason);
|
|
1529
|
+
run.retired = true;
|
|
1530
|
+
};
|
|
1531
|
+
const interruptAndRetire = async (run, reason) => {
|
|
1532
|
+
try {
|
|
1533
|
+
await host.interrupt({ sessionID: run.child.id });
|
|
1534
|
+
} finally {
|
|
1535
|
+
retire(run, reason);
|
|
1536
|
+
}
|
|
1537
|
+
};
|
|
1538
|
+
return {
|
|
1539
|
+
capabilities: V2_HIDDEN_EXECUTOR_CAPABILITIES,
|
|
1540
|
+
async open(identity) {
|
|
1541
|
+
const role = roleFor(identity);
|
|
1542
|
+
const releaseRole = await acquireRole(role);
|
|
1543
|
+
let openedChild;
|
|
1544
|
+
try {
|
|
1545
|
+
await options.ensureAgent?.();
|
|
1546
|
+
const head = await resolveHead(identity);
|
|
1547
|
+
const active = await lifecycle.open(identity, role, head);
|
|
1548
|
+
openedChild = active;
|
|
1549
|
+
const handle = { id: active.id, childSessionId: active.id };
|
|
1550
|
+
const tokenBudget = identity.metadata?.tokenBudget;
|
|
1551
|
+
const run = {
|
|
1552
|
+
identity,
|
|
1553
|
+
...hiddenToolLoop(identity) && typeof tokenBudget === "number" ? { budget: createDreamTokenBudget(tokenBudget) } : {},
|
|
1554
|
+
role,
|
|
1555
|
+
child: active,
|
|
1556
|
+
releaseRole,
|
|
1557
|
+
failed: false,
|
|
1558
|
+
unsettledFailure: false,
|
|
1559
|
+
retired: false
|
|
1560
|
+
};
|
|
1561
|
+
runs.set(handle, run);
|
|
1562
|
+
await switchChildModel(run, head);
|
|
1563
|
+
return handle;
|
|
1564
|
+
} catch (error) {
|
|
1565
|
+
if (openedChild)
|
|
1566
|
+
lifecycle.retire(openedChild, "hidden-run-open-failed");
|
|
1567
|
+
releaseRole();
|
|
1568
|
+
throw error;
|
|
1569
|
+
}
|
|
1570
|
+
},
|
|
1571
|
+
async attempt(handle, request) {
|
|
1572
|
+
const run = runs.get(handle);
|
|
1573
|
+
if (!run)
|
|
1574
|
+
throw new Error("Unknown hidden completion run");
|
|
1575
|
+
if (request.signal?.aborted) {
|
|
1576
|
+
await interruptAndRetire(run, "aborted-before-prompt");
|
|
1577
|
+
throw new Error("Hidden completion prompt aborted");
|
|
1578
|
+
}
|
|
1579
|
+
const requested = await validateVariant(requestModel(request, run.child.model));
|
|
1580
|
+
if (run.retired) {
|
|
1581
|
+
run.child = await lifecycle.create(run.identity, run.role, requested);
|
|
1582
|
+
run.failed = false;
|
|
1583
|
+
run.unsettledFailure = false;
|
|
1584
|
+
run.retired = false;
|
|
1585
|
+
handle.id = run.child.id;
|
|
1586
|
+
handle.childSessionId = run.child.id;
|
|
1587
|
+
}
|
|
1588
|
+
await switchChildModel(run, requested);
|
|
1589
|
+
const baseline = withReader(options.openReader, (reader) => reader.latestSequence(run.child.id));
|
|
1590
|
+
const marker = `mc:hidden:${crypto.randomUUID()}:${crypto.randomUUID()}`;
|
|
1591
|
+
run.completion = undefined;
|
|
1592
|
+
const attempt = {
|
|
1593
|
+
childSessionId: run.child.id,
|
|
1594
|
+
identity: run.identity,
|
|
1595
|
+
request,
|
|
1596
|
+
shaped: false,
|
|
1597
|
+
budget: run.budget
|
|
1598
|
+
};
|
|
1599
|
+
options.hook.registerAttempt(marker, attempt);
|
|
1600
|
+
const deadline = Date.now() + run.identity.timeoutMs;
|
|
1601
|
+
const budget = run.budget;
|
|
1602
|
+
if (budget?.snapshot().finalizeFired) {
|
|
1603
|
+
throw new DreamTokenBudgetExceeded(run.child.id, budget.snapshot().spent);
|
|
1604
|
+
}
|
|
1605
|
+
let usageSeq = baseline;
|
|
1606
|
+
let budgetPoll;
|
|
1607
|
+
let budgetReject;
|
|
1608
|
+
const budgetStopped = new Promise((_resolve, reject) => {
|
|
1609
|
+
budgetReject = reject;
|
|
1610
|
+
});
|
|
1611
|
+
if (budget) {
|
|
1612
|
+
budgetPoll = setInterval(() => {
|
|
1613
|
+
try {
|
|
1614
|
+
const fresh = withReader(options.openReader, (reader) => reader.assistantSince?.(run.child.id, usageSeq) ?? (() => {
|
|
1615
|
+
const latest = reader.latestAssistant(run.child.id);
|
|
1616
|
+
return latest ? [latest] : [];
|
|
1617
|
+
})());
|
|
1618
|
+
for (const row of fresh) {
|
|
1619
|
+
if (row.seq <= usageSeq)
|
|
1620
|
+
continue;
|
|
1621
|
+
usageSeq = row.seq;
|
|
1622
|
+
const tokens = row.data.tokens;
|
|
1623
|
+
const decision = budget.charge(Math.max(0, tokens?.input ?? 0), Math.max(0, tokens?.cache?.read ?? 0), Math.max(0, tokens?.cache?.write ?? 0), row.data.finish === "stop" && !(row.data.content ?? []).some((part) => part.type === "tool-call"), false);
|
|
1624
|
+
const onBudgetUpdate = run.identity.metadata?.onBudgetUpdate;
|
|
1625
|
+
if (typeof onBudgetUpdate === "function")
|
|
1626
|
+
onBudgetUpdate({ ...budget.snapshot(), sessionId: run.child.id });
|
|
1627
|
+
if (decision !== "continue") {
|
|
1628
|
+
if (budgetPoll)
|
|
1629
|
+
clearInterval(budgetPoll);
|
|
1630
|
+
attempt.budgetExceeded = new DreamTokenBudgetExceeded(run.child.id, budget.snapshot().spent);
|
|
1631
|
+
interruptAndRetire(run, "token-budget").finally(() => budgetReject(attempt.budgetExceeded));
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
} catch (error) {
|
|
1636
|
+
if (budgetPoll)
|
|
1637
|
+
clearInterval(budgetPoll);
|
|
1638
|
+
budgetReject(error instanceof Error ? error : new Error(String(error)));
|
|
1639
|
+
}
|
|
1640
|
+
}, POLL_INTERVAL_MS);
|
|
1641
|
+
}
|
|
1642
|
+
let abortReject;
|
|
1643
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
1644
|
+
abortReject = reject;
|
|
1645
|
+
});
|
|
1646
|
+
const onAbort = () => {
|
|
1647
|
+
interruptAndRetire(run, "prompt-aborted").finally(() => abortReject(new Error("Hidden completion prompt aborted")));
|
|
1648
|
+
};
|
|
1649
|
+
const deadlineTimer = setTimeout(() => {
|
|
1650
|
+
interruptAndRetire(run, "prompt-timeout").finally(() => abortReject(new Error("Hidden completion prompt timed out")));
|
|
1651
|
+
}, Math.max(0, deadline - Date.now()));
|
|
1652
|
+
request.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1653
|
+
try {
|
|
1654
|
+
await Promise.race([
|
|
1655
|
+
host.prompt({ sessionID: run.child.id, text: marker }),
|
|
1656
|
+
aborted,
|
|
1657
|
+
...budget ? [budgetStopped] : []
|
|
1658
|
+
]);
|
|
1659
|
+
await Promise.race([
|
|
1660
|
+
host.wait({ sessionID: run.child.id }),
|
|
1661
|
+
aborted,
|
|
1662
|
+
...budget ? [budgetStopped] : []
|
|
1663
|
+
]);
|
|
1664
|
+
const row = await Promise.race([
|
|
1665
|
+
awaitAssistantRow(options.openReader, async () => {
|
|
1666
|
+
try {
|
|
1667
|
+
const eventError = await host.terminalError?.({
|
|
1668
|
+
sessionID: run.child.id
|
|
1669
|
+
});
|
|
1670
|
+
if (eventError !== undefined)
|
|
1671
|
+
return eventError;
|
|
1672
|
+
return (await host.get({ sessionID: run.child.id })).error;
|
|
1673
|
+
} catch {
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
}, () => attempt.refusal ?? attempt.stepLimit, run.child.id, baseline, deadline, request.signal),
|
|
1677
|
+
aborted,
|
|
1678
|
+
...budget ? [budgetStopped] : []
|
|
1679
|
+
]);
|
|
1680
|
+
if (!attempt.shaped) {
|
|
1681
|
+
throw new HiddenCompletionRefusal("hidden_prompt_unrecognized", "Host did not dispatch the hidden child context hook", true);
|
|
1682
|
+
}
|
|
1683
|
+
const text = assistantText(row);
|
|
1684
|
+
const system = typeof request.body.system === "string" ? request.body.system : run.identity.system;
|
|
1685
|
+
const tokens = row.data.tokens;
|
|
1686
|
+
const tokenNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
1687
|
+
const reportedInput = tokenNumber(tokens?.input);
|
|
1688
|
+
const reportedOutput = tokenNumber(tokens?.output);
|
|
1689
|
+
if (request.signal?.aborted || run.retired || Date.now() >= deadline) {
|
|
1690
|
+
await interruptAndRetire(run, "prompt-aborted-or-timeout");
|
|
1691
|
+
throw new Error(request.signal?.aborted ? "Hidden completion prompt aborted" : "Hidden completion prompt timed out");
|
|
1692
|
+
}
|
|
1693
|
+
run.completion = {
|
|
1694
|
+
text,
|
|
1695
|
+
tokenLog: runTokenLog(tokens, run.identity.maxOutputTokens, row.data.finish),
|
|
1696
|
+
...hiddenToolLoop(run.identity) ? { messages: toolLoopMessages(attempt) } : {},
|
|
1697
|
+
reasoning: text ? null : assistantReasoning(row),
|
|
1698
|
+
usage: reportedInput !== undefined || reportedOutput !== undefined ? {
|
|
1699
|
+
input: reportedInput ?? 0,
|
|
1700
|
+
output: reportedOutput ?? 0,
|
|
1701
|
+
cacheRead: tokenNumber(tokens?.cache?.read) ?? 0,
|
|
1702
|
+
cacheWrite: tokenNumber(tokens?.cache?.write) ?? 0
|
|
1703
|
+
} : meter(system, promptText(request), text ?? ""),
|
|
1704
|
+
lengthCapped: ["length", "max_tokens"].includes(row.data.finish ?? ""),
|
|
1705
|
+
providerId: row.data.model?.providerID ?? requested.providerID,
|
|
1706
|
+
modelId: row.data.model?.id ?? requested.modelID
|
|
1707
|
+
};
|
|
1708
|
+
if (!run.child.ever_settled)
|
|
1709
|
+
run.child = lifecycle.markEverSettled(run.child);
|
|
1710
|
+
} catch (caught) {
|
|
1711
|
+
const error = attempt.budgetExceeded ?? attempt.stepLimit ?? attempt.refusal ?? caught;
|
|
1712
|
+
run.failed = true;
|
|
1713
|
+
if (!(error instanceof HiddenProviderError)) {
|
|
1714
|
+
run.unsettledFailure = true;
|
|
1715
|
+
}
|
|
1716
|
+
if (request.signal?.aborted && !run.retired) {
|
|
1717
|
+
await interruptAndRetire(run, "prompt-aborted");
|
|
1718
|
+
} else if (error instanceof Error && error.message.includes("timed out") && !run.retired) {
|
|
1719
|
+
await interruptAndRetire(run, "prompt-timeout");
|
|
1720
|
+
} else if ((error instanceof HiddenAgentStepLimit || error instanceof HiddenProviderError && error.settled) && !run.retired) {
|
|
1721
|
+
await interruptAndRetire(run, error instanceof HiddenAgentStepLimit ? "hidden-run-step-limit" : "hidden-run-provider-error");
|
|
1722
|
+
}
|
|
1723
|
+
throw error;
|
|
1724
|
+
} finally {
|
|
1725
|
+
clearTimeout(deadlineTimer);
|
|
1726
|
+
if (budgetPoll)
|
|
1727
|
+
clearInterval(budgetPoll);
|
|
1728
|
+
request.signal?.removeEventListener("abort", onAbort);
|
|
1729
|
+
options.hook.releaseAttempt(marker);
|
|
1730
|
+
}
|
|
1731
|
+
},
|
|
1732
|
+
async collect(handle) {
|
|
1733
|
+
const completion = runs.get(handle)?.completion;
|
|
1734
|
+
if (!completion)
|
|
1735
|
+
throw new Error("Hidden completion has no settled output");
|
|
1736
|
+
return completion;
|
|
1737
|
+
},
|
|
1738
|
+
async close(handle, settlement) {
|
|
1739
|
+
if (!handle)
|
|
1740
|
+
return;
|
|
1741
|
+
const run = runs.get(handle);
|
|
1742
|
+
if (!run)
|
|
1743
|
+
return;
|
|
1744
|
+
try {
|
|
1745
|
+
const reusable = !run.retired && run.failed && !run.unsettledFailure;
|
|
1746
|
+
if (hiddenToolLoop(run.identity)) {
|
|
1747
|
+
retire(run, settlement.promptSettled ? "tool-loop-settled" : "tool-loop-failed");
|
|
1748
|
+
} else if (!run.completion && (run.failed || !settlement.promptSettled) && !reusable) {
|
|
1749
|
+
retire(run, "hidden-run-failed");
|
|
1750
|
+
}
|
|
1751
|
+
} finally {
|
|
1752
|
+
runs.delete(handle);
|
|
1753
|
+
run.releaseRole();
|
|
1754
|
+
}
|
|
1755
|
+
await lifecycle.finish(run.child, run.retired);
|
|
1756
|
+
}
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
// src/v2/hooks/channel2.ts
|
|
1761
|
+
import { randomBytes } from "node:crypto";
|
|
1762
|
+
var pendingCandidates = new WeakMap;
|
|
1763
|
+
function syntheticCandidates(context, sessionID, stored) {
|
|
1764
|
+
const pending = pendingCandidates.get(context.storage);
|
|
1765
|
+
for (const [key, id] of pending ?? []) {
|
|
1766
|
+
if (!key.startsWith(`${sessionID}/`))
|
|
1767
|
+
continue;
|
|
1768
|
+
if (stored.has(id))
|
|
1769
|
+
pending?.delete(key);
|
|
1770
|
+
else
|
|
1771
|
+
stored.add(id);
|
|
1772
|
+
}
|
|
1773
|
+
return stored;
|
|
1774
|
+
}
|
|
1775
|
+
function clearSyntheticCandidates(context) {
|
|
1776
|
+
pendingCandidates.delete(context.storage);
|
|
1777
|
+
}
|
|
1778
|
+
async function deliverSynthetic(context, sessionID, text) {
|
|
1779
|
+
const id = `msg_${randomBytes(12).toString("hex")}`;
|
|
1780
|
+
await context.storage.set(`synthetic/${sessionID}/${id}`, { id });
|
|
1781
|
+
let candidates = pendingCandidates.get(context.storage);
|
|
1782
|
+
if (!candidates) {
|
|
1783
|
+
candidates = new Map;
|
|
1784
|
+
pendingCandidates.set(context.storage, candidates);
|
|
1785
|
+
}
|
|
1786
|
+
candidates.set(`${sessionID}/${id}`, id);
|
|
1787
|
+
await context.session.synthetic({ sessionID, id, text, delivery: "steer" });
|
|
1788
|
+
return id;
|
|
1789
|
+
}
|
|
1790
|
+
async function isAdmittedSynthetic(context, sessionID, id) {
|
|
1791
|
+
const record = await context.storage.get(`synthetic/${sessionID}/${id}`);
|
|
1792
|
+
return record !== null && typeof record === "object" && "id" in record && record.id === id;
|
|
1793
|
+
}
|
|
1794
|
+
async function deliverPendingChannel2(context, db, sessionID, baseline) {
|
|
1795
|
+
if (getChannel2NudgeState(db, sessionID) !== "pending")
|
|
1796
|
+
return;
|
|
1797
|
+
const evaluation = evaluateChannel2(baseline);
|
|
1798
|
+
if (!evaluation.evaluable || !evaluation.shouldTrigger)
|
|
1799
|
+
return;
|
|
1800
|
+
const token = randomBytes(16).toString("hex");
|
|
1801
|
+
if (!claimChannel2NudgeState(db, sessionID, token))
|
|
1802
|
+
return;
|
|
1803
|
+
try {
|
|
1804
|
+
await deliverSynthetic(context, sessionID, buildChannel2Reminder(evaluation.reclaimableTokens, reclaimableToolOutputCount(baseline?.baselineParts ?? [])));
|
|
1805
|
+
} catch (error) {
|
|
1806
|
+
casChannel2NudgeClaim(db, sessionID, "pending", token);
|
|
1807
|
+
throw error;
|
|
1808
|
+
}
|
|
1809
|
+
casChannel2NudgeClaim(db, sessionID, "delivered", token);
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
// src/v2/hooks/commands.ts
|
|
1813
|
+
async function registerV2Commands(args) {
|
|
1814
|
+
const domain = args.command;
|
|
1815
|
+
if (!domain || typeof domain.transform !== "function") {
|
|
1816
|
+
log("[magic-context] v2 host exposes no command domain; /ctx-* commands are TUI-only");
|
|
1817
|
+
return false;
|
|
1818
|
+
}
|
|
1819
|
+
const descriptions = getMagicContextBuiltinCommands(args.compactionEnabled);
|
|
1820
|
+
const commands = buildV2Commands(args.rpc, args.directory);
|
|
1821
|
+
try {
|
|
1822
|
+
await domain.transform((editor) => {
|
|
1823
|
+
for (const command of commands) {
|
|
1824
|
+
editor.add({
|
|
1825
|
+
name: command.name,
|
|
1826
|
+
description: descriptions[command.name].description,
|
|
1827
|
+
execute: command.execute
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
});
|
|
1831
|
+
} catch (error) {
|
|
1832
|
+
log("[magic-context] v2 command registration failed", error);
|
|
1833
|
+
return false;
|
|
1834
|
+
}
|
|
1835
|
+
log(`[magic-context] registered server-side commands: ${commands.map((command) => command.name).join(" ")}`);
|
|
1836
|
+
return true;
|
|
1837
|
+
}
|
|
1838
|
+
function commandArgument(input, name) {
|
|
1839
|
+
const raw = typeof input.prompt?.text === "string" ? input.prompt.text.trim() : "";
|
|
1840
|
+
const prefix = `/${name}`;
|
|
1841
|
+
if (raw === prefix)
|
|
1842
|
+
return "";
|
|
1843
|
+
if (raw.startsWith(`${prefix} `))
|
|
1844
|
+
return raw.slice(prefix.length).trim();
|
|
1845
|
+
return raw;
|
|
1846
|
+
}
|
|
1847
|
+
function reportResult(sessionID, title, message) {
|
|
1848
|
+
pushNotification2("action", { action: "show-result-dialog", title, message }, sessionID);
|
|
1849
|
+
}
|
|
1850
|
+
function reportUsage(sessionID, message) {
|
|
1851
|
+
pushNotification2("toast", { message, variant: "warning" }, sessionID);
|
|
1852
|
+
}
|
|
1853
|
+
function reportRpcReply(sessionID, title, reply, pending) {
|
|
1854
|
+
if (reply.ok !== true) {
|
|
1855
|
+
const error = typeof reply.error === "string" ? reply.error : `${title} request failed`;
|
|
1856
|
+
pushNotification2("toast", { message: error, variant: "error" }, sessionID);
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1859
|
+
if (reply.started === true) {
|
|
1860
|
+
pushNotification2("toast", { message: pending, variant: "info" }, sessionID);
|
|
1861
|
+
return;
|
|
1862
|
+
}
|
|
1863
|
+
reportResult(sessionID, title, typeof reply.message === "string" ? reply.message : "");
|
|
1864
|
+
}
|
|
1865
|
+
function buildV2Commands(rpc, directory) {
|
|
1866
|
+
return [
|
|
1867
|
+
{
|
|
1868
|
+
name: "ctx-status",
|
|
1869
|
+
execute: async (input) => {
|
|
1870
|
+
const detail = await rpc.dispatch("status-detail", {
|
|
1871
|
+
sessionId: input.sessionID,
|
|
1872
|
+
directory
|
|
1873
|
+
});
|
|
1874
|
+
if (typeof detail.error === "string") {
|
|
1875
|
+
throw new Error(`Magic Context status is unavailable: ${detail.error}`);
|
|
1876
|
+
}
|
|
1877
|
+
pushNotification2("action", { action: "show-status-dialog" }, input.sessionID);
|
|
1878
|
+
}
|
|
1879
|
+
},
|
|
1880
|
+
{
|
|
1881
|
+
name: "ctx-recomp",
|
|
1882
|
+
execute: async (input) => {
|
|
1883
|
+
if (isTuiConnected2(input.sessionID)) {
|
|
1884
|
+
pushNotification2("action", { action: "show-recomp-dialog" }, input.sessionID);
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1887
|
+
const reply = await rpc.dispatch("recomp", { sessionId: input.sessionID });
|
|
1888
|
+
if (reply.ok !== true) {
|
|
1889
|
+
throw new Error("Magic Context could not start the history rebuild.");
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
},
|
|
1893
|
+
{
|
|
1894
|
+
name: "ctx-dream",
|
|
1895
|
+
execute: async (input) => {
|
|
1896
|
+
const task = commandArgument(input, "ctx-dream");
|
|
1897
|
+
const reply = await rpc.dispatch("dream", {
|
|
1898
|
+
sessionId: input.sessionID,
|
|
1899
|
+
...task ? { task } : {}
|
|
1900
|
+
});
|
|
1901
|
+
if (reply.ok !== true) {
|
|
1902
|
+
const error = typeof reply.error === "string" ? reply.error : "dream request failed";
|
|
1903
|
+
throw new Error(`Magic Context could not start the dream run: ${error}`);
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
},
|
|
1907
|
+
{
|
|
1908
|
+
name: "ctx-flush",
|
|
1909
|
+
execute: async (input) => {
|
|
1910
|
+
const reply = await rpc.dispatch("flush", { sessionId: input.sessionID });
|
|
1911
|
+
reportRpcReply(input.sessionID, "Flush", reply, "Flush started");
|
|
1912
|
+
}
|
|
1913
|
+
},
|
|
1914
|
+
{
|
|
1915
|
+
name: "ctx-embed",
|
|
1916
|
+
execute: async (input) => {
|
|
1917
|
+
const argument = commandArgument(input, "ctx-embed").toLowerCase();
|
|
1918
|
+
if (argument !== "" && argument !== "start" && argument !== "pause") {
|
|
1919
|
+
reportUsage(input.sessionID, "Usage: /ctx-embed (status), /ctx-embed start, or /ctx-embed pause");
|
|
1920
|
+
throw new Error("Usage: /ctx-embed (status), /ctx-embed start, or /ctx-embed pause");
|
|
1921
|
+
}
|
|
1922
|
+
const reply = await rpc.dispatch("embed", {
|
|
1923
|
+
sessionId: input.sessionID,
|
|
1924
|
+
action: argument === "" ? "status" : argument,
|
|
1925
|
+
directory
|
|
1926
|
+
});
|
|
1927
|
+
reportRpcReply(input.sessionID, "Embed", reply, "Embedding started; the summary appears when it finishes");
|
|
1928
|
+
}
|
|
1929
|
+
},
|
|
1930
|
+
{
|
|
1931
|
+
name: "ctx-wrapup",
|
|
1932
|
+
execute: async (input) => {
|
|
1933
|
+
const argument = commandArgument(input, "ctx-wrapup");
|
|
1934
|
+
if (argument !== "" && !/^\d+$/.test(argument)) {
|
|
1935
|
+
const usage = "Usage: /ctx-wrapup [messages_to_keep] where messages_to_keep is a positive integer";
|
|
1936
|
+
reportUsage(input.sessionID, usage);
|
|
1937
|
+
throw new Error(usage);
|
|
1938
|
+
}
|
|
1939
|
+
const messagesToKeep = argument === "" ? 20 : Number.parseInt(argument, 10);
|
|
1940
|
+
if (messagesToKeep <= 0) {
|
|
1941
|
+
const usage = "messages_to_keep must be a positive integer";
|
|
1942
|
+
reportUsage(input.sessionID, usage);
|
|
1943
|
+
throw new Error(usage);
|
|
1944
|
+
}
|
|
1945
|
+
const reply = await rpc.dispatch("wrapup", {
|
|
1946
|
+
sessionId: input.sessionID,
|
|
1947
|
+
messagesToKeep
|
|
1948
|
+
});
|
|
1949
|
+
reportRpcReply(input.sessionID, "Wrapup", reply, "Wrapup started; the summary appears when it finishes");
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
];
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
// src/v2/hooks/deleted-session-tombstones.ts
|
|
1956
|
+
class DeletedSessionTombstones {
|
|
1957
|
+
limit;
|
|
1958
|
+
#ids = new Set;
|
|
1959
|
+
constructor(limit = 1000) {
|
|
1960
|
+
this.limit = limit;
|
|
1961
|
+
if (!Number.isSafeInteger(limit) || limit < 1) {
|
|
1962
|
+
throw new Error("Deleted-session tombstone limit must be a positive integer");
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
add(sessionID) {
|
|
1966
|
+
this.#ids.delete(sessionID);
|
|
1967
|
+
this.#ids.add(sessionID);
|
|
1968
|
+
while (this.#ids.size > this.limit) {
|
|
1969
|
+
const oldest = this.#ids.values().next().value;
|
|
1970
|
+
if (oldest === undefined)
|
|
1971
|
+
break;
|
|
1972
|
+
this.#ids.delete(oldest);
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
has(sessionID) {
|
|
1976
|
+
return this.#ids.has(sessionID);
|
|
1977
|
+
}
|
|
1978
|
+
clear() {
|
|
1979
|
+
this.#ids.clear();
|
|
1980
|
+
}
|
|
1981
|
+
get size() {
|
|
1982
|
+
return this.#ids.size;
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
// src/v2/retrospective-raw-provider.ts
|
|
1987
|
+
class V2RetrospectiveRawProvider {
|
|
1988
|
+
contextDb;
|
|
1989
|
+
openReader;
|
|
1990
|
+
reader;
|
|
1991
|
+
constructor(contextDb, openReader = () => new V2StoreReader(gaDatabasePath(getDataDir(), process.env.OPENCODE_CHANNEL ?? "latest"))) {
|
|
1992
|
+
this.contextDb = contextDb;
|
|
1993
|
+
this.openReader = openReader;
|
|
1994
|
+
}
|
|
1995
|
+
store() {
|
|
1996
|
+
return this.reader ??= this.openReader();
|
|
1997
|
+
}
|
|
1998
|
+
listProjectSessions(projectIdentity) {
|
|
1999
|
+
const sessions = this.contextDb.prepare(`
|
|
2000
|
+
SELECT sp.session_id FROM session_projects sp
|
|
2001
|
+
LEFT JOIN session_meta m ON m.session_id = sp.session_id
|
|
2002
|
+
WHERE sp.project_path = ? AND sp.harness = 'opencode2'
|
|
2003
|
+
AND COALESCE(m.is_subagent, 0) = 0
|
|
2004
|
+
ORDER BY sp.session_id`).all(projectIdentity);
|
|
2005
|
+
const roots = this.store().rootSessionActivity(sessions.map((row) => row.session_id));
|
|
2006
|
+
return sessions.filter((row) => roots.has(row.session_id)).map((row) => ({ sessionId: row.session_id, updatedAt: roots.get(row.session_id) }));
|
|
2007
|
+
}
|
|
2008
|
+
readOldestMessageTimesSince(sessionIds, sinceMs) {
|
|
2009
|
+
return this.store().oldestUserMessageTimesSince(sessionIds, sinceMs);
|
|
2010
|
+
}
|
|
2011
|
+
read(sessionId, boundaryMs, limit, before = false) {
|
|
2012
|
+
return this.store().retrospectiveUserPage(sessionId, {
|
|
2013
|
+
boundaryMs,
|
|
2014
|
+
before,
|
|
2015
|
+
limit,
|
|
2016
|
+
maxChars: RETROSPECTIVE_MAX_USER_MESSAGE_CHARS,
|
|
2017
|
+
truncationMarker: RETROSPECTIVE_MESSAGE_TRUNCATION_MARKER
|
|
2018
|
+
}).map((row) => ({
|
|
2019
|
+
sessionId,
|
|
2020
|
+
ordinal: row.seq,
|
|
2021
|
+
role: "user",
|
|
2022
|
+
text: cleanUserText2(row.text),
|
|
2023
|
+
ts: row.time_created
|
|
2024
|
+
}));
|
|
2025
|
+
}
|
|
2026
|
+
readUserMessagesSince(sessionId, sinceMs, capPerSession) {
|
|
2027
|
+
const cap = Math.max(1, Math.min(9999, Math.floor(capPerSession)));
|
|
2028
|
+
const rows = this.read(sessionId, sinceMs, cap + 1);
|
|
2029
|
+
return { messages: rows.slice(0, cap), truncated: rows.length > cap };
|
|
2030
|
+
}
|
|
2031
|
+
readUserMessagesBefore(sessionId, beforeMs, count) {
|
|
2032
|
+
return this.read(sessionId, beforeMs, Math.max(1, Math.min(1e4, Math.floor(count))), true).reverse().filter((row) => hasMeaningfulUserText2([{ type: "text", text: row.text }]));
|
|
2033
|
+
}
|
|
2034
|
+
dispose() {
|
|
2035
|
+
this.reader?.close();
|
|
2036
|
+
this.reader = undefined;
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
// src/v2/hooks/dream-manual.ts
|
|
2041
|
+
function resolveManualDreamTask(raw) {
|
|
2042
|
+
const requested = typeof raw === "string" ? raw.trim() : "";
|
|
2043
|
+
if (!requested)
|
|
2044
|
+
return {};
|
|
2045
|
+
if (!isCanonicalDreamTask(requested)) {
|
|
2046
|
+
return {
|
|
2047
|
+
error: `Unknown task "${requested}". Valid tasks: ${CANONICAL_DREAM_TASKS.join(", ")}.`
|
|
2048
|
+
};
|
|
2049
|
+
}
|
|
2050
|
+
return { task: requested };
|
|
2051
|
+
}
|
|
2052
|
+
function selectRunnableDreamTasks(args) {
|
|
2053
|
+
const requiresTools = (task) => DREAM_TASK_CAPABILITIES[task].requiresTools;
|
|
2054
|
+
if (args.toolsSupported)
|
|
2055
|
+
return { runnable: [...args.tasks], unsupported: [] };
|
|
2056
|
+
if (args.requestedTask !== undefined) {
|
|
2057
|
+
if (requiresTools(args.requestedTask)) {
|
|
2058
|
+
return { runnable: [], unsupported: [args.requestedTask] };
|
|
2059
|
+
}
|
|
2060
|
+
return {
|
|
2061
|
+
runnable: args.tasks.filter((config) => config.task === args.requestedTask),
|
|
2062
|
+
unsupported: []
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
2065
|
+
const unsupported = args.tasks.filter((config) => config.schedule.trim() !== "" && requiresTools(config.task)).map((config) => config.task);
|
|
2066
|
+
return {
|
|
2067
|
+
runnable: args.tasks.filter((config) => !requiresTools(config.task)),
|
|
2068
|
+
unsupported
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
async function runManualDreamNow(args) {
|
|
2072
|
+
const tasks = buildDreamTaskRuntimeConfigs2(args.dreamer, "opencode", args.language, args.mural?.model);
|
|
2073
|
+
const executor = createDreamTaskExecutor({
|
|
2074
|
+
hiddenCompletionExecutor: args.executor,
|
|
2075
|
+
parentSessionId: args.sessionId,
|
|
2076
|
+
sessionDirectory: args.directory,
|
|
2077
|
+
openOpenCodeDb: () => null,
|
|
2078
|
+
retrospectiveRawProvider: (db) => new V2RetrospectiveRawProvider(db),
|
|
2079
|
+
language: args.language,
|
|
2080
|
+
mural: args.mural
|
|
2081
|
+
});
|
|
2082
|
+
const selection = selectRunnableDreamTasks({
|
|
2083
|
+
tasks,
|
|
2084
|
+
toolsSupported: args.executor.capabilities.tools === true,
|
|
2085
|
+
...args.task !== undefined ? { requestedTask: args.task } : {}
|
|
2086
|
+
});
|
|
2087
|
+
const summary = await runManualDream({
|
|
2088
|
+
db: args.db,
|
|
2089
|
+
projectIdentity: args.projectIdentity,
|
|
2090
|
+
tasks: selection.runnable,
|
|
2091
|
+
executor,
|
|
2092
|
+
...args.task !== undefined ? { task: args.task } : {}
|
|
2093
|
+
});
|
|
2094
|
+
return { summary, unsupportedTasks: selection.unsupported };
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
// src/v2/hooks/dream-timer.ts
|
|
2098
|
+
function findV2DreamParentSession(openReader, directory) {
|
|
2099
|
+
let reader;
|
|
2100
|
+
try {
|
|
2101
|
+
reader = openReader();
|
|
2102
|
+
const parent = reader.latestRootSessionInDirectory(directory);
|
|
2103
|
+
if (!parent) {
|
|
2104
|
+
log(`[dreamer] no OpenCode 2 session in ${directory} to parent a timer-started dream run; tasks that need a child session are skipped until one exists`);
|
|
2105
|
+
}
|
|
2106
|
+
return parent;
|
|
2107
|
+
} catch (error) {
|
|
2108
|
+
log(`[dreamer] OpenCode 2 parent session lookup failed for ${directory}:`, error);
|
|
2109
|
+
return;
|
|
2110
|
+
} finally {
|
|
2111
|
+
reader?.close();
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
function registerV2DreamScheduleTimer(args) {
|
|
2115
|
+
const { config, directory, dreamer, executor } = args;
|
|
2116
|
+
const gitCommitIndexing = config.memory.git_commit_indexing;
|
|
2117
|
+
let disposed = false;
|
|
2118
|
+
const registration = startDreamScheduleTimer({
|
|
2119
|
+
directory,
|
|
2120
|
+
projectIdentity: args.projectIdentity,
|
|
2121
|
+
harness: "opencode",
|
|
2122
|
+
hiddenCompletionExecutor: executor,
|
|
2123
|
+
findParentSessionId: () => findV2DreamParentSession(args.openReader, directory),
|
|
2124
|
+
openOpenCodeDb: () => null,
|
|
2125
|
+
retrospectiveRawProvider: (db) => new V2RetrospectiveRawProvider(db),
|
|
2126
|
+
dreamerConfig: dreamer,
|
|
2127
|
+
sampleDreamRun: () => {
|
|
2128
|
+
const current = dreamerRunConfig(config, args.liveConfig());
|
|
2129
|
+
return { dreamerConfig: current.dreamer ?? dreamer, mural: current.mural };
|
|
2130
|
+
},
|
|
2131
|
+
validateTaskModels: (tasks) => selectRunnableDreamTasks({ tasks, toolsSupported: executor.capabilities.tools }).runnable,
|
|
2132
|
+
language: config.language,
|
|
2133
|
+
mural: config.mural,
|
|
2134
|
+
memoryEnabled: config.memory.enabled === true,
|
|
2135
|
+
memoryInjectionBudgetTokens: config.memory.injection_budget_tokens,
|
|
2136
|
+
retinaHandoff: config.smart_notes.retina_handoff,
|
|
2137
|
+
embeddingConfig: config.embedding,
|
|
2138
|
+
gitCommitIndexing: gitCommitIndexing?.enabled ? {
|
|
2139
|
+
enabled: true,
|
|
2140
|
+
since_days: gitCommitIndexing.since_days,
|
|
2141
|
+
max_commits: gitCommitIndexing.max_commits
|
|
2142
|
+
} : undefined,
|
|
2143
|
+
ensureRegistered: ensureProjectRegisteredFromOpenCodeDirectory
|
|
2144
|
+
}).catch((error) => {
|
|
2145
|
+
log(`[magic-context] v2 dream timer registration failed (continuing without it): ${error}`);
|
|
2146
|
+
return;
|
|
2147
|
+
});
|
|
2148
|
+
return {
|
|
2149
|
+
async dispose() {
|
|
2150
|
+
if (disposed)
|
|
2151
|
+
return;
|
|
2152
|
+
disposed = true;
|
|
2153
|
+
const stop = await registration;
|
|
2154
|
+
stop?.();
|
|
2155
|
+
}
|
|
2156
|
+
};
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
// src/v2/hooks/dream-trigger.ts
|
|
2160
|
+
function startDreamTrigger(context, args) {
|
|
2161
|
+
const controller = new AbortController;
|
|
2162
|
+
const done = (async () => {
|
|
2163
|
+
try {
|
|
2164
|
+
for await (const value of context.event.subscribe({ signal: controller.signal })) {
|
|
2165
|
+
if (controller.signal.aborted)
|
|
2166
|
+
break;
|
|
2167
|
+
const event = value;
|
|
2168
|
+
if (event.type !== "session.execution.succeeded" || !event.data?.sessionID)
|
|
2169
|
+
continue;
|
|
2170
|
+
const db = openCurrentDatabase();
|
|
2171
|
+
if (!db)
|
|
2172
|
+
continue;
|
|
2173
|
+
try {
|
|
2174
|
+
const reader = args.openReader?.() ?? new V2StoreReader(gaDatabasePath(getDataDir(), process.env.OPENCODE_CHANNEL ?? "latest"));
|
|
2175
|
+
try {
|
|
2176
|
+
const activity = reader.rootSessionActivity([event.data.sessionID]).get(event.data.sessionID);
|
|
2177
|
+
if (activity !== undefined)
|
|
2178
|
+
advanceSessionActivity(db, event.data.sessionID, activity);
|
|
2179
|
+
} finally {
|
|
2180
|
+
reader.close();
|
|
2181
|
+
}
|
|
2182
|
+
const sampled = args.sample?.();
|
|
2183
|
+
const { runnable } = selectRunnableDreamTasks({
|
|
2184
|
+
tasks: buildDreamTaskRuntimeConfigs2(sampled?.config ?? args.config, "opencode", args.language, (sampled?.mural ?? args.mural)?.model),
|
|
2185
|
+
toolsSupported: args.executor.capabilities.tools === true
|
|
2186
|
+
});
|
|
2187
|
+
await runDueTasksForProject({
|
|
2188
|
+
db,
|
|
2189
|
+
projectIdentity: args.projectIdentity(),
|
|
2190
|
+
projectMemoryEnabled: args.projectMemoryEnabled,
|
|
2191
|
+
tasks: runnable,
|
|
2192
|
+
executor: createDreamTaskExecutor({
|
|
2193
|
+
hiddenCompletionExecutor: args.executor,
|
|
2194
|
+
parentSessionId: event.data.sessionID,
|
|
2195
|
+
sessionDirectory: context.location.directory,
|
|
2196
|
+
openOpenCodeDb: () => null,
|
|
2197
|
+
retrospectiveRawProvider: (db) => new V2RetrospectiveRawProvider(db),
|
|
2198
|
+
language: args.language,
|
|
2199
|
+
mural: sampled?.mural ?? args.mural
|
|
2200
|
+
})
|
|
2201
|
+
});
|
|
2202
|
+
} catch (error) {
|
|
2203
|
+
log("[magic-context] v2 dream scheduling failed", error);
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
} catch (error) {
|
|
2207
|
+
if (!controller.signal.aborted)
|
|
2208
|
+
log("[magic-context] v2 dream event subscription failed", error);
|
|
2209
|
+
}
|
|
2210
|
+
})();
|
|
2211
|
+
return {
|
|
2212
|
+
async dispose() {
|
|
2213
|
+
controller.abort();
|
|
2214
|
+
await done;
|
|
2215
|
+
}
|
|
2216
|
+
};
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
// src/v2/tool-result.ts
|
|
2220
|
+
function isToolError(part) {
|
|
2221
|
+
const result = part.result;
|
|
2222
|
+
return part.resultType === "error" || result?.type === "error" || result !== null && typeof result === "object" && "error" in result;
|
|
2223
|
+
}
|
|
2224
|
+
function toolErrorText(result) {
|
|
2225
|
+
const value = result.value === undefined ? result : result.value;
|
|
2226
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
2227
|
+
}
|
|
2228
|
+
function toolStateContent(state) {
|
|
2229
|
+
if (typeof state.output === "string")
|
|
2230
|
+
return state.output;
|
|
2231
|
+
if (state.status === "error")
|
|
2232
|
+
return toolErrorText({ error: state.error, content: state.content ?? [] });
|
|
2233
|
+
if (typeof state.content === "string")
|
|
2234
|
+
return state.content;
|
|
2235
|
+
if (!Array.isArray(state.content))
|
|
2236
|
+
return "";
|
|
2237
|
+
return state.content.map((part) => {
|
|
2238
|
+
if (typeof part === "string")
|
|
2239
|
+
return part;
|
|
2240
|
+
if (!part || typeof part !== "object")
|
|
2241
|
+
return "";
|
|
2242
|
+
const value = part;
|
|
2243
|
+
if (typeof value.text === "string")
|
|
2244
|
+
return value.text;
|
|
2245
|
+
if (typeof value.value === "string")
|
|
2246
|
+
return value.value;
|
|
2247
|
+
return "";
|
|
2248
|
+
}).filter(Boolean).join(`
|
|
2249
|
+
`);
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
// src/v2/hooks/payload.ts
|
|
2253
|
+
var HEAD_IDS = ["__magic_context_v2_m0__", "__magic_context_v2_m1__"];
|
|
2254
|
+
var HOST_CONTENT_TYPES = new Set([
|
|
2255
|
+
"text",
|
|
2256
|
+
"media",
|
|
2257
|
+
"tool-call",
|
|
2258
|
+
"tool-result",
|
|
2259
|
+
"reasoning",
|
|
2260
|
+
"compaction",
|
|
2261
|
+
"effort"
|
|
2262
|
+
]);
|
|
2263
|
+
function rejectContentPart(message, part, reason) {
|
|
2264
|
+
const type = typeof part.type === "string" ? part.type : "<missing>";
|
|
2265
|
+
const text = `v2 context omitted a content part the host would reject: type=${type} message=${String(message.info.id ?? "<head>")} reason=${reason}`;
|
|
2266
|
+
if (typeof message.info.sessionID === "string")
|
|
2267
|
+
sessionLog(message.info.sessionID, text);
|
|
2268
|
+
else
|
|
2269
|
+
log(text);
|
|
2270
|
+
}
|
|
2271
|
+
function isHostInstance(value) {
|
|
2272
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
2273
|
+
return false;
|
|
2274
|
+
const prototype = Object.getPrototypeOf(value);
|
|
2275
|
+
return prototype !== Object.prototype && prototype !== null;
|
|
2276
|
+
}
|
|
2277
|
+
function containsHostInstance(value) {
|
|
2278
|
+
if (isHostInstance(value))
|
|
2279
|
+
return true;
|
|
2280
|
+
if (Array.isArray(value))
|
|
2281
|
+
return value.some(containsHostInstance);
|
|
2282
|
+
if (value && typeof value === "object")
|
|
2283
|
+
return Object.values(value).some(containsHostInstance);
|
|
2284
|
+
return false;
|
|
2285
|
+
}
|
|
2286
|
+
function clonePreservingInstances(value) {
|
|
2287
|
+
if (isHostInstance(value))
|
|
2288
|
+
return value;
|
|
2289
|
+
if (Array.isArray(value))
|
|
2290
|
+
return value.map(clonePreservingInstances);
|
|
2291
|
+
if (value && typeof value === "object") {
|
|
2292
|
+
const copy = {};
|
|
2293
|
+
for (const [key, entry] of Object.entries(value))
|
|
2294
|
+
copy[key] = clonePreservingInstances(entry);
|
|
2295
|
+
return copy;
|
|
2296
|
+
}
|
|
2297
|
+
return value;
|
|
2298
|
+
}
|
|
2299
|
+
function contentKey(value) {
|
|
2300
|
+
const plain = (entry) => {
|
|
2301
|
+
if (ArrayBuffer.isView(entry)) {
|
|
2302
|
+
return {
|
|
2303
|
+
bytes: Buffer.from(entry.buffer, entry.byteOffset, entry.byteLength).toString("base64")
|
|
2304
|
+
};
|
|
2305
|
+
}
|
|
2306
|
+
if (Array.isArray(entry))
|
|
2307
|
+
return entry.map(plain);
|
|
2308
|
+
if (entry && typeof entry === "object") {
|
|
2309
|
+
return Object.fromEntries(Object.entries(entry).map(([key, nested]) => [key, plain(nested)]));
|
|
2310
|
+
}
|
|
2311
|
+
return entry;
|
|
2312
|
+
};
|
|
2313
|
+
return JSON.stringify(plain(value));
|
|
2314
|
+
}
|
|
2315
|
+
function fileContentValue(result) {
|
|
2316
|
+
const value = result?.value;
|
|
2317
|
+
if (result?.type !== "content" || !Array.isArray(value))
|
|
2318
|
+
return;
|
|
2319
|
+
return value.some((item) => item?.type === "file") ? value : undefined;
|
|
2320
|
+
}
|
|
2321
|
+
function contentValueText(value) {
|
|
2322
|
+
return value.filter((item) => item?.type === "text" && typeof item.text === "string").map((item) => item.text).join(`
|
|
2323
|
+
`);
|
|
2324
|
+
}
|
|
2325
|
+
function contentValueAttachments(value) {
|
|
2326
|
+
return value.filter((item) => item?.type === "file").map((item) => ({
|
|
2327
|
+
type: "file",
|
|
2328
|
+
mime: item.mime,
|
|
2329
|
+
url: item.uri,
|
|
2330
|
+
...typeof item.name === "string" ? { filename: item.name } : {}
|
|
2331
|
+
}));
|
|
2332
|
+
}
|
|
2333
|
+
function rebuildFileContent(value, output) {
|
|
2334
|
+
if (isDroppedToolOutput(output))
|
|
2335
|
+
return { type: "text", value: output };
|
|
2336
|
+
const rebuilt = [];
|
|
2337
|
+
let placed = false;
|
|
2338
|
+
for (const item of value) {
|
|
2339
|
+
if (item?.type !== "text") {
|
|
2340
|
+
rebuilt.push(item);
|
|
2341
|
+
continue;
|
|
2342
|
+
}
|
|
2343
|
+
if (placed)
|
|
2344
|
+
continue;
|
|
2345
|
+
placed = true;
|
|
2346
|
+
rebuilt.push({ ...item, text: output });
|
|
2347
|
+
}
|
|
2348
|
+
if (!placed && output)
|
|
2349
|
+
rebuilt.unshift({ type: "text", text: output });
|
|
2350
|
+
return { type: "content", value: rebuilt };
|
|
2351
|
+
}
|
|
2352
|
+
function adaptPayload(draft, admittedIDs = new Set) {
|
|
2353
|
+
const existingHeads = new Map(draft.messages.filter((m) => HEAD_IDS.some((id) => m.id === id)).map((m) => [m.id, m]));
|
|
2354
|
+
const source = draft.messages.filter((m) => !existingHeads.has(m.id));
|
|
2355
|
+
const results = new Map;
|
|
2356
|
+
const hostParts = new Map;
|
|
2357
|
+
const hostPartTypes = new Set;
|
|
2358
|
+
for (const message of source) {
|
|
2359
|
+
for (const part of message.content) {
|
|
2360
|
+
if (part.type !== "tool-call" && part.type !== "tool-result" && part.type !== "tool" && containsHostInstance(part)) {
|
|
2361
|
+
const key = contentKey(part);
|
|
2362
|
+
if (!hostParts.has(key))
|
|
2363
|
+
hostParts.set(key, part);
|
|
2364
|
+
hostPartTypes.add(part.type);
|
|
2365
|
+
}
|
|
2366
|
+
if (part.type === "tool-result" && typeof part.id === "string") {
|
|
2367
|
+
const queue = results.get(part.id) ?? [];
|
|
2368
|
+
queue.push({ part, message });
|
|
2369
|
+
results.set(part.id, queue);
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
const paired = new Set;
|
|
2374
|
+
const bridges = new Map;
|
|
2375
|
+
const originals = new Map;
|
|
2376
|
+
const bridgesByMessageID = new Map;
|
|
2377
|
+
const bridgesByCarrier = new Map;
|
|
2378
|
+
const originalsByID = new Map;
|
|
2379
|
+
const bridgesFor = (message) => typeof message.info.id === "string" ? bridgesByMessageID.get(message.info.id) : bridgesByCarrier.get(message);
|
|
2380
|
+
const nativeTool = (call, result) => {
|
|
2381
|
+
const hostResult = result?.part.result;
|
|
2382
|
+
const failed = result !== undefined && isToolError(result.part);
|
|
2383
|
+
const value = hostResult?.value;
|
|
2384
|
+
const files = fileContentValue(hostResult);
|
|
2385
|
+
const output = failed && hostResult ? toolErrorText(hostResult) : files ? contentValueText(files) : typeof value === "string" ? value : value === undefined ? "" : JSON.stringify(value);
|
|
2386
|
+
const part = {
|
|
2387
|
+
type: "tool",
|
|
2388
|
+
callID: call?.id ?? result?.part.id,
|
|
2389
|
+
tool: call?.name ?? result?.part.name,
|
|
2390
|
+
state: {
|
|
2391
|
+
input: structuredClone(call?.input ?? {}),
|
|
2392
|
+
status: failed ? "error" : result ? "completed" : "running",
|
|
2393
|
+
...result ? { output } : {},
|
|
2394
|
+
...result && (call?.name ?? result.part.name) === "question" ? { metadata: { userAnswer: true } } : {},
|
|
2395
|
+
...files ? { attachments: contentValueAttachments(files) } : {}
|
|
2396
|
+
}
|
|
2397
|
+
};
|
|
2398
|
+
bridges.set(part, {
|
|
2399
|
+
call,
|
|
2400
|
+
result: result?.part,
|
|
2401
|
+
resultMessage: result?.message,
|
|
2402
|
+
output,
|
|
2403
|
+
files
|
|
2404
|
+
});
|
|
2405
|
+
if (result)
|
|
2406
|
+
paired.add(result.part);
|
|
2407
|
+
return part;
|
|
2408
|
+
};
|
|
2409
|
+
const convertedTool = (source) => {
|
|
2410
|
+
const sourceState = source.state && typeof source.state === "object" ? source.state : {};
|
|
2411
|
+
const output = toolStateContent(sourceState);
|
|
2412
|
+
const { content: _content, ...projectedState } = structuredClone(sourceState);
|
|
2413
|
+
const part = {
|
|
2414
|
+
type: "tool",
|
|
2415
|
+
callID: source.callID ?? source.id,
|
|
2416
|
+
tool: source.tool ?? source.name,
|
|
2417
|
+
state: { ...projectedState, output }
|
|
2418
|
+
};
|
|
2419
|
+
bridges.set(part, { native: source, output });
|
|
2420
|
+
return part;
|
|
2421
|
+
};
|
|
2422
|
+
const callParts = new Map;
|
|
2423
|
+
for (const message of source) {
|
|
2424
|
+
for (const part of message.content) {
|
|
2425
|
+
if (part.type === "tool-call") {
|
|
2426
|
+
callParts.set(part, nativeTool(part, results.get(String(part.id))?.shift()));
|
|
2427
|
+
} else if (part.type === "tool") {
|
|
2428
|
+
callParts.set(part, convertedTool(part));
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
const messages = [];
|
|
2433
|
+
for (const message of source) {
|
|
2434
|
+
const parts = [];
|
|
2435
|
+
for (const content of message.content) {
|
|
2436
|
+
if (paired.has(content))
|
|
2437
|
+
continue;
|
|
2438
|
+
const part = callParts.get(content) ?? (content.type === "tool-result" ? nativeTool(undefined, { part: content, message }) : clonePreservingInstances(content));
|
|
2439
|
+
if (message.id && admittedIDs.has(message.id))
|
|
2440
|
+
part.synthetic = true;
|
|
2441
|
+
parts.push(part);
|
|
2442
|
+
}
|
|
2443
|
+
if (!parts.length && message.content.length)
|
|
2444
|
+
continue;
|
|
2445
|
+
const byCallID = new Map;
|
|
2446
|
+
for (const part of parts) {
|
|
2447
|
+
const bridge = bridges.get(part);
|
|
2448
|
+
if (bridge && typeof part.callID === "string")
|
|
2449
|
+
byCallID.set(part.callID, bridge);
|
|
2450
|
+
}
|
|
2451
|
+
if (typeof message.id === "string") {
|
|
2452
|
+
bridgesByMessageID.set(message.id, byCallID);
|
|
2453
|
+
originalsByID.set(message.id, message);
|
|
2454
|
+
}
|
|
2455
|
+
const mapped = {
|
|
2456
|
+
info: {
|
|
2457
|
+
id: message.id,
|
|
2458
|
+
sessionID: draft.sessionID,
|
|
2459
|
+
role: message.role,
|
|
2460
|
+
...{
|
|
2461
|
+
providerID: draft.model.providerID,
|
|
2462
|
+
modelID: draft.model.id,
|
|
2463
|
+
agent: draft.agent
|
|
2464
|
+
}
|
|
2465
|
+
},
|
|
2466
|
+
parts
|
|
2467
|
+
};
|
|
2468
|
+
originals.set(mapped, message);
|
|
2469
|
+
if (typeof message.id !== "string")
|
|
2470
|
+
bridgesByCarrier.set(mapped, byCallID);
|
|
2471
|
+
messages.push(mapped);
|
|
2472
|
+
}
|
|
2473
|
+
return {
|
|
2474
|
+
messages,
|
|
2475
|
+
commit() {
|
|
2476
|
+
let head = 0;
|
|
2477
|
+
const rendered = [];
|
|
2478
|
+
for (const message of messages) {
|
|
2479
|
+
const original = originals.get(message) ?? (typeof message.info.id === "string" ? originalsByID.get(message.info.id) : undefined);
|
|
2480
|
+
const id = message.info.syntheticHead ? HEAD_IDS[head++] : original?.id;
|
|
2481
|
+
const content = [];
|
|
2482
|
+
const following = [];
|
|
2483
|
+
for (const candidate of message.parts) {
|
|
2484
|
+
const part = candidate;
|
|
2485
|
+
const bridge = bridges.get(part) ?? (part.type === "tool" && typeof part.callID === "string" ? bridgesFor(message)?.get(part.callID) : undefined);
|
|
2486
|
+
if (!bridge) {
|
|
2487
|
+
if (part.type === "tool") {
|
|
2488
|
+
const state = part.state;
|
|
2489
|
+
if (typeof part.callID !== "string" || typeof part.tool !== "string" || !state || typeof state !== "object") {
|
|
2490
|
+
rejectContentPart(message, part, "tool has no call ID, name, or state");
|
|
2491
|
+
continue;
|
|
2492
|
+
}
|
|
2493
|
+
content.push({
|
|
2494
|
+
type: "tool-call",
|
|
2495
|
+
id: part.callID,
|
|
2496
|
+
name: part.tool,
|
|
2497
|
+
input: state.input
|
|
2498
|
+
});
|
|
2499
|
+
if (state.status === "completed" || state.status === "error") {
|
|
2500
|
+
following.push({
|
|
2501
|
+
role: "tool",
|
|
2502
|
+
content: [
|
|
2503
|
+
{
|
|
2504
|
+
type: "tool-result",
|
|
2505
|
+
id: part.callID,
|
|
2506
|
+
name: part.tool,
|
|
2507
|
+
result: {
|
|
2508
|
+
type: state.status === "error" ? "error" : "text",
|
|
2509
|
+
value: toolStateContent(state)
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
]
|
|
2513
|
+
});
|
|
2514
|
+
}
|
|
2515
|
+
continue;
|
|
2516
|
+
}
|
|
2517
|
+
if (part.type === "file") {
|
|
2518
|
+
const mime = part.mime;
|
|
2519
|
+
const url = part.url;
|
|
2520
|
+
const prefix = `data:${mime};base64,`;
|
|
2521
|
+
if (typeof mime !== "string" || !/^image\/(png|jpeg|gif|webp)$/.test(mime) || typeof url !== "string" || !url.startsWith(prefix)) {
|
|
2522
|
+
rejectContentPart(message, part, "file is not an inline supported image");
|
|
2523
|
+
continue;
|
|
2524
|
+
}
|
|
2525
|
+
const asset = hostMediaAsset(url.slice(prefix.length), mime);
|
|
2526
|
+
if (typeof asset === "string") {
|
|
2527
|
+
rejectContentPart(message, part, `host media unavailable: ${asset}`);
|
|
2528
|
+
continue;
|
|
2529
|
+
}
|
|
2530
|
+
content.push({ type: "media", media: asset });
|
|
2531
|
+
continue;
|
|
2532
|
+
}
|
|
2533
|
+
if (!HOST_CONTENT_TYPES.has(String(part.type))) {
|
|
2534
|
+
rejectContentPart(message, part, "unknown host content type");
|
|
2535
|
+
continue;
|
|
2536
|
+
}
|
|
2537
|
+
const { synthetic: _synthetic, ...clean } = part;
|
|
2538
|
+
const original = hostPartTypes.has(clean.type) && !containsHostInstance(clean) ? hostParts.get(contentKey(clean)) : undefined;
|
|
2539
|
+
content.push(original ?? clean);
|
|
2540
|
+
continue;
|
|
2541
|
+
}
|
|
2542
|
+
const state = part.state;
|
|
2543
|
+
if (bridge.native) {
|
|
2544
|
+
const native = bridge.native;
|
|
2545
|
+
const callID = String(native.id ?? native.callID ?? part.callID);
|
|
2546
|
+
const name = String(native.name ?? native.tool ?? part.tool);
|
|
2547
|
+
content.push({ type: "tool-call", id: callID, name, input: state.input });
|
|
2548
|
+
if (state.status === "completed" || state.status === "error") {
|
|
2549
|
+
following.push({
|
|
2550
|
+
role: "tool",
|
|
2551
|
+
content: [
|
|
2552
|
+
{
|
|
2553
|
+
type: "tool-result",
|
|
2554
|
+
id: callID,
|
|
2555
|
+
name,
|
|
2556
|
+
result: {
|
|
2557
|
+
type: state.status === "error" ? "error" : "text",
|
|
2558
|
+
value: state.output
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
]
|
|
2562
|
+
});
|
|
2563
|
+
}
|
|
2564
|
+
continue;
|
|
2565
|
+
}
|
|
2566
|
+
if (bridge.call)
|
|
2567
|
+
content.push({ ...bridge.call, input: state.input });
|
|
2568
|
+
if (bridge.result && bridge.resultMessage) {
|
|
2569
|
+
const result = {
|
|
2570
|
+
...bridge.result,
|
|
2571
|
+
result: state.output === bridge.output ? bridge.result.result : bridge.files && typeof state.output === "string" ? rebuildFileContent(bridge.files, state.output) : {
|
|
2572
|
+
type: state.status === "error" ? "error" : "text",
|
|
2573
|
+
value: state.output
|
|
2574
|
+
}
|
|
2575
|
+
};
|
|
2576
|
+
if (bridge.resultMessage === original)
|
|
2577
|
+
content.push(result);
|
|
2578
|
+
else
|
|
2579
|
+
following.push({ ...bridge.resultMessage, content: [result] });
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
if (content.length || !original?.content.length) {
|
|
2583
|
+
const value = {
|
|
2584
|
+
...original,
|
|
2585
|
+
id,
|
|
2586
|
+
role: message.info.role ?? "user",
|
|
2587
|
+
content
|
|
2588
|
+
};
|
|
2589
|
+
const existing = id ? existingHeads.get(id) : undefined;
|
|
2590
|
+
rendered.push(existing ? Object.assign(existing, { content }) : value);
|
|
2591
|
+
}
|
|
2592
|
+
rendered.push(...following);
|
|
2593
|
+
}
|
|
2594
|
+
draft.messages.splice(0, draft.messages.length, ...rendered);
|
|
2595
|
+
}
|
|
2596
|
+
};
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
// src/v2/hooks/generate.ts
|
|
2600
|
+
class V2GenerateReplay {
|
|
2601
|
+
prefixes = new Map;
|
|
2602
|
+
capture(draft, anchor) {
|
|
2603
|
+
if (!anchor)
|
|
2604
|
+
return;
|
|
2605
|
+
this.prefixes.set(draft.sessionID, {
|
|
2606
|
+
anchor,
|
|
2607
|
+
model: `${draft.model.providerID}/${draft.model.id}`,
|
|
2608
|
+
agent: draft.agent,
|
|
2609
|
+
system: clonePreservingInstances(draft.system),
|
|
2610
|
+
messages: clonePreservingInstances(draft.messages),
|
|
2611
|
+
tools: clonePreservingInstances(draft.tools)
|
|
2612
|
+
});
|
|
2613
|
+
}
|
|
2614
|
+
forget(sessionID) {
|
|
2615
|
+
this.prefixes.delete(sessionID);
|
|
2616
|
+
}
|
|
2617
|
+
apply(draft) {
|
|
2618
|
+
const prefix = this.prefixes.get(draft.sessionID);
|
|
2619
|
+
if (!prefix || prefix.model !== `${draft.model.providerID}/${draft.model.id}` || prefix.agent !== draft.agent)
|
|
2620
|
+
return false;
|
|
2621
|
+
const index = draft.messages.findIndex((message) => message.id === prefix.anchor);
|
|
2622
|
+
if (index < 0)
|
|
2623
|
+
return false;
|
|
2624
|
+
const tail = draft.messages.slice(index + 1);
|
|
2625
|
+
draft.messages.splice(0, draft.messages.length, ...clonePreservingInstances(prefix.messages), ...tail);
|
|
2626
|
+
draft.system.splice(0, draft.system.length, ...clonePreservingInstances(prefix.system));
|
|
2627
|
+
draft.tools = clonePreservingInstances(prefix.tools);
|
|
2628
|
+
return true;
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
// src/v2/hooks/hidden-terminal-error.ts
|
|
2633
|
+
function hiddenTerminalError(event) {
|
|
2634
|
+
if (event.type === "session.execution.failed")
|
|
2635
|
+
return event.data?.error;
|
|
2636
|
+
if (event.type === "session.error")
|
|
2637
|
+
return getSessionErrorInfo(event.data)?.error;
|
|
2638
|
+
return;
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
// src/v2/hooks/lkg-system.ts
|
|
2642
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
2643
|
+
function identity(slot) {
|
|
2644
|
+
return createHash2("sha256").update(JSON.stringify({
|
|
2645
|
+
capturedAt: slot.capturedAt,
|
|
2646
|
+
captureSequence: slot.captureSequence,
|
|
2647
|
+
rowVersion: slot.rowVersion,
|
|
2648
|
+
modelKey: slot.modelKey,
|
|
2649
|
+
providerKey: slot.providerKey,
|
|
2650
|
+
inputIdSeq: slot.inputIdSeq,
|
|
2651
|
+
inputContentDigests: slot.inputContentDigests
|
|
2652
|
+
})).update(slot.jsonPrefix).digest("hex");
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
class V2LkgSystemReplay {
|
|
2656
|
+
systems = new BoundedSessionMap(1000);
|
|
2657
|
+
capture(sessionId, slot, input, served) {
|
|
2658
|
+
this.systems.set(sessionId, {
|
|
2659
|
+
slot: identity(slot),
|
|
2660
|
+
input: JSON.stringify(input),
|
|
2661
|
+
served: structuredClone(served)
|
|
2662
|
+
});
|
|
2663
|
+
}
|
|
2664
|
+
restore(sessionId, slot, input, target) {
|
|
2665
|
+
const saved = this.systems.get(sessionId);
|
|
2666
|
+
if (!slot || !saved || saved.slot !== identity(slot) || saved.input !== JSON.stringify(input))
|
|
2667
|
+
return false;
|
|
2668
|
+
target.splice(0, target.length, ...structuredClone(saved.served));
|
|
2669
|
+
return true;
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
// src/v2/hooks/model-limit-cache.ts
|
|
2674
|
+
var warmStarted = false;
|
|
2675
|
+
function modelLimitCacheWarm() {
|
|
2676
|
+
const state = getModelsDevCacheState();
|
|
2677
|
+
return state.apiLoaded && state.apiCount > 0;
|
|
2678
|
+
}
|
|
2679
|
+
function catalogProvidersPayload(listed) {
|
|
2680
|
+
const rows = Array.isArray(listed) ? listed : listed && typeof listed === "object" && Array.isArray(listed.data) ? listed.data : [];
|
|
2681
|
+
const byProvider = new Map;
|
|
2682
|
+
for (const row of rows) {
|
|
2683
|
+
if (!row || typeof row !== "object")
|
|
2684
|
+
continue;
|
|
2685
|
+
const entry = row;
|
|
2686
|
+
if (typeof entry.id !== "string" || typeof entry.providerID !== "string")
|
|
2687
|
+
continue;
|
|
2688
|
+
const models = byProvider.get(entry.providerID) ?? {};
|
|
2689
|
+
models[entry.id] = entry;
|
|
2690
|
+
byProvider.set(entry.providerID, models);
|
|
2691
|
+
}
|
|
2692
|
+
return [...byProvider.entries()].map(([id, models]) => ({ id, models }));
|
|
2693
|
+
}
|
|
2694
|
+
async function warmModelLimitCacheFromCatalog(context, options = {}) {
|
|
2695
|
+
if (warmStarted)
|
|
2696
|
+
return;
|
|
2697
|
+
warmStarted = true;
|
|
2698
|
+
try {
|
|
2699
|
+
await refreshModelLimitsFromApi({
|
|
2700
|
+
config: {
|
|
2701
|
+
providers: async () => ({
|
|
2702
|
+
data: {
|
|
2703
|
+
providers: catalogProvidersPayload(await Promise.resolve(context.model.list()))
|
|
2704
|
+
}
|
|
2705
|
+
})
|
|
2706
|
+
}
|
|
2707
|
+
}, {
|
|
2708
|
+
retries: options.retries ?? 3,
|
|
2709
|
+
retryDelayMs: options.retryDelayMs ?? 1000
|
|
2710
|
+
});
|
|
2711
|
+
} catch (error) {
|
|
2712
|
+
sessionLog("global", `v2 model-limit cache warm failed: ${getErrorMessage(error)}`);
|
|
2713
|
+
} finally {
|
|
2714
|
+
if (!modelLimitCacheWarm())
|
|
2715
|
+
warmStarted = false;
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2719
|
+
// src/v2/hooks/refusal.ts
|
|
2720
|
+
class V2ContextRefusal extends Error {
|
|
2721
|
+
constructor(message = "Context full — /ctx-flush or /clear to continue.", options) {
|
|
2722
|
+
super(message, options);
|
|
2723
|
+
this.name = "V2ContextRefusal";
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
var INTERRUPT_CONFIRMATION_TIMEOUT_MS = 2000;
|
|
2727
|
+
var systemTimers = {
|
|
2728
|
+
setTimeout: (callback, ms) => setTimeout(callback, ms),
|
|
2729
|
+
clearTimeout: (handle) => clearTimeout(handle)
|
|
2730
|
+
};
|
|
2731
|
+
async function interruptBeforeProvider(session, sessionID, timers = systemTimers) {
|
|
2732
|
+
let timer;
|
|
2733
|
+
try {
|
|
2734
|
+
const result = await Promise.race([
|
|
2735
|
+
session.interrupt({ sessionID }),
|
|
2736
|
+
new Promise((_, reject) => {
|
|
2737
|
+
timer = timers.setTimeout(() => reject(new Error(`interrupt exceeded ${INTERRUPT_CONFIRMATION_TIMEOUT_MS} ms`)), INTERRUPT_CONFIRMATION_TIMEOUT_MS);
|
|
2738
|
+
})
|
|
2739
|
+
]);
|
|
2740
|
+
if (result.interrupted !== true)
|
|
2741
|
+
throw new Error("interrupt arrived after the turn became idle");
|
|
2742
|
+
} catch (cause) {
|
|
2743
|
+
throw new V2ContextRefusal(undefined, { cause });
|
|
2744
|
+
} finally {
|
|
2745
|
+
if (timer !== undefined)
|
|
2746
|
+
timers.clearTimeout(timer);
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2750
|
+
// src/v2/hooks/restore-rows.ts
|
|
2751
|
+
class RestoredRowCache {
|
|
2752
|
+
capacity;
|
|
2753
|
+
sessions = new Map;
|
|
2754
|
+
constructor(capacity = 16) {
|
|
2755
|
+
this.capacity = capacity;
|
|
2756
|
+
}
|
|
2757
|
+
rows(reader, sessionID, after, through) {
|
|
2758
|
+
if (through <= after)
|
|
2759
|
+
return [];
|
|
2760
|
+
const kept = this.sessions.get(sessionID) ?? new Map;
|
|
2761
|
+
const stamps = reader.spanRowStamps(sessionID, after, through);
|
|
2762
|
+
const runs = [];
|
|
2763
|
+
let previous = after;
|
|
2764
|
+
let open;
|
|
2765
|
+
for (const [seq, stamp] of stamps) {
|
|
2766
|
+
if (kept.get(seq)?.stamp === stamp) {
|
|
2767
|
+
open = undefined;
|
|
2768
|
+
} else if (open) {
|
|
2769
|
+
open.through = seq;
|
|
2770
|
+
} else {
|
|
2771
|
+
open = { after: previous, through: seq };
|
|
2772
|
+
runs.push(open);
|
|
2773
|
+
}
|
|
2774
|
+
previous = seq;
|
|
2775
|
+
}
|
|
2776
|
+
const fresh = new Map;
|
|
2777
|
+
for (const run of runs)
|
|
2778
|
+
for (const row of readPages(reader, sessionID, run.after, run.through))
|
|
2779
|
+
fresh.set(row.seq, row);
|
|
2780
|
+
const next = new Map;
|
|
2781
|
+
const rows = [];
|
|
2782
|
+
for (const [seq, stamp] of stamps) {
|
|
2783
|
+
const row = fresh.get(seq) ?? kept.get(seq)?.row;
|
|
2784
|
+
if (!row)
|
|
2785
|
+
continue;
|
|
2786
|
+
next.set(seq, { stamp, row });
|
|
2787
|
+
rows.push(row);
|
|
2788
|
+
}
|
|
2789
|
+
this.sessions.delete(sessionID);
|
|
2790
|
+
this.sessions.set(sessionID, next);
|
|
2791
|
+
while (this.sessions.size > this.capacity) {
|
|
2792
|
+
const oldest = this.sessions.keys().next().value;
|
|
2793
|
+
if (oldest === undefined)
|
|
2794
|
+
break;
|
|
2795
|
+
this.sessions.delete(oldest);
|
|
2796
|
+
}
|
|
2797
|
+
return structuredClone(rows);
|
|
2798
|
+
}
|
|
2799
|
+
forget(sessionID) {
|
|
2800
|
+
this.sessions.delete(sessionID);
|
|
2801
|
+
}
|
|
2802
|
+
clear() {
|
|
2803
|
+
this.sessions.clear();
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
function readPages(reader, sessionID, after, through) {
|
|
2807
|
+
const rows = [];
|
|
2808
|
+
let cursor = after;
|
|
2809
|
+
while (cursor < through) {
|
|
2810
|
+
const page = reader.page(sessionID, { after: cursor, through, limit: 100 });
|
|
2811
|
+
rows.push(...page.rows);
|
|
2812
|
+
if (page.rows.length < 100 || page.cursor === undefined)
|
|
2813
|
+
break;
|
|
2814
|
+
cursor = page.cursor;
|
|
2815
|
+
}
|
|
2816
|
+
return rows;
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
// src/v2/hooks/rpc-live-state.ts
|
|
2820
|
+
function createV2RpcLiveSessionState(overrides) {
|
|
2821
|
+
return Object.assign(createLiveSessionState(), overrides);
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
// src/v2/hooks/rust-mode.ts
|
|
2825
|
+
var USER_TURN_TYPES = new Set(["user", "synthetic", "skill", "shell", "system"]);
|
|
2826
|
+
function resolveV2RustModeModuleClient(config, directory) {
|
|
2827
|
+
if (config.transform_mode !== "rust")
|
|
2828
|
+
return;
|
|
2829
|
+
return createSubcModuleClient({
|
|
2830
|
+
...config.subc?.connection_file !== undefined ? { connectionFile: config.subc.connection_file } : {},
|
|
2831
|
+
projectRoot: directory
|
|
2832
|
+
});
|
|
2833
|
+
}
|
|
2834
|
+
function refusedStepStillCurrent(rows, refusedUserMessageId) {
|
|
2835
|
+
const refused = rows.findIndex((row) => row.id === refusedUserMessageId);
|
|
2836
|
+
if (refused < 0)
|
|
2837
|
+
return false;
|
|
2838
|
+
return !rows.slice(refused + 1).some((row) => USER_TURN_TYPES.has(row.type));
|
|
2839
|
+
}
|
|
2840
|
+
function createV2RustRefusalRecovery(args) {
|
|
2841
|
+
return createRustRefusalRecovery({
|
|
2842
|
+
...args.pollIntervalMs !== undefined ? { pollIntervalMs: args.pollIntervalMs } : {},
|
|
2843
|
+
...args.maxDurationMs !== undefined ? { maxDurationMs: args.maxDurationMs } : {},
|
|
2844
|
+
...args.probeTimeoutMs !== undefined ? { probeTimeoutMs: args.probeTimeoutMs } : {},
|
|
2845
|
+
moduleClient: args.moduleClient,
|
|
2846
|
+
client: undefined,
|
|
2847
|
+
deliverSynthetic: async ({ sessionId, text, beforeSend }) => {
|
|
2848
|
+
if (!beforeSend())
|
|
2849
|
+
return false;
|
|
2850
|
+
await deliverSynthetic(args.context, sessionId, text);
|
|
2851
|
+
return true;
|
|
2852
|
+
},
|
|
2853
|
+
isRefusedStepStillCurrent: (sessionId, refusedUserMessageId) => refusedStepStillCurrent(args.readRowsFrom(sessionId, refusedUserMessageId), refusedUserMessageId)
|
|
2854
|
+
});
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2857
|
+
// src/v2/hooks/session-api-gate.ts
|
|
2858
|
+
var OPENCODE2_SESSION_API_NOTICE = "Magic Context is disabled: OpenCode 2.0.22 or newer is required (session.remove and session.compact). Upgrade OpenCode and restart.";
|
|
2859
|
+
function hasRequiredSessionAPI(session) {
|
|
2860
|
+
const api = session;
|
|
2861
|
+
return typeof api.remove === "function" && typeof api.compact === "function";
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
// src/v2/hooks/session-project-backfill.ts
|
|
2865
|
+
function runV2SessionProjectBackfill(db, openStoreReader, allowHomeProject = false) {
|
|
2866
|
+
return runSessionProjectBackfill(db, (afterSessionId, limit) => {
|
|
2867
|
+
const reader = openStoreReader();
|
|
2868
|
+
try {
|
|
2869
|
+
return reader.sessionDirectoryPage(afterSessionId, limit);
|
|
2870
|
+
} finally {
|
|
2871
|
+
reader.close();
|
|
2872
|
+
}
|
|
2873
|
+
}, { leaseKey: `${getHarness()}:session-projects-creation-v2`, allowHomeProject });
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
// src/v2/hooks/storage-gate.ts
|
|
2877
|
+
var V2_STORAGE_REOPEN_INTERVAL_MS = 5000;
|
|
2878
|
+
function createV2StorageGate(options = {}) {
|
|
2879
|
+
const open = options.open ?? (async () => {
|
|
2880
|
+
const opened = await openDatabaseAsync({ busyTimeoutMs: 0 });
|
|
2881
|
+
opened?.exec(`PRAGMA busy_timeout=${BOOT_SQLITE_BUSY_TIMEOUT_MS}`);
|
|
2882
|
+
return opened;
|
|
2883
|
+
});
|
|
2884
|
+
const now = options.now ?? (() => Date.now());
|
|
2885
|
+
const interval = options.reopenIntervalMs ?? V2_STORAGE_REOPEN_INTERVAL_MS;
|
|
2886
|
+
let db;
|
|
2887
|
+
let failure = null;
|
|
2888
|
+
let failureKey = null;
|
|
2889
|
+
let lastAttemptAt;
|
|
2890
|
+
let pending;
|
|
2891
|
+
const attempt = async () => {
|
|
2892
|
+
let next;
|
|
2893
|
+
try {
|
|
2894
|
+
const opened = await open();
|
|
2895
|
+
if (opened && isDatabasePersisted(opened)) {
|
|
2896
|
+
db = opened;
|
|
2897
|
+
const recovered = failure !== null;
|
|
2898
|
+
failure = null;
|
|
2899
|
+
failureKey = null;
|
|
2900
|
+
if (recovered)
|
|
2901
|
+
options.onRecovered?.(opened);
|
|
2902
|
+
return db;
|
|
2903
|
+
}
|
|
2904
|
+
next = describeStorageUnavailability(getDatabasePersistenceError(opened) ?? "context storage is not durable");
|
|
2905
|
+
} catch (error) {
|
|
2906
|
+
next = describeStorageUnavailability(getErrorMessage(error));
|
|
2907
|
+
}
|
|
2908
|
+
const key = JSON.stringify(next);
|
|
2909
|
+
failure = next;
|
|
2910
|
+
if (key !== failureKey) {
|
|
2911
|
+
failureKey = key;
|
|
2912
|
+
options.onUnavailable?.(next);
|
|
2913
|
+
}
|
|
2914
|
+
return;
|
|
2915
|
+
};
|
|
2916
|
+
const probe = () => {
|
|
2917
|
+
if (db)
|
|
2918
|
+
return Promise.resolve(db);
|
|
2919
|
+
if (pending)
|
|
2920
|
+
return pending;
|
|
2921
|
+
if (lastAttemptAt !== undefined && now() - lastAttemptAt < interval)
|
|
2922
|
+
return Promise.resolve(undefined);
|
|
2923
|
+
pending = Promise.resolve().then(attempt).finally(() => {
|
|
2924
|
+
lastAttemptAt = now();
|
|
2925
|
+
pending = undefined;
|
|
2926
|
+
});
|
|
2927
|
+
return pending;
|
|
2928
|
+
};
|
|
2929
|
+
return {
|
|
2930
|
+
current: () => db,
|
|
2931
|
+
reason: () => failure,
|
|
2932
|
+
probe,
|
|
2933
|
+
require: () => {
|
|
2934
|
+
if (db)
|
|
2935
|
+
return db;
|
|
2936
|
+
probe();
|
|
2937
|
+
throw createFailClosedBlockingError(failure ?? { kind: "storage_failure", cause: "context storage is not durable" });
|
|
2938
|
+
}
|
|
2939
|
+
};
|
|
2940
|
+
}
|
|
2941
|
+
async function probeV2StorageAtBoot(storage, timeoutMs = 15000) {
|
|
2942
|
+
const deadline = startBootDeadline(timeoutMs);
|
|
2943
|
+
try {
|
|
2944
|
+
return await Promise.race([storage.probe(), deadline.expired.then(() => {
|
|
2945
|
+
return;
|
|
2946
|
+
})]);
|
|
2947
|
+
} finally {
|
|
2948
|
+
deadline.cancel();
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2952
|
+
// src/v2/hooks/store.ts
|
|
2953
|
+
var rawMessageTypes = new Set(RAW_MESSAGE_TYPES);
|
|
2954
|
+
var isRawRow = (row) => rawMessageTypes.has(row.type);
|
|
2955
|
+
var NO_MODEL = { providerID: "", id: "" };
|
|
2956
|
+
function hostServesRowById(row) {
|
|
2957
|
+
return isRawRow(row) && restoreRow(row, NO_MODEL).some((message) => message.id === row.id);
|
|
2958
|
+
}
|
|
2959
|
+
function projectRawMessages(rows, ordinalFor) {
|
|
2960
|
+
return rows.filter(isRawRow).map((row, index) => {
|
|
2961
|
+
const message = {
|
|
2962
|
+
id: row.id,
|
|
2963
|
+
ordinal: ordinalFor(row, index),
|
|
2964
|
+
role: row.type === "assistant" ? "assistant" : "user",
|
|
2965
|
+
createdAt: row.time_created ?? row.data.time?.created,
|
|
2966
|
+
parts: row.type === "assistant" ? (row.data.content ?? []).map((part) => {
|
|
2967
|
+
if (part.type !== "tool")
|
|
2968
|
+
return { ...part };
|
|
2969
|
+
const state = part.state;
|
|
2970
|
+
const content = state.content;
|
|
2971
|
+
return {
|
|
2972
|
+
type: "tool",
|
|
2973
|
+
tool: part.name,
|
|
2974
|
+
callID: part.id,
|
|
2975
|
+
state: {
|
|
2976
|
+
...state,
|
|
2977
|
+
output: state.status === "error" ? toolStateContent(state) : content?.filter((p) => p.type === "text").map((p) => p.text).join(`
|
|
2978
|
+
`) ?? ""
|
|
2979
|
+
}
|
|
2980
|
+
};
|
|
2981
|
+
}) : [{ type: "text", text: row.data.text ?? "" }]
|
|
2982
|
+
};
|
|
2983
|
+
Object.defineProperty(message, "storeType", { value: row.type, enumerable: false });
|
|
2984
|
+
if (!hostServesRowById(row))
|
|
2985
|
+
markHostUnservedRow(message);
|
|
2986
|
+
return message;
|
|
2987
|
+
});
|
|
2988
|
+
}
|
|
2989
|
+
function rawMessages(rows, history = rows) {
|
|
2990
|
+
const ordinals = new Map(history.filter(isRawRow).map((row, index) => [row.id, index + 1]));
|
|
2991
|
+
return projectRawMessages(rows, (row) => {
|
|
2992
|
+
return ordinals.get(row.id) ?? 0;
|
|
2993
|
+
});
|
|
2994
|
+
}
|
|
2995
|
+
function rawMessagePage(rows, afterOrdinal) {
|
|
2996
|
+
return projectRawMessages(rows, (_row, index) => afterOrdinal + index + 1);
|
|
2997
|
+
}
|
|
2998
|
+
var SERVED_BOUNDARY_PAGE = 50;
|
|
2999
|
+
function servedBoundaryRow(reader, sessionID, messageID) {
|
|
3000
|
+
const boundary = reader.messageById(sessionID, messageID);
|
|
3001
|
+
if (!boundary)
|
|
3002
|
+
return null;
|
|
3003
|
+
if (hostServesRowById(boundary))
|
|
3004
|
+
return boundary;
|
|
3005
|
+
let through = boundary.seq - 1;
|
|
3006
|
+
for (;; ) {
|
|
3007
|
+
const rows = reader.rawRowsThrough(sessionID, through, SERVED_BOUNDARY_PAGE);
|
|
3008
|
+
const served = rows.find(hostServesRowById);
|
|
3009
|
+
if (served)
|
|
3010
|
+
return served;
|
|
3011
|
+
const oldest = rows.at(-1);
|
|
3012
|
+
if (rows.length < SERVED_BOUNDARY_PAGE || !oldest)
|
|
3013
|
+
return null;
|
|
3014
|
+
through = oldest.seq - 1;
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
function resolveV2BoundaryUserMessage(reader, sessionID, endMessageID) {
|
|
3018
|
+
const end = reader.messageById(sessionID, endMessageID);
|
|
3019
|
+
if (!end || !isRawRow(end))
|
|
3020
|
+
return null;
|
|
3021
|
+
let through = end.seq;
|
|
3022
|
+
for (;; ) {
|
|
3023
|
+
const rows = reader.rawRowsThrough(sessionID, through, SERVED_BOUNDARY_PAGE);
|
|
3024
|
+
const user = rows.find((row) => row.type !== "assistant");
|
|
3025
|
+
if (user)
|
|
3026
|
+
return rawMessagePage([user], 0)[0] ?? null;
|
|
3027
|
+
const oldest = rows.at(-1);
|
|
3028
|
+
if (rows.length < SERVED_BOUNDARY_PAGE || !oldest)
|
|
3029
|
+
return null;
|
|
3030
|
+
through = oldest.seq - 1;
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
function readAllV2RawMessagesForConversion(openReader, sessionID) {
|
|
3034
|
+
const reader = openReader();
|
|
3035
|
+
try {
|
|
3036
|
+
return rawMessages(reader.history(sessionID));
|
|
3037
|
+
} finally {
|
|
3038
|
+
reader.close();
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
var CONVERSION_PAGE_ROWS = 1000;
|
|
3042
|
+
function* readV2RawMessagePagesForConversion(openReader, sessionID, pageRows = CONVERSION_PAGE_ROWS) {
|
|
3043
|
+
const reader = openReader();
|
|
3044
|
+
try {
|
|
3045
|
+
let after = -1;
|
|
3046
|
+
let ordinal = 0;
|
|
3047
|
+
for (;; ) {
|
|
3048
|
+
const page = reader.page(sessionID, { after, limit: pageRows });
|
|
3049
|
+
const messages = rawMessagePage(page.rows, ordinal);
|
|
3050
|
+
ordinal += messages.length;
|
|
3051
|
+
if (messages.length > 0)
|
|
3052
|
+
yield messages;
|
|
3053
|
+
if (page.rows.length < pageRows || page.cursor === undefined)
|
|
3054
|
+
return;
|
|
3055
|
+
after = page.cursor;
|
|
3056
|
+
}
|
|
3057
|
+
} finally {
|
|
3058
|
+
reader.close();
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
function createV2RawMessageReader(openReader) {
|
|
3062
|
+
const servedBoundaryCache = new Map;
|
|
3063
|
+
return {
|
|
3064
|
+
readPage: (sessionID, afterOrdinal, limit, finalWatermark, after) => {
|
|
3065
|
+
const reader = openReader();
|
|
3066
|
+
try {
|
|
3067
|
+
return rawMessagePage(reader.messagePage(sessionID, afterOrdinal, limit, finalWatermark, after), afterOrdinal);
|
|
3068
|
+
} finally {
|
|
3069
|
+
reader.close();
|
|
3070
|
+
}
|
|
3071
|
+
},
|
|
3072
|
+
findById: (sessionID, messageID) => {
|
|
3073
|
+
const reader = openReader();
|
|
3074
|
+
try {
|
|
3075
|
+
const ordinal = reader.messageOrdinalById(sessionID, messageID);
|
|
3076
|
+
const row = reader.messageById(sessionID, messageID);
|
|
3077
|
+
if (ordinal === null || row === null)
|
|
3078
|
+
return null;
|
|
3079
|
+
return rawMessagePage([row], ordinal - 1)[0] ?? null;
|
|
3080
|
+
} finally {
|
|
3081
|
+
reader.close();
|
|
3082
|
+
}
|
|
3083
|
+
},
|
|
3084
|
+
findPartsById: (sessionID, messageID) => {
|
|
3085
|
+
const reader = openReader();
|
|
3086
|
+
try {
|
|
3087
|
+
const row = reader.messageById(sessionID, messageID);
|
|
3088
|
+
return row ? rawMessagePage([row], 0)[0] ?? null : null;
|
|
3089
|
+
} finally {
|
|
3090
|
+
reader.close();
|
|
3091
|
+
}
|
|
3092
|
+
},
|
|
3093
|
+
hasById: (sessionID, messageID) => {
|
|
3094
|
+
const reader = openReader();
|
|
3095
|
+
try {
|
|
3096
|
+
return reader.messageExistsById(sessionID, messageID);
|
|
3097
|
+
} finally {
|
|
3098
|
+
reader.close();
|
|
3099
|
+
}
|
|
3100
|
+
},
|
|
3101
|
+
ordinalOf: (sessionID, messageID) => {
|
|
3102
|
+
const reader = openReader();
|
|
3103
|
+
try {
|
|
3104
|
+
return reader.messageOrdinalById(sessionID, messageID);
|
|
3105
|
+
} finally {
|
|
3106
|
+
reader.close();
|
|
3107
|
+
}
|
|
3108
|
+
},
|
|
3109
|
+
ordinalMapForRange: (sessionID, fromOrdinal, toOrdinal) => {
|
|
3110
|
+
const reader = openReader();
|
|
3111
|
+
try {
|
|
3112
|
+
return reader.messageIdOrdinals(sessionID, fromOrdinal, toOrdinal);
|
|
3113
|
+
} finally {
|
|
3114
|
+
reader.close();
|
|
3115
|
+
}
|
|
3116
|
+
},
|
|
3117
|
+
readOrdinalPage: (sessionID, after, limit) => {
|
|
3118
|
+
const reader = openReader();
|
|
3119
|
+
try {
|
|
3120
|
+
return reader.messageOrdinalPage(sessionID, after, limit);
|
|
3121
|
+
} finally {
|
|
3122
|
+
reader.close();
|
|
3123
|
+
}
|
|
3124
|
+
},
|
|
3125
|
+
getCount: (sessionID) => {
|
|
3126
|
+
const reader = openReader();
|
|
3127
|
+
try {
|
|
3128
|
+
return reader.messageCount(sessionID);
|
|
3129
|
+
} finally {
|
|
3130
|
+
reader.close();
|
|
3131
|
+
}
|
|
3132
|
+
},
|
|
3133
|
+
getStoredCount: (sessionID) => {
|
|
3134
|
+
const reader = openReader();
|
|
3135
|
+
try {
|
|
3136
|
+
return reader.storedMessageCount(sessionID);
|
|
3137
|
+
} finally {
|
|
3138
|
+
reader.close();
|
|
3139
|
+
}
|
|
3140
|
+
},
|
|
3141
|
+
servedBoundaryIdOf: (sessionID, messageID) => {
|
|
3142
|
+
const key = `${sessionID}\x00${messageID}`;
|
|
3143
|
+
if (servedBoundaryCache.has(key))
|
|
3144
|
+
return servedBoundaryCache.get(key) ?? null;
|
|
3145
|
+
const reader = openReader();
|
|
3146
|
+
try {
|
|
3147
|
+
const served = servedBoundaryRow(reader, sessionID, messageID)?.id ?? null;
|
|
3148
|
+
if (servedBoundaryCache.size >= 1000)
|
|
3149
|
+
servedBoundaryCache.clear();
|
|
3150
|
+
if (served !== null)
|
|
3151
|
+
servedBoundaryCache.set(key, served);
|
|
3152
|
+
return served;
|
|
3153
|
+
} finally {
|
|
3154
|
+
reader.close();
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
};
|
|
3158
|
+
}
|
|
3159
|
+
function createV2RawMessageProvider(reader, sessionID) {
|
|
3160
|
+
return {
|
|
3161
|
+
readMessagePage: (afterOrdinal, limit, finalWatermark, after) => reader.readPage(sessionID, afterOrdinal, limit, finalWatermark, after),
|
|
3162
|
+
readMessageById: (messageID) => reader.findById(sessionID, messageID),
|
|
3163
|
+
readMessagePartsById: (messageID) => reader.findPartsById(sessionID, messageID),
|
|
3164
|
+
hasMessageById: (messageID) => reader.hasById(sessionID, messageID),
|
|
3165
|
+
readMessageOrdinalById: (messageID) => reader.ordinalOf(sessionID, messageID),
|
|
3166
|
+
readMessageIdOrdinalsForRange: (fromOrdinal, toOrdinal) => reader.ordinalMapForRange(sessionID, fromOrdinal, toOrdinal),
|
|
3167
|
+
readMessageOrdinalPage: (after, limit) => reader.readOrdinalPage(sessionID, after, limit),
|
|
3168
|
+
getMessageCount: () => reader.getCount(sessionID),
|
|
3169
|
+
getStoredMessageCount: () => reader.getStoredCount(sessionID),
|
|
3170
|
+
...reader.servedBoundaryIdOf ? {
|
|
3171
|
+
readServedBoundaryId: (messageID) => reader.servedBoundaryIdOf?.(sessionID, messageID) ?? null
|
|
3172
|
+
} : {}
|
|
3173
|
+
};
|
|
3174
|
+
}
|
|
3175
|
+
|
|
3176
|
+
// src/v2/hooks/tools.ts
|
|
3177
|
+
import { tool } from "@opencode-ai/plugin";
|
|
3178
|
+
async function registerTools(context, db, config, rustToolBackends, isInternalChild) {
|
|
3179
|
+
const compaction = isCompactionEnabled(config);
|
|
3180
|
+
setCtxReduceRegisteredGlobally(compaction);
|
|
3181
|
+
const project = {
|
|
3182
|
+
db,
|
|
3183
|
+
resolveProjectPath: (directory) => resolveProjectIdentityForSession(directory, config.allow_home_project),
|
|
3184
|
+
ensureProjectRegistered: ensureProjectRegisteredFromOpenCodeDirectory
|
|
3185
|
+
};
|
|
3186
|
+
const definitions = {
|
|
3187
|
+
...compaction ? createCtxReduceTools({
|
|
3188
|
+
db,
|
|
3189
|
+
protectedTools: config.protected_tools,
|
|
3190
|
+
getProtectionWindow: (sessionID) => getProtectionWindowForSession(db, sessionID, getObservedEpochFloor(db, sessionID)),
|
|
3191
|
+
...rustToolBackends ? { rustToolBackends } : {}
|
|
3192
|
+
}) : {},
|
|
3193
|
+
...createCtxExpandTools({ db, expandTools: config.historian?.expand_tools }),
|
|
3194
|
+
...createCtxNoteTools({
|
|
3195
|
+
...project,
|
|
3196
|
+
dreamerEnabled: isDreamerRunnable(config),
|
|
3197
|
+
...rustToolBackends ? { rustToolBackends } : {}
|
|
3198
|
+
}),
|
|
3199
|
+
...createCtxSearchTools(project),
|
|
3200
|
+
...config.memory.enabled ? {
|
|
3201
|
+
...createCtxMemoryTools({
|
|
3202
|
+
...project,
|
|
3203
|
+
...rustToolBackends ? { rustToolBackends } : {}
|
|
3204
|
+
}),
|
|
3205
|
+
...createCtxMemoryListTools({
|
|
3206
|
+
...project,
|
|
3207
|
+
...rustToolBackends ? { rustToolBackends } : {}
|
|
3208
|
+
})
|
|
3209
|
+
} : {}
|
|
3210
|
+
};
|
|
3211
|
+
const controller = new AbortController;
|
|
3212
|
+
await context.tool.transform?.((editor) => {
|
|
3213
|
+
for (const [name, definition] of Object.entries(definitions)) {
|
|
3214
|
+
editor.add?.({
|
|
3215
|
+
name,
|
|
3216
|
+
description: definition.description,
|
|
3217
|
+
input: tool.schema.toJSONSchema(tool.schema.object(definition.args)),
|
|
3218
|
+
options: { codemode: false },
|
|
3219
|
+
async execute(input, call) {
|
|
3220
|
+
const refusal = subagentToolRefusal(db, name, call.sessionID, isInternalChild);
|
|
3221
|
+
if (refusal)
|
|
3222
|
+
return { content: refusal };
|
|
3223
|
+
if (name === "ctx_memory" || name === "ctx_note" || name === "ctx_reduce")
|
|
3224
|
+
await withAsyncPrivilegedWriter(db, () => {
|
|
3225
|
+
return;
|
|
3226
|
+
});
|
|
3227
|
+
const parsedInput = tool.schema.object(definition.args).passthrough().safeParse(input);
|
|
3228
|
+
const result = await definition.execute(parsedInput.success ? parsedInput.data : input, {
|
|
3229
|
+
sessionID: call.sessionID,
|
|
3230
|
+
messageID: call.messageID,
|
|
3231
|
+
agent: call.agent,
|
|
3232
|
+
directory: context.location.directory,
|
|
3233
|
+
worktree: context.location.directory,
|
|
3234
|
+
abort: controller.signal,
|
|
3235
|
+
metadata: (value) => {
|
|
3236
|
+
call.progress(value);
|
|
3237
|
+
},
|
|
3238
|
+
ask: async () => {
|
|
3239
|
+
throw new Error("This tool requires an unavailable permission request");
|
|
3240
|
+
}
|
|
3241
|
+
});
|
|
3242
|
+
return { content: typeof result === "string" ? result : result.output };
|
|
3243
|
+
}
|
|
3244
|
+
});
|
|
3245
|
+
}
|
|
3246
|
+
});
|
|
3247
|
+
return { dispose: () => controller.abort() };
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3250
|
+
// src/v2/hooks/usage-reading.ts
|
|
3251
|
+
function usageReadingMatchesDraft(reading, draftModel) {
|
|
3252
|
+
return reading.modelKey === undefined || reading.modelKey === `${draftModel.providerID}/${draftModel.id}`;
|
|
3253
|
+
}
|
|
3254
|
+
function resolveUsageReading(input) {
|
|
3255
|
+
const { tokens } = input;
|
|
3256
|
+
const successfulCompletion = isSuccessfulProviderCompletion({
|
|
3257
|
+
completedAt: input.completed,
|
|
3258
|
+
finish: input.finish,
|
|
3259
|
+
error: input.error
|
|
3260
|
+
});
|
|
3261
|
+
if (!tokens && !successfulCompletion)
|
|
3262
|
+
return;
|
|
3263
|
+
const numeric = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
3264
|
+
const rowProviderID = typeof input.rowModel?.providerID === "string" ? input.rowModel.providerID : undefined;
|
|
3265
|
+
const rowModelID = typeof input.rowModel?.id === "string" ? input.rowModel.id : undefined;
|
|
3266
|
+
const measuredProviderID = rowProviderID ?? input.draftModel.providerID;
|
|
3267
|
+
const measuredModelID = rowModelID ?? input.draftModel.id;
|
|
3268
|
+
const inputTokens = numeric(tokens?.input) + numeric(tokens?.cache?.read) + numeric(tokens?.cache?.write);
|
|
3269
|
+
const limit = input.limitFor(measuredProviderID, measuredModelID);
|
|
3270
|
+
if ((!Number.isFinite(limit) || limit <= 0) && !successfulCompletion)
|
|
3271
|
+
return;
|
|
3272
|
+
const sameModel = measuredProviderID === input.draftModel.providerID && measuredModelID === input.draftModel.id;
|
|
3273
|
+
const draftLimit = sameModel ? limit : input.limitFor(input.draftModel.providerID, input.draftModel.id);
|
|
3274
|
+
return {
|
|
3275
|
+
inputTokens,
|
|
3276
|
+
limit,
|
|
3277
|
+
admissionLimit: Number.isFinite(draftLimit) && draftLimit > 0 ? draftLimit : limit,
|
|
3278
|
+
...rowProviderID !== undefined && rowModelID !== undefined ? { modelKey: `${measuredProviderID}/${measuredModelID}` } : {},
|
|
3279
|
+
...typeof input.completed === "number" && Number.isFinite(input.completed) ? { completed: input.completed } : {},
|
|
3280
|
+
...input.finish !== undefined ? { finish: input.finish } : {},
|
|
3281
|
+
...input.error !== undefined ? { error: input.error } : {}
|
|
3282
|
+
};
|
|
3283
|
+
}
|
|
3284
|
+
|
|
3285
|
+
// src/v2/hooks/usage-persist.ts
|
|
3286
|
+
function persistV2UsageReading(args) {
|
|
3287
|
+
const { db, sessionID, draftModel, reading } = args;
|
|
3288
|
+
const currentMeta = getOrCreateSessionMeta(db, sessionID);
|
|
3289
|
+
if (reading.completed !== undefined && currentMeta.lastResponseTime > 0 && reading.completed <= currentMeta.lastResponseTime) {
|
|
3290
|
+
sessionLog(sessionID, `v2 usage: skipped stale reading completed=${reading.completed} <= lastResponseTime=${currentMeta.lastResponseTime}`);
|
|
3291
|
+
return;
|
|
3292
|
+
}
|
|
3293
|
+
const draftModelKey = `${draftModel.providerID}/${draftModel.id}`;
|
|
3294
|
+
const readingMatchesDraft = usageReadingMatchesDraft(reading, draftModel);
|
|
3295
|
+
const completion = {
|
|
3296
|
+
completedAt: reading.completed,
|
|
3297
|
+
finish: reading.finish,
|
|
3298
|
+
error: reading.error
|
|
3299
|
+
};
|
|
3300
|
+
const served = !providerResponseFailed(completion) && (reading.inputTokens > 0 || isSuccessfulProviderCompletion(completion));
|
|
3301
|
+
const responseTime = served && reading.completed;
|
|
3302
|
+
if (reading.inputTokens <= 0 || !Number.isFinite(reading.limit) || reading.limit <= 0) {
|
|
3303
|
+
if (responseTime)
|
|
3304
|
+
updateSessionMeta(db, sessionID, { lastResponseTime: responseTime });
|
|
3305
|
+
return;
|
|
3306
|
+
}
|
|
3307
|
+
const percentage = reading.inputTokens / reading.limit * 100;
|
|
3308
|
+
const updates = {
|
|
3309
|
+
lastContextPercentage: percentage,
|
|
3310
|
+
lastInputTokens: reading.inputTokens,
|
|
3311
|
+
lastUsageContextLimit: reading.limit,
|
|
3312
|
+
lastObservedModelKey: reading.modelKey ?? draftModelKey
|
|
3313
|
+
};
|
|
3314
|
+
if (responseTime)
|
|
3315
|
+
updates.lastResponseTime = responseTime;
|
|
3316
|
+
updateSessionMeta(db, sessionID, updates);
|
|
3317
|
+
sessionLog(sessionID, `v2 usage: inputTokens=${reading.inputTokens} contextLimit=${reading.limit} percentage=${percentage} responseModel=${reading.modelKey ?? "legacy"} draftContextLimit=${reading.admissionLimit} pressure=${readingMatchesDraft ? "current" : "stale-model-ignored"}`);
|
|
3318
|
+
if (readingMatchesDraft) {
|
|
3319
|
+
args.contextUsageMap.set(sessionID, {
|
|
3320
|
+
usage: { inputTokens: reading.inputTokens, percentage },
|
|
3321
|
+
hasUsageTokens: true,
|
|
3322
|
+
updatedAt: Date.now()
|
|
3323
|
+
});
|
|
3324
|
+
} else {
|
|
3325
|
+
args.contextUsageMap.delete(sessionID);
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
|
|
3329
|
+
// src/v2/hooks/context.ts
|
|
3330
|
+
var HIDDEN_SESSION_ERROR_GRACE_MS = 50;
|
|
3331
|
+
function isBlockingV2TransformError(error) {
|
|
3332
|
+
return error instanceof EmergencyFailClosedError || error instanceof UnresolvedHistoryBoundaryError || error instanceof UnmanagedOverWindowError || error instanceof DegradedPassRefusalError || isFailClosedBlockingError(error);
|
|
3333
|
+
}
|
|
3334
|
+
function checkHistoryBoundary(db, reader, sessionID) {
|
|
3335
|
+
try {
|
|
3336
|
+
const storeHasSession = reader.earliestSequence(sessionID) !== undefined;
|
|
3337
|
+
return repairMissingHistoryBoundary({
|
|
3338
|
+
db,
|
|
3339
|
+
sessionId: sessionID,
|
|
3340
|
+
isInHostStore: (messageId) => storeHasSession ? reader.sequenceForId(sessionID, messageId) !== undefined : null
|
|
3341
|
+
});
|
|
3342
|
+
} catch (error) {
|
|
3343
|
+
sessionLog(sessionID, "history boundary check failed; continuing without repair", error);
|
|
3344
|
+
return;
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
async function cacheV2SessionDirectory(session, sessionID, directories) {
|
|
3348
|
+
if (directories.has(sessionID))
|
|
3349
|
+
return;
|
|
3350
|
+
try {
|
|
3351
|
+
const directory = (await session.get({ sessionID }))?.location?.directory;
|
|
3352
|
+
if (typeof directory === "string" && directory.length > 0) {
|
|
3353
|
+
directories.set(sessionID, directory);
|
|
3354
|
+
}
|
|
3355
|
+
} catch (error) {
|
|
3356
|
+
sessionLog(sessionID, "v2 session directory lookup failed; using the launch directory:", error);
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
function refuseBeforeProvider(session, sessionID, arm, cause) {
|
|
3360
|
+
const detail = cause instanceof Error ? `${cause.name}: ${cause.message}` : cause === undefined ? "" : String(cause);
|
|
3361
|
+
sessionLog(sessionID, `v2 refusal: interrupting the turn before the provider request arm=${arm}${detail ? ` cause=${JSON.stringify(detail.slice(0, 500))}` : ""}`);
|
|
3362
|
+
return interruptBeforeProvider(session, sessionID);
|
|
3363
|
+
}
|
|
3364
|
+
function createHostSeams(context, readAllForConversion, reconciliationSource, liveModels) {
|
|
3365
|
+
return {
|
|
3366
|
+
hostRawMessages: readAllForConversion,
|
|
3367
|
+
hostMessageReconciliationSource: reconciliationSource,
|
|
3368
|
+
hostProtectedTailBoundary: (args) => resolveOpenCodeProtectedTailBoundary({
|
|
3369
|
+
...args,
|
|
3370
|
+
cacheNamespace: `opencode2:${args.sessionId}`
|
|
3371
|
+
}),
|
|
3372
|
+
hostModelFallback: (sessionID) => liveModels.get(sessionID) ?? null,
|
|
3373
|
+
getModelKey: (sessionID) => {
|
|
3374
|
+
const model = liveModels.get(sessionID);
|
|
3375
|
+
return resolveModelKey(model?.providerID, model?.modelID);
|
|
3376
|
+
},
|
|
3377
|
+
hostRefusalNotice: async (_client, sessionID, message) => {
|
|
3378
|
+
pushNotification2("toast", { message, variant: "error" }, sessionID);
|
|
3379
|
+
await deliverSynthetic(context, sessionID, message);
|
|
3380
|
+
},
|
|
3381
|
+
hostRefuse: (_client, sessionID) => refuseBeforeProvider(context.session, sessionID, "transform-fail-closed")
|
|
3382
|
+
};
|
|
3383
|
+
}
|
|
3384
|
+
function createV2ThresholdDeps(config) {
|
|
3385
|
+
return {
|
|
3386
|
+
scheduler: createScheduler({
|
|
3387
|
+
executeThresholdPercentage: config.execute_threshold_percentage,
|
|
3388
|
+
executeThresholdTokens: config.execute_threshold_tokens
|
|
3389
|
+
}),
|
|
3390
|
+
executeThresholdPercentage: config.execute_threshold_percentage,
|
|
3391
|
+
executeThresholdTokens: config.execute_threshold_tokens
|
|
3392
|
+
};
|
|
3393
|
+
}
|
|
3394
|
+
function toolResultText2(result) {
|
|
3395
|
+
const content = result?.content ?? result?.output;
|
|
3396
|
+
if (typeof content === "string")
|
|
3397
|
+
return content;
|
|
3398
|
+
if (content && typeof content === "object" && !Array.isArray(content)) {
|
|
3399
|
+
const record = content;
|
|
3400
|
+
if (typeof record.text === "string")
|
|
3401
|
+
return record.text;
|
|
3402
|
+
if (typeof record.value === "string")
|
|
3403
|
+
return record.value;
|
|
3404
|
+
return "";
|
|
3405
|
+
}
|
|
3406
|
+
if (!Array.isArray(content))
|
|
3407
|
+
return "";
|
|
3408
|
+
return content.map((part) => {
|
|
3409
|
+
if (typeof part === "string")
|
|
3410
|
+
return part;
|
|
3411
|
+
if (!part || typeof part !== "object")
|
|
3412
|
+
return "";
|
|
3413
|
+
const record = part;
|
|
3414
|
+
if (typeof record.text === "string")
|
|
3415
|
+
return record.text;
|
|
3416
|
+
if (typeof record.value === "string")
|
|
3417
|
+
return record.value;
|
|
3418
|
+
return "";
|
|
3419
|
+
}).filter(Boolean).join(`
|
|
3420
|
+
`);
|
|
3421
|
+
}
|
|
3422
|
+
function reportPreProviderRefusal(sessionID, error) {
|
|
3423
|
+
sessionLog(sessionID, `v2 refusing this turn before the model call: the context could not be read: ${getErrorMessage(error)}`);
|
|
3424
|
+
}
|
|
3425
|
+
function catalogModels(listed, draftModel) {
|
|
3426
|
+
const rows = Array.isArray(listed) ? listed : listed && typeof listed === "object" && Array.isArray(listed.data) ? listed.data : [];
|
|
3427
|
+
const models = rows.flatMap((row) => {
|
|
3428
|
+
if (!row || typeof row !== "object")
|
|
3429
|
+
return [];
|
|
3430
|
+
const model = row;
|
|
3431
|
+
if (typeof model.id !== "string" || typeof model.providerID !== "string")
|
|
3432
|
+
return [];
|
|
3433
|
+
const contextLimit = model.limit?.context;
|
|
3434
|
+
if (typeof contextLimit !== "number" || !Number.isFinite(contextLimit))
|
|
3435
|
+
return [];
|
|
3436
|
+
return [
|
|
3437
|
+
{
|
|
3438
|
+
id: model.id,
|
|
3439
|
+
providerID: model.providerID,
|
|
3440
|
+
limit: { ...model.limit, context: contextLimit }
|
|
3441
|
+
}
|
|
3442
|
+
];
|
|
3443
|
+
});
|
|
3444
|
+
const draftContextLimit = draftModel?.limit?.context;
|
|
3445
|
+
if (draftModel && typeof draftContextLimit === "number" && Number.isFinite(draftContextLimit) && draftContextLimit > 0) {
|
|
3446
|
+
const key = `${draftModel.providerID}/${draftModel.id}`;
|
|
3447
|
+
const byKey = new Map(models.map((model) => [`${model.providerID}/${model.id}`, model]));
|
|
3448
|
+
byKey.set(key, {
|
|
3449
|
+
id: draftModel.id,
|
|
3450
|
+
providerID: draftModel.providerID,
|
|
3451
|
+
limit: { ...draftModel.limit, context: draftContextLimit }
|
|
3452
|
+
});
|
|
3453
|
+
return [...byKey.values()];
|
|
3454
|
+
}
|
|
3455
|
+
return models;
|
|
3456
|
+
}
|
|
3457
|
+
function removeDreamerOnlyTools(draft) {
|
|
3458
|
+
if (draft.tools)
|
|
3459
|
+
delete draft.tools.ctx_memory_list;
|
|
3460
|
+
}
|
|
3461
|
+
function applyV2PromptSurfaceTools(draft, runtime, config) {
|
|
3462
|
+
if (!draft.tools)
|
|
3463
|
+
return;
|
|
3464
|
+
const modelKey = `${draft.model.providerID}/${draft.model.id}`;
|
|
3465
|
+
const registration = runtime.resolveRegistration(config, modelKey);
|
|
3466
|
+
for (const id of ACTIVE_TOOL_IDS) {
|
|
3467
|
+
const tool = draft.tools[id];
|
|
3468
|
+
if (!tool)
|
|
3469
|
+
continue;
|
|
3470
|
+
tool.description = registration.descriptionFor(id, tool.description);
|
|
3471
|
+
applyJsonSchemaParameterDescriptions(id, tool.input, registration.preset);
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
function recordV2ToolDefinitions(draft) {
|
|
3475
|
+
if (!draft.tools)
|
|
3476
|
+
return;
|
|
3477
|
+
for (const [id, tool] of Object.entries(draft.tools)) {
|
|
3478
|
+
if (!tool)
|
|
3479
|
+
continue;
|
|
3480
|
+
recordToolParameters(id, tool.input);
|
|
3481
|
+
recordToolDefinition(draft.model.providerID, draft.model.id, draft.agent, id, typeof tool.description === "string" ? tool.description : "", tool.input);
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
async function applyV2SystemPrompt(systemPrompt, draft) {
|
|
3485
|
+
resolveCtxReduceAvailabilityFromMessages(draft.sessionID, draft.messages.map((message) => ({
|
|
3486
|
+
info: {
|
|
3487
|
+
role: message.role,
|
|
3488
|
+
tools: { ctx_reduce: Object.hasOwn(draft.tools, "ctx_reduce") }
|
|
3489
|
+
}
|
|
3490
|
+
})));
|
|
3491
|
+
const system = { system: draft.system.map((part) => String(part.text ?? "")) };
|
|
3492
|
+
await systemPrompt.handler({
|
|
3493
|
+
sessionID: draft.sessionID,
|
|
3494
|
+
model: { providerID: draft.model.providerID, modelID: draft.model.id }
|
|
3495
|
+
}, system);
|
|
3496
|
+
const originals = [...draft.system];
|
|
3497
|
+
draft.system.splice(0, draft.system.length, ...system.system.map((text, index) => ({
|
|
3498
|
+
...originals[index],
|
|
3499
|
+
type: "text",
|
|
3500
|
+
text
|
|
3501
|
+
})));
|
|
3502
|
+
}
|
|
3503
|
+
async function registerContext(context) {
|
|
3504
|
+
const directory = context.location.directory;
|
|
3505
|
+
const config = loadPluginConfigDetailed(directory).config;
|
|
3506
|
+
setHomeProjectPermission(config.allow_home_project);
|
|
3507
|
+
if (!config.enabled)
|
|
3508
|
+
return;
|
|
3509
|
+
const nativeRemove = nativeSessionRemove(context.session);
|
|
3510
|
+
if (!hasRequiredSessionAPI(context.session) || !nativeRemove) {
|
|
3511
|
+
const message = OPENCODE2_SESSION_API_NOTICE;
|
|
3512
|
+
console.warn(`[magic-context] v2 host API unavailable: ${message}`);
|
|
3513
|
+
log(`[magic-context] ${message}`);
|
|
3514
|
+
let noticed = false;
|
|
3515
|
+
const refuseUnsupportedHost = async (draft) => {
|
|
3516
|
+
if (!noticed) {
|
|
3517
|
+
noticed = true;
|
|
3518
|
+
pushNotification2("toast", { message, variant: "error" }, draft.sessionID);
|
|
3519
|
+
context.session.wait({ sessionID: draft.sessionID }).then(() => deliverSynthetic(context, draft.sessionID, message)).catch((error) => sessionLog(draft.sessionID, "host API notice could not be delivered:", error));
|
|
3520
|
+
}
|
|
3521
|
+
await refuseBeforeProvider(context.session, draft.sessionID, "unsupported-session-api");
|
|
3522
|
+
};
|
|
3523
|
+
await context.session.hook("context", refuseUnsupportedHost);
|
|
3524
|
+
await context.session.hook("compaction", async (draft) => {
|
|
3525
|
+
draft.result = { summary: message };
|
|
3526
|
+
await refuseUnsupportedHost(draft);
|
|
3527
|
+
});
|
|
3528
|
+
return;
|
|
3529
|
+
}
|
|
3530
|
+
const liveConfigReader = pluginConfigReader(directory, config);
|
|
3531
|
+
const checkoutClaim = createSubcCheckoutClaimGate("opencode", () => config.subc?.connection_file ?? getDefaultSubcConnectionFile());
|
|
3532
|
+
const compactionOff = !isCompactionEnabled(config);
|
|
3533
|
+
const conflicts = detectConflicts(directory, {
|
|
3534
|
+
compactionEnabled: !compactionOff,
|
|
3535
|
+
hostGeneration: "v2"
|
|
3536
|
+
});
|
|
3537
|
+
if (conflicts.hasConflict) {
|
|
3538
|
+
console.warn(`[magic-context] v2 setup disabled by conflicting context hooks: ${conflicts.reasons.join("; ")}`);
|
|
3539
|
+
return;
|
|
3540
|
+
}
|
|
3541
|
+
const folds = new FoldOwner(context.storage);
|
|
3542
|
+
setOutputReserveConfig(config.output_reserve);
|
|
3543
|
+
const queriedModels = new Set;
|
|
3544
|
+
const rawLimits = new Map;
|
|
3545
|
+
const hiddenSessionErrors = new Map;
|
|
3546
|
+
const liveModels = new Map;
|
|
3547
|
+
const promptSurfaceRuntime = createPromptSurfaceRuntime({
|
|
3548
|
+
harness: "opencode2",
|
|
3549
|
+
directory,
|
|
3550
|
+
warn: (message) => log(`[magic-context] config warning: ${message}`)
|
|
3551
|
+
});
|
|
3552
|
+
const storage = createV2StorageGate({
|
|
3553
|
+
onUnavailable: (reason) => {
|
|
3554
|
+
const message = formatFailClosedBlockingMessage(reason);
|
|
3555
|
+
console.warn(`[magic-context] v2 storage unavailable: ${message}`);
|
|
3556
|
+
log(`[magic-context] v2 storage unavailable: ${message}`);
|
|
3557
|
+
}
|
|
3558
|
+
});
|
|
3559
|
+
let db = await probeV2StorageAtBoot(storage);
|
|
3560
|
+
const storageOpenedAtBoot = db !== undefined;
|
|
3561
|
+
let legacyCleanupStarted = false;
|
|
3562
|
+
const cleanupLegacyOnce = async (database) => {
|
|
3563
|
+
if (legacyCleanupStarted)
|
|
3564
|
+
return;
|
|
3565
|
+
legacyCleanupStarted = true;
|
|
3566
|
+
await cleanupLegacyHiddenChildren(database, nativeRemove, log);
|
|
3567
|
+
};
|
|
3568
|
+
if (db && isDatabasePersisted(db))
|
|
3569
|
+
await cleanupLegacyOnce(db);
|
|
3570
|
+
let storageRecoveryAnnounced = false;
|
|
3571
|
+
const storageNoticeBySession = new Map;
|
|
3572
|
+
const storeStorageNotice = (sessionID, text, what) => {
|
|
3573
|
+
withoutSqliteTransformPass(() => context.session.wait({ sessionID }).then(() => deliverSynthetic(context, sessionID, text)).catch((error) => sessionLog(sessionID, `v2 storage ${what} notice could not be delivered:`, error)));
|
|
3574
|
+
};
|
|
3575
|
+
const noticeStorageRefusal = (sessionID, reason) => {
|
|
3576
|
+
const message = formatStorageRefusalNotice(reason);
|
|
3577
|
+
pushNotification2("toast", { message, variant: "error" }, sessionID);
|
|
3578
|
+
if (storageNoticeBySession.get(sessionID) === message)
|
|
3579
|
+
return;
|
|
3580
|
+
storageNoticeBySession.set(sessionID, message);
|
|
3581
|
+
storeStorageNotice(sessionID, message, "refusal");
|
|
3582
|
+
};
|
|
3583
|
+
const rustModeModuleClient = resolveV2RustModeModuleClient(config, directory);
|
|
3584
|
+
const rustMemorySyncRequestedSessions = new Set;
|
|
3585
|
+
const moduleToolBackends = db && isDatabasePersisted(db) ? createModuleToolBackends({
|
|
3586
|
+
db,
|
|
3587
|
+
moduleClient: rustModeModuleClient,
|
|
3588
|
+
directory,
|
|
3589
|
+
memorySyncRequestedSessions: rustMemorySyncRequestedSessions
|
|
3590
|
+
}) : undefined;
|
|
3591
|
+
const hiddenChildHook = new HiddenChildHook;
|
|
3592
|
+
const tools = db && isDatabasePersisted(db) ? await registerTools(context, db, config, moduleToolBackends?.backends, (id) => hiddenChildHook.owns(id)) : undefined;
|
|
3593
|
+
const usage = new Map;
|
|
3594
|
+
const generateReplay = new V2GenerateReplay;
|
|
3595
|
+
await context.session.hook("http.response", async (draft) => {
|
|
3596
|
+
if (!db || draft.kind !== "primary" || draft.response.ok)
|
|
3597
|
+
return;
|
|
3598
|
+
const detection = detectOverflow(await draft.response.clone().text());
|
|
3599
|
+
if (!detection.isOverflow)
|
|
3600
|
+
return;
|
|
3601
|
+
const modelKey = `${draft.model.providerID}/${draft.model.id}`;
|
|
3602
|
+
if (compactionOff) {
|
|
3603
|
+
if (detection.reportedLimit)
|
|
3604
|
+
recordDetectedContextLimit(db, draft.sessionID, detection.reportedLimit, modelKey, detection.reportedLimitProvenance);
|
|
3605
|
+
} else {
|
|
3606
|
+
recordOverflowDetected(db, draft.sessionID, detection.reportedLimit, modelKey, "provider_overflow", detection.reportedLimitProvenance, detection.reportedInputTokens);
|
|
3607
|
+
if (detection.reportedInputTokens) {
|
|
3608
|
+
const provenLimit = getOverflowState(db, draft.sessionID, modelKey).detectedContextLimit;
|
|
3609
|
+
usage.set(draft.sessionID, {
|
|
3610
|
+
usage: {
|
|
3611
|
+
inputTokens: detection.reportedInputTokens,
|
|
3612
|
+
percentage: provenLimit > 0 ? detection.reportedInputTokens / provenLimit * 100 : 100
|
|
3613
|
+
},
|
|
3614
|
+
hasUsageTokens: true,
|
|
3615
|
+
updatedAt: Date.now()
|
|
3616
|
+
});
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
});
|
|
3620
|
+
await registerHiddenChildAgents(context.agent);
|
|
3621
|
+
let hiddenAgentsReady;
|
|
3622
|
+
const readerPool = new V2StoreReaderPool;
|
|
3623
|
+
const openStoreReader = () => readerPool.open(gaDatabasePath(getDataDir(), process.env.OPENCODE_CHANNEL ?? "latest"));
|
|
3624
|
+
const createHiddenExecutor = async (database) => {
|
|
3625
|
+
await cleanupLegacyOnce(database);
|
|
3626
|
+
return createV2HiddenCompletionExecutor({
|
|
3627
|
+
...context.session,
|
|
3628
|
+
get: async (input) => {
|
|
3629
|
+
const session = await context.session.get(input);
|
|
3630
|
+
const error = hiddenSessionErrors.get(input.sessionID);
|
|
3631
|
+
return error === undefined ? session : { ...session, error };
|
|
3632
|
+
},
|
|
3633
|
+
terminalError: async (input) => {
|
|
3634
|
+
const deadline = Date.now() + HIDDEN_SESSION_ERROR_GRACE_MS;
|
|
3635
|
+
do {
|
|
3636
|
+
const error = hiddenSessionErrors.get(input.sessionID);
|
|
3637
|
+
if (error !== undefined)
|
|
3638
|
+
return error;
|
|
3639
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
3640
|
+
} while (Date.now() < deadline);
|
|
3641
|
+
return;
|
|
3642
|
+
},
|
|
3643
|
+
prompt: async (input) => {
|
|
3644
|
+
hiddenSessionErrors.delete(input.sessionID);
|
|
3645
|
+
return context.session.prompt(input);
|
|
3646
|
+
},
|
|
3647
|
+
removeSession: nativeRemove
|
|
3648
|
+
}, {
|
|
3649
|
+
db: database,
|
|
3650
|
+
projectIdentity: resolveProjectIdentityForSession(directory, config.allow_home_project) ?? "",
|
|
3651
|
+
directory,
|
|
3652
|
+
hook: hiddenChildHook,
|
|
3653
|
+
keepSubagents: config.keep_subagents === true,
|
|
3654
|
+
ensureAgent: () => hiddenAgentsReady ??= context.agent.reload(),
|
|
3655
|
+
modelCatalog: () => Promise.resolve(context.model.list()),
|
|
3656
|
+
openReader: openStoreReader
|
|
3657
|
+
});
|
|
3658
|
+
};
|
|
3659
|
+
const dreamerAtBoot = config.dreamer;
|
|
3660
|
+
const startDreamer = (executor) => {
|
|
3661
|
+
const projectIdentity = resolveProjectIdentityForSession(directory, config.allow_home_project);
|
|
3662
|
+
if (!projectIdentity || !dreamerAtBoot || dreamerAtBoot.disable)
|
|
3663
|
+
return;
|
|
3664
|
+
const cappedExecutor = withLiveDreamerOutputCap(executor, config, () => liveConfigReader.poll().effective);
|
|
3665
|
+
const trigger = startDreamTrigger(context, {
|
|
3666
|
+
config: dreamerAtBoot,
|
|
3667
|
+
sample: () => {
|
|
3668
|
+
const current = dreamerRunConfig(config, liveConfigReader.poll().effective);
|
|
3669
|
+
return { config: current.dreamer ?? dreamerAtBoot, mural: current.mural };
|
|
3670
|
+
},
|
|
3671
|
+
executor: cappedExecutor,
|
|
3672
|
+
projectIdentity: () => resolveProjectIdentityForSession(directory, config.allow_home_project) ?? "",
|
|
3673
|
+
projectMemoryEnabled: config.memory.enabled,
|
|
3674
|
+
language: config.language,
|
|
3675
|
+
mural: config.mural
|
|
3676
|
+
});
|
|
3677
|
+
const timer = registerV2DreamScheduleTimer({
|
|
3678
|
+
directory,
|
|
3679
|
+
projectIdentity,
|
|
3680
|
+
config,
|
|
3681
|
+
dreamer: dreamerAtBoot,
|
|
3682
|
+
liveConfig: () => liveConfigReader.poll().effective,
|
|
3683
|
+
executor: cappedExecutor,
|
|
3684
|
+
openReader: openStoreReader
|
|
3685
|
+
});
|
|
3686
|
+
return {
|
|
3687
|
+
async dispose() {
|
|
3688
|
+
await Promise.all([trigger.dispose(), timer.dispose()]);
|
|
3689
|
+
}
|
|
3690
|
+
};
|
|
3691
|
+
};
|
|
3692
|
+
let hiddenCompletionExecutor = db && isDatabasePersisted(db) && resolveProjectIdentityForSession(directory, config.allow_home_project) ? await createHiddenExecutor(db) : undefined;
|
|
3693
|
+
let dreamTrigger = hiddenCompletionExecutor ? startDreamer(hiddenCompletionExecutor) : undefined;
|
|
3694
|
+
const lateHiddenExecutor = createLateHiddenExecutor(() => hiddenCompletionExecutor);
|
|
3695
|
+
let hiddenWorkRecovery;
|
|
3696
|
+
const recoverHiddenWork = (database) => !resolveProjectIdentityForSession(directory, config.allow_home_project) ? Promise.resolve(false) : hiddenWorkRecovery ??= (async () => {
|
|
3697
|
+
try {
|
|
3698
|
+
hiddenCompletionExecutor ??= await createHiddenExecutor(database);
|
|
3699
|
+
dreamTrigger ??= startDreamer(hiddenCompletionExecutor);
|
|
3700
|
+
return true;
|
|
3701
|
+
} catch (error) {
|
|
3702
|
+
log("[magic-context] v2 historian and dreamer could not start after recovery:", error);
|
|
3703
|
+
return false;
|
|
3704
|
+
}
|
|
3705
|
+
})();
|
|
3706
|
+
const sampleHistorian = () => {
|
|
3707
|
+
const fresh = historianRunConfig(config, liveConfigReader.poll().effective);
|
|
3708
|
+
const models = resolveHistorianModel2(fresh, "opencode");
|
|
3709
|
+
return {
|
|
3710
|
+
model: models.primary,
|
|
3711
|
+
fallbackModels: models.fallbacks,
|
|
3712
|
+
contextLimit: resolveKnownHistorianContextLimit2(models.primary?.model),
|
|
3713
|
+
maxOutputTokens: fresh.historian?.maxTokens,
|
|
3714
|
+
timeoutMs: fresh.historian_timeout_ms,
|
|
3715
|
+
twoPass: fresh.historian?.two_pass === true,
|
|
3716
|
+
expandTools: fresh.historian?.expand_tools,
|
|
3717
|
+
autoPromote: fresh.memory?.auto_promote ?? true,
|
|
3718
|
+
userMemoriesEnabled: userMemoryCollectionEnabled2(fresh.dreamer),
|
|
3719
|
+
commitClusterTrigger: fresh.commit_cluster_trigger,
|
|
3720
|
+
chunkTokens: deriveHistorianChunkTokens2(resolveHistorianContextLimit2(models.primary?.model))
|
|
3721
|
+
};
|
|
3722
|
+
};
|
|
3723
|
+
const historianModels = resolveHistorianModel2(config, "opencode");
|
|
3724
|
+
const channel1 = new Map;
|
|
3725
|
+
const variants = new Map;
|
|
3726
|
+
const agents = new Map;
|
|
3727
|
+
const sessionDirectories = new Map;
|
|
3728
|
+
const historyRefreshSessions = new Set;
|
|
3729
|
+
const pendingMaterializationSessions = new Set;
|
|
3730
|
+
const lastHeuristicsTurnId = new Map;
|
|
3731
|
+
const restoredRows = new RestoredRowCache;
|
|
3732
|
+
const rawProviders = new Map;
|
|
3733
|
+
let passDuties;
|
|
3734
|
+
let toolDuties;
|
|
3735
|
+
const droppedInputGuard = createDroppedInputGuard();
|
|
3736
|
+
await context.tool.hook("execute.before", (draft) => assertExecutableToolInput(droppedInputGuard, {
|
|
3737
|
+
sessionID: draft.sessionID,
|
|
3738
|
+
toolName: draft.tool,
|
|
3739
|
+
input: draft.input
|
|
3740
|
+
}));
|
|
3741
|
+
await context.tool.hook("execute.after", async (draft) => {
|
|
3742
|
+
if (!db)
|
|
3743
|
+
return;
|
|
3744
|
+
if (draft.status && draft.status !== "completed")
|
|
3745
|
+
return;
|
|
3746
|
+
try {
|
|
3747
|
+
toolDuties ??= createToolExecuteAfterHook({ db, channel1StateBySession: channel1 });
|
|
3748
|
+
const text = toolResultText2(draft.result);
|
|
3749
|
+
const output = { output: text };
|
|
3750
|
+
await toolDuties({ ...draft, args: draft.input }, output);
|
|
3751
|
+
if (draft.result && output.output !== text) {
|
|
3752
|
+
const content = draft.result.content;
|
|
3753
|
+
if (typeof content === "string")
|
|
3754
|
+
draft.result.content = output.output;
|
|
3755
|
+
else if (Array.isArray(content) && output.output.startsWith(text))
|
|
3756
|
+
content.push({ type: "text", text: output.output.slice(text.length) });
|
|
3757
|
+
}
|
|
3758
|
+
const baseline = channel1.get(draft.sessionID);
|
|
3759
|
+
await deliverPendingChannel2(context, db, draft.sessionID, baseline);
|
|
3760
|
+
} catch (error) {
|
|
3761
|
+
log("[magic-context] v2 Channel 2 delivery deferred", error);
|
|
3762
|
+
}
|
|
3763
|
+
});
|
|
3764
|
+
const pagedRead = createV2RawMessageReader(openStoreReader);
|
|
3765
|
+
const prepareHistorySession = (sessionID) => {
|
|
3766
|
+
if (!rawProviders.has(sessionID))
|
|
3767
|
+
rawProviders.set(sessionID, setBoundedRawMessageProvider2(sessionID, createV2RawMessageProvider(pagedRead, sessionID)));
|
|
3768
|
+
};
|
|
3769
|
+
if (db && isDatabasePersisted(db)) {
|
|
3770
|
+
const backfillDb = db;
|
|
3771
|
+
scheduleAfterBootQuiet(() => {
|
|
3772
|
+
runV2SessionProjectBackfill(backfillDb, openStoreReader, config.allow_home_project).then(() => backfillSessionActivity(backfillDb, "opencode", (sessionId) => {
|
|
3773
|
+
const reader = openStoreReader();
|
|
3774
|
+
try {
|
|
3775
|
+
return reader.latestMessageTime(sessionId);
|
|
3776
|
+
} finally {
|
|
3777
|
+
reader.close();
|
|
3778
|
+
}
|
|
3779
|
+
})).catch((error) => log("[session-project-backfill] OpenCode 2 backfill failed:", error));
|
|
3780
|
+
});
|
|
3781
|
+
}
|
|
3782
|
+
const readAllForConversion = (sessionID) => readAllV2RawMessagesForConversion(openStoreReader, sessionID);
|
|
3783
|
+
const readRowsFrom = (sessionID, messageID) => {
|
|
3784
|
+
const reader = openStoreReader();
|
|
3785
|
+
try {
|
|
3786
|
+
const seq = reader.sequenceForId(sessionID, messageID);
|
|
3787
|
+
if (seq === undefined)
|
|
3788
|
+
return [];
|
|
3789
|
+
return reader.range(sessionID, seq - 1, reader.latestSequence(sessionID));
|
|
3790
|
+
} finally {
|
|
3791
|
+
reader.close();
|
|
3792
|
+
}
|
|
3793
|
+
};
|
|
3794
|
+
const rustRefusalRecovery = rustModeModuleClient ? createV2RustRefusalRecovery({
|
|
3795
|
+
context,
|
|
3796
|
+
moduleClient: rustModeModuleClient,
|
|
3797
|
+
readRowsFrom
|
|
3798
|
+
}) : undefined;
|
|
3799
|
+
const lkgSystems = new V2LkgSystemReplay;
|
|
3800
|
+
let transform;
|
|
3801
|
+
let systemPrompt;
|
|
3802
|
+
const systemPromptRefreshSessions = new Set;
|
|
3803
|
+
const tagger = createTagger();
|
|
3804
|
+
const deletedSessions = new DeletedSessionTombstones;
|
|
3805
|
+
const forkSeedSettled = new Set;
|
|
3806
|
+
const inheritForkState = (database, sessionID) => {
|
|
3807
|
+
if (forkSeedSettled.has(sessionID))
|
|
3808
|
+
return;
|
|
3809
|
+
if (sessionHasMagicContextState(database, sessionID)) {
|
|
3810
|
+
forkSeedSettled.add(sessionID);
|
|
3811
|
+
return;
|
|
3812
|
+
}
|
|
3813
|
+
let reader;
|
|
3814
|
+
try {
|
|
3815
|
+
reader = openStoreReader();
|
|
3816
|
+
const outcome = seedV2ForkFromParent({
|
|
3817
|
+
db: database,
|
|
3818
|
+
store: reader,
|
|
3819
|
+
sessionId: sessionID
|
|
3820
|
+
});
|
|
3821
|
+
forkSeedSettled.add(sessionID);
|
|
3822
|
+
if (outcome.kind === "seeded") {
|
|
3823
|
+
const { result } = outcome;
|
|
3824
|
+
sessionLog(sessionID, `v2 fork inheritance: seeded from ${outcome.parentSessionID} through the fork boundary; paired rows=${outcome.pairedRows} compartments=${result.compartmentsCopied} tags=${result.tagsCopied} pending_ops=${result.pendingOpsCopied} facts=${result.factsCopied} notes=${result.notesCopied}`);
|
|
3825
|
+
} else if (outcome.kind === "parent-missing") {
|
|
3826
|
+
sessionLog(sessionID, `v2 fork inheritance: fork of ${outcome.parentSessionID} starts without inherited state (${outcome.reason})`);
|
|
3827
|
+
} else if (outcome.kind === "destination-not-empty") {
|
|
3828
|
+
sessionLog(sessionID, `v2 fork inheritance: fork of ${outcome.parentSessionID} already has state; not copied again`);
|
|
3829
|
+
}
|
|
3830
|
+
} catch (error) {
|
|
3831
|
+
if (isTransientSqliteError(error))
|
|
3832
|
+
throw error;
|
|
3833
|
+
forkSeedSettled.add(sessionID);
|
|
3834
|
+
sessionLog(sessionID, "v2 fork inheritance failed; continuing without it:", error);
|
|
3835
|
+
} finally {
|
|
3836
|
+
reader?.close();
|
|
3837
|
+
}
|
|
3838
|
+
};
|
|
3839
|
+
const latestLkgResponseIds = new BoundedSessionMap(1000);
|
|
3840
|
+
const recordUsage = async (draft) => {
|
|
3841
|
+
let unsafe = false;
|
|
3842
|
+
try {
|
|
3843
|
+
await storage.probe();
|
|
3844
|
+
db = storage.require();
|
|
3845
|
+
getOrCreateSessionMeta(db, draft.sessionID);
|
|
3846
|
+
const reader = openStoreReader();
|
|
3847
|
+
try {
|
|
3848
|
+
const latest = reader.latestAssistant(draft.sessionID);
|
|
3849
|
+
latestLkgResponseIds.set(draft.sessionID, latest?.id);
|
|
3850
|
+
if (latest)
|
|
3851
|
+
noteLkgProviderResponse({
|
|
3852
|
+
sessionId: draft.sessionID,
|
|
3853
|
+
responseId: latest.id,
|
|
3854
|
+
modelKey: latest.data.model?.providerID && latest.data.model?.id ? `${latest.data.model.providerID}/${latest.data.model.id}` : undefined,
|
|
3855
|
+
inputTokens: lkgProviderInputTotal(latest.data.tokens),
|
|
3856
|
+
completedAt: latest.data.time?.completed,
|
|
3857
|
+
createdAt: latest.data.time?.created ?? latest.time_created,
|
|
3858
|
+
finish: latest.data.finish,
|
|
3859
|
+
error: latest.data.error,
|
|
3860
|
+
v2: true
|
|
3861
|
+
});
|
|
3862
|
+
const draftModelKey = `${draft.model.providerID}/${draft.model.id}`;
|
|
3863
|
+
if (!queriedModels.has(draftModelKey)) {
|
|
3864
|
+
const catalog = await Promise.resolve(context.model.list());
|
|
3865
|
+
const providers = new Map;
|
|
3866
|
+
for (const model of catalogModels(catalog, draft.model)) {
|
|
3867
|
+
rawLimits.set(`${model.providerID}/${model.id}`, model.limit);
|
|
3868
|
+
const provider = providers.get(model.providerID) ?? {
|
|
3869
|
+
id: model.providerID,
|
|
3870
|
+
models: {}
|
|
3871
|
+
};
|
|
3872
|
+
provider.models[model.id] = { limit: model.limit };
|
|
3873
|
+
providers.set(model.providerID, provider);
|
|
3874
|
+
}
|
|
3875
|
+
await refreshModelLimitsFromApi({
|
|
3876
|
+
config: {
|
|
3877
|
+
providers: async () => ({
|
|
3878
|
+
data: { providers: [...providers.values()] }
|
|
3879
|
+
})
|
|
3880
|
+
}
|
|
3881
|
+
});
|
|
3882
|
+
queriedModels.add(draftModelKey);
|
|
3883
|
+
}
|
|
3884
|
+
const usageDb = db;
|
|
3885
|
+
const limitFor = (providerID, modelID) => {
|
|
3886
|
+
const modelKey = `${providerID}/${modelID}`;
|
|
3887
|
+
const rawLimit = rawLimits.get(modelKey);
|
|
3888
|
+
return rawLimit && !isSaneLimit(rawLimit.context) ? resolveLimit(rawLimit, providerID, modelID) ?? 0 : resolveContextLimit(providerID, modelID, {
|
|
3889
|
+
db: usageDb,
|
|
3890
|
+
sessionID: draft.sessionID
|
|
3891
|
+
});
|
|
3892
|
+
};
|
|
3893
|
+
const reading = resolveUsageReading({
|
|
3894
|
+
rowModel: latest?.data.model,
|
|
3895
|
+
draftModel: { providerID: draft.model.providerID, id: draft.model.id },
|
|
3896
|
+
tokens: latest?.data.tokens,
|
|
3897
|
+
completed: latest?.data.time?.completed ?? (latest?.data.finish ? latest.data.time?.streamed ?? latest.data.time?.created : undefined),
|
|
3898
|
+
finish: latest?.data.finish,
|
|
3899
|
+
error: latest?.data.error,
|
|
3900
|
+
limitFor
|
|
3901
|
+
});
|
|
3902
|
+
if (reading) {
|
|
3903
|
+
persistV2UsageReading({
|
|
3904
|
+
db: usageDb,
|
|
3905
|
+
sessionID: draft.sessionID,
|
|
3906
|
+
draftModel: draft.model,
|
|
3907
|
+
reading,
|
|
3908
|
+
contextUsageMap: usage
|
|
3909
|
+
});
|
|
3910
|
+
}
|
|
3911
|
+
} finally {
|
|
3912
|
+
reader.close();
|
|
3913
|
+
}
|
|
3914
|
+
} catch (error) {
|
|
3915
|
+
reportPreProviderRefusal(draft.sessionID, error);
|
|
3916
|
+
unsafe = true;
|
|
3917
|
+
}
|
|
3918
|
+
return unsafe;
|
|
3919
|
+
};
|
|
3920
|
+
const usageController = new AbortController;
|
|
3921
|
+
const usageDone = (async () => {
|
|
3922
|
+
try {
|
|
3923
|
+
for await (const value of context.event.subscribe({ signal: usageController.signal })) {
|
|
3924
|
+
if (usageController.signal.aborted)
|
|
3925
|
+
break;
|
|
3926
|
+
const event = value;
|
|
3927
|
+
if (!event.data?.sessionID)
|
|
3928
|
+
continue;
|
|
3929
|
+
const sessionID = event.data.sessionID;
|
|
3930
|
+
if (event.type === "session.error" || event.type === "session.execution.failed") {
|
|
3931
|
+
const error = hiddenTerminalError(event);
|
|
3932
|
+
if (error !== undefined)
|
|
3933
|
+
hiddenSessionErrors.set(sessionID, error);
|
|
3934
|
+
continue;
|
|
3935
|
+
}
|
|
3936
|
+
if (event.type === "session.deleted") {
|
|
3937
|
+
deletedSessions.add(sessionID);
|
|
3938
|
+
if (db) {
|
|
3939
|
+
markSessionCleanupPending(db, sessionID);
|
|
3940
|
+
clearSession(db, sessionID);
|
|
3941
|
+
}
|
|
3942
|
+
rawProviders.get(sessionID)?.();
|
|
3943
|
+
rawProviders.delete(sessionID);
|
|
3944
|
+
usage.delete(sessionID);
|
|
3945
|
+
hiddenSessionErrors.delete(sessionID);
|
|
3946
|
+
storageNoticeBySession.delete(sessionID);
|
|
3947
|
+
liveModels.delete(sessionID);
|
|
3948
|
+
variants.delete(sessionID);
|
|
3949
|
+
agents.delete(sessionID);
|
|
3950
|
+
channel1.delete(sessionID);
|
|
3951
|
+
historyRefreshSessions.delete(sessionID);
|
|
3952
|
+
pendingMaterializationSessions.delete(sessionID);
|
|
3953
|
+
lastHeuristicsTurnId.delete(sessionID);
|
|
3954
|
+
restoredRows.forget(sessionID);
|
|
3955
|
+
generateReplay.forget(sessionID);
|
|
3956
|
+
systemPromptRefreshSessions.delete(sessionID);
|
|
3957
|
+
systemPrompt?.clearSession(sessionID);
|
|
3958
|
+
tagger.cleanup(sessionID);
|
|
3959
|
+
continue;
|
|
3960
|
+
}
|
|
3961
|
+
if (event.type !== "session.execution.succeeded")
|
|
3962
|
+
continue;
|
|
3963
|
+
const model = liveModels.get(sessionID);
|
|
3964
|
+
if (model)
|
|
3965
|
+
await recordUsage({
|
|
3966
|
+
sessionID,
|
|
3967
|
+
model: { providerID: model.providerID, id: model.modelID }
|
|
3968
|
+
});
|
|
3969
|
+
}
|
|
3970
|
+
} catch (error) {
|
|
3971
|
+
if (!usageController.signal.aborted)
|
|
3972
|
+
log("[magic-context] v2 usage subscription failed", error);
|
|
3973
|
+
}
|
|
3974
|
+
})();
|
|
3975
|
+
const materialize = (draft) => {
|
|
3976
|
+
db = storage.require();
|
|
3977
|
+
const state = getOrCreateSessionMeta(db, draft.sessionID);
|
|
3978
|
+
return materializeM0({
|
|
3979
|
+
db,
|
|
3980
|
+
sessionId: draft.sessionID,
|
|
3981
|
+
state,
|
|
3982
|
+
projectPath: resolveProjectIdentityForSession(directory, config.allow_home_project) ?? "",
|
|
3983
|
+
projectDirectory: directory,
|
|
3984
|
+
memoryEnabled: config.memory.enabled && !!resolveProjectIdentityForSession(directory, config.allow_home_project),
|
|
3985
|
+
memoryInjectionBudgetTokens: config.memory.injection_budget_tokens,
|
|
3986
|
+
hardSignals: {
|
|
3987
|
+
systemHash: foldDigest(JSON.stringify(draft.system)),
|
|
3988
|
+
toolSetHash: getCurrentToolSetHash(draft.model.providerID, draft.model.id, draft.agent),
|
|
3989
|
+
modelKey: `${draft.model.providerID}/${draft.model.id}`,
|
|
3990
|
+
cacheExpired: false,
|
|
3991
|
+
lastResponseTime: state.lastResponseTime
|
|
3992
|
+
}
|
|
3993
|
+
}).m0Text;
|
|
3994
|
+
};
|
|
3995
|
+
await context.session.hook("compaction", async (draft) => {
|
|
3996
|
+
const hiddenSummary = hiddenChildHook.compactionSummary(draft.sessionID);
|
|
3997
|
+
if (hiddenSummary !== undefined) {
|
|
3998
|
+
draft.result = { summary: hiddenSummary };
|
|
3999
|
+
return;
|
|
4000
|
+
}
|
|
4001
|
+
if (compactionOff)
|
|
4002
|
+
return;
|
|
4003
|
+
const reader = openStoreReader();
|
|
4004
|
+
try {
|
|
4005
|
+
const watermark = reader.latestSequenceForIds(draft.sessionID, draft.messages.flatMap((message) => message.id ? [message.id] : []));
|
|
4006
|
+
const running = reader.latestRunningCompaction(draft.sessionID);
|
|
4007
|
+
const moduleBaseline = rustModeModuleClient ? servedModuleM0Text(draft.sessionID) : undefined;
|
|
4008
|
+
const source = !rustModeModuleClient ? "typescript" : moduleBaseline === null ? "typescript_fallback" : "module";
|
|
4009
|
+
const fold = await folds.supply({
|
|
4010
|
+
sessionID: draft.sessionID,
|
|
4011
|
+
watermark,
|
|
4012
|
+
runningCut: running?.seq,
|
|
4013
|
+
materialize: () => moduleBaseline ?? materialize(draft)
|
|
4014
|
+
});
|
|
4015
|
+
sessionLog(draft.sessionID, `v2 compaction hook: fired answered=true source=${source}`);
|
|
4016
|
+
draft.result = { summary: fold.submitted };
|
|
4017
|
+
} catch (cause) {
|
|
4018
|
+
await refuseBeforeProvider(context.session, draft.sessionID, "compaction-fold", cause);
|
|
4019
|
+
throw new V2ContextRefusal("Magic Context could not preserve the host checkpoint.", {
|
|
4020
|
+
cause
|
|
4021
|
+
});
|
|
4022
|
+
} finally {
|
|
4023
|
+
reader.close();
|
|
4024
|
+
}
|
|
4025
|
+
});
|
|
4026
|
+
const runManagedContext = async (draft) => {
|
|
4027
|
+
rememberHostMedia(draft.messages);
|
|
4028
|
+
if (hiddenChildHook.apply(draft))
|
|
4029
|
+
return;
|
|
4030
|
+
removeDreamerOnlyTools(draft);
|
|
4031
|
+
if (deletedSessions.has(draft.sessionID))
|
|
4032
|
+
return;
|
|
4033
|
+
const claimRefusal = await checkoutClaim.refusal(draft.sessionID, directory);
|
|
4034
|
+
if (claimRefusal) {
|
|
4035
|
+
pushNotification2("toast", { message: claimRefusal.message, variant: "error" }, draft.sessionID);
|
|
4036
|
+
await refuseBeforeProvider(context.session, draft.sessionID, "checkout-claim-held-elsewhere", claimRefusal);
|
|
4037
|
+
throw new V2ContextRefusal(claimRefusal.message, { cause: claimRefusal });
|
|
4038
|
+
}
|
|
4039
|
+
const systemAtEntry = structuredClone(draft.system);
|
|
4040
|
+
const slotAtEntry = getSlot(draft.sessionID);
|
|
4041
|
+
const restoreLkgSystem = () => {
|
|
4042
|
+
if (!lkgSystems.restore(draft.sessionID, getSlot(draft.sessionID), systemAtEntry, draft.system)) {
|
|
4043
|
+
sessionLog(draft.sessionID, "lkg_system_state_mismatch");
|
|
4044
|
+
throw new Error("LKG system identity is unavailable or changed");
|
|
4045
|
+
}
|
|
4046
|
+
};
|
|
4047
|
+
const isMagicContextSynthetic = (id) => isAdmittedSynthetic(context, draft.sessionID, id);
|
|
4048
|
+
const newest = draft.messages.at(-1);
|
|
4049
|
+
if (newest && hasStorageNoticeShape(newest) && await isMagicContextSynthetic(newest.id)) {
|
|
4050
|
+
await refuseBeforeProvider(context.session, draft.sessionID, "storage-notice-turn");
|
|
4051
|
+
return;
|
|
4052
|
+
}
|
|
4053
|
+
await dropStorageNotices(draft.messages, isMagicContextSynthetic);
|
|
4054
|
+
liveModels.set(draft.sessionID, {
|
|
4055
|
+
providerID: draft.model.providerID,
|
|
4056
|
+
modelID: draft.model.id
|
|
4057
|
+
});
|
|
4058
|
+
variants.set(draft.sessionID, draft.model.variant);
|
|
4059
|
+
if (!modelLimitCacheWarm())
|
|
4060
|
+
withoutSqliteTransformPass(() => warmModelLimitCacheFromCatalog(context));
|
|
4061
|
+
agents.set(draft.sessionID, draft.agent);
|
|
4062
|
+
applyV2PromptSurfaceTools(draft, promptSurfaceRuntime, config.prompt_surface);
|
|
4063
|
+
let postFold = false;
|
|
4064
|
+
try {
|
|
4065
|
+
const admissionDb = db ?? storage.current();
|
|
4066
|
+
if (!compactionOff && admissionDb)
|
|
4067
|
+
await withAsyncPrivilegedWriter(admissionDb, () => {
|
|
4068
|
+
return;
|
|
4069
|
+
});
|
|
4070
|
+
if (admissionDb)
|
|
4071
|
+
hideSubagentTools(draft, admissionDb);
|
|
4072
|
+
recordV2ToolDefinitions(draft);
|
|
4073
|
+
if (await recordUsage(draft) && !compactionOff) {
|
|
4074
|
+
const storageReason = storage.current() ? null : storage.reason();
|
|
4075
|
+
if (storageReason) {
|
|
4076
|
+
noticeStorageRefusal(draft.sessionID, storageReason);
|
|
4077
|
+
await refuseBeforeProvider(context.session, draft.sessionID, "storage-unavailable", formatFailClosedBlockingMessage(storageReason));
|
|
4078
|
+
return;
|
|
4079
|
+
}
|
|
4080
|
+
await refuseBeforeProvider(context.session, draft.sessionID, "usage-unavailable");
|
|
4081
|
+
return;
|
|
4082
|
+
}
|
|
4083
|
+
if (!db)
|
|
4084
|
+
return;
|
|
4085
|
+
if (!storageOpenedAtBoot && !storageRecoveryAnnounced) {
|
|
4086
|
+
storageRecoveryAnnounced = true;
|
|
4087
|
+
const recovered = await recoverHiddenWork(db);
|
|
4088
|
+
const message = formatStorageRecoveryNotice(recovered);
|
|
4089
|
+
log(`[magic-context] v2 storage recovered: ${message}`);
|
|
4090
|
+
pushNotification2("toast", { message, variant: "info" }, draft.sessionID);
|
|
4091
|
+
storeStorageNotice(draft.sessionID, message, "recovery");
|
|
4092
|
+
}
|
|
4093
|
+
if (!rustModeModuleClient && !compactionOff)
|
|
4094
|
+
inheritForkState(db, draft.sessionID);
|
|
4095
|
+
try {
|
|
4096
|
+
await rebaseSessionCoordinatesAsync({
|
|
4097
|
+
db,
|
|
4098
|
+
sessionId: draft.sessionID,
|
|
4099
|
+
generation: "v2",
|
|
4100
|
+
readMessages: readAllForConversion,
|
|
4101
|
+
readMessagePages: (sessionID) => readV2RawMessagePagesForConversion(openStoreReader, sessionID)
|
|
4102
|
+
});
|
|
4103
|
+
} catch (error) {
|
|
4104
|
+
sessionLog(draft.sessionID, "store projection rebase failed before system prompt (retrying in transform):", error);
|
|
4105
|
+
}
|
|
4106
|
+
systemPrompt ??= createSystemPromptHashHandler({
|
|
4107
|
+
db,
|
|
4108
|
+
dreamerEnabled: config.dreamer !== undefined && !config.dreamer.disable,
|
|
4109
|
+
memoryEnabled: config.memory.enabled && !!resolveProjectIdentityForSession(directory, config.allow_home_project),
|
|
4110
|
+
language: config.language,
|
|
4111
|
+
promptSurface: config.prompt_surface,
|
|
4112
|
+
promptSurfaceRuntime,
|
|
4113
|
+
systemPromptRefreshSessions,
|
|
4114
|
+
historyRefreshSessions,
|
|
4115
|
+
pendingMaterializationSessions,
|
|
4116
|
+
lastHeuristicsTurnId,
|
|
4117
|
+
injectionEnabled: config.system_prompt_injection.enabled,
|
|
4118
|
+
injectionSkipSignatures: config.system_prompt_injection.skip_signatures
|
|
4119
|
+
});
|
|
4120
|
+
await applyV2SystemPrompt(systemPrompt, draft);
|
|
4121
|
+
await preloadTokenizer();
|
|
4122
|
+
passDuties ??= createChatMessageHook({
|
|
4123
|
+
db,
|
|
4124
|
+
liveModelBySession: liveModels,
|
|
4125
|
+
variantBySession: variants,
|
|
4126
|
+
agentBySession: agents,
|
|
4127
|
+
historyRefreshSessions,
|
|
4128
|
+
pendingMaterializationSessions,
|
|
4129
|
+
lastHeuristicsTurnId,
|
|
4130
|
+
systemPromptRefreshSessions,
|
|
4131
|
+
cacheTtlConfig: config.cache_ttl
|
|
4132
|
+
});
|
|
4133
|
+
await passDuties({
|
|
4134
|
+
sessionID: draft.sessionID,
|
|
4135
|
+
agent: draft.agent,
|
|
4136
|
+
variant: draft.model.variant,
|
|
4137
|
+
model: { providerID: draft.model.providerID, modelID: draft.model.id }
|
|
4138
|
+
});
|
|
4139
|
+
await cacheV2SessionDirectory(context.session, draft.sessionID, sessionDirectories);
|
|
4140
|
+
prepareHistorySession(draft.sessionID);
|
|
4141
|
+
transform ??= createTransform({
|
|
4142
|
+
cacheTtlConfig: config.cache_ttl,
|
|
4143
|
+
cacheTtlConfigured: config.cacheTtlConfigured,
|
|
4144
|
+
sampleCacheTtlConfig: () => liveConfigReader.poll().effective,
|
|
4145
|
+
db,
|
|
4146
|
+
tagger,
|
|
4147
|
+
...createV2ThresholdDeps(config),
|
|
4148
|
+
contextUsageMap: usage,
|
|
4149
|
+
compactionOff,
|
|
4150
|
+
storeGeneration: "v2",
|
|
4151
|
+
hostCleanupCompactionMarkers: () => ({
|
|
4152
|
+
verified: true,
|
|
4153
|
+
removedLineages: 0,
|
|
4154
|
+
removedRows: 0,
|
|
4155
|
+
retainedLineages: 0
|
|
4156
|
+
}),
|
|
4157
|
+
protectedTokens: config.protected_tokens,
|
|
4158
|
+
protectedTokenTierOverrides: getProtectedTokensTierOverrides(config),
|
|
4159
|
+
liveModelBySession: liveModels,
|
|
4160
|
+
getToolSetHash: (sessionId) => {
|
|
4161
|
+
const model = liveModels.get(sessionId);
|
|
4162
|
+
if (!model)
|
|
4163
|
+
return "";
|
|
4164
|
+
return getCurrentToolSetHash(model.providerID, model.modelID, agents.get(sessionId));
|
|
4165
|
+
},
|
|
4166
|
+
channel1StateBySession: channel1,
|
|
4167
|
+
historyRefreshSessions,
|
|
4168
|
+
pendingMaterializationSessions,
|
|
4169
|
+
lastHeuristicsTurnId,
|
|
4170
|
+
variantBySession: variants,
|
|
4171
|
+
clearReasoningAge: config.clear_reasoning_age,
|
|
4172
|
+
directory,
|
|
4173
|
+
sessionDirectoryBySession: sessionDirectories,
|
|
4174
|
+
projectPath: directory,
|
|
4175
|
+
hiddenCompletionExecutor,
|
|
4176
|
+
historianRunnable: !compactionOff && hiddenCompletionExecutor !== undefined && config.historian?.disable !== true,
|
|
4177
|
+
historianModel: historianModels.primary,
|
|
4178
|
+
fallbackModels: historianModels.fallbacks,
|
|
4179
|
+
resolveHistorianRun: sampleHistorian,
|
|
4180
|
+
historianTimeoutMs: config.historian_timeout_ms,
|
|
4181
|
+
historianMaxOutputTokens: config.historian?.maxTokens,
|
|
4182
|
+
historianTwoPass: config.historian?.two_pass,
|
|
4183
|
+
historianExpandTools: config.historian?.expand_tools,
|
|
4184
|
+
historianRunner: config.historian?.runner,
|
|
4185
|
+
historianHostRunnerEnabled: config.historian?.host_runner?.enabled,
|
|
4186
|
+
compactionMarkerStrategy: rustModeModuleClient ? createV2RustCompactionMarkerStrategy((sessionID, endMessageID) => {
|
|
4187
|
+
const reader = openStoreReader();
|
|
4188
|
+
try {
|
|
4189
|
+
return resolveV2BoundaryUserMessage(reader, sessionID, endMessageID);
|
|
4190
|
+
} finally {
|
|
4191
|
+
reader.close();
|
|
4192
|
+
}
|
|
4193
|
+
}) : v2CompactionMarkerStrategy,
|
|
4194
|
+
transformMode: config.transform_mode,
|
|
4195
|
+
rustModeModuleClient,
|
|
4196
|
+
rustModeProjectRoot: directory,
|
|
4197
|
+
rustMemorySyncRequestedSessions,
|
|
4198
|
+
onRustModeParked: (sessionId, message) => pushNotification2("toast", {
|
|
4199
|
+
message: `Rust Magic Context paused: ${message}`,
|
|
4200
|
+
variant: "warning"
|
|
4201
|
+
}, sessionId),
|
|
4202
|
+
promptSurface: config.prompt_surface,
|
|
4203
|
+
promptSurfaceRuntime,
|
|
4204
|
+
onRustEngineReconnectRefusal: (refusal) => rustRefusalRecovery?.arm(refusal),
|
|
4205
|
+
memoryConfig: {
|
|
4206
|
+
enabled: config.memory.enabled,
|
|
4207
|
+
injectionBudgetTokens: config.memory.injection_budget_tokens,
|
|
4208
|
+
autoPromote: config.memory.auto_promote
|
|
4209
|
+
},
|
|
4210
|
+
...createHostSeams(context, readAllForConversion, pagedRead, liveModels)
|
|
4211
|
+
});
|
|
4212
|
+
const admitted = new Set;
|
|
4213
|
+
const reader = openStoreReader();
|
|
4214
|
+
let checkpoint;
|
|
4215
|
+
let submitted;
|
|
4216
|
+
try {
|
|
4217
|
+
const candidates = syntheticCandidates(context, draft.sessionID, reader.syntheticMessageIDs(draft.sessionID));
|
|
4218
|
+
for (const message of draft.messages) {
|
|
4219
|
+
if (message.id && candidates.has(message.id) && await isAdmittedSynthetic(context, draft.sessionID, message.id))
|
|
4220
|
+
admitted.add(message.id);
|
|
4221
|
+
}
|
|
4222
|
+
const cut = reader.latestCompaction(draft.sessionID);
|
|
4223
|
+
const boundaryRepair = !rustModeModuleClient && db && !compactionOff ? checkHistoryBoundary(db, reader, draft.sessionID) : undefined;
|
|
4224
|
+
if (boundaryRepair?.kind === "repaired" || boundaryRepair?.kind === "baseline-reset") {
|
|
4225
|
+
historyRefreshSessions.add(draft.sessionID);
|
|
4226
|
+
pendingMaterializationSessions.add(draft.sessionID);
|
|
4227
|
+
}
|
|
4228
|
+
const incoming = cut && draft.messages.find((message) => message.id === cut.id);
|
|
4229
|
+
postFold = cut !== undefined;
|
|
4230
|
+
if (cut && !incoming)
|
|
4231
|
+
throw new Error("The host checkpoint disappeared from the context draft");
|
|
4232
|
+
if (cut && incoming) {
|
|
4233
|
+
const identity = await folds.observe({
|
|
4234
|
+
sessionID: draft.sessionID,
|
|
4235
|
+
cutSeq: cut.seq,
|
|
4236
|
+
summary: cut.data.summary ?? "",
|
|
4237
|
+
rendered: incoming,
|
|
4238
|
+
onHard: (reason) => {
|
|
4239
|
+
sessionLog(draft.sessionID, `HARD reason=${reason}`);
|
|
4240
|
+
materialize(draft);
|
|
4241
|
+
pendingMaterializationSessions.add(draft.sessionID);
|
|
4242
|
+
}
|
|
4243
|
+
});
|
|
4244
|
+
checkpoint = structuredClone(identity.rendered ?? incoming);
|
|
4245
|
+
submitted = identity.rendered ? identity.renderedSummary ?? identity.submitted : cut.data.summary ?? "";
|
|
4246
|
+
const moduleBoundaryID = rustModeModuleClient ? getPersistedCompactionMarkerState(db, draft.sessionID)?.boundaryMessageId ?? null : null;
|
|
4247
|
+
const moduleBoundarySeq = moduleBoundaryID ? reader.sequenceForId(draft.sessionID, moduleBoundaryID) : undefined;
|
|
4248
|
+
const boundaryID = db.prepare("SELECT cached_m0_last_baseline_end_message_id AS id FROM session_meta WHERE session_id = ?").get(draft.sessionID)?.id ?? (boundaryRepair?.kind === "repaired" || boundaryRepair?.kind === "baseline-reset" ? boundaryRepair.anchorEndMessageId : null);
|
|
4249
|
+
const servedBoundary = boundaryID ? servedBoundaryRow(reader, draft.sessionID, boundaryID) : null;
|
|
4250
|
+
const boundary = (moduleBoundarySeq !== undefined ? moduleBoundarySeq - 1 : undefined) ?? servedBoundary?.seq ?? reader.sequenceForId(draft.sessionID, boundaryID) ?? (reader.earliestSequence(draft.sessionID) ?? 0) - 1;
|
|
4251
|
+
if (moduleBoundarySeq !== undefined)
|
|
4252
|
+
sessionLog(draft.sessionID, `v2 restore: from the module boundary ${moduleBoundaryID} to the host checkpoint`);
|
|
4253
|
+
const present = new Set(draft.messages.map((message) => message.id));
|
|
4254
|
+
const restored = restoredRows.rows(reader, draft.sessionID, boundary, cut.seq).filter((row) => !present.has(row.id)).flatMap((row) => restoreRow(row, draft.model, hostUsesMediaAssets() ? {
|
|
4255
|
+
asset: hostMediaAsset,
|
|
4256
|
+
unavailable: (detail) => sessionLog(draft.sessionID, `v2 restore: attachment replaced by a note row=${detail.rowID} name=${JSON.stringify(detail.name ?? null)} mediaType=${detail.mediaType} reason=${JSON.stringify(detail.reason)}`)
|
|
4257
|
+
} : undefined));
|
|
4258
|
+
draft.messages.splice(0, draft.messages.length, ...restored, ...draft.messages.filter((message) => message !== incoming));
|
|
4259
|
+
}
|
|
4260
|
+
} finally {
|
|
4261
|
+
reader.close();
|
|
4262
|
+
}
|
|
4263
|
+
if (rustModeModuleClient && db) {
|
|
4264
|
+
const dropped = trimToRecordedBoundary(db, draft.sessionID, draft.messages);
|
|
4265
|
+
if (dropped > 0)
|
|
4266
|
+
sessionLog(draft.sessionID, `v2 boundary trim: dropped ${dropped} messages before the module boundary`);
|
|
4267
|
+
}
|
|
4268
|
+
const mapped = adaptPayload(draft, admitted);
|
|
4269
|
+
beginV2LkgRequest(draft.sessionID, `${draft.model.providerID}/${draft.model.id}`, latestLkgResponseIds.get(draft.sessionID));
|
|
4270
|
+
await createMessagesTransformHandler({
|
|
4271
|
+
magicContext: { "experimental.chat.messages.transform": transform },
|
|
4272
|
+
compactionOff,
|
|
4273
|
+
onLkgReplay: restoreLkgSystem,
|
|
4274
|
+
rustReplayParticipant: () => transform?.getRustReplayParticipant() ?? null
|
|
4275
|
+
})({}, mapped);
|
|
4276
|
+
mapped.commit();
|
|
4277
|
+
if (db) {
|
|
4278
|
+
await deliverPendingChannel2(context, db, draft.sessionID, channel1.get(draft.sessionID));
|
|
4279
|
+
}
|
|
4280
|
+
if (checkpoint && submitted !== undefined) {
|
|
4281
|
+
const head = draft.messages.find((message) => message.id === HEAD_IDS[0]);
|
|
4282
|
+
const baseline = head?.content.find((part) => part.type === "text")?.text;
|
|
4283
|
+
if (typeof baseline === "string") {
|
|
4284
|
+
for (const part of checkpoint.content)
|
|
4285
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
4286
|
+
part.text = part.text.replace(`<summary>
|
|
4287
|
+
${submitted}
|
|
4288
|
+
</summary>`, `<summary>
|
|
4289
|
+
${baseline}
|
|
4290
|
+
</summary>`);
|
|
4291
|
+
}
|
|
4292
|
+
const volatile = draft.messages.find((message) => message.id === HEAD_IDS[1]);
|
|
4293
|
+
if (volatile && head)
|
|
4294
|
+
volatile.content.push(...head.content.filter((part) => part.type !== "text"));
|
|
4295
|
+
draft.messages.splice(0, draft.messages.length, checkpoint, ...draft.messages.filter((message) => message !== head));
|
|
4296
|
+
}
|
|
4297
|
+
}
|
|
4298
|
+
const capturedSlot = getSlot(draft.sessionID);
|
|
4299
|
+
if (capturedSlot && (!slotAtEntry || capturedSlot.capturedAt !== slotAtEntry.capturedAt || capturedSlot.captureSequence !== slotAtEntry.captureSequence || capturedSlot.jsonPrefix !== slotAtEntry.jsonPrefix)) {
|
|
4300
|
+
lkgSystems.capture(draft.sessionID, capturedSlot, systemAtEntry, draft.system);
|
|
4301
|
+
}
|
|
4302
|
+
} catch (error) {
|
|
4303
|
+
if (error instanceof V2ContextRefusal)
|
|
4304
|
+
throw error;
|
|
4305
|
+
if (!compactionOff && (isTransientSqliteError(error) || error instanceof StorageBusyRefusalError)) {
|
|
4306
|
+
if (isTransientSqliteError(error)) {
|
|
4307
|
+
const mapped = adaptPayload(draft);
|
|
4308
|
+
try {
|
|
4309
|
+
await createMessagesTransformHandler({
|
|
4310
|
+
onLkgReplay: restoreLkgSystem,
|
|
4311
|
+
rustReplayParticipant: () => transform?.getRustReplayParticipant() ?? null,
|
|
4312
|
+
magicContext: {
|
|
4313
|
+
"experimental.chat.messages.transform": async () => {
|
|
4314
|
+
throw error;
|
|
4315
|
+
}
|
|
4316
|
+
}
|
|
4317
|
+
})({}, mapped);
|
|
4318
|
+
mapped.commit();
|
|
4319
|
+
return;
|
|
4320
|
+
} catch (replayError) {
|
|
4321
|
+
if (!(replayError instanceof StorageBusyRefusalError))
|
|
4322
|
+
throw replayError;
|
|
4323
|
+
}
|
|
4324
|
+
}
|
|
4325
|
+
const refusal = error instanceof StorageBusyRefusalError ? error : new StorageBusyRefusalError(error, "v2-context");
|
|
4326
|
+
pushNotification2("toast", { message: STORAGE_BUSY_MESSAGE, variant: "error" }, draft.sessionID);
|
|
4327
|
+
storeStorageNotice(draft.sessionID, `${STORAGE_NOTICE_PREFIX}${STORAGE_BUSY_MESSAGE}`, "busy");
|
|
4328
|
+
await refuseBeforeProvider(context.session, draft.sessionID, "storage-busy", refusal);
|
|
4329
|
+
throw new V2ContextRefusal(STORAGE_BUSY_MESSAGE, { cause: refusal });
|
|
4330
|
+
}
|
|
4331
|
+
const managedError = !compactionOff && !isBlockingV2TransformError(error) ? new DegradedPassRefusalError("v2-context-failed", { cause: error }) : error;
|
|
4332
|
+
if (isBlockingV2TransformError(managedError)) {
|
|
4333
|
+
if (!compactionOff) {
|
|
4334
|
+
if (managedError instanceof UnresolvedHistoryBoundaryError || managedError instanceof UnmanagedOverWindowError || managedError instanceof DegradedPassRefusalError) {
|
|
4335
|
+
pushNotification2("toast", { message: managedError.message, variant: "error" }, draft.sessionID);
|
|
4336
|
+
}
|
|
4337
|
+
await refuseBeforeProvider(context.session, draft.sessionID, "blocking-transform-error", managedError);
|
|
4338
|
+
throw new V2ContextRefusal("Magic Context refused to send an unsafe prompt.", {
|
|
4339
|
+
cause: managedError
|
|
4340
|
+
});
|
|
4341
|
+
}
|
|
4342
|
+
log("[magic-context] compaction-off: fail-closed inert, passing through", error);
|
|
4343
|
+
} else if (postFold) {
|
|
4344
|
+
await refuseBeforeProvider(context.session, draft.sessionID, "post-fold-restore", error);
|
|
4345
|
+
throw new V2ContextRefusal("Magic Context could not restore the unarchived host history.", { cause: error });
|
|
4346
|
+
} else {
|
|
4347
|
+
log("[magic-context] v2 context unavailable", error);
|
|
4348
|
+
}
|
|
4349
|
+
}
|
|
4350
|
+
};
|
|
4351
|
+
await context.session.hook("context", (draft) => withSqliteTransformPass(async () => {
|
|
4352
|
+
const anchor = draft.messages.at(-1)?.id;
|
|
4353
|
+
await runManagedContext(draft);
|
|
4354
|
+
if (!compactionOff && draft.messages.some((message) => message.id === HEAD_IDS[0] || message.content.some((part) => typeof part.text === "string" && part.text.includes("<session-history>"))))
|
|
4355
|
+
generateReplay.capture(draft, anchor);
|
|
4356
|
+
}));
|
|
4357
|
+
await context.session.hook("generate", async (draft) => {
|
|
4358
|
+
if (!compactionOff && !deletedSessions.has(draft.sessionID))
|
|
4359
|
+
generateReplay.apply(draft);
|
|
4360
|
+
});
|
|
4361
|
+
withoutSqliteTransformPass(() => warmModelLimitCacheFromCatalog(context));
|
|
4362
|
+
const rpcLiveSessionState = createV2RpcLiveSessionState({
|
|
4363
|
+
liveModelBySession: liveModels,
|
|
4364
|
+
variantBySession: variants,
|
|
4365
|
+
agentBySession: agents,
|
|
4366
|
+
channel1StateBySession: channel1,
|
|
4367
|
+
historyRefreshSessions,
|
|
4368
|
+
pendingMaterializationSessions,
|
|
4369
|
+
systemPromptRefreshSessions,
|
|
4370
|
+
sessionDirectoryBySession: sessionDirectories
|
|
4371
|
+
});
|
|
4372
|
+
const storageDir = getMagicContextStorageDir();
|
|
4373
|
+
const rpcServer = new MagicContextRpcServer(storageDir, directory);
|
|
4374
|
+
let rpcStopped = false;
|
|
4375
|
+
registerRpcHandlers(rpcServer, {
|
|
4376
|
+
directory,
|
|
4377
|
+
config,
|
|
4378
|
+
client: undefined,
|
|
4379
|
+
liveSessionState: rpcLiveSessionState,
|
|
4380
|
+
getDatabase: () => {
|
|
4381
|
+
try {
|
|
4382
|
+
return storage.require();
|
|
4383
|
+
} catch {
|
|
4384
|
+
return null;
|
|
4385
|
+
}
|
|
4386
|
+
},
|
|
4387
|
+
rustModeModuleClient,
|
|
4388
|
+
hiddenCompletionExecutor: storageOpenedAtBoot ? hiddenCompletionExecutor : lateHiddenExecutor,
|
|
4389
|
+
compactionMarkerStrategy: v2CompactionMarkerStrategy,
|
|
4390
|
+
prepareHistorySession,
|
|
4391
|
+
storageDir
|
|
4392
|
+
});
|
|
4393
|
+
const manualDreamer = config.dreamer && config.dreamer.disable !== true ? config.dreamer : undefined;
|
|
4394
|
+
rpcServer.handle("dream", async (params) => {
|
|
4395
|
+
const sessionId = String(params.sessionId ?? "");
|
|
4396
|
+
if (!sessionId)
|
|
4397
|
+
return { ok: false, error: "no session" };
|
|
4398
|
+
if (!manualDreamer || !hiddenCompletionExecutor) {
|
|
4399
|
+
pushNotification2("toast", { message: "Dreaming is not configured for this project.", variant: "warning" }, sessionId);
|
|
4400
|
+
return { ok: false, error: "dreamer unavailable" };
|
|
4401
|
+
}
|
|
4402
|
+
const requested = resolveManualDreamTask(params.task);
|
|
4403
|
+
if (requested.error) {
|
|
4404
|
+
pushNotification2("toast", { message: requested.error, variant: "warning" }, sessionId);
|
|
4405
|
+
return { ok: false, error: requested.error };
|
|
4406
|
+
}
|
|
4407
|
+
try {
|
|
4408
|
+
db = storage.require();
|
|
4409
|
+
} catch {
|
|
4410
|
+
const reason = storage.reason();
|
|
4411
|
+
pushNotification2("toast", {
|
|
4412
|
+
message: `Dreaming is unavailable: ${reason ? formatFailClosedBlockingSummary(reason) : "context storage is not durable."}`,
|
|
4413
|
+
variant: "error"
|
|
4414
|
+
}, sessionId);
|
|
4415
|
+
return { ok: false, error: "storage unavailable" };
|
|
4416
|
+
}
|
|
4417
|
+
const runDb = db;
|
|
4418
|
+
const runExecutor = hiddenCompletionExecutor;
|
|
4419
|
+
runManualDreamNow({
|
|
4420
|
+
db: runDb,
|
|
4421
|
+
dreamer: manualDreamer,
|
|
4422
|
+
projectIdentity: resolveProjectIdentityForSession(directory, config.allow_home_project) ?? "",
|
|
4423
|
+
directory,
|
|
4424
|
+
language: config.language,
|
|
4425
|
+
mural: config.mural,
|
|
4426
|
+
executor: runExecutor,
|
|
4427
|
+
sessionId,
|
|
4428
|
+
...requested.task !== undefined ? { task: requested.task } : {}
|
|
4429
|
+
}).then(({ summary, unsupportedTasks }) => {
|
|
4430
|
+
const hasSummaryContent = summary.ran.length > 0 || summary.failed.length > 0 || summary.skippedNoWork.length > 0 || (summary.skipped?.length ?? 0) > 0 || summary.deferredBusy.length > 0 || Object.keys(summary.backlogBefore ?? {}).length > 0 || Object.keys(summary.backlogAfter ?? {}).length > 0;
|
|
4431
|
+
const message = [
|
|
4432
|
+
hasSummaryContent || unsupportedTasks.length === 0 ? summarizeManualDream(summary) : undefined,
|
|
4433
|
+
unsupportedTasks.length > 0 ? [
|
|
4434
|
+
renderUserFacingFailure("dream_task_needs_tool_loop", "plain"),
|
|
4435
|
+
formatUnsupportedDreamTasks(unsupportedTasks, userFacingFailureCode("dream_task_needs_tool_loop"))
|
|
4436
|
+
].join(`
|
|
4437
|
+
`) : undefined
|
|
4438
|
+
].filter((line) => line !== undefined).join(`
|
|
4439
|
+
|
|
4440
|
+
`);
|
|
4441
|
+
pushNotification2("action", {
|
|
4442
|
+
action: "show-result-dialog",
|
|
4443
|
+
title: "Magic Context dream run",
|
|
4444
|
+
message
|
|
4445
|
+
}, sessionId);
|
|
4446
|
+
}).catch((error) => {
|
|
4447
|
+
pushNotification2("toast", { message: `Dream run failed: ${getErrorMessage(error)}`, variant: "error" }, sessionId);
|
|
4448
|
+
});
|
|
4449
|
+
return { ok: true };
|
|
4450
|
+
});
|
|
4451
|
+
await registerV2Commands({
|
|
4452
|
+
command: context.command,
|
|
4453
|
+
rpc: rpcServer,
|
|
4454
|
+
directory,
|
|
4455
|
+
compactionEnabled: !compactionOff
|
|
4456
|
+
});
|
|
4457
|
+
setTimeout(() => {
|
|
4458
|
+
if (rpcStopped)
|
|
4459
|
+
return;
|
|
4460
|
+
rpcServer.start().catch((error) => log("[magic-context] v2 RPC server failed to start", error));
|
|
4461
|
+
}, 0);
|
|
4462
|
+
return {
|
|
4463
|
+
async dispose() {
|
|
4464
|
+
rpcStopped = true;
|
|
4465
|
+
rpcServer.stop();
|
|
4466
|
+
transform?.disposeRust();
|
|
4467
|
+
tools?.dispose();
|
|
4468
|
+
usageController.abort();
|
|
4469
|
+
await usageDone;
|
|
4470
|
+
deletedSessions.clear();
|
|
4471
|
+
await dreamTrigger?.dispose();
|
|
4472
|
+
for (const release of rawProviders.values())
|
|
4473
|
+
release();
|
|
4474
|
+
rawProviders.clear();
|
|
4475
|
+
restoredRows.clear();
|
|
4476
|
+
readerPool.close();
|
|
4477
|
+
clearSyntheticCandidates(context);
|
|
4478
|
+
}
|
|
4479
|
+
};
|
|
4480
|
+
}
|
|
4481
|
+
export {
|
|
4482
|
+
registerContext
|
|
4483
|
+
};
|