@adhdev/daemon-core 0.9.82-rc.49 → 0.9.82-rc.490
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boot/daemon-lifecycle.d.ts +9 -0
- package/dist/boot/process-hardening.d.ts +50 -0
- package/dist/build-info.d.ts +37 -0
- package/dist/chat/source-machine.d.ts +166 -0
- package/dist/chat/source-resolver.d.ts +104 -0
- package/dist/chat/subscription-updates.d.ts +1 -0
- package/dist/cli-adapter-types.d.ts +68 -2
- package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +220 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +137 -64
- package/dist/cli-adapters/provider-cli-parse.d.ts +5 -0
- package/dist/cli-adapters/provider-cli-runtime.d.ts +34 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +74 -0
- package/dist/cli-adapters/pty-write-chunking.d.ts +34 -0
- package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
- package/dist/cli-adapters/resolve-executable.d.ts +69 -0
- package/dist/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +6 -3
- package/dist/cli-adapters/terminal-backends/types.d.ts +10 -2
- package/dist/cli-adapters/terminal-screen.d.ts +4 -11
- package/dist/commands/chat-commands-debug-bundle.d.ts +14 -0
- package/dist/commands/chat-commands-read.d.ts +23 -0
- package/dist/commands/chat-commands-scope.d.ts +39 -0
- package/dist/commands/chat-commands-shared.d.ts +33 -0
- package/dist/commands/chat-commands-write.d.ts +14 -0
- package/dist/commands/chat-commands.d.ts +10 -23
- package/dist/commands/cli-manager.d.ts +71 -1
- package/dist/commands/handler.d.ts +125 -0
- package/dist/commands/high-family/index.d.ts +3 -0
- package/dist/commands/high-family/mesh-coordinator-launch.d.ts +2 -0
- package/dist/commands/high-family/mesh-events.d.ts +2 -0
- package/dist/commands/high-family/mesh-status.d.ts +2 -0
- package/dist/commands/high-family/types.d.ts +64 -0
- package/dist/commands/low-family/coordinator-prompt.d.ts +9 -0
- package/dist/commands/low-family/daemon-lifecycle.d.ts +2 -0
- package/dist/commands/low-family/diagnostics.d.ts +2 -0
- package/dist/commands/low-family/index.d.ts +3 -0
- package/dist/commands/low-family/mesh-ledger.d.ts +10 -0
- package/dist/commands/low-family/mesh-node-logs.d.ts +2 -0
- package/dist/commands/low-family/notification.d.ts +2 -0
- package/dist/commands/low-family/refine-config.d.ts +2 -0
- package/dist/commands/low-family/session-host.d.ts +2 -0
- package/dist/commands/low-family/spec-providerdev.d.ts +11 -0
- package/dist/commands/low-family/status-meta.d.ts +2 -0
- package/dist/commands/low-family/types.d.ts +33 -0
- package/dist/commands/med-family/cli-agent.d.ts +2 -0
- package/dist/commands/med-family/fast-forward.d.ts +2 -0
- package/dist/commands/med-family/ide.d.ts +10 -0
- package/dist/commands/med-family/index.d.ts +3 -0
- package/dist/commands/med-family/mesh-crud.d.ts +48 -0
- package/dist/commands/med-family/mesh-host-pairing.d.ts +2 -0
- package/dist/commands/med-family/mesh-queue.d.ts +2 -0
- package/dist/commands/med-family/mesh-restart.d.ts +2 -0
- package/dist/commands/med-family/types.d.ts +152 -0
- package/dist/commands/mesh-coordinator.d.ts +85 -1
- package/dist/commands/router-aggregate-status.d.ts +24 -0
- package/dist/commands/router-mesh-session-owner.d.ts +59 -0
- package/dist/commands/router-refine.d.ts +151 -0
- package/dist/commands/router-worktree-cleanup.d.ts +133 -0
- package/dist/commands/router.d.ts +203 -15
- package/dist/commands/upgrade-helper.d.ts +41 -1
- package/dist/config/chat-history.d.ts +11 -0
- package/dist/config/config.d.ts +19 -0
- package/dist/config/mesh-config.d.ts +65 -0
- package/dist/config/mesh-json-config.d.ts +199 -0
- package/dist/config/registry-resolver.d.ts +54 -0
- package/dist/config/repo-settings.d.ts +77 -0
- package/dist/config/state-store.d.ts +30 -0
- package/dist/daemon/dev-server.d.ts +0 -2
- package/dist/detection/cli-detector.d.ts +17 -0
- package/dist/detection/ide-detector.d.ts +13 -0
- package/dist/detection/win32-ide-version.d.ts +37 -0
- package/dist/git/change-impact-config.d.ts +159 -0
- package/dist/git/git-commands.d.ts +26 -2
- package/dist/git/git-diff.d.ts +6 -0
- package/dist/git/git-executor.d.ts +11 -0
- package/dist/git/git-status.d.ts +80 -0
- package/dist/git/git-types.d.ts +2 -50
- package/dist/git/git-worktree.d.ts +71 -1
- package/dist/git/index.d.ts +3 -1
- package/dist/index.d.ts +70 -17
- package/dist/index.js +53532 -19073
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +53283 -18989
- package/dist/index.mjs.map +1 -1
- package/dist/installer.d.ts +1 -4
- package/dist/ipc/local-ipc-server.d.ts +91 -0
- package/dist/launch.d.ts +1 -1
- package/dist/logging/async-batch-writer.d.ts +10 -0
- package/dist/logging/debug-config.d.ts +16 -0
- package/dist/logging/log-redactor.d.ts +24 -0
- package/dist/logging/log-tail-reader.d.ts +81 -0
- package/dist/logging/logger.d.ts +1 -1
- package/dist/mesh/contracts.d.ts +256 -0
- package/dist/mesh/coordinator-prompt.d.ts +76 -0
- package/dist/mesh/coordinator-registry.d.ts +59 -0
- package/dist/mesh/mesh-active-work.d.ts +203 -0
- package/dist/mesh/mesh-clone-grace.d.ts +8 -0
- package/dist/mesh/mesh-completion-synthesis.d.ts +4 -0
- package/dist/mesh/mesh-coordinator-config.d.ts +21 -0
- package/dist/mesh/mesh-delivery-policy.d.ts +104 -0
- package/dist/mesh/mesh-event-classify.d.ts +7 -0
- package/dist/mesh/mesh-event-forwarding.d.ts +58 -0
- package/dist/mesh/mesh-event-trace.d.ts +21 -0
- package/dist/mesh/mesh-events-coordinator.d.ts +4 -0
- package/dist/mesh/mesh-events-pending.d.ts +258 -0
- package/dist/mesh/mesh-events-stale.d.ts +61 -0
- package/dist/mesh/mesh-events-utils.d.ts +75 -0
- package/dist/mesh/mesh-events.d.ts +6 -49
- package/dist/mesh/mesh-fast-forward.d.ts +81 -0
- package/dist/mesh/mesh-host-ownership.d.ts +21 -1
- package/dist/mesh/mesh-init.d.ts +128 -0
- package/dist/mesh/mesh-ledger-reconciliation.d.ts +23 -4
- package/dist/mesh/mesh-ledger.d.ts +139 -1
- package/dist/mesh/mesh-magi-status.d.ts +63 -0
- package/dist/mesh/mesh-missions.d.ts +281 -0
- package/dist/mesh/mesh-node-identity.d.ts +310 -0
- package/dist/mesh/mesh-queue-assignment.d.ts +162 -0
- package/dist/mesh/mesh-reconcile-acked-hold.d.ts +17 -0
- package/dist/mesh/mesh-reconcile-config.d.ts +6 -0
- package/dist/mesh/mesh-reconcile-identity.d.ts +6 -0
- package/dist/mesh/mesh-reconcile-loop.d.ts +84 -0
- package/dist/mesh/mesh-reconcile-v2-backstop.d.ts +16 -0
- package/dist/mesh/mesh-refine-batch.d.ts +68 -0
- package/dist/mesh/mesh-refine-gates.d.ts +457 -0
- package/dist/mesh/mesh-refine-status.d.ts +62 -0
- package/dist/mesh/mesh-remote-event-pull.d.ts +15 -0
- package/dist/mesh/mesh-review-inbox.d.ts +90 -0
- package/dist/mesh/mesh-routing.d.ts +70 -0
- package/dist/mesh/mesh-runtime-store.d.ts +567 -0
- package/dist/mesh/mesh-scheduling-runtime.d.ts +78 -0
- package/dist/mesh/mesh-task-inflight.d.ts +46 -0
- package/dist/mesh/mesh-task-stats.d.ts +49 -0
- package/dist/mesh/mesh-unresolved-forward-outbox.d.ts +34 -0
- package/dist/mesh/mesh-warmup-deadline.d.ts +68 -0
- package/dist/mesh/mesh-work-queue.d.ts +392 -1
- package/dist/mesh/preview-freshness.d.ts +28 -0
- package/dist/mesh/refine-config.d.ts +97 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +219 -0
- package/dist/providers/acp-provider-instance.d.ts +5 -0
- package/dist/providers/approval-utils.d.ts +46 -0
- package/dist/providers/chat-message-normalization.d.ts +58 -1
- package/dist/providers/cli-provider-effect-format.d.ts +30 -0
- package/dist/providers/cli-provider-history-dedup.d.ts +17 -0
- package/dist/providers/cli-provider-input-prompt.d.ts +12 -0
- package/dist/providers/cli-provider-instance-types.d.ts +45 -0
- package/dist/providers/cli-provider-instance.d.ts +426 -30
- package/dist/providers/cli-provider-status-helpers.d.ts +46 -0
- package/dist/providers/cli-provider-transcript-merge.d.ts +7 -0
- package/dist/providers/contracts.d.ts +196 -6
- package/dist/providers/external-sources.d.ts +71 -0
- package/dist/providers/manual-attendance.d.ts +79 -0
- package/dist/providers/native-history/antigravity-claim-registry.d.ts +39 -0
- package/dist/providers/native-history/antigravity-cli-transcript.d.ts +137 -0
- package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
- package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
- package/dist/providers/native-history/constants.d.ts +12 -0
- package/dist/providers/native-history/dispatcher.d.ts +44 -0
- package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
- package/dist/providers/native-history/index.d.ts +13 -0
- package/dist/providers/provider-instance-manager.d.ts +30 -0
- package/dist/providers/provider-instance.d.ts +31 -1
- package/dist/providers/provider-loader.d.ts +42 -5
- package/dist/providers/provider-trust.d.ts +31 -0
- package/dist/providers/read-chat-contract.d.ts +29 -0
- package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
- package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +86 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
- package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
- package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
- package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
- package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
- package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
- package/dist/providers/sdk/v1/index.d.ts +30 -0
- package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
- package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
- package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
- package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
- package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
- package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
- package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
- package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
- package/dist/providers/spec/adapter.d.ts +98 -0
- package/dist/providers/spec/cli-adapter.d.ts +223 -0
- package/dist/providers/spec/evaluator.d.ts +45 -0
- package/dist/providers/spec/fsm-driver.d.ts +449 -0
- package/dist/providers/spec/fsm-evaluator.d.ts +93 -0
- package/dist/providers/spec/fsm-loader.d.ts +14 -0
- package/dist/providers/spec/fsm-types.d.ts +202 -0
- package/dist/providers/spec/native-history-executor.d.ts +41 -0
- package/dist/providers/spec/pre-launch-trust.d.ts +16 -0
- package/dist/providers/spec/route.d.ts +4 -0
- package/dist/providers/spec/types.d.ts +244 -0
- package/dist/providers/status-monitor.d.ts +7 -7
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/providers/types/interactive-prompt.d.ts +84 -0
- package/dist/providers/working-dir.d.ts +17 -0
- package/dist/repo-mesh-types.d.ts +616 -12
- package/dist/runtime-defaults.d.ts +2 -0
- package/dist/session-host/managed-host.d.ts +64 -0
- package/dist/session-host/runtime-surface.d.ts +10 -16
- package/dist/sessions/registry.d.ts +26 -0
- package/dist/shared-types-extra.d.ts +2 -4
- package/dist/shared-types.d.ts +110 -55
- package/dist/status/chat-tail-hot-sessions.d.ts +40 -0
- package/dist/status/normalize.d.ts +1 -1
- package/dist/status/normalize.js +1 -0
- package/dist/status/normalize.js.map +1 -1
- package/dist/status/normalize.mjs +1 -0
- package/dist/status/normalize.mjs.map +1 -1
- package/dist/status/reporter.d.ts +2 -0
- package/dist/status/snapshot.d.ts +27 -0
- package/dist/system/hash.d.ts +8 -0
- package/dist/system/load-better-sqlite3.d.ts +21 -0
- package/dist/types.d.ts +20 -6
- package/package.json +11 -4
- package/src/agent-stream/poller.ts +2 -3
- package/src/agent-stream/provider-adapter.ts +1 -1
- package/src/boot/daemon-lifecycle.ts +80 -14
- package/src/boot/process-hardening.ts +89 -0
- package/src/build-info.ts +73 -0
- package/src/chat/source-machine.ts +534 -0
- package/src/chat/source-resolver.ts +0 -0
- package/src/chat/subscription-updates.ts +20 -1
- package/src/cli-adapter-types.d.ts +3 -1
- package/src/cli-adapter-types.ts +68 -2
- package/src/cli-adapters/cli-script-runner.ts +421 -0
- package/src/cli-adapters/cli-state-engine.ts +1282 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +1048 -1143
- package/src/cli-adapters/provider-cli-parse.d.ts +2 -0
- package/src/cli-adapters/provider-cli-parse.ts +19 -0
- package/src/cli-adapters/provider-cli-runtime.ts +56 -7
- package/src/cli-adapters/provider-cli-shared.d.ts +4 -0
- package/src/cli-adapters/provider-cli-shared.ts +121 -11
- package/src/cli-adapters/pty-transport.ts +2 -1
- package/src/cli-adapters/pty-write-chunking.ts +106 -0
- package/src/cli-adapters/raw-terminal-io.ts +252 -0
- package/src/cli-adapters/resolve-executable.ts +204 -0
- package/src/cli-adapters/session-host-transport.ts +2 -1
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +63 -29
- package/src/cli-adapters/terminal-backends/types.ts +10 -3
- package/src/cli-adapters/terminal-screen.ts +16 -81
- package/src/commands/chat-commands-debug-bundle.ts +398 -0
- package/src/commands/chat-commands-read.ts +2788 -0
- package/src/commands/chat-commands-scope.ts +54 -0
- package/src/commands/chat-commands-shared.ts +114 -0
- package/src/commands/chat-commands-write.ts +891 -0
- package/src/commands/chat-commands.ts +19 -1841
- package/src/commands/cli-manager.d.ts +2 -0
- package/src/commands/cli-manager.ts +724 -27
- package/src/commands/handler.ts +865 -2
- package/src/commands/high-family/index.ts +28 -0
- package/src/commands/high-family/mesh-coordinator-launch.ts +696 -0
- package/src/commands/high-family/mesh-events.ts +102 -0
- package/src/commands/high-family/mesh-status.ts +857 -0
- package/src/commands/high-family/types.ts +80 -0
- package/src/commands/low-family/coordinator-prompt.ts +125 -0
- package/src/commands/low-family/daemon-lifecycle.ts +120 -0
- package/src/commands/low-family/diagnostics.ts +57 -0
- package/src/commands/low-family/index.ts +37 -0
- package/src/commands/low-family/mesh-ledger.ts +62 -0
- package/src/commands/low-family/mesh-node-logs.ts +87 -0
- package/src/commands/low-family/notification.ts +116 -0
- package/src/commands/low-family/refine-config.ts +106 -0
- package/src/commands/low-family/session-host.ts +282 -0
- package/src/commands/low-family/spec-providerdev.ts +217 -0
- package/src/commands/low-family/status-meta.ts +112 -0
- package/src/commands/low-family/types.ts +39 -0
- package/src/commands/med-family/cli-agent.ts +270 -0
- package/src/commands/med-family/fast-forward.ts +230 -0
- package/src/commands/med-family/ide.ts +163 -0
- package/src/commands/med-family/index.ts +37 -0
- package/src/commands/med-family/mesh-crud.ts +1268 -0
- package/src/commands/med-family/mesh-host-pairing.ts +234 -0
- package/src/commands/med-family/mesh-queue.ts +167 -0
- package/src/commands/med-family/mesh-restart.ts +92 -0
- package/src/commands/med-family/types.ts +153 -0
- package/src/commands/mesh-coordinator.ts +334 -124
- package/src/commands/router-aggregate-status.ts +209 -0
- package/src/commands/router-mesh-session-owner.ts +114 -0
- package/src/commands/router-refine.ts +1794 -0
- package/src/commands/router-worktree-cleanup.ts +870 -0
- package/src/commands/router.ts +567 -4149
- package/src/commands/stream-commands.ts +8 -0
- package/src/commands/upgrade-helper.ts +310 -45
- package/src/config/chat-history.ts +517 -24
- package/src/config/config.ts +30 -0
- package/src/config/mesh-config.ts +300 -24
- package/src/config/mesh-json-config.ts +376 -0
- package/src/config/recent-activity.ts +8 -2
- package/src/config/registry-resolver.ts +100 -0
- package/src/config/repo-settings.ts +111 -0
- package/src/config/state-store.ts +55 -0
- package/src/daemon/dev-auto-implement.ts +3 -2
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/daemon/dev-server.ts +0 -541
- package/src/detection/cli-detector.ts +94 -9
- package/src/detection/ide-detector.ts +55 -16
- package/src/detection/win32-ide-version.ts +106 -0
- package/src/git/change-impact-config.ts +354 -0
- package/src/git/git-commands.ts +112 -16
- package/src/git/git-diff.ts +81 -11
- package/src/git/git-executor.ts +12 -0
- package/src/git/git-status.ts +822 -48
- package/src/git/git-types.ts +14 -62
- package/src/git/git-worktree.ts +261 -4
- package/src/git/index.ts +17 -0
- package/src/index.ts +190 -13
- package/src/installer.d.ts +1 -1
- package/src/installer.ts +8 -6
- package/src/ipc/local-ipc-server.ts +278 -0
- package/src/launch.d.ts +1 -1
- package/src/launch.ts +37 -28
- package/src/logging/async-batch-writer.ts +55 -0
- package/src/logging/command-log.ts +7 -5
- package/src/logging/debug-config.ts +25 -0
- package/src/logging/debug-trace.ts +7 -2
- package/src/logging/log-redactor.ts +100 -0
- package/src/logging/log-tail-reader.ts +341 -0
- package/src/logging/logger.ts +14 -7
- package/src/mesh/contracts.ts +510 -0
- package/src/mesh/coordinator-prompt.ts +627 -34
- package/src/mesh/coordinator-registry.ts +121 -0
- package/src/mesh/mesh-active-work.ts +706 -0
- package/src/mesh/mesh-clone-grace.ts +68 -0
- package/src/mesh/mesh-completion-synthesis.ts +416 -0
- package/src/mesh/mesh-coordinator-config.ts +97 -0
- package/src/mesh/mesh-delivery-policy.ts +284 -0
- package/src/mesh/mesh-event-classify.ts +78 -0
- package/src/mesh/mesh-event-forwarding.ts +2030 -0
- package/src/mesh/mesh-event-trace.ts +67 -0
- package/src/mesh/mesh-events-coordinator.ts +32 -0
- package/src/mesh/mesh-events-pending.ts +1548 -0
- package/src/mesh/mesh-events-stale.ts +452 -0
- package/src/mesh/mesh-events-utils.ts +443 -0
- package/src/mesh/mesh-events.ts +43 -1035
- package/src/mesh/mesh-fast-forward.ts +856 -0
- package/src/mesh/mesh-host-ownership.ts +41 -3
- package/src/mesh/mesh-init.ts +350 -0
- package/src/mesh/mesh-ledger-reconciliation.ts +12 -7
- package/src/mesh/mesh-ledger.ts +897 -102
- package/src/mesh/mesh-magi-status.ts +223 -0
- package/src/mesh/mesh-missions.ts +695 -0
- package/src/mesh/mesh-node-identity.ts +2065 -0
- package/src/mesh/mesh-queue-assignment.ts +2498 -0
- package/src/mesh/mesh-reconcile-acked-hold.ts +230 -0
- package/src/mesh/mesh-reconcile-config.ts +66 -0
- package/src/mesh/mesh-reconcile-identity.ts +103 -0
- package/src/mesh/mesh-reconcile-loop.ts +1696 -0
- package/src/mesh/mesh-reconcile-v2-backstop.ts +62 -0
- package/src/mesh/mesh-refine-batch.ts +205 -0
- package/src/mesh/mesh-refine-gates.ts +1792 -0
- package/src/mesh/mesh-refine-status.ts +231 -0
- package/src/mesh/mesh-remote-event-pull.ts +279 -0
- package/src/mesh/mesh-review-inbox.ts +308 -0
- package/src/mesh/mesh-routing.ts +291 -0
- package/src/mesh/mesh-runtime-store.ts +2292 -0
- package/src/mesh/mesh-scheduling-runtime.ts +198 -0
- package/src/mesh/mesh-task-inflight.ts +70 -0
- package/src/mesh/mesh-task-stats.ts +161 -0
- package/src/mesh/mesh-unresolved-forward-outbox.ts +215 -0
- package/src/mesh/mesh-warmup-deadline.ts +152 -0
- package/src/mesh/mesh-work-queue.ts +1489 -145
- package/src/mesh/preview-freshness.ts +163 -0
- package/src/mesh/refine-config.ts +129 -12
- package/src/mesh/worktree-bootstrap-config.ts +599 -0
- package/src/providers/acp-provider-instance.ts +43 -10
- package/src/providers/approval-utils.d.ts +5 -0
- package/src/providers/approval-utils.ts +100 -6
- package/src/providers/chat-message-normalization.ts +126 -4
- package/src/providers/cli-provider-effect-format.ts +53 -0
- package/src/providers/cli-provider-history-dedup.ts +75 -0
- package/src/providers/cli-provider-input-prompt.ts +133 -0
- package/src/providers/cli-provider-instance-types.ts +131 -0
- package/src/providers/cli-provider-instance.ts +2771 -499
- package/src/providers/cli-provider-status-helpers.ts +123 -0
- package/src/providers/cli-provider-transcript-merge.ts +114 -0
- package/src/providers/contracts.d.ts +55 -0
- package/src/providers/contracts.ts +207 -6
- package/src/providers/extension-provider-instance.ts +12 -7
- package/src/providers/external-sources.ts +218 -0
- package/src/providers/ide-provider-instance.ts +35 -12
- package/src/providers/manual-attendance.ts +105 -0
- package/src/providers/native-history/antigravity-claim-registry.ts +138 -0
- package/src/providers/native-history/antigravity-cli-transcript.ts +1275 -0
- package/src/providers/native-history/claude-cli-transcript.ts +396 -0
- package/src/providers/native-history/codex-cli-transcript.ts +479 -0
- package/src/providers/native-history/constants.ts +19 -0
- package/src/providers/native-history/dispatcher.ts +617 -0
- package/src/providers/native-history/hermes-cli-transcript.ts +312 -0
- package/src/providers/native-history/index.ts +30 -0
- package/src/providers/provider-instance-manager.ts +71 -0
- package/src/providers/provider-instance.ts +24 -1
- package/src/providers/provider-loader.ts +700 -55
- package/src/providers/provider-schema.ts +93 -14
- package/src/providers/provider-trust.ts +114 -0
- package/src/providers/read-chat-contract.ts +76 -16
- package/src/providers/sdk/README.md +49 -0
- package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
- package/src/providers/sdk/v1/builders/cli/detect-status.ts +313 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval.ts +261 -0
- package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
- package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
- package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
- package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
- package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
- package/src/providers/sdk/v1/index.ts +152 -0
- package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
- package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
- package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +538 -0
- package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
- package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
- package/src/providers/sdk/v1/types/cli/index.ts +365 -0
- package/src/providers/sdk/v1/types/common/index.ts +229 -0
- package/src/providers/sdk/v1/validators/index.ts +19 -0
- package/src/providers/sdk/v1/validators/manifest.ts +110 -0
- package/src/providers/sdk/v1/validators/taint.ts +309 -0
- package/src/providers/spec/adapter.ts +246 -0
- package/src/providers/spec/cli-adapter.ts +1064 -0
- package/src/providers/spec/evaluator.ts +407 -0
- package/src/providers/spec/fsm-driver.ts +1528 -0
- package/src/providers/spec/fsm-evaluator.ts +290 -0
- package/src/providers/spec/fsm-loader.ts +128 -0
- package/src/providers/spec/fsm-types.ts +301 -0
- package/src/providers/spec/native-history-executor.ts +1241 -0
- package/src/providers/spec/pre-launch-trust.ts +104 -0
- package/src/providers/spec/route.ts +51 -0
- package/src/providers/spec/types.ts +284 -0
- package/src/providers/status-monitor.d.ts +7 -7
- package/src/providers/status-monitor.ts +37 -22
- package/src/providers/transcript-v2.ts +567 -0
- package/src/providers/types/interactive-prompt.ts +536 -0
- package/src/providers/version-archive.ts +64 -24
- package/src/providers/working-dir.ts +23 -0
- package/src/repo-mesh-types.ts +861 -14
- package/src/runtime-defaults.ts +39 -0
- package/src/session-host/managed-host.ts +218 -0
- package/src/session-host/runtime-surface.ts +20 -80
- package/src/sessions/registry.ts +44 -0
- package/src/shared-types-extra.ts +2 -4
- package/src/shared-types.d.ts +8 -0
- package/src/shared-types.ts +148 -55
- package/src/status/builders.ts +69 -6
- package/src/status/chat-tail-hot-sessions.ts +117 -2
- package/src/status/normalize.ts +2 -0
- package/src/status/reporter.ts +19 -1
- package/src/status/snapshot.ts +100 -27
- package/src/system/hash.ts +23 -0
- package/src/system/host-memory.ts +29 -12
- package/src/system/load-better-sqlite3.ts +68 -0
- package/src/types.ts +26 -6
- package/dist/cli-adapters/terminal-backends/xterm-backend.d.ts +0 -17
- package/dist/mesh/mesh-sync.d.ts +0 -53
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +0 -16
- package/src/cli-adapters/terminal-backends/xterm-backend.ts +0 -97
- package/src/mesh/mesh-sync.ts +0 -111
|
@@ -0,0 +1,1275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* antigravity-cli-transcript — Daemon-side built-in native history adapter for antigravity-cli.
|
|
3
|
+
*
|
|
4
|
+
* Antigravity CLI stores conversations in multiple complementary locations:
|
|
5
|
+
*
|
|
6
|
+
* 1. ~/.gemini/antigravity-cli/history.jsonl
|
|
7
|
+
* Lightweight index of user prompts, one row per turn:
|
|
8
|
+
* { conversationId: string, display: string, workspace: string, timestamp: number }
|
|
9
|
+
*
|
|
10
|
+
* 2. ~/.gemini/antigravity-cli/brain/<uuid>/.system_generated/logs/transcript*.jsonl
|
|
11
|
+
* Per-session full transcript, one row per agent step:
|
|
12
|
+
* { source: 'USER_EXPLICIT'|'MODEL', type: string, content: string, status: 'DONE'|..., created_at: number }
|
|
13
|
+
*
|
|
14
|
+
* 3. ~/.gemini/antigravity-cli/conversations/<uuid>.pb
|
|
15
|
+
* Legacy protobuf binary — schema not publicly documented. Adapter extracts
|
|
16
|
+
* printable UTF-8 text runs as best-effort content (no proto library needed).
|
|
17
|
+
*
|
|
18
|
+
* 4. ~/.gemini/antigravity-cli/conversations/<uuid>.db ← current format
|
|
19
|
+
* Per-session SQLite database. Recent antigravity migrated conversation
|
|
20
|
+
* storage from .pb (+ brain/*.jsonl) to a per-session SQLite db. The
|
|
21
|
+
* schema is a trajectory of `steps`, NOT a simple messages(role,content)
|
|
22
|
+
* table:
|
|
23
|
+
* steps(idx INTEGER PK, step_type INTEGER, status INTEGER,
|
|
24
|
+
* step_payload BLOB [protobuf], ...)
|
|
25
|
+
* Each `step_payload` is a protobuf message. Empirically (introspected
|
|
26
|
+
* from real stores):
|
|
27
|
+
* - step_type 14 → a USER turn. The prompt text is the largest
|
|
28
|
+
* contiguous UTF-8 run inside the payload (field 19 subtree).
|
|
29
|
+
* - step_type 15 → a MODEL/assistant turn. The assistant's final
|
|
30
|
+
* natural-language answer lives at payload field 20 → field 1
|
|
31
|
+
* (identical to field 8). Field 20 → field 3 is the internal
|
|
32
|
+
* reasoning summary and is intentionally NOT surfaced.
|
|
33
|
+
* - other step types are tool calls / ephemeral system context.
|
|
34
|
+
* We read the blobs with a tiny dependency-free protobuf field walker
|
|
35
|
+
* (no proto schema / codegen needed) and map the two message step types.
|
|
36
|
+
* Because the daemon does NOT read this db, native history previously
|
|
37
|
+
* returned 0 rows for these sessions and read_chat fell back to the
|
|
38
|
+
* pty parser (which only echoes the user's own input) — assistant
|
|
39
|
+
* answers appeared lost even though they were on disk.
|
|
40
|
+
*
|
|
41
|
+
* Schema-drift resilience: the exact field path (20 → 1/8 for the answer,
|
|
42
|
+
* 19 → 2/3 for the prompt) is empirically verified against real stores, but
|
|
43
|
+
* antigravity may move it in a future build. So when the known path yields
|
|
44
|
+
* no text, instead of silently dropping the turn we fall back to a UTF-8
|
|
45
|
+
* printable-run scan of the payload (recoverMessageText) that recovers the
|
|
46
|
+
* answer/prompt even if the field number drifted — while explicitly
|
|
47
|
+
* EXCLUDING the reasoning subtree (20 → 3) so internal reasoning is never
|
|
48
|
+
* surfaced as the answer. Only when even that finds nothing beyond
|
|
49
|
+
* reasoning/metadata is the step dropped, and a content-free DEBUG
|
|
50
|
+
* breadcrumb is logged so a real drift is greppable rather than invisible.
|
|
51
|
+
*
|
|
52
|
+
* This adapter provides:
|
|
53
|
+
* - Full coverage from a per-session .db (current format) — preferred.
|
|
54
|
+
* - Full coverage when a brain transcript exists (legacy authoritative source).
|
|
55
|
+
* - Partial coverage (user prompts only) from history.jsonl as fallback.
|
|
56
|
+
* - Best-effort raw-string extraction from .pb files when no other source exists.
|
|
57
|
+
*
|
|
58
|
+
* watchPath (from provider.v1.json):
|
|
59
|
+
* ~/.gemini/antigravity-cli/history.jsonl
|
|
60
|
+
* ~/.gemini/antigravity-cli/brain/{uuid}/.system_generated/logs/transcript*.jsonl
|
|
61
|
+
* ~/.gemini/antigravity-cli/conversations/{uuid}.pb
|
|
62
|
+
* ~/.gemini/antigravity-cli/conversations/{uuid}.db
|
|
63
|
+
*
|
|
64
|
+
* OSS code (AGPL-3.0). Must not import from packages/ (proprietary).
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
import * as fs from 'fs';
|
|
68
|
+
import * as path from 'path';
|
|
69
|
+
import * as os from 'os';
|
|
70
|
+
import { loadBetterSqlite3 } from '../../system/load-better-sqlite3.js';
|
|
71
|
+
import { LOG } from '../../logging/logger.js';
|
|
72
|
+
|
|
73
|
+
// ─── Types ─────────────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
export type NativeHistoryRole = 'user' | 'assistant' | 'system';
|
|
76
|
+
export type NativeHistoryKind = 'standard' | 'tool' | 'session_start';
|
|
77
|
+
|
|
78
|
+
export interface NativeHistoryMessage {
|
|
79
|
+
ts: string;
|
|
80
|
+
receivedAt: number;
|
|
81
|
+
role: NativeHistoryRole;
|
|
82
|
+
content: string;
|
|
83
|
+
kind: NativeHistoryKind;
|
|
84
|
+
senderName?: string;
|
|
85
|
+
agent: 'antigravity-cli';
|
|
86
|
+
historySessionId: string;
|
|
87
|
+
workspace?: string;
|
|
88
|
+
/** Stable per-message identity (v2 contract). */
|
|
89
|
+
providerUnitKey?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface NativeHistorySession {
|
|
93
|
+
messages: NativeHistoryMessage[];
|
|
94
|
+
providerSessionId: string;
|
|
95
|
+
source: 'provider-native';
|
|
96
|
+
sourcePath: string;
|
|
97
|
+
sourceMtimeMs: number;
|
|
98
|
+
/**
|
|
99
|
+
* 'full' — brain transcript was available and parsed.
|
|
100
|
+
* 'partial' — only history.jsonl user-prompts are available.
|
|
101
|
+
* 'best-effort' — .pb file bytes were used for raw text extraction.
|
|
102
|
+
*/
|
|
103
|
+
nativeHistoryCoverage: 'full' | 'partial' | 'best-effort';
|
|
104
|
+
workspace?: string;
|
|
105
|
+
partialReason?: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface NativeHistorySessionMeta {
|
|
109
|
+
historySessionId: string;
|
|
110
|
+
sessionId: string;
|
|
111
|
+
sourcePath: string;
|
|
112
|
+
sourceMtimeMs: number;
|
|
113
|
+
messageCount: number;
|
|
114
|
+
firstMessageAt: number;
|
|
115
|
+
lastMessageAt: number;
|
|
116
|
+
sessionTitle?: string;
|
|
117
|
+
preview?: string;
|
|
118
|
+
workspace?: string;
|
|
119
|
+
agent: 'antigravity-cli';
|
|
120
|
+
source: 'provider-native';
|
|
121
|
+
nativeHistoryCoverage: 'full' | 'partial' | 'best-effort';
|
|
122
|
+
partialReason?: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ─── Internal helpers ───────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
function extractTimestampValue(value: unknown): number {
|
|
128
|
+
if (typeof value === 'number' && Number.isFinite(value) && value > 0) return value;
|
|
129
|
+
if (typeof value === 'string') {
|
|
130
|
+
const numeric = Number(value);
|
|
131
|
+
if (Number.isFinite(numeric) && numeric > 0) return numeric;
|
|
132
|
+
const parsed = Date.parse(value);
|
|
133
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
134
|
+
}
|
|
135
|
+
return 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function statMtimeMs(filePath: string): number {
|
|
139
|
+
try { return fs.statSync(filePath).mtimeMs; } catch { return 0; }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function isUuidLike(value: string): boolean {
|
|
143
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function antigravityRoot(): string {
|
|
147
|
+
return path.join(os.homedir(), '.gemini', 'antigravity-cli');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function historyJsonlPath(): string {
|
|
151
|
+
return path.join(antigravityRoot(), 'history.jsonl');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function brainRoot(): string {
|
|
155
|
+
return path.join(antigravityRoot(), 'brain');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function conversationsRoot(): string {
|
|
159
|
+
return path.join(antigravityRoot(), 'conversations');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function resolvePathInside(root: string, ...segments: string[]): string | null {
|
|
163
|
+
const rootPath = path.resolve(root);
|
|
164
|
+
const targetPath = path.resolve(rootPath, ...segments);
|
|
165
|
+
if (targetPath !== rootPath && !targetPath.startsWith(rootPath + path.sep)) return null;
|
|
166
|
+
return targetPath;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Find the brain transcript file for a given session UUID.
|
|
171
|
+
* Prefers transcript_full.jsonl over transcript.jsonl when both exist.
|
|
172
|
+
*/
|
|
173
|
+
function findBrainTranscriptPath(sessionId: string): string | null {
|
|
174
|
+
if (!isUuidLike(sessionId)) return null;
|
|
175
|
+
const logsRoot = resolvePathInside(brainRoot(), sessionId, '.system_generated', 'logs');
|
|
176
|
+
if (!logsRoot || !fs.existsSync(logsRoot)) return null;
|
|
177
|
+
|
|
178
|
+
const candidates = (['transcript_full.jsonl', 'transcript.jsonl'] as const)
|
|
179
|
+
.map((file) => resolvePathInside(logsRoot, file))
|
|
180
|
+
.filter((p): p is string => p !== null && fs.existsSync(p));
|
|
181
|
+
|
|
182
|
+
if (candidates.length === 0) {
|
|
183
|
+
// Look for any transcript*.jsonl
|
|
184
|
+
let entries: fs.Dirent[] = [];
|
|
185
|
+
try { entries = fs.readdirSync(logsRoot, { withFileTypes: true }); } catch { return null; }
|
|
186
|
+
const transcriptFiles = entries
|
|
187
|
+
.filter((e) => e.isFile() && /^transcript.*\.jsonl$/.test(e.name))
|
|
188
|
+
.map((e) => path.join(logsRoot, e.name));
|
|
189
|
+
if (transcriptFiles.length === 0) return null;
|
|
190
|
+
transcriptFiles.sort((a, b) => statMtimeMs(b) - statMtimeMs(a));
|
|
191
|
+
return transcriptFiles[0];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
candidates.sort((a, b) => statMtimeMs(b) - statMtimeMs(a));
|
|
195
|
+
return candidates[0];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* List all session UUIDs that have a brain transcript directory.
|
|
200
|
+
*/
|
|
201
|
+
function listBrainSessions(): Array<{ sessionId: string; transcriptPath: string; sourceMtimeMs: number }> {
|
|
202
|
+
const root = brainRoot();
|
|
203
|
+
let entries: fs.Dirent[] = [];
|
|
204
|
+
try { entries = fs.readdirSync(root, { withFileTypes: true }); } catch { return []; }
|
|
205
|
+
|
|
206
|
+
const results: Array<{ sessionId: string; transcriptPath: string; sourceMtimeMs: number }> = [];
|
|
207
|
+
for (const entry of entries) {
|
|
208
|
+
if (!entry.isDirectory() || !isUuidLike(entry.name)) continue;
|
|
209
|
+
const transcriptPath = findBrainTranscriptPath(entry.name);
|
|
210
|
+
if (!transcriptPath) continue;
|
|
211
|
+
results.push({
|
|
212
|
+
sessionId: entry.name,
|
|
213
|
+
transcriptPath,
|
|
214
|
+
sourceMtimeMs: statMtimeMs(transcriptPath),
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return results;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Strip USER_REQUEST XML wrapper from antigravity user prompts.
|
|
222
|
+
* Antigravity wraps structured user input in <USER_REQUEST>...</USER_REQUEST>.
|
|
223
|
+
*/
|
|
224
|
+
function extractUserRequestContent(content: string): string {
|
|
225
|
+
const raw = content.trim();
|
|
226
|
+
const match = raw.match(/<USER_REQUEST>\s*([\s\S]*?)\s*<\/USER_REQUEST>/i);
|
|
227
|
+
if (match) return match[1].trim();
|
|
228
|
+
return raw
|
|
229
|
+
.replace(/<ADDITIONAL_METADATA>[\s\S]*?<\/ADDITIONAL_METADATA>/gi, '')
|
|
230
|
+
.replace(/<USER_SETTINGS_CHANGE>[\s\S]*?<\/USER_SETTINGS_CHANGE>/gi, '')
|
|
231
|
+
.replace(/<\/?USER_REQUEST>/gi, '')
|
|
232
|
+
.trim();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Determine the NativeHistoryKind from an antigravity transcript row type.
|
|
237
|
+
*/
|
|
238
|
+
function antigravityRowKind(rowType: string): NativeHistoryKind {
|
|
239
|
+
if (rowType === 'PLANNER_RESPONSE') return 'standard';
|
|
240
|
+
if (rowType && rowType !== 'USER_INPUT') return 'tool';
|
|
241
|
+
return 'standard';
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ─── Brain transcript parser ─────────────────────────────────────────────────
|
|
245
|
+
|
|
246
|
+
interface TranscriptRow {
|
|
247
|
+
source?: string;
|
|
248
|
+
type?: string;
|
|
249
|
+
content?: string;
|
|
250
|
+
status?: string;
|
|
251
|
+
created_at?: unknown;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Parse a brain transcript JSONL file into NativeHistoryMessages.
|
|
256
|
+
* Only rows with status === 'DONE' are processed.
|
|
257
|
+
* USER_EXPLICIT / USER_INPUT rows → user role.
|
|
258
|
+
* MODEL rows → assistant role.
|
|
259
|
+
*/
|
|
260
|
+
function parseBrainTranscript(
|
|
261
|
+
filePath: string,
|
|
262
|
+
sessionId: string,
|
|
263
|
+
workspace?: string,
|
|
264
|
+
): NativeHistoryMessage[] | null {
|
|
265
|
+
let raw: string;
|
|
266
|
+
try { raw = fs.readFileSync(filePath, 'utf-8'); } catch { return null; }
|
|
267
|
+
|
|
268
|
+
const lines = raw.split('\n').filter(Boolean);
|
|
269
|
+
if (lines.length === 0) return null;
|
|
270
|
+
|
|
271
|
+
const records: NativeHistoryMessage[] = [];
|
|
272
|
+
const normalizedWorkspace = typeof workspace === 'string' ? workspace.trim() : '';
|
|
273
|
+
let fallbackTs = statMtimeMs(filePath) || Date.now();
|
|
274
|
+
|
|
275
|
+
for (const line of lines) {
|
|
276
|
+
let row: TranscriptRow | null = null;
|
|
277
|
+
try { row = JSON.parse(line) as TranscriptRow; } catch { continue; }
|
|
278
|
+
if (!row || typeof row !== 'object') continue;
|
|
279
|
+
if (row.status !== 'DONE') continue;
|
|
280
|
+
|
|
281
|
+
const rowSource = String(row.source ?? '').trim();
|
|
282
|
+
const rowType = String(row.type ?? '').trim();
|
|
283
|
+
const rawContent = typeof row.content === 'string' ? row.content : '';
|
|
284
|
+
const receivedAt = extractTimestampValue(row.created_at) || fallbackTs + records.length;
|
|
285
|
+
|
|
286
|
+
if (rowSource === 'USER_EXPLICIT' && rowType === 'USER_INPUT') {
|
|
287
|
+
const content = extractUserRequestContent(rawContent);
|
|
288
|
+
if (!content) continue;
|
|
289
|
+
const msg: NativeHistoryMessage = {
|
|
290
|
+
ts: new Date(receivedAt).toISOString(),
|
|
291
|
+
receivedAt,
|
|
292
|
+
role: 'user',
|
|
293
|
+
content,
|
|
294
|
+
kind: 'standard',
|
|
295
|
+
agent: 'antigravity-cli',
|
|
296
|
+
historySessionId: sessionId,
|
|
297
|
+
};
|
|
298
|
+
if (normalizedWorkspace) msg.workspace = normalizedWorkspace;
|
|
299
|
+
records.push(msg);
|
|
300
|
+
} else if (rowSource === 'MODEL') {
|
|
301
|
+
const content = rawContent.trim();
|
|
302
|
+
if (!content) continue;
|
|
303
|
+
const kind = antigravityRowKind(rowType);
|
|
304
|
+
const msg: NativeHistoryMessage = {
|
|
305
|
+
ts: new Date(receivedAt).toISOString(),
|
|
306
|
+
receivedAt,
|
|
307
|
+
role: 'assistant',
|
|
308
|
+
content,
|
|
309
|
+
kind,
|
|
310
|
+
agent: 'antigravity-cli',
|
|
311
|
+
historySessionId: sessionId,
|
|
312
|
+
};
|
|
313
|
+
if (kind === 'tool') msg.senderName = 'Tool';
|
|
314
|
+
if (normalizedWorkspace) msg.workspace = normalizedWorkspace;
|
|
315
|
+
records.push(msg);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return records.length > 0 ? records : null;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ─── History JSONL parser ────────────────────────────────────────────────────
|
|
323
|
+
|
|
324
|
+
interface HistoryRow {
|
|
325
|
+
conversationId: string;
|
|
326
|
+
display: string;
|
|
327
|
+
workspace: string;
|
|
328
|
+
receivedAt: number;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function readHistoryRows(): HistoryRow[] {
|
|
332
|
+
const sourcePath = historyJsonlPath();
|
|
333
|
+
let lines: string[] = [];
|
|
334
|
+
try { lines = fs.readFileSync(sourcePath, 'utf-8').split('\n').filter(Boolean); } catch { return []; }
|
|
335
|
+
|
|
336
|
+
const rows: HistoryRow[] = [];
|
|
337
|
+
for (const line of lines) {
|
|
338
|
+
let parsed: unknown = null;
|
|
339
|
+
try { parsed = JSON.parse(line); } catch { continue; }
|
|
340
|
+
if (!parsed || typeof parsed !== 'object') continue;
|
|
341
|
+
|
|
342
|
+
const row = parsed as Record<string, unknown>;
|
|
343
|
+
const conversationId = String(row.conversationId ?? '').trim();
|
|
344
|
+
const display = typeof row.display === 'string' ? row.display.trim() : '';
|
|
345
|
+
const workspace = typeof row.workspace === 'string' ? row.workspace.trim() : '';
|
|
346
|
+
const receivedAt = extractTimestampValue(row.timestamp);
|
|
347
|
+
|
|
348
|
+
if (!conversationId || !isUuidLike(conversationId) || !display || !receivedAt) continue;
|
|
349
|
+
rows.push({ conversationId, display, workspace, receivedAt });
|
|
350
|
+
}
|
|
351
|
+
return rows;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ─── Protobuf best-effort text extraction ────────────────────────────────────
|
|
355
|
+
|
|
356
|
+
const MIN_PRINTABLE_RUN = 8;
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Extract printable UTF-8 text runs from raw binary data (similar to the `strings` command).
|
|
360
|
+
* Runs of printable ASCII/UTF-8 characters of length >= MIN_PRINTABLE_RUN are collected.
|
|
361
|
+
* This is used as a best-effort fallback when the .pb schema is not available.
|
|
362
|
+
*/
|
|
363
|
+
function extractStringsFromBuffer(buf: Buffer): string[] {
|
|
364
|
+
const strings: string[] = [];
|
|
365
|
+
let current: number[] = [];
|
|
366
|
+
|
|
367
|
+
for (let i = 0; i < buf.length; i++) {
|
|
368
|
+
const byte = buf[i];
|
|
369
|
+
// Accept printable ASCII (32-126) and common whitespace (9=tab, 10=LF, 13=CR)
|
|
370
|
+
if ((byte >= 32 && byte <= 126) || byte === 9 || byte === 10 || byte === 13) {
|
|
371
|
+
current.push(byte);
|
|
372
|
+
} else {
|
|
373
|
+
if (current.length >= MIN_PRINTABLE_RUN) {
|
|
374
|
+
const str = Buffer.from(current).toString('utf-8').trim();
|
|
375
|
+
if (str) strings.push(str);
|
|
376
|
+
}
|
|
377
|
+
current = [];
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// Flush remaining
|
|
381
|
+
if (current.length >= MIN_PRINTABLE_RUN) {
|
|
382
|
+
const str = Buffer.from(current).toString('utf-8').trim();
|
|
383
|
+
if (str) strings.push(str);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return strings;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Read a .pb conversation file and extract best-effort text content.
|
|
391
|
+
* Returns a single NativeHistoryMessage with the extracted content,
|
|
392
|
+
* or null if nothing readable was found.
|
|
393
|
+
*/
|
|
394
|
+
function parsePbFile(
|
|
395
|
+
filePath: string,
|
|
396
|
+
sessionId: string,
|
|
397
|
+
): NativeHistoryMessage[] | null {
|
|
398
|
+
let buf: Buffer;
|
|
399
|
+
try { buf = fs.readFileSync(filePath); } catch { return null; }
|
|
400
|
+
if (buf.length === 0) return null;
|
|
401
|
+
|
|
402
|
+
const strings = extractStringsFromBuffer(buf);
|
|
403
|
+
// Filter out very short or likely-binary strings
|
|
404
|
+
const meaningful = strings.filter((s) => s.length >= MIN_PRINTABLE_RUN && /\w/.test(s));
|
|
405
|
+
if (meaningful.length === 0) return null;
|
|
406
|
+
|
|
407
|
+
const content = meaningful.join('\n');
|
|
408
|
+
const sourceMtimeMs = statMtimeMs(filePath);
|
|
409
|
+
|
|
410
|
+
return [
|
|
411
|
+
{
|
|
412
|
+
ts: new Date(sourceMtimeMs).toISOString(),
|
|
413
|
+
receivedAt: sourceMtimeMs,
|
|
414
|
+
role: 'assistant',
|
|
415
|
+
content,
|
|
416
|
+
kind: 'standard',
|
|
417
|
+
agent: 'antigravity-cli',
|
|
418
|
+
historySessionId: sessionId,
|
|
419
|
+
},
|
|
420
|
+
];
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// ─── SQLite (.db) conversation reader ────────────────────────────────────────
|
|
424
|
+
//
|
|
425
|
+
// Recent antigravity stores each conversation in a per-session SQLite db at
|
|
426
|
+
// conversations/<uuid>.db. See the file header for the schema. We decode the
|
|
427
|
+
// protobuf `step_payload` blobs with a minimal, dependency-free field walker —
|
|
428
|
+
// we only need two leaf strings (user prompt / assistant answer), so a full
|
|
429
|
+
// proto schema is unnecessary.
|
|
430
|
+
|
|
431
|
+
/** Antigravity step_type values that map to a chat message. */
|
|
432
|
+
const AGY_STEP_TYPE_USER = 14;
|
|
433
|
+
const AGY_STEP_TYPE_MODEL = 15;
|
|
434
|
+
|
|
435
|
+
interface ProtoField {
|
|
436
|
+
field: number;
|
|
437
|
+
wireType: number;
|
|
438
|
+
/** For wireType 2 (length-delimited): the raw bytes. */
|
|
439
|
+
bytes?: Buffer;
|
|
440
|
+
/** For wireType 0 (varint): the value. */
|
|
441
|
+
varint?: number;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Read a base-128 varint starting at `offset`. Returns [value, nextOffset].
|
|
446
|
+
* Values are read as JS numbers (safe: the fields we consume are small).
|
|
447
|
+
*/
|
|
448
|
+
function readVarint(buf: Buffer, offset: number): [number, number] {
|
|
449
|
+
let result = 0;
|
|
450
|
+
let shift = 0;
|
|
451
|
+
let i = offset;
|
|
452
|
+
while (i < buf.length) {
|
|
453
|
+
const byte = buf[i];
|
|
454
|
+
i += 1;
|
|
455
|
+
result += (byte & 0x7f) * Math.pow(2, shift);
|
|
456
|
+
if ((byte & 0x80) === 0) return [result, i];
|
|
457
|
+
shift += 7;
|
|
458
|
+
if (shift > 63) break; // malformed / oversized
|
|
459
|
+
}
|
|
460
|
+
return [result, i];
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Decode the top-level fields of a protobuf message. Best-effort: stops on the
|
|
465
|
+
* first malformed byte rather than throwing, so a partially-corrupt blob still
|
|
466
|
+
* yields the fields decoded so far.
|
|
467
|
+
*/
|
|
468
|
+
function decodeProtoFields(buf: Buffer): ProtoField[] {
|
|
469
|
+
const fields: ProtoField[] = [];
|
|
470
|
+
let i = 0;
|
|
471
|
+
while (i < buf.length) {
|
|
472
|
+
const [key, afterKey] = readVarint(buf, i);
|
|
473
|
+
if (afterKey === i) break;
|
|
474
|
+
i = afterKey;
|
|
475
|
+
const field = Math.floor(key / 8);
|
|
476
|
+
const wireType = key & 7;
|
|
477
|
+
if (field <= 0) break;
|
|
478
|
+
if (wireType === 0) {
|
|
479
|
+
const [value, next] = readVarint(buf, i);
|
|
480
|
+
if (next === i) break;
|
|
481
|
+
i = next;
|
|
482
|
+
fields.push({ field, wireType, varint: value });
|
|
483
|
+
} else if (wireType === 2) {
|
|
484
|
+
const [len, afterLen] = readVarint(buf, i);
|
|
485
|
+
i = afterLen;
|
|
486
|
+
if (len < 0 || i + len > buf.length) break;
|
|
487
|
+
fields.push({ field, wireType, bytes: buf.subarray(i, i + len) });
|
|
488
|
+
i += len;
|
|
489
|
+
} else if (wireType === 5) {
|
|
490
|
+
i += 4;
|
|
491
|
+
} else if (wireType === 1) {
|
|
492
|
+
i += 8;
|
|
493
|
+
} else {
|
|
494
|
+
break; // wireType 3/4 (groups) — unused by antigravity payloads
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return fields;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/** Return the bytes of the first length-delimited field with number `field`. */
|
|
501
|
+
function firstLenField(buf: Buffer, field: number): Buffer | null {
|
|
502
|
+
for (const f of decodeProtoFields(buf)) {
|
|
503
|
+
if (f.field === field && f.wireType === 2 && f.bytes) return f.bytes;
|
|
504
|
+
}
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/** Heuristic: is this buffer (mostly) printable UTF-8 text? */
|
|
509
|
+
function looksLikeText(buf: Buffer): boolean {
|
|
510
|
+
if (buf.length === 0) return false;
|
|
511
|
+
let printable = 0;
|
|
512
|
+
for (let i = 0; i < buf.length; i++) {
|
|
513
|
+
const b = buf[i];
|
|
514
|
+
// ASCII printable + common whitespace, or any high byte (UTF-8 lead/cont).
|
|
515
|
+
if ((b >= 32 && b <= 126) || b === 9 || b === 10 || b === 13 || b >= 0x80) printable += 1;
|
|
516
|
+
}
|
|
517
|
+
return printable / buf.length >= 0.9;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Antigravity prefixes some assistant answers with a literal `MARKER_V1`
|
|
522
|
+
* sentinel followed by a blank line. Strip it so the user-visible bubble starts
|
|
523
|
+
* at the real text.
|
|
524
|
+
*/
|
|
525
|
+
function stripAnswerMarker(text: string): string {
|
|
526
|
+
return text.replace(/^\s*MARKER_V1\s*/, '');
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Extract the assistant's final natural-language answer from a step_type 15
|
|
531
|
+
* payload: field 20 → field 1 (identical to field 8). Field 20 → field 3 is the
|
|
532
|
+
* private reasoning summary and is deliberately skipped. Returns '' if absent
|
|
533
|
+
* (e.g. a pure reasoning / tool-only model step, which carries no user-visible
|
|
534
|
+
* answer text).
|
|
535
|
+
*/
|
|
536
|
+
function extractModelAnswer(payload: Buffer): string {
|
|
537
|
+
const inner = firstLenField(payload, 20);
|
|
538
|
+
if (!inner) return '';
|
|
539
|
+
const answer = firstLenField(inner, 1) ?? firstLenField(inner, 8);
|
|
540
|
+
if (!answer || !looksLikeText(answer)) return '';
|
|
541
|
+
return stripAnswerMarker(answer.toString('utf-8')).trim();
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Extract the user prompt from a step_type 14 payload: field 19 → field 2 (the
|
|
546
|
+
* clean prompt text; field 19 → field 3 wraps the same string with a leading
|
|
547
|
+
* newline and is used only as a fallback). The USER_REQUEST XML wrapper, when
|
|
548
|
+
* present, is unwrapped to match the brain-transcript reader's output.
|
|
549
|
+
*/
|
|
550
|
+
function extractUserPrompt(payload: Buffer): string {
|
|
551
|
+
const inner = firstLenField(payload, 19);
|
|
552
|
+
if (!inner) return '';
|
|
553
|
+
const raw = firstLenField(inner, 2) ?? firstLenField(inner, 3);
|
|
554
|
+
if (!raw || !looksLikeText(raw)) return '';
|
|
555
|
+
const text = raw.toString('utf-8').trim();
|
|
556
|
+
if (!text) return '';
|
|
557
|
+
return extractUserRequestContent(text);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* The model step's private reasoning summary lives at field 20 → field 3. We
|
|
562
|
+
* surface it nowhere, but we DO need it: the schema-drift recovery below scans
|
|
563
|
+
* the raw payload for a plausible answer run, and the reasoning is itself a long
|
|
564
|
+
* natural-language run — so we extract it here purely to EXCLUDE it and avoid
|
|
565
|
+
* accidentally surfacing internal reasoning as the assistant answer.
|
|
566
|
+
*/
|
|
567
|
+
function extractModelReasoning(payload: Buffer): string {
|
|
568
|
+
const inner = firstLenField(payload, 20);
|
|
569
|
+
if (!inner) return '';
|
|
570
|
+
const reasoning = firstLenField(inner, 3);
|
|
571
|
+
if (!reasoning || !looksLikeText(reasoning)) return '';
|
|
572
|
+
return reasoning.toString('utf-8').trim();
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/** Top-level protobuf field numbers present in a payload (for drift breadcrumbs). */
|
|
576
|
+
function topLevelFieldNumbers(payload: Buffer): number[] {
|
|
577
|
+
return decodeProtoFields(payload).map((f) => f.field);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const MIN_RECOVERED_MESSAGE_CHARS = 12;
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Split a payload into UTF-8 text runs, schema-agnostically. Unlike
|
|
584
|
+
* extractStringsFromBuffer (ASCII-only, used for legacy .pb), this is UTF-8 aware
|
|
585
|
+
* so CJK / accented answers survive intact: we decode the whole blob as UTF-8
|
|
586
|
+
* (invalid byte sequences collapse to U+FFFD) and split on runs of C0/C1 control
|
|
587
|
+
* chars + the replacement char. Protobuf framing bytes (field tags, varint length
|
|
588
|
+
* prefixes) are almost always control or invalid-UTF-8, so each natural-language
|
|
589
|
+
* string field emerges as its own run while binary framing is discarded.
|
|
590
|
+
*/
|
|
591
|
+
function extractUtf8TextRuns(buf: Buffer): string[] {
|
|
592
|
+
if (buf.length === 0) return [];
|
|
593
|
+
const decoded = buf.toString('utf-8');
|
|
594
|
+
// Keep tab/newline/CR (0x09/0x0A/0x0D) inside runs — answers contain newlines.
|
|
595
|
+
// Everything else in C0 (incl. 0x1A, the field-3 tag that separates reasoning
|
|
596
|
+
// from the answer), DEL, and the replacement char are run separators.
|
|
597
|
+
const parts = decoded.split(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F\uFFFD]+/);
|
|
598
|
+
const runs: string[] = [];
|
|
599
|
+
for (const part of parts) {
|
|
600
|
+
const trimmed = part.trim();
|
|
601
|
+
if (trimmed.length >= MIN_PRINTABLE_RUN) runs.push(trimmed);
|
|
602
|
+
}
|
|
603
|
+
return runs;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Is this run plausibly a user-visible prose message, as opposed to the other
|
|
608
|
+
* text the payload also carries — internal reasoning (excluded separately),
|
|
609
|
+
* tool-call JSON arguments, code blobs, file paths, and uuid/session-id
|
|
610
|
+
* metadata? These filters were tuned against real antigravity stores so that a
|
|
611
|
+
* blind printable-run scan recovers a genuinely drifted answer while surfacing
|
|
612
|
+
* NONE of the tool-call / metadata runs that legitimately answer-less steps
|
|
613
|
+
* carry (verified: zero false recoveries across real conversation dbs).
|
|
614
|
+
*/
|
|
615
|
+
function isPlausibleMessageText(s: string): boolean {
|
|
616
|
+
if (s.length < MIN_RECOVERED_MESSAGE_CHARS) return false;
|
|
617
|
+
if (!/[A-Za-zÀ-]/.test(s)) return false; // must contain letters (incl. CJK)
|
|
618
|
+
if (/^(file:\/\/|[A-Za-z]:[\\/]|\/[A-Za-z0-9._-]+\/)/.test(s)) return false; // path/URI
|
|
619
|
+
// Prose is multi-word: real answers have several spaces; uuids / ids / tokens
|
|
620
|
+
// have none. This is the single strongest prose-vs-metadata discriminator.
|
|
621
|
+
if ((s.match(/ /g) ?? []).length < 2) return false;
|
|
622
|
+
// Reject structured tool-call args / JSON / code blobs. A model tool step
|
|
623
|
+
// carries its arguments as JSON (e.g. {"Query":...}, {"CommandLine":...});
|
|
624
|
+
// those must never be surfaced as an assistant answer.
|
|
625
|
+
if (/[[{]\s*"/.test(s)) return false;
|
|
626
|
+
const structural = (s.match(/[{}[\]":\\]/g) ?? []).length;
|
|
627
|
+
if (structural / s.length > 0.12) return false;
|
|
628
|
+
return true;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Schema-agnostic recovery of a message's text when the known field path yields
|
|
633
|
+
* nothing (a possible antigravity step_payload schema drift). Scans the payload
|
|
634
|
+
* for UTF-8 text runs and returns the longest plausible message run, EXCLUDING
|
|
635
|
+
* any run that matches one of `excludeTexts` (e.g. the reasoning subtree) so we
|
|
636
|
+
* never surface internal reasoning as the answer. Returns '' when nothing beyond
|
|
637
|
+
* reasoning/metadata is present — i.e. a legitimately answer-less step.
|
|
638
|
+
*/
|
|
639
|
+
function recoverMessageText(payload: Buffer, excludeTexts: string[]): string {
|
|
640
|
+
const exclusions = excludeTexts.map((t) => t.trim()).filter(Boolean);
|
|
641
|
+
let best = '';
|
|
642
|
+
for (const run of extractUtf8TextRuns(payload)) {
|
|
643
|
+
const candidate = stripAnswerMarker(run).trim();
|
|
644
|
+
if (!isPlausibleMessageText(candidate)) continue;
|
|
645
|
+
// Drop runs that are (or are contained in / contain) an excluded subtree.
|
|
646
|
+
if (exclusions.some((e) => e === candidate || e.includes(candidate) || candidate.includes(e))) {
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
if (candidate.length > best.length) best = candidate;
|
|
650
|
+
}
|
|
651
|
+
return best;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
interface AgyDbStepRow {
|
|
655
|
+
idx: number;
|
|
656
|
+
step_type: number;
|
|
657
|
+
step_payload: Buffer | null;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Parse a per-session conversations/<uuid>.db (SQLite) into NativeHistoryMessages.
|
|
662
|
+
* Returns null when the db is unreadable, empty, or yields no chat messages.
|
|
663
|
+
*/
|
|
664
|
+
/**
|
|
665
|
+
* True when an error thrown by better-sqlite3 open/read is a transient
|
|
666
|
+
* SQLITE_BUSY / "database is locked" condition rather than a permanent one.
|
|
667
|
+
*
|
|
668
|
+
* On win32 antigravity holds a mandatory WAL write/checkpoint lock while it
|
|
669
|
+
* persists a step; a readonly open racing that lock throws SQLITE_BUSY. That
|
|
670
|
+
* is transient — the answer IS already on disk — so it must be retried, NOT
|
|
671
|
+
* collapsed to "no session" (which erases the just-written assistant answer on
|
|
672
|
+
* a chat_history re-query). macOS advisory locking + WAL reader-doesn't-block-
|
|
673
|
+
* writer masks this, which is why it is win32-specific.
|
|
674
|
+
*/
|
|
675
|
+
function isSqliteBusyError(err: unknown): boolean {
|
|
676
|
+
if (!err) return false;
|
|
677
|
+
const code = (err as any).code;
|
|
678
|
+
if (typeof code === 'string' && code.includes('SQLITE_BUSY')) return true;
|
|
679
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
680
|
+
return /SQLITE_BUSY|database is locked|database table is locked/i.test(msg);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const AGY_DB_BUSY_TIMEOUT_MS = 3000;
|
|
684
|
+
const AGY_DB_MAX_ATTEMPTS = 4;
|
|
685
|
+
const AGY_DB_RETRY_BACKOFF_MS = [50, 100, 150];
|
|
686
|
+
|
|
687
|
+
function sleepBusy(ms: number): void {
|
|
688
|
+
// Synchronous busy-wait: parseConversationDb is a sync function called from a
|
|
689
|
+
// sync read path, and better-sqlite3 itself is synchronous. The waits are
|
|
690
|
+
// tiny (≤150ms) and only occur under genuine lock contention, so a short
|
|
691
|
+
// spin-sleep is acceptable and keeps the call site synchronous.
|
|
692
|
+
const end = Date.now() + ms;
|
|
693
|
+
while (Date.now() < end) { /* spin */ }
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function parseConversationDb(
|
|
697
|
+
filePath: string,
|
|
698
|
+
sessionId: string,
|
|
699
|
+
workspace?: string,
|
|
700
|
+
): NativeHistoryMessage[] | null {
|
|
701
|
+
let Database: any;
|
|
702
|
+
try {
|
|
703
|
+
Database = loadBetterSqlite3();
|
|
704
|
+
} catch (err) {
|
|
705
|
+
// better-sqlite3 binding genuinely unavailable (ABI mismatch / not built
|
|
706
|
+
// into this bundle). This is the only true "cannot read at all" case — a
|
|
707
|
+
// real load failure, distinct from transient lock contention below. Warn
|
|
708
|
+
// once at WARN so it is greppable; the reader degrades gracefully (returns
|
|
709
|
+
// null → dispatcher falls back to brain/.pb).
|
|
710
|
+
LOG.warn(
|
|
711
|
+
'NativeHistory',
|
|
712
|
+
`antigravity .db reader could not load better-sqlite3 for ${path.basename(filePath)}: ${err instanceof Error ? err.message : String(err)} (native binding unavailable — assistant answers in this .db will not surface)`,
|
|
713
|
+
);
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
let rows: AgyDbStepRow[] | null = null;
|
|
718
|
+
let lastBusyErr: unknown;
|
|
719
|
+
|
|
720
|
+
for (let attempt = 1; attempt <= AGY_DB_MAX_ATTEMPTS; attempt++) {
|
|
721
|
+
let db: any;
|
|
722
|
+
try {
|
|
723
|
+
db = new Database(filePath, { readonly: true, fileMustExist: true });
|
|
724
|
+
// Ask SQLite itself to wait (rather than failing fast) if the WAL
|
|
725
|
+
// lock is momentarily held by antigravity. Set as early as possible
|
|
726
|
+
// after open so the prepare/all below inherits the wait.
|
|
727
|
+
try { db.pragma(`busy_timeout = ${AGY_DB_BUSY_TIMEOUT_MS}`); } catch { /* ignore */ }
|
|
728
|
+
rows = db
|
|
729
|
+
.prepare(
|
|
730
|
+
`SELECT idx, step_type, step_payload
|
|
731
|
+
FROM steps
|
|
732
|
+
WHERE step_type IN (${AGY_STEP_TYPE_USER}, ${AGY_STEP_TYPE_MODEL})
|
|
733
|
+
ORDER BY idx ASC`,
|
|
734
|
+
)
|
|
735
|
+
.all() as AgyDbStepRow[];
|
|
736
|
+
break; // success
|
|
737
|
+
} catch (err) {
|
|
738
|
+
if (isSqliteBusyError(err)) {
|
|
739
|
+
// Transient WAL lock contention. Do NOT collapse to null on the first
|
|
740
|
+
// failure — the assistant answer is already persisted; treating a busy
|
|
741
|
+
// lock as "no session" is exactly what erased answers on re-query.
|
|
742
|
+
// Retry with a small backoff; only give up after attempts exhausted.
|
|
743
|
+
lastBusyErr = err;
|
|
744
|
+
if (attempt < AGY_DB_MAX_ATTEMPTS) {
|
|
745
|
+
sleepBusy(AGY_DB_RETRY_BACKOFF_MS[attempt - 1] ?? 150);
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
LOG.warn(
|
|
749
|
+
'NativeHistory',
|
|
750
|
+
`antigravity .db ${path.basename(filePath)} stayed locked (SQLITE_BUSY) after ${AGY_DB_MAX_ATTEMPTS} attempts: ${err instanceof Error ? err.message : String(err)} (WAL write/checkpoint lock contention — assistant answers may transiently not surface this read)`,
|
|
751
|
+
);
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
// `steps` table absent / unexpected schema, or a genuine open/parse
|
|
755
|
+
// failure that is not lock contention — a real (but recoverable) shape
|
|
756
|
+
// mismatch. Log at debug so a schema drift in a future antigravity
|
|
757
|
+
// release is diagnosable without spamming logs for every legacy db.
|
|
758
|
+
LOG.debug(
|
|
759
|
+
'NativeHistory',
|
|
760
|
+
`antigravity .db ${path.basename(filePath)} not readable: ${err instanceof Error ? err.message : String(err)}`,
|
|
761
|
+
);
|
|
762
|
+
return null;
|
|
763
|
+
} finally {
|
|
764
|
+
try { db?.close(); } catch { /* ignore */ }
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
void lastBusyErr; // referenced only for retry bookkeeping above
|
|
769
|
+
|
|
770
|
+
if (!Array.isArray(rows) || rows.length === 0) return null;
|
|
771
|
+
|
|
772
|
+
const normalizedWorkspace = typeof workspace === 'string' ? workspace.trim() : '';
|
|
773
|
+
const baseTs = statMtimeMs(filePath) || Date.now();
|
|
774
|
+
const messages: NativeHistoryMessage[] = [];
|
|
775
|
+
|
|
776
|
+
for (const row of rows) {
|
|
777
|
+
const payload = row.step_payload;
|
|
778
|
+
if (!payload || !Buffer.isBuffer(payload) || payload.length === 0) continue;
|
|
779
|
+
|
|
780
|
+
// Steps are ordered by idx; the db carries no per-step timestamp we can
|
|
781
|
+
// trust as ms, so synthesize a monotonically increasing receivedAt that
|
|
782
|
+
// preserves order (idx-derived) around the file mtime.
|
|
783
|
+
const receivedAt = baseTs + messages.length;
|
|
784
|
+
|
|
785
|
+
if (row.step_type === AGY_STEP_TYPE_USER) {
|
|
786
|
+
let content = extractUserPrompt(payload);
|
|
787
|
+
if (!content) {
|
|
788
|
+
// Primary field path (field 19 → 2/3) missed. Recover schema-agnostically
|
|
789
|
+
// rather than silently drop a user turn: scan the payload for the longest
|
|
790
|
+
// plausible prompt run (metadata/paths/tokens are filtered out). There is
|
|
791
|
+
// no reasoning subtree to exclude on the user side.
|
|
792
|
+
const recovered = extractUserRequestContent(recoverMessageText(payload, []));
|
|
793
|
+
if (recovered) {
|
|
794
|
+
content = recovered;
|
|
795
|
+
LOG.debug(
|
|
796
|
+
'NativeHistory',
|
|
797
|
+
`antigravity .db ${path.basename(filePath)} step ${row.idx} (type ${row.step_type}): user prompt absent at field 19; recovered ${content.length} chars via printable-run fallback — possible step_payload schema drift`,
|
|
798
|
+
);
|
|
799
|
+
} else {
|
|
800
|
+
LOG.debug(
|
|
801
|
+
'NativeHistory',
|
|
802
|
+
`antigravity .db ${path.basename(filePath)} step ${row.idx} (type ${row.step_type}) dropped: no user prompt text (payload ${payload.length}B, top-level fields [${topLevelFieldNumbers(payload).join(',')}])`,
|
|
803
|
+
);
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
const msg: NativeHistoryMessage = {
|
|
808
|
+
ts: new Date(receivedAt).toISOString(),
|
|
809
|
+
receivedAt,
|
|
810
|
+
role: 'user',
|
|
811
|
+
content,
|
|
812
|
+
kind: 'standard',
|
|
813
|
+
agent: 'antigravity-cli',
|
|
814
|
+
historySessionId: sessionId,
|
|
815
|
+
};
|
|
816
|
+
if (normalizedWorkspace) msg.workspace = normalizedWorkspace;
|
|
817
|
+
messages.push(msg);
|
|
818
|
+
} else if (row.step_type === AGY_STEP_TYPE_MODEL) {
|
|
819
|
+
let content = extractModelAnswer(payload);
|
|
820
|
+
if (!content) {
|
|
821
|
+
// The known answer path (field 20 → 1/8) yielded nothing. This is either
|
|
822
|
+
// (a) a legitimate reasoning-only / tool-planning step — the common case,
|
|
823
|
+
// which carries no user-visible answer — or (b) antigravity moved the
|
|
824
|
+
// answer to a different field/subtree (schema drift). Attempt a
|
|
825
|
+
// schema-agnostic recovery that EXCLUDES the reasoning subtree (field
|
|
826
|
+
// 20 → 3) so internal reasoning is never surfaced as the answer.
|
|
827
|
+
const reasoning = extractModelReasoning(payload);
|
|
828
|
+
const recovered = recoverMessageText(payload, reasoning ? [reasoning] : []);
|
|
829
|
+
if (recovered) {
|
|
830
|
+
content = recovered;
|
|
831
|
+
LOG.debug(
|
|
832
|
+
'NativeHistory',
|
|
833
|
+
`antigravity .db ${path.basename(filePath)} step ${row.idx} (type ${row.step_type}): answer absent at field 20; recovered ${content.length} chars via printable-run fallback — possible step_payload schema drift`,
|
|
834
|
+
);
|
|
835
|
+
} else {
|
|
836
|
+
// No answer at the primary path and nothing recoverable beyond
|
|
837
|
+
// reasoning/metadata → drop. Content-free breadcrumb so a genuine
|
|
838
|
+
// future drift (answer present but unreadable) is greppable, and the
|
|
839
|
+
// expected reasoning-only case is distinguishable via reasoningOnly.
|
|
840
|
+
LOG.debug(
|
|
841
|
+
'NativeHistory',
|
|
842
|
+
`antigravity .db ${path.basename(filePath)} step ${row.idx} (type ${row.step_type}) dropped: no answer text (payload ${payload.length}B, top-level fields [${topLevelFieldNumbers(payload).join(',')}], reasoningOnly=${reasoning ? 'yes' : 'no'})`,
|
|
843
|
+
);
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
const msg: NativeHistoryMessage = {
|
|
848
|
+
ts: new Date(receivedAt).toISOString(),
|
|
849
|
+
receivedAt,
|
|
850
|
+
role: 'assistant',
|
|
851
|
+
content,
|
|
852
|
+
kind: 'standard',
|
|
853
|
+
agent: 'antigravity-cli',
|
|
854
|
+
historySessionId: sessionId,
|
|
855
|
+
};
|
|
856
|
+
if (normalizedWorkspace) msg.workspace = normalizedWorkspace;
|
|
857
|
+
messages.push(msg);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
return messages.length > 0 ? messages : null;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* Assemble the history.jsonl user-prompt index for a single session id into a
|
|
866
|
+
* partial NativeHistorySession (user prompts only — history.jsonl never carries
|
|
867
|
+
* assistant answers). Returns null when the index has no rows for this session.
|
|
868
|
+
* Shared by the history.jsonl read case and the .db sibling fallback.
|
|
869
|
+
*/
|
|
870
|
+
function readHistoryJsonlSession(
|
|
871
|
+
resolvedSessionId: string,
|
|
872
|
+
workspace?: string,
|
|
873
|
+
): NativeHistorySession | null {
|
|
874
|
+
if (!isUuidLike(resolvedSessionId)) return null;
|
|
875
|
+
const rows = readHistoryRows().filter((r) => r.conversationId === resolvedSessionId);
|
|
876
|
+
if (rows.length === 0) return null;
|
|
877
|
+
|
|
878
|
+
rows.sort((a, b) => a.receivedAt - b.receivedAt);
|
|
879
|
+
const firstWorkspace = workspace || rows.find((r) => r.workspace)?.workspace || '';
|
|
880
|
+
|
|
881
|
+
const messages: NativeHistoryMessage[] = [];
|
|
882
|
+
if (firstWorkspace) {
|
|
883
|
+
messages.push({
|
|
884
|
+
ts: new Date(rows[0].receivedAt).toISOString(),
|
|
885
|
+
receivedAt: rows[0].receivedAt,
|
|
886
|
+
role: 'system',
|
|
887
|
+
content: firstWorkspace,
|
|
888
|
+
kind: 'session_start',
|
|
889
|
+
agent: 'antigravity-cli',
|
|
890
|
+
historySessionId: resolvedSessionId,
|
|
891
|
+
workspace: firstWorkspace,
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
for (const row of rows) {
|
|
896
|
+
const msg: NativeHistoryMessage = {
|
|
897
|
+
ts: new Date(row.receivedAt).toISOString(),
|
|
898
|
+
receivedAt: row.receivedAt,
|
|
899
|
+
role: 'user',
|
|
900
|
+
content: row.display,
|
|
901
|
+
kind: 'standard',
|
|
902
|
+
agent: 'antigravity-cli',
|
|
903
|
+
historySessionId: resolvedSessionId,
|
|
904
|
+
};
|
|
905
|
+
if (row.workspace) msg.workspace = row.workspace;
|
|
906
|
+
messages.push(msg);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
return {
|
|
910
|
+
messages,
|
|
911
|
+
providerSessionId: resolvedSessionId,
|
|
912
|
+
source: 'provider-native',
|
|
913
|
+
sourcePath: historyJsonlPath(),
|
|
914
|
+
sourceMtimeMs: statMtimeMs(historyJsonlPath()),
|
|
915
|
+
nativeHistoryCoverage: 'partial',
|
|
916
|
+
partialReason: 'antigravity_cli_history_jsonl_contains_user_prompts_only',
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* ANTIGRAVITY-COMPLETION-DASHBOARD-GAP: recover a bound session's transcript
|
|
922
|
+
* from the legacy sibling sources when its per-session .db read came back empty
|
|
923
|
+
* (transient WAL lock, assistant step not yet flushed, or a decode miss on a
|
|
924
|
+
* future step_payload schema). Tried in descending fidelity for the SAME
|
|
925
|
+
* session id — never cross-binds to another conversation:
|
|
926
|
+
* 1. brain transcript (full coverage, authoritative when present),
|
|
927
|
+
* 2. sibling conversations/<uuid>.pb (best-effort raw text),
|
|
928
|
+
* 3. history.jsonl (partial — user prompts only).
|
|
929
|
+
* Returns null when no sibling yields any message.
|
|
930
|
+
*/
|
|
931
|
+
function readAntigravitySiblingFallback(
|
|
932
|
+
sessionId: string,
|
|
933
|
+
workspace?: string,
|
|
934
|
+
): NativeHistorySession | null {
|
|
935
|
+
if (!isUuidLike(sessionId)) return null;
|
|
936
|
+
|
|
937
|
+
// (1) brain transcript — full coverage when antigravity actually wrote it.
|
|
938
|
+
const brainPath = findBrainTranscriptPath(sessionId);
|
|
939
|
+
if (brainPath && statMtimeMs(brainPath) > 0) {
|
|
940
|
+
const brainMessages = parseBrainTranscript(brainPath, sessionId, workspace);
|
|
941
|
+
if (brainMessages && brainMessages.length > 0) {
|
|
942
|
+
return {
|
|
943
|
+
messages: brainMessages,
|
|
944
|
+
providerSessionId: sessionId,
|
|
945
|
+
source: 'provider-native',
|
|
946
|
+
sourcePath: brainPath,
|
|
947
|
+
sourceMtimeMs: statMtimeMs(brainPath),
|
|
948
|
+
nativeHistoryCoverage: 'full',
|
|
949
|
+
workspace,
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// (2) sibling .pb — best-effort raw text extraction.
|
|
955
|
+
const pbPath = resolvePathInside(conversationsRoot(), `${sessionId}.pb`);
|
|
956
|
+
if (pbPath && fs.existsSync(pbPath)) {
|
|
957
|
+
const pbMessages = parsePbFile(pbPath, sessionId);
|
|
958
|
+
if (pbMessages && pbMessages.length > 0) {
|
|
959
|
+
return {
|
|
960
|
+
messages: pbMessages,
|
|
961
|
+
providerSessionId: sessionId,
|
|
962
|
+
source: 'provider-native',
|
|
963
|
+
sourcePath: pbPath,
|
|
964
|
+
sourceMtimeMs: statMtimeMs(pbPath),
|
|
965
|
+
nativeHistoryCoverage: 'best-effort',
|
|
966
|
+
partialReason: 'antigravity_cli_pb_raw_text_extraction',
|
|
967
|
+
workspace,
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// (3) history.jsonl — partial (user prompts only). Last resort so the
|
|
973
|
+
// dashboard at least shows the prompt when no answer source is readable.
|
|
974
|
+
return readHistoryJsonlSession(sessionId, workspace);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* Read a single Antigravity CLI session by its source file path.
|
|
981
|
+
*
|
|
982
|
+
* `sessionPath` is the absolute path to one of:
|
|
983
|
+
* - A brain transcript JSONL: ~/.gemini/antigravity-cli/brain/<uuid>/.system_generated/logs/transcript*.jsonl
|
|
984
|
+
* - The shared history.jsonl: ~/.gemini/antigravity-cli/history.jsonl
|
|
985
|
+
* - A conversation SQLite db: ~/.gemini/antigravity-cli/conversations/<uuid>.db
|
|
986
|
+
* - A conversation protobuf: ~/.gemini/antigravity-cli/conversations/<uuid>.pb
|
|
987
|
+
*
|
|
988
|
+
* The session UUID is inferred from the directory name (brain), filename (pb), or
|
|
989
|
+
* must be provided explicitly via `sessionId` for history.jsonl paths.
|
|
990
|
+
*
|
|
991
|
+
* Returns `null` when the file is missing, empty, or yields no parseable messages.
|
|
992
|
+
*/
|
|
993
|
+
export function readSession(
|
|
994
|
+
sessionPath: string,
|
|
995
|
+
sessionId?: string,
|
|
996
|
+
workspace?: string,
|
|
997
|
+
): NativeHistorySession | null {
|
|
998
|
+
if (!sessionPath || !path.isAbsolute(sessionPath)) return null;
|
|
999
|
+
if (!fs.existsSync(sessionPath)) return null;
|
|
1000
|
+
|
|
1001
|
+
const sourceMtimeMs = statMtimeMs(sessionPath);
|
|
1002
|
+
|
|
1003
|
+
// ── Case 1: brain transcript JSONL ──────────────────────────────────────
|
|
1004
|
+
const brainRootPath = brainRoot();
|
|
1005
|
+
if (sessionPath.startsWith(brainRootPath + path.sep) && sessionPath.endsWith('.jsonl')) {
|
|
1006
|
+
// Extract UUID from the path: brain/<uuid>/.system_generated/...
|
|
1007
|
+
const relative = sessionPath.slice(brainRootPath.length + 1);
|
|
1008
|
+
const uuidFromPath = relative.split(path.sep)[0];
|
|
1009
|
+
const resolvedSessionId = sessionId || (isUuidLike(uuidFromPath) ? uuidFromPath : '');
|
|
1010
|
+
if (!resolvedSessionId) return null;
|
|
1011
|
+
|
|
1012
|
+
const messages = parseBrainTranscript(sessionPath, resolvedSessionId, workspace);
|
|
1013
|
+
if (!messages || messages.length === 0) return null;
|
|
1014
|
+
|
|
1015
|
+
return {
|
|
1016
|
+
messages,
|
|
1017
|
+
providerSessionId: resolvedSessionId,
|
|
1018
|
+
source: 'provider-native',
|
|
1019
|
+
sourcePath: sessionPath,
|
|
1020
|
+
sourceMtimeMs,
|
|
1021
|
+
nativeHistoryCoverage: 'full',
|
|
1022
|
+
workspace,
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// ── Case 2a: .db conversation file (current per-session SQLite format) ───
|
|
1027
|
+
if (sessionPath.endsWith('.db')) {
|
|
1028
|
+
const dbSessionId = sessionId || path.basename(sessionPath, '.db');
|
|
1029
|
+
if (!isUuidLike(dbSessionId)) return null;
|
|
1030
|
+
|
|
1031
|
+
const messages = parseConversationDb(sessionPath, dbSessionId, workspace);
|
|
1032
|
+
if (messages && messages.length > 0) {
|
|
1033
|
+
return {
|
|
1034
|
+
messages,
|
|
1035
|
+
providerSessionId: dbSessionId,
|
|
1036
|
+
source: 'provider-native',
|
|
1037
|
+
sourcePath: sessionPath,
|
|
1038
|
+
sourceMtimeMs,
|
|
1039
|
+
nativeHistoryCoverage: 'full',
|
|
1040
|
+
workspace,
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// ANTIGRAVITY-COMPLETION-DASHBOARD-GAP: the bound .db yielded nothing this
|
|
1045
|
+
// read — transient (WAL lock / assistant step not yet flushed) or a decode
|
|
1046
|
+
// miss on a future schema. The dispatcher binds STRAIGHT to <uuid>.db once
|
|
1047
|
+
// the session id is known and never re-resolves to a sibling source, so a
|
|
1048
|
+
// null here previously collapsed the whole read to native-unavailable even
|
|
1049
|
+
// when the SAME session's assistant answer was recoverable from the legacy
|
|
1050
|
+
// brain transcript / .pb / history.jsonl. Fall back across those sibling
|
|
1051
|
+
// sources for the same session id before giving up. This is read-only,
|
|
1052
|
+
// scoped to the bound session, and cannot cross-bind to another conversation.
|
|
1053
|
+
const siblingFallback = readAntigravitySiblingFallback(dbSessionId, workspace);
|
|
1054
|
+
if (siblingFallback) return siblingFallback;
|
|
1055
|
+
|
|
1056
|
+
return null;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// ── Case 2b: .pb conversation file (legacy protobuf) ─────────────────────
|
|
1060
|
+
if (sessionPath.endsWith('.pb')) {
|
|
1061
|
+
const pbSessionId = sessionId || path.basename(sessionPath, '.pb');
|
|
1062
|
+
if (!isUuidLike(pbSessionId)) return null;
|
|
1063
|
+
|
|
1064
|
+
const messages = parsePbFile(sessionPath, pbSessionId);
|
|
1065
|
+
if (!messages || messages.length === 0) return null;
|
|
1066
|
+
|
|
1067
|
+
return {
|
|
1068
|
+
messages,
|
|
1069
|
+
providerSessionId: pbSessionId,
|
|
1070
|
+
source: 'provider-native',
|
|
1071
|
+
sourcePath: sessionPath,
|
|
1072
|
+
sourceMtimeMs,
|
|
1073
|
+
nativeHistoryCoverage: 'best-effort',
|
|
1074
|
+
partialReason: 'antigravity_cli_pb_raw_text_extraction',
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// ── Case 3: history.jsonl (user prompts index) ──────────────────────────
|
|
1079
|
+
if (path.basename(sessionPath) === 'history.jsonl') {
|
|
1080
|
+
return readHistoryJsonlSession(sessionId || '', workspace);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
return null;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
/**
|
|
1087
|
+
* List all Antigravity CLI sessions found across all watchPath locations.
|
|
1088
|
+
*
|
|
1089
|
+
* Priority:
|
|
1090
|
+
* 1. Brain transcript sessions (full coverage) — preferred source.
|
|
1091
|
+
* 2. History JSONL entries without a brain transcript (partial coverage).
|
|
1092
|
+
* 3. .pb conversation files without any other source (best-effort).
|
|
1093
|
+
*
|
|
1094
|
+
* Returns summary metadata sorted by most recently updated first.
|
|
1095
|
+
*/
|
|
1096
|
+
export async function listSessions(_watchPath: string): Promise<NativeHistorySessionMeta[]> {
|
|
1097
|
+
const results: NativeHistorySessionMeta[] = [];
|
|
1098
|
+
const seen = new Set<string>();
|
|
1099
|
+
|
|
1100
|
+
// ── Step 1: brain transcripts (full coverage) ──────────────────────────
|
|
1101
|
+
const historyRows = readHistoryRows();
|
|
1102
|
+
const workspaceBySession = new Map<string, string>();
|
|
1103
|
+
for (const row of historyRows) {
|
|
1104
|
+
if (row.workspace && !workspaceBySession.has(row.conversationId)) {
|
|
1105
|
+
workspaceBySession.set(row.conversationId, row.workspace);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
for (const { sessionId, transcriptPath, sourceMtimeMs } of listBrainSessions()) {
|
|
1110
|
+
const workspace = workspaceBySession.get(sessionId);
|
|
1111
|
+
const messages = parseBrainTranscript(transcriptPath, sessionId, workspace);
|
|
1112
|
+
if (!messages || messages.length === 0) continue;
|
|
1113
|
+
|
|
1114
|
+
const visible = messages.filter((m) => m.kind !== 'session_start');
|
|
1115
|
+
if (visible.length === 0) continue;
|
|
1116
|
+
|
|
1117
|
+
const firstMsg = visible[0];
|
|
1118
|
+
const lastMsg = visible[visible.length - 1];
|
|
1119
|
+
|
|
1120
|
+
results.push({
|
|
1121
|
+
historySessionId: sessionId,
|
|
1122
|
+
sessionId,
|
|
1123
|
+
sourcePath: transcriptPath,
|
|
1124
|
+
sourceMtimeMs,
|
|
1125
|
+
messageCount: visible.length,
|
|
1126
|
+
firstMessageAt: firstMsg.receivedAt || sourceMtimeMs,
|
|
1127
|
+
lastMessageAt: lastMsg.receivedAt || sourceMtimeMs,
|
|
1128
|
+
sessionTitle: lastMsg.content,
|
|
1129
|
+
preview: lastMsg.content,
|
|
1130
|
+
workspace,
|
|
1131
|
+
agent: 'antigravity-cli',
|
|
1132
|
+
source: 'provider-native',
|
|
1133
|
+
nativeHistoryCoverage: 'full',
|
|
1134
|
+
});
|
|
1135
|
+
seen.add(sessionId);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
// ── Step 2: history.jsonl entries without brain transcripts ────────────
|
|
1139
|
+
const grouped = new Map<
|
|
1140
|
+
string,
|
|
1141
|
+
{
|
|
1142
|
+
firstMessageAt: number;
|
|
1143
|
+
lastMessageAt: number;
|
|
1144
|
+
messageCount: number;
|
|
1145
|
+
sessionTitle?: string;
|
|
1146
|
+
preview?: string;
|
|
1147
|
+
workspace?: string;
|
|
1148
|
+
}
|
|
1149
|
+
>();
|
|
1150
|
+
for (const row of historyRows) {
|
|
1151
|
+
const existing = grouped.get(row.conversationId) ?? {
|
|
1152
|
+
firstMessageAt: row.receivedAt,
|
|
1153
|
+
lastMessageAt: row.receivedAt,
|
|
1154
|
+
messageCount: 0,
|
|
1155
|
+
};
|
|
1156
|
+
existing.messageCount += 1;
|
|
1157
|
+
existing.firstMessageAt = Math.min(existing.firstMessageAt, row.receivedAt);
|
|
1158
|
+
if (row.receivedAt >= existing.lastMessageAt) {
|
|
1159
|
+
existing.lastMessageAt = row.receivedAt;
|
|
1160
|
+
existing.sessionTitle = row.display;
|
|
1161
|
+
existing.preview = row.display;
|
|
1162
|
+
if (row.workspace) existing.workspace = row.workspace;
|
|
1163
|
+
}
|
|
1164
|
+
grouped.set(row.conversationId, existing);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
const historySrc = historyJsonlPath();
|
|
1168
|
+
const historyMtime = statMtimeMs(historySrc);
|
|
1169
|
+
|
|
1170
|
+
for (const [sessionId, info] of grouped.entries()) {
|
|
1171
|
+
if (seen.has(sessionId)) continue;
|
|
1172
|
+
results.push({
|
|
1173
|
+
historySessionId: sessionId,
|
|
1174
|
+
sessionId,
|
|
1175
|
+
sourcePath: historySrc,
|
|
1176
|
+
sourceMtimeMs: historyMtime,
|
|
1177
|
+
messageCount: info.messageCount,
|
|
1178
|
+
firstMessageAt: info.firstMessageAt,
|
|
1179
|
+
lastMessageAt: info.lastMessageAt,
|
|
1180
|
+
sessionTitle: info.sessionTitle,
|
|
1181
|
+
preview: info.preview,
|
|
1182
|
+
workspace: info.workspace,
|
|
1183
|
+
agent: 'antigravity-cli',
|
|
1184
|
+
source: 'provider-native',
|
|
1185
|
+
nativeHistoryCoverage: 'partial',
|
|
1186
|
+
partialReason: 'antigravity_cli_history_jsonl_contains_user_prompts_only',
|
|
1187
|
+
});
|
|
1188
|
+
seen.add(sessionId);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// ── Step 3: conversations/<uuid>.db and .pb without any other source ────
|
|
1192
|
+
//
|
|
1193
|
+
// Current antigravity writes per-session SQLite dbs; older sessions kept a
|
|
1194
|
+
// .pb protobuf. Both can coexist in conversations/. Discover both, but when
|
|
1195
|
+
// the same uuid has a .db, prefer it (full coverage) over the .pb (best
|
|
1196
|
+
// effort). brain/history sources already in `seen` still win over either.
|
|
1197
|
+
const convRoot = conversationsRoot();
|
|
1198
|
+
if (fs.existsSync(convRoot)) {
|
|
1199
|
+
let entries: fs.Dirent[] = [];
|
|
1200
|
+
try { entries = fs.readdirSync(convRoot, { withFileTypes: true }); } catch { /* ignore */ }
|
|
1201
|
+
|
|
1202
|
+
// Group by uuid so a .db supersedes a sibling .pb of the same session.
|
|
1203
|
+
const byUuid = new Map<string, { db?: string; pb?: string }>();
|
|
1204
|
+
for (const entry of entries) {
|
|
1205
|
+
if (!entry.isFile()) continue;
|
|
1206
|
+
const dbMatch = /^([0-9a-f-]+)\.db$/i.exec(entry.name);
|
|
1207
|
+
const pbMatch = /^([0-9a-f-]+)\.pb$/i.exec(entry.name);
|
|
1208
|
+
if (dbMatch && isUuidLike(dbMatch[1])) {
|
|
1209
|
+
const g = byUuid.get(dbMatch[1]) ?? {};
|
|
1210
|
+
g.db = path.join(convRoot, entry.name);
|
|
1211
|
+
byUuid.set(dbMatch[1], g);
|
|
1212
|
+
} else if (pbMatch && isUuidLike(pbMatch[1])) {
|
|
1213
|
+
const g = byUuid.get(pbMatch[1]) ?? {};
|
|
1214
|
+
g.pb = path.join(convRoot, entry.name);
|
|
1215
|
+
byUuid.set(pbMatch[1], g);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
for (const [uuid, files] of byUuid.entries()) {
|
|
1220
|
+
if (seen.has(uuid)) continue;
|
|
1221
|
+
|
|
1222
|
+
if (files.db) {
|
|
1223
|
+
// Parse the db so listSessions reports accurate counts/preview and the
|
|
1224
|
+
// session surfaces with full coverage (assistant answers included).
|
|
1225
|
+
const workspace = workspaceBySession.get(uuid);
|
|
1226
|
+
const messages = parseConversationDb(files.db, uuid, workspace);
|
|
1227
|
+
const dbMtime = statMtimeMs(files.db);
|
|
1228
|
+
if (messages && messages.length > 0) {
|
|
1229
|
+
const lastMsg = messages[messages.length - 1];
|
|
1230
|
+
const firstMsg = messages[0];
|
|
1231
|
+
results.push({
|
|
1232
|
+
historySessionId: uuid,
|
|
1233
|
+
sessionId: uuid,
|
|
1234
|
+
sourcePath: files.db,
|
|
1235
|
+
sourceMtimeMs: dbMtime,
|
|
1236
|
+
messageCount: messages.length,
|
|
1237
|
+
firstMessageAt: firstMsg.receivedAt || dbMtime,
|
|
1238
|
+
lastMessageAt: lastMsg.receivedAt || dbMtime,
|
|
1239
|
+
sessionTitle: lastMsg.content,
|
|
1240
|
+
preview: lastMsg.content,
|
|
1241
|
+
workspace,
|
|
1242
|
+
agent: 'antigravity-cli',
|
|
1243
|
+
source: 'provider-native',
|
|
1244
|
+
nativeHistoryCoverage: 'full',
|
|
1245
|
+
});
|
|
1246
|
+
seen.add(uuid);
|
|
1247
|
+
continue;
|
|
1248
|
+
}
|
|
1249
|
+
// db unreadable/empty (e.g. sqlite binding unavailable) → fall through
|
|
1250
|
+
// to the .pb best-effort entry below if one exists.
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
if (files.pb) {
|
|
1254
|
+
const pbMtime = statMtimeMs(files.pb);
|
|
1255
|
+
results.push({
|
|
1256
|
+
historySessionId: uuid,
|
|
1257
|
+
sessionId: uuid,
|
|
1258
|
+
sourcePath: files.pb,
|
|
1259
|
+
sourceMtimeMs: pbMtime,
|
|
1260
|
+
messageCount: 0,
|
|
1261
|
+
firstMessageAt: pbMtime,
|
|
1262
|
+
lastMessageAt: pbMtime,
|
|
1263
|
+
agent: 'antigravity-cli',
|
|
1264
|
+
source: 'provider-native',
|
|
1265
|
+
nativeHistoryCoverage: 'best-effort',
|
|
1266
|
+
partialReason: 'antigravity_cli_pb_raw_text_extraction',
|
|
1267
|
+
});
|
|
1268
|
+
seen.add(uuid);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
results.sort((a, b) => (b.lastMessageAt || 0) - (a.lastMessageAt || 0));
|
|
1274
|
+
return results;
|
|
1275
|
+
}
|