mixdog 1.4.0 → 1.4.2
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/package.json +3 -2
- package/scripts/build-shipped-history.mjs +115 -33
- package/scripts/check-voice-runtime-inputs.mjs +25 -0
- package/scripts/gate-local.mjs +183 -63
- package/scripts/latest-guard.mjs +21 -9
- package/scripts/lib/gate-scheduler.mjs +138 -32
- package/scripts/lib/gate-timing-reporter.mjs +16 -0
- package/scripts/lib/isolated-test-env.mjs +10 -5
- package/scripts/lib/run-node-tests.mjs +91 -66
- package/scripts/lib/scoped-temp.d.mts +16 -0
- package/scripts/lib/scoped-temp.mjs +124 -0
- package/scripts/lib/stale-temp.mjs +44 -0
- package/scripts/lib/test-failure-records.mjs +14 -1
- package/scripts/lib/test-failure-summary.mjs +16 -4
- package/scripts/lib/test-spawn-ledger.mjs +12 -3
- package/scripts/post-release-check.mjs +1 -6
- package/scripts/pptx-kit-index.mjs +17 -5
- package/scripts/suite-health.mjs +115 -24
- package/scripts/test-data-dir.mjs +10 -7
- package/scripts/test.mjs +9 -14
- package/src/agents/advisor/AGENT.md +4 -4
- package/src/agents/advisor/agent.json +1 -1
- package/src/agents/explore/AGENT.md +22 -0
- package/src/agents/explore/agent.json +6 -0
- package/src/app.mjs +4 -0
- package/src/cli.mjs +7 -0
- package/src/defaults/agents.json +7 -0
- package/src/defaults/shipped-history.json +23 -2
- package/src/defaults/skills/browser-use/SKILL.md +10 -5
- package/src/defaults/skills/code-tidy/SKILL.md +71 -102
- package/src/defaults/skills/code-tidy/references/agent-cleanup.md +66 -114
- package/src/defaults/skills/code-tidy/references/cleanup-report.md +19 -35
- package/src/defaults/skills/code-tidy/references/dead-code.md +3 -9
- package/src/defaults/skills/code-tidy/references/test-suite-slop.md +16 -27
- package/src/defaults/skills/computer-use/SKILL.md +16 -4
- package/src/defaults/skills/goal-management/SKILL.md +1 -1
- package/src/defaults/skills/setup/references/actions.md +12 -6
- package/src/defaults/skills/setup/references/surfaces.md +2 -2
- package/src/defaults/skills/setup-guide/SKILL.md +5 -2
- package/src/defaults/skills/setup-guide/model-recommendations.json +13 -0
- package/src/defaults/skills/skill-creator/SKILL.md +85 -190
- package/src/defaults/skills/skill-creator/references/design.md +70 -162
- package/src/defaults/skills/skill-creator/references/evaluation.md +33 -92
- package/src/defaults/skills/test-suite/SKILL.md +194 -0
- package/src/defaults/skills/test-suite/references/case-management.md +51 -0
- package/src/defaults/skills/test-suite/references/flaky-policy.md +39 -0
- package/src/defaults/skills/test-suite/references/reliability.md +49 -0
- package/src/defaults/skills/test-suite/references/suite-design.md +78 -0
- package/src/defaults/skills/test-suite/scripts/inventory.mjs +335 -0
- package/src/defaults/skills/visualize/SKILL.md +6 -2
- package/src/lib/rules-builder.cjs +35 -42
- package/src/output-styles/common.md +2 -2
- package/src/output-styles/simple.md +1 -1
- package/src/rules/agent/AGENT.md +5 -4
- package/src/rules/lead/LEAD.md +12 -7
- package/src/rules/shared/10-tool-workflow.md +3 -1
- package/src/rules/shared/20-research.md +3 -6
- package/src/rules/shared/40-editing.md +1 -0
- package/src/rules/shared/45-destructive.md +0 -2
- package/src/rules/shared/50-execution.md +2 -0
- package/src/rules/shared/60-verification.md +10 -9
- package/src/rules/shared/70-delivery.md +8 -9
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/abort-link.mjs +5 -1
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +10 -1
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +156 -24
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +13 -0
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +4 -1
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +2 -0
- package/src/runtime/agent/orchestrator/config.mjs +12 -7
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +8 -8
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +5 -26
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +3 -1
- package/src/runtime/agent/orchestrator/internal-agents.mjs +3 -14
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +122 -12
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -3
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +13 -2
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +152 -58
- package/src/runtime/agent/orchestrator/mcp/client.mjs +51 -18
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +88 -54
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +42 -26
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +54 -8
- package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +37 -31
- package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +8 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +31 -21
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +14 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +1 -10
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +40 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider-keys.mjs +34 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +14 -7
- package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/devin.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +248 -41
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -11
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +45 -6
- package/src/runtime/agent/orchestrator/providers/lib/first-output-window.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/lib/listen-on.mjs +14 -0
- package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +0 -18
- package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +138 -6
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +1 -10
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/model-cache.mjs +100 -33
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/oauth-secret-fields.fixtures.mjs +45 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +14 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +5 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +13 -3
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +13 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +81 -42
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +160 -31
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +22 -12
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +49 -22
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/registry.mjs +45 -2
- package/src/runtime/agent/orchestrator/providers/vertex-ai-auth.mjs +622 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai-maas.mjs +185 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai-models.mjs +445 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +567 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +41 -13
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +8 -6
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +7 -3
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +14 -5
- package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -0
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -5
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +171 -37
- package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +102 -12
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -8
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +23 -0
- package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +103 -23
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +160 -36
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +164 -23
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +139 -19
- package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +38 -7
- package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +100 -22
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -6
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +33 -3
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +9 -2
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +13 -5
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +23 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +23 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +39 -21
- package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +13 -8
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +50 -28
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +30 -13
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +63 -6
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +35 -13
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +24 -14
- package/src/runtime/agent/orchestrator/session/manager/session-scratch-environment.mjs +70 -0
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +0 -3
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +10 -6
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +56 -29
- package/src/runtime/agent/orchestrator/session/manager/turn-reap.mjs +125 -0
- package/src/runtime/agent/orchestrator/session/mixy-name.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +30 -7
- package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +154 -0
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +37 -8
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +252 -39
- package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +163 -64
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +164 -4
- package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +142 -18
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +61 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +23 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +46 -20
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +67 -31
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +25 -13
- package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +9 -5
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +58 -20
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +23 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -8
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +20 -15
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +96 -20
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +59 -10
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/lib/isolated-test-env.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +99 -2
- package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +103 -13
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +49 -15
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +45 -27
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +34 -10
- package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +28 -4
- package/src/runtime/agent/orchestrator/tools/lib/wait-until-fixture.mjs +26 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -2
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
- package/src/runtime/channels/lib/webhook/signature.mjs +4 -1
- package/src/runtime/channels/lib/whisper-port.mjs +29 -11
- package/src/runtime/channels/lib/whisper-server.mjs +10 -1
- package/src/runtime/computer-bridge/action-schema.mjs +2 -2
- package/src/runtime/computer-bridge/core-actions.mjs +2 -2
- package/src/runtime/computer-bridge/error-recovery.mjs +5 -4
- package/src/runtime/computer-bridge/linux-action-capabilities.mjs +24 -5
- package/src/runtime/computer-bridge/linux-readiness.d.mts +7 -2
- package/src/runtime/connectors/connector-accounts-mcp.mjs +236 -103
- package/src/runtime/connectors/gmail-app-password.mjs +3 -3
- package/src/runtime/explore/explorer.mjs +93 -0
- package/src/runtime/explore/tool-defs.mjs +35 -0
- package/src/runtime/explore/tool.mjs +123 -0
- package/src/runtime/local-provider/server-process.mjs +2 -1
- package/src/runtime/media/adapters/gemini-video.mjs +4 -1
- package/src/runtime/media/adapters/xai-media.mjs +2 -1
- package/src/runtime/memory/index.mjs +4 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +4 -1
- package/src/runtime/memory/lib/embedding-worker.mjs +10 -3
- package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +1 -7
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +1 -6
- package/src/runtime/office/authoring/pdf-author-action.mjs +1 -6
- package/src/runtime/office/design/design-art-direction.mjs +29 -27
- package/src/runtime/office/pdf/pdf-writer.mjs +6 -1
- package/src/runtime/office/portable/portable-snapshot-docx.mjs +7 -1
- package/src/runtime/office/portable/portable-snapshot-shared.mjs +6 -1
- package/src/runtime/office/quality/assurance-rendered.mjs +9 -2
- package/src/runtime/office/quality/assurance-structure-docx.mjs +4 -2
- package/src/runtime/office/quality/design-review.mjs +1 -5
- package/src/runtime/office/quality/document-brief.mjs +1 -5
- package/src/runtime/office/quality/forced-break-pages.mjs +9 -6
- package/src/runtime/office/quality/quality-pipeline.mjs +1 -1
- package/src/runtime/shared/agent-route-config.mjs +17 -2
- package/src/runtime/shared/atomic-file.mjs +103 -2
- package/src/runtime/shared/background-tasks.mjs +15 -6
- package/src/runtime/shared/bot-owned-work.mjs +56 -0
- package/src/runtime/shared/connector-accounts.mjs +5 -5
- package/src/runtime/shared/connector-catalog.d.mts +11 -4
- package/src/runtime/shared/connector-catalog.mjs +60 -52
- package/src/runtime/shared/custom-provider-keys.mjs +20 -0
- package/src/runtime/shared/defaults-separation.mjs +1 -4
- package/src/runtime/shared/fetch-safe-listen.d.mts +16 -0
- package/src/runtime/shared/fetch-safe-listen.mjs +50 -0
- package/src/runtime/shared/file-has-data.mjs +26 -2
- package/src/runtime/shared/file-lock.mjs +13 -15
- package/src/runtime/shared/git-launch.d.mts +9 -0
- package/src/runtime/shared/git-launch.mjs +213 -0
- package/src/runtime/shared/git-runner-worker.mjs +2 -1
- package/src/runtime/shared/handoff-artifacts.d.mts +2 -1
- package/src/runtime/shared/handoff-artifacts.mjs +18 -8
- package/src/runtime/shared/idle-auto-clear-gate.mjs +29 -7
- package/src/runtime/shared/llm/cost.mjs +11 -2
- package/src/runtime/shared/llm/http-agent.mjs +313 -29
- package/src/runtime/shared/llm/model-catalog-projection.mjs +3 -2
- package/src/runtime/shared/llm/model-catalog.mjs +46 -6
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +3 -0
- package/src/runtime/shared/llm/system-proxy.mjs +452 -0
- package/src/runtime/shared/loopback-listener.mjs +25 -2
- package/src/runtime/shared/model-route-chain.d.mts +7 -2
- package/src/runtime/shared/model-route-chain.mjs +22 -4
- package/src/runtime/shared/open-url.mjs +10 -0
- package/src/runtime/shared/provider-accounts.mjs +52 -2
- package/src/runtime/shared/session-activity-signal.mjs +14 -0
- package/src/runtime/shared/session-scratch.mjs +37 -0
- package/src/runtime/shared/staged-install-worker.mjs +5 -0
- package/src/runtime/shared/staged-update.mjs +47 -2
- package/src/runtime/shared/tool-card-model/agent-surface.mjs +1 -3
- package/src/runtime/shared/tool-card-model/card-status.mjs +15 -2
- package/src/runtime/shared/tool-result-summary.mjs +1 -1
- package/src/runtime/shared/tool-surface.mjs +113 -36
- package/src/runtime/shared/tool-work-units.mjs +6 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +13 -6
- package/src/runtime/shared/turn-worktree-snapshot.mjs +414 -54
- package/src/runtime/shared/worktrees/activity-gate.mjs +215 -318
- package/src/runtime/shared/worktrees/activity-ledger-worker.mjs +56 -0
- package/src/runtime/shared/worktrees/activity-ledger.mjs +391 -0
- package/src/runtime/shared/worktrees/branch-management.mjs +9 -3
- package/src/runtime/shared/worktrees/contract.mjs +4 -1
- package/src/runtime/shared/worktrees/git.mjs +72 -0
- package/src/runtime/shared/worktrees/manager.mjs +285 -168
- package/src/runtime/shared/worktrees/test-support.mjs +6 -10
- package/src/runtime/tidy/install.mjs +95 -15
- package/src/runtime/tidy/languages.mjs +17 -15
- package/src/runtime/tidy/process.mjs +7 -4
- package/src/runtime/tidy/resolve.mjs +19 -16
- package/src/runtime/tidy/runners/biome.mjs +3 -3
- package/src/runtime/tidy/tool.mjs +1 -1
- package/src/runtime/tidy/worktree.mjs +10 -6
- package/src/runtime/visualize-bridge/client.mjs +6 -1
- package/src/runtime/visualize-bridge/tool-defs.mjs +7 -2
- package/src/runtime/web-search/index.mjs +1 -1
- package/src/session-runtime/agent-home-delivery.mjs +41 -0
- package/src/session-runtime/agent-task-sessions.mjs +458 -312
- package/src/session-runtime/agent-tool-routing.mjs +8 -41
- package/src/session-runtime/boot/tools.mjs +7 -1
- package/src/session-runtime/bot-status.mjs +107 -101
- package/src/session-runtime/bot-target.mjs +38 -0
- package/src/session-runtime/channel-config-api.mjs +6 -1
- package/src/session-runtime/connector-merge-migration.mjs +18 -15
- package/src/session-runtime/conversation-import/codex-titles.mjs +88 -0
- package/src/session-runtime/conversation-import/discovery.mjs +401 -36
- package/src/session-runtime/conversation-import/service.mjs +35 -8
- package/src/session-runtime/handoff-attachments.mjs +82 -31
- package/src/session-runtime/held-requests.mjs +3 -1
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +12 -13
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +1 -0
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -0
- package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +8 -0
- package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +4 -1
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +145 -112
- package/src/session-runtime/lifecycle/cold-heir.mjs +11 -6
- package/src/session-runtime/lifecycle/session-catalog.mjs +9 -1
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -1
- package/src/session-runtime/lifecycle/teardown.mjs +1 -1
- package/src/session-runtime/mcp-glue/server-catalog.mjs +16 -10
- package/src/session-runtime/mcp-glue/server-input.mjs +4 -1
- package/src/session-runtime/mixy-heartbeat.mjs +1 -1
- package/src/session-runtime/model-route/set-route.mjs +11 -2
- package/src/session-runtime/provider-auth/login.mjs +2 -2
- package/src/session-runtime/provider-catalog-cache.mjs +82 -36
- package/src/session-runtime/provider-models/catalog-load.mjs +23 -6
- package/src/session-runtime/provider-models.mjs +4 -2
- package/src/session-runtime/resource-mcp-api.mjs +98 -35
- package/src/session-runtime/resource-mcp-connector-accounts.mjs +44 -31
- package/src/session-runtime/resource-mcp-secrets.mjs +28 -11
- package/src/session-runtime/resource-mcp-servers.mjs +5 -4
- package/src/session-runtime/resource-recall-api.mjs +6 -1
- package/src/session-runtime/runtime-feature-gates.mjs +5 -1
- package/src/session-runtime/services/agent-tool/execute.mjs +14 -9
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +20 -4
- package/src/session-runtime/services/agent-tool/model-select.mjs +48 -7
- package/src/session-runtime/services/agent-tool/render.mjs +71 -17
- package/src/session-runtime/services/agent-tool/send-flow.mjs +10 -2
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +8 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +6 -2
- package/src/session-runtime/services/agent-tool/tool-def.mjs +9 -9
- package/src/session-runtime/services/agent-watchdog-registry.mjs +32 -4
- package/src/session-runtime/services/channel-worker/daemon-spawn.mjs +2 -0
- package/src/session-runtime/services/daemon-candidate.mjs +4 -1
- package/src/session-runtime/services/memory-runtime-proxy/daemon-spawn.mjs +4 -0
- package/src/session-runtime/services/plugin-admin.mjs +10 -6
- package/src/session-runtime/services/provider-admin.mjs +283 -11
- package/src/session-runtime/session-turn-api.mjs +2 -0
- package/src/session-runtime/settings-builtin-tools-api.mjs +4 -1
- package/src/session-runtime/settings-system-api.mjs +1 -1
- package/src/session-runtime/setup-tool/agent-settings-actions.mjs +13 -3
- package/src/session-runtime/setup-tool/connector-widen-approval.mjs +96 -0
- package/src/session-runtime/setup-tool/executor.mjs +6 -2
- package/src/session-runtime/setup-tool/tool-defs.mjs +2 -1
- package/src/session-runtime/tool-policy/policy-stage.mjs +1 -0
- package/src/session-runtime/turn/agent-ops.mjs +23 -0
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +49 -11
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +25 -5
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -3
- package/src/session-runtime/workspace/activity-guards.mjs +176 -0
- package/src/session-runtime/workspace/session-view.mjs +149 -0
- package/src/session-runtime/workspace/side-checkouts.mjs +203 -0
- package/src/session-runtime/workspace/transition-runner.mjs +188 -0
- package/src/session-runtime/workspace/transitions.mjs +108 -1041
- package/src/session-runtime/workspace/workspace-merge.mjs +132 -0
- package/src/session-runtime/workspace/worktree-intents.mjs +242 -0
- package/src/session-runtime/workspace/worktree-maintenance.mjs +80 -0
- package/src/session-runtime/workspace/worktree-setup.mjs +85 -0
- package/src/session-runtime/workspace/worktree-tool.mjs +73 -56
- package/src/standalone/daemon-desktop-runtime.mjs +2 -0
- package/src/standalone/daemon-session-control.mjs +169 -138
- package/src/standalone/daemon.mjs +7 -0
- package/src/standalone/desktop-service-registry.mjs +9 -0
- package/src/standalone/local-session-runtime.mjs +5 -1
- package/src/standalone/session-client/daemon-unavailable.mjs +3 -0
- package/src/standalone/session-client.mjs +5 -1
- package/src/standalone/session-frame-stream.mjs +83 -11
- package/src/standalone/session-protocol.mjs +4 -0
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +16 -12
- package/src/standalone/session-service/agent-tree.mjs +45 -9
- package/src/standalone/session-service/projection/transcript-window.mjs +700 -64
- package/src/standalone/session-service/session-calls/session-action.mjs +10 -3
- package/src/standalone/session-service/session-calls/session-views.mjs +160 -104
- package/src/standalone/session-service/stored-reader.mjs +127 -40
- package/src/standalone/session-transport/client-registry/client-grace.mjs +12 -9
- package/src/tui/App.jsx +154 -164
- package/src/tui/app/app-view/floating-panel.jsx +10 -4
- package/src/tui/app/app-view/text-entry-labels.mjs +1 -0
- package/src/tui/app/clipboard.mjs +21 -10
- package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +1 -3
- package/src/tui/app/model-picker/route-selection.mjs +5 -6
- package/src/tui/app/prompt-handlers/use-prompt-history-nav.mjs +1 -1
- package/src/tui/app/prompt-handlers/use-prompt-interrupt.mjs +3 -0
- package/src/tui/app/prompt-submit/provider-prompts.mjs +3 -1
- package/src/tui/app/provider-setup/api-key-actions.mjs +31 -1
- package/src/tui/app/provider-setup/oauth-actions.mjs +58 -21
- package/src/tui/app/provider-setup/provider-items.mjs +8 -2
- package/src/tui/app/provider-setup-picker.mjs +29 -18
- package/src/tui/app/shell-layout/row-budgets.mjs +12 -10
- package/src/tui/app/slash-commands.mjs +1 -0
- package/src/tui/app/slash-dispatch/session-commands.mjs +46 -0
- package/src/tui/app/slash-dispatch.mjs +1 -1
- package/src/tui/app/use-btw-panel.mjs +29 -0
- package/src/tui/app/use-context-panel-refresh.mjs +31 -0
- package/src/tui/app/use-model-catalog-cache.mjs +13 -10
- package/src/tui/app/use-mouse-input.mjs +11 -0
- package/src/tui/app/use-onboarding-start.mjs +8 -3
- package/src/tui/app/use-panel-transition.mjs +10 -1
- package/src/tui/app/use-pasted-buffers.mjs +28 -20
- package/src/tui/app/use-prompt-draft-flow.mjs +7 -0
- package/src/tui/app/use-prompt-layout-rows.mjs +3 -3
- package/src/tui/app/use-prompt-queue-history.mjs +3 -2
- package/src/tui/app/use-selection-focus.mjs +11 -2
- package/src/tui/app/use-selection-geometry.mjs +18 -9
- package/src/tui/app/use-selection-paint.mjs +4 -4
- package/src/tui/app/use-selection-stitch.mjs +3 -3
- package/src/tui/app/use-slash-palette.mjs +62 -0
- package/src/tui/app/use-terminal-chrome.mjs +2 -2
- package/src/tui/app/use-transcript-activity.mjs +47 -32
- package/src/tui/app/use-transcript-follow.mjs +12 -4
- package/src/tui/app/use-transcript-scroll.mjs +46 -17
- package/src/tui/app/use-transcript-swap-reset.mjs +27 -0
- package/src/tui/app/use-transcript-window/geometry.mjs +19 -5
- package/src/tui/app/use-transcript-window/instance-state.mjs +4 -0
- package/src/tui/app/use-transcript-window/selection-shift.mjs +5 -1
- package/src/tui/app/use-transcript-window.mjs +21 -11
- package/src/tui/app/use-tui-ready.mjs +18 -0
- package/src/tui/app/use-welcome-prompt-hint.mjs +5 -0
- package/src/tui/app/use-workflow-tab-cycle.mjs +5 -3
- package/src/tui/components/Picker.jsx +27 -3
- package/src/tui/components/QuestionPanel.jsx +13 -4
- package/src/tui/components/SignInUrl.jsx +68 -0
- package/src/tui/components/TextEntryPanel.jsx +76 -46
- package/src/tui/components/sign-in-url-layout.mjs +24 -0
- package/src/tui/dist/index.mjs +1963 -1361
- package/src/tui/session/goal-turn-state.mjs +6 -2
- package/src/tui/session/session-api/controls.mjs +4 -0
- package/src/tui/session/session-api/extensions.mjs +8 -0
- package/src/tui/session/session-api/intake/abort.mjs +2 -1
- package/src/tui/session/session-api/intake.mjs +2 -0
- package/src/tui/session/session-api/integrations.mjs +2 -2
- package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
- package/src/tui/session/session-api/settings.mjs +2 -2
- package/src/tui/session/session-flow/queue/take-entries.mjs +11 -0
- package/src/tui/session/transcript-spill/spill-writer.mjs +15 -0
- package/src/tui/session/transcript-spill.mjs +92 -4
- package/src/tui/session/transcript-store.mjs +2 -0
- package/src/ui/statusline-agents.mjs +1 -3
- package/src/defaults/skills/skill-creator/references/source-review.md +0 -94
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Discovery of importable foreign conversations on disk (read-only).
|
|
3
3
|
*
|
|
4
|
-
* codex $CODEX_HOME/sessions/**\/rollout-*.jsonl (default ~/.codex)
|
|
4
|
+
* codex $CODEX_HOME/sessions/**\/rollout-*.jsonl[.zst] (default ~/.codex)
|
|
5
5
|
* claude $CLAUDE_CONFIG_DIR/projects/*\/*.jsonl (default ~/.claude)
|
|
6
6
|
*
|
|
7
7
|
* Enumeration is stat-only; per-file metadata (foreign id, cwd, title, and
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
import { createHash, randomUUID } from 'node:crypto';
|
|
14
14
|
import { open, readdir, realpath, stat } from 'node:fs/promises';
|
|
15
15
|
import { homedir } from 'node:os';
|
|
16
|
-
import { basename,
|
|
16
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
17
|
+
import { pipeline } from 'node:stream';
|
|
18
|
+
import zlib from 'node:zlib';
|
|
17
19
|
import {
|
|
18
20
|
IMPORT_MAX_FILE_BYTES,
|
|
19
21
|
IMPORT_MAX_LINE_BYTES,
|
|
@@ -24,8 +26,15 @@ import {
|
|
|
24
26
|
isImportSource,
|
|
25
27
|
} from '../../runtime/agent/orchestrator/session/conversation-import/convert.mjs';
|
|
26
28
|
import { codexMetaFromTruncatedLine } from '../../runtime/agent/orchestrator/session/conversation-import/codex.mjs';
|
|
27
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
IMPORT_ERROR_NEEDS_NEWER_NODE,
|
|
31
|
+
IMPORT_WARNING_CWD_MISSING,
|
|
32
|
+
IMPORT_WARNING_HISTORY_PARTIAL,
|
|
33
|
+
IMPORT_WARNING_HISTORY_SIZE_LIMIT,
|
|
34
|
+
IMPORT_WARNING_HISTORY_UNAVAILABLE,
|
|
35
|
+
} from '../../runtime/agent/orchestrator/session/conversation-import/turns.mjs';
|
|
28
36
|
import { importOriginKey } from '../../runtime/agent/orchestrator/session/conversation-import/imported-session.mjs';
|
|
37
|
+
import { loadCodexThreadTitles } from './codex-titles.mjs';
|
|
29
38
|
|
|
30
39
|
export const PREFIX_CHUNK_BYTES = 64 * 1024;
|
|
31
40
|
export const PREFIX_MAX_BYTES = 512 * 1024;
|
|
@@ -49,10 +58,12 @@ const state = {
|
|
|
49
58
|
queue: [],
|
|
50
59
|
active: 0,
|
|
51
60
|
views: new Map(), // viewId → view
|
|
61
|
+
scanMaxBytes: IMPORT_MAX_FILE_BYTES, // per-file budget of a sidechain-only scan
|
|
52
62
|
};
|
|
53
63
|
|
|
54
|
-
/** Test seam: forget every cached candidate, metadata entry and view. */
|
|
55
|
-
export function resetConversationDiscovery() {
|
|
64
|
+
/** Test seam: forget every cached candidate, metadata entry and view; `scanMaxBytes` overrides the scan budget. */
|
|
65
|
+
export function resetConversationDiscovery({ scanMaxBytes = IMPORT_MAX_FILE_BYTES } = {}) {
|
|
66
|
+
state.scanMaxBytes = scanMaxBytes;
|
|
56
67
|
state.candidates.clear();
|
|
57
68
|
state.meta.clear();
|
|
58
69
|
state.pendingMeta.clear();
|
|
@@ -74,6 +85,16 @@ export function requireSource(source) {
|
|
|
74
85
|
return source;
|
|
75
86
|
}
|
|
76
87
|
|
|
88
|
+
/** Titles the source app stores per foreign session id (Codex only), read fresh; else null. */
|
|
89
|
+
export function loadStoredTitles(source, env = process.env) {
|
|
90
|
+
return source === 'codex' ? loadCodexThreadTitles(dirname(sourceRoot(source, env))) : null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The app's stored title of a conversation, else the one derived from its messages. */
|
|
94
|
+
export function displayTitle(meta, titles) {
|
|
95
|
+
return (meta?.foreignSessionId && titles?.get(meta.foreignSessionId)) || meta?.title || null;
|
|
96
|
+
}
|
|
97
|
+
|
|
77
98
|
const caseFold = (value) => (process.platform === 'win32' ? value.toLowerCase() : value);
|
|
78
99
|
|
|
79
100
|
function isInside(root, path) {
|
|
@@ -93,6 +114,51 @@ export function isNetworkOrDevicePath(path) {
|
|
|
93
114
|
|
|
94
115
|
const fileIdentity = (info) => `${info.dev}:${info.ino}`;
|
|
95
116
|
|
|
117
|
+
// Rollout file-name rules, as the source app applies them: a compressed rollout is a
|
|
118
|
+
// name ending `.jsonl.zst`; a rollout name is, after dropping a `.zst` suffix,
|
|
119
|
+
// `rollout-` … `.jsonl`. Both are case-sensitive.
|
|
120
|
+
const COMPRESSED_ROLLOUT_SUFFIX = '.jsonl.zst';
|
|
121
|
+
const isCompressed = (path) => basename(path).endsWith(COMPRESSED_ROLLOUT_SUFFIX);
|
|
122
|
+
const isRolloutName = (name) => {
|
|
123
|
+
const plain = name.endsWith(COMPRESSED_ROLLOUT_SUFFIX) ? name.slice(0, -'.zst'.length) : name;
|
|
124
|
+
return plain.startsWith('rollout-') && plain.endsWith('.jsonl');
|
|
125
|
+
};
|
|
126
|
+
const zstdSupported = () => typeof zlib.createZstdDecompress === 'function';
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Sequential `read(buffer, offset, length, position)` / `close()` over the
|
|
130
|
+
* decompressed bytes of an open compressed file, shaped like a file handle so
|
|
131
|
+
* the line reader needs no other change. Positions must run on from the last
|
|
132
|
+
* read; closing stops the decompression.
|
|
133
|
+
*/
|
|
134
|
+
function zstdReader(handle) {
|
|
135
|
+
if (!zstdSupported()) throw importError(IMPORT_ERROR_NEEDS_NEWER_NODE);
|
|
136
|
+
const decoder = zlib.createZstdDecompress();
|
|
137
|
+
pipeline(handle.createReadStream({ autoClose: false }), decoder, () => {});
|
|
138
|
+
const chunks = decoder[Symbol.asyncIterator]();
|
|
139
|
+
let rest = Buffer.alloc(0);
|
|
140
|
+
let position = 0;
|
|
141
|
+
return {
|
|
142
|
+
async read(buffer, offset, length, from) {
|
|
143
|
+
if (from !== position) throw importError('unreadable');
|
|
144
|
+
if (!rest.length) {
|
|
145
|
+
const next = await chunks.next();
|
|
146
|
+
if (next.done) return { bytesRead: 0, buffer };
|
|
147
|
+
rest = next.value;
|
|
148
|
+
}
|
|
149
|
+
const bytesRead = Math.min(length, rest.length);
|
|
150
|
+
rest.copy(buffer, offset, 0, bytesRead);
|
|
151
|
+
rest = rest.subarray(bytesRead);
|
|
152
|
+
position += bytesRead;
|
|
153
|
+
return { bytesRead, buffer };
|
|
154
|
+
},
|
|
155
|
+
async close() {
|
|
156
|
+
decoder.destroy();
|
|
157
|
+
await handle.close();
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
96
162
|
export function candidateId(source, canonicalPath) {
|
|
97
163
|
return createHash('sha256')
|
|
98
164
|
.update(`${source}\0${caseFold(canonicalPath)}`)
|
|
@@ -131,7 +197,10 @@ async function walkCodex(rootReal, dir, files, seenDirs) {
|
|
|
131
197
|
continue;
|
|
132
198
|
}
|
|
133
199
|
}
|
|
134
|
-
if (
|
|
200
|
+
if (!isRolloutName(entry.name)) continue;
|
|
201
|
+
// A compressed rollout is skipped while its plain sibling exists.
|
|
202
|
+
if (isCompressed(entry.name) && (await stat(path.slice(0, -'.zst'.length)).catch(() => null))) continue;
|
|
203
|
+
files.push(path);
|
|
135
204
|
}
|
|
136
205
|
}
|
|
137
206
|
|
|
@@ -227,6 +296,11 @@ function metaFromConversion(conversion, whole) {
|
|
|
227
296
|
foreignSessionId: conversion.foreignSessionId,
|
|
228
297
|
cwd: conversion.cwd,
|
|
229
298
|
title: conversion.title,
|
|
299
|
+
// A Codex marker sits in the first line; a Claude transcript is sidechain-only only once read to the end.
|
|
300
|
+
internalKind: whole || conversion.source === 'codex' ? (conversion.internalKind ?? null) : null,
|
|
301
|
+
// Rollout id of the history base this file continues, else null.
|
|
302
|
+
historyBaseId:
|
|
303
|
+
typeof conversion.historyBase?.thread_id === 'string' ? conversion.historyBase.thread_id.toLowerCase() : null,
|
|
230
304
|
messageCount: whole && !conversion.errorCode ? conversion.messageCount : null,
|
|
231
305
|
warnings: whole ? conversion.warnings : conversion.warnings.filter((code) => code !== 'malformed'),
|
|
232
306
|
errorCode: whole ? conversion.errorCode : null,
|
|
@@ -257,7 +331,7 @@ async function openCandidate(candidate) {
|
|
|
257
331
|
) {
|
|
258
332
|
throw importError('unreadable');
|
|
259
333
|
}
|
|
260
|
-
return handle;
|
|
334
|
+
return isCompressed(candidate.path) ? zstdReader(handle) : handle;
|
|
261
335
|
} catch (error) {
|
|
262
336
|
await handle.close();
|
|
263
337
|
throw error;
|
|
@@ -324,42 +398,242 @@ async function streamJsonlLines(
|
|
|
324
398
|
return outcome({ eof: true });
|
|
325
399
|
}
|
|
326
400
|
|
|
401
|
+
// Complete lines of the last prefix-chunk of a file (the cut-off first line dropped).
|
|
402
|
+
async function readTailLines(handle, size) {
|
|
403
|
+
const length = Math.min(size, PREFIX_CHUNK_BYTES);
|
|
404
|
+
const buffer = Buffer.allocUnsafe(length);
|
|
405
|
+
const { bytesRead } = await handle.read(buffer, 0, length, size - length);
|
|
406
|
+
const text = buffer.subarray(0, bytesRead).toString('utf8');
|
|
407
|
+
return (size > length ? text.slice(text.indexOf('\n') + 1) : text).split('\n');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Whether the rollout a listed file continues is absent (a stat-only search): its own records are then all there is to name and count.
|
|
411
|
+
const baseIsMissing = async (candidate, meta) =>
|
|
412
|
+
!!meta.historyBaseId && !(await findRolloutById(dirname(candidate.root ?? sourceRoot('codex')), meta.historyBaseId));
|
|
413
|
+
|
|
327
414
|
async function readPrefixMeta(candidate) {
|
|
328
415
|
const converter = createConversationConverter(candidate.source);
|
|
329
416
|
const handle = await openCandidate(candidate);
|
|
417
|
+
// Only the end of a Claude transcript proves it is sidechain-only, so while
|
|
418
|
+
// nothing but sidechain messages has been seen the scan streams on, line by
|
|
419
|
+
// line, to EOF or to the import file-size limit. A file over that limit stays
|
|
420
|
+
// unclassified (listed); importing it fails as tooLarge.
|
|
421
|
+
const claude = candidate.source === 'claude';
|
|
330
422
|
let read;
|
|
331
423
|
try {
|
|
332
424
|
read = await streamJsonlLines(handle, (line) => converter.pushLine(line), {
|
|
333
|
-
maxLineBytes: PREFIX_MAX_BYTES,
|
|
425
|
+
maxLineBytes: claude ? IMPORT_MAX_LINE_BYTES : PREFIX_MAX_BYTES,
|
|
334
426
|
chunkBytes: PREFIX_CHUNK_BYTES,
|
|
335
|
-
maxBytes: PREFIX_MAX_BYTES,
|
|
427
|
+
maxBytes: claude ? state.scanMaxBytes : PREFIX_MAX_BYTES,
|
|
336
428
|
onChunk: (position) => {
|
|
337
429
|
// A file read to its known size keeps going to EOF (whole-file metadata).
|
|
338
430
|
if (position >= candidate.size) return true;
|
|
431
|
+
if (claude) {
|
|
432
|
+
if (converter.sidechainOnly) return true;
|
|
433
|
+
if (position >= PREFIX_MAX_BYTES) return false;
|
|
434
|
+
}
|
|
339
435
|
const partial = converter.result();
|
|
340
436
|
return !(partial.foreignSessionId && partial.cwd && partial.title);
|
|
341
437
|
},
|
|
342
438
|
});
|
|
439
|
+
// The stored title records sit near the end of a transcript.
|
|
440
|
+
if (claude && !read.eof) {
|
|
441
|
+
for (const line of await readTailLines(handle, candidate.size)) converter.pushTitleLine(line);
|
|
442
|
+
}
|
|
343
443
|
} finally {
|
|
344
444
|
await handle.close();
|
|
345
445
|
}
|
|
346
446
|
const conversion = converter.result();
|
|
347
|
-
|
|
348
|
-
const
|
|
447
|
+
// A thread continuing a history base is only counted, named and judged empty once the base is followed (on import).
|
|
448
|
+
const listing = async (meta) =>
|
|
449
|
+
conversion.historyBase && !(await baseIsMissing(candidate, meta))
|
|
450
|
+
? { ...meta, title: null, messageCount: null, errorCode: meta.errorCode === 'empty' ? null : meta.errorCode }
|
|
451
|
+
: conversion.historyBase
|
|
452
|
+
? { ...meta, warnings: [...meta.warnings, IMPORT_WARNING_HISTORY_UNAVAILABLE] }
|
|
453
|
+
: meta;
|
|
454
|
+
if (read.eof) return listing(metaFromConversion(conversion, true));
|
|
455
|
+
const meta = await listing(metaFromConversion(conversion, false));
|
|
349
456
|
if (candidate.source === 'codex' && (!meta.foreignSessionId || !meta.cwd) && read.firstPartial) {
|
|
350
457
|
// A session_meta line longer than the prefix still names its id/cwd.
|
|
351
458
|
const fromLine = codexMetaFromTruncatedLine(read.firstPartial.replace(/^\uFEFF/, ''));
|
|
352
459
|
meta.foreignSessionId ||= fromLine?.foreignSessionId || null;
|
|
353
460
|
meta.cwd ||= fromLine?.cwd || null;
|
|
461
|
+
meta.internalKind ||= fromLine?.internalKind || null;
|
|
354
462
|
}
|
|
355
463
|
return meta;
|
|
356
464
|
}
|
|
357
465
|
|
|
466
|
+
// ── Codex history base chain ─────────────────────────────────────────────────
|
|
467
|
+
const HISTORY_CHAIN_MAX_DEPTH = 32;
|
|
468
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
469
|
+
const ROLLOUT_ID_RE = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl(?:\.zst)?$/i;
|
|
470
|
+
|
|
471
|
+
/** Rollout id a rollout file name ends with (`…-<thread>.jsonl[.zst]` or `…-<thread>_<rollout>.jsonl[.zst]`). */
|
|
472
|
+
const rolloutIdOf = (path) => {
|
|
473
|
+
const name = basename(path);
|
|
474
|
+
return isRolloutName(name) ? (name.match(ROLLOUT_ID_RE)?.[1].toLowerCase() ?? null) : null;
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
const META_CHUNK_BYTES = 8 * 1024;
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* First JSON record of a candidate file (null when none completes within
|
|
481
|
+
* `maxBytes`, blank lines included, or it is not JSON) and the bytes read for it.
|
|
482
|
+
*/
|
|
483
|
+
async function firstRecord(candidate, { maxBytes, maxLineBytes }) {
|
|
484
|
+
const handle = await openCandidate(candidate);
|
|
485
|
+
try {
|
|
486
|
+
let first = null;
|
|
487
|
+
const read = await streamJsonlLines(
|
|
488
|
+
handle,
|
|
489
|
+
(line) => {
|
|
490
|
+
first = line.replace(/^\uFEFF/, '').trim();
|
|
491
|
+
return !first;
|
|
492
|
+
},
|
|
493
|
+
{ maxLineBytes, chunkBytes: META_CHUNK_BYTES, maxBytes }
|
|
494
|
+
);
|
|
495
|
+
let record = null;
|
|
496
|
+
try {
|
|
497
|
+
record = first ? JSON.parse(first) : null;
|
|
498
|
+
} catch {
|
|
499
|
+
/* not a record */
|
|
500
|
+
}
|
|
501
|
+
return { record, bytes: read.bytesRead };
|
|
502
|
+
} finally {
|
|
503
|
+
await handle.close();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Whether a cutoff, in (decompressed) file bytes, falls on a record boundary: the end of the file or just after a line feed.
|
|
508
|
+
async function endsOnRecord(candidate, end) {
|
|
509
|
+
const compressed = isCompressed(candidate.path);
|
|
510
|
+
if (!compressed && end === candidate.size) return true;
|
|
511
|
+
const handle = await openCandidate(candidate);
|
|
512
|
+
try {
|
|
513
|
+
const byte = Buffer.alloc(1);
|
|
514
|
+
if (!compressed) {
|
|
515
|
+
const { bytesRead } = await handle.read(byte, 0, 1, end - 1);
|
|
516
|
+
return bytesRead === 1 && byte[0] === 10;
|
|
517
|
+
}
|
|
518
|
+
// The decompressed size is unknown: read up to the cutoff, then see whether the file ends there.
|
|
519
|
+
const chunk = Buffer.allocUnsafe(IMPORT_CHUNK_BYTES);
|
|
520
|
+
let position = 0;
|
|
521
|
+
let last = 0;
|
|
522
|
+
while (position < end) {
|
|
523
|
+
const { bytesRead } = await handle.read(chunk, 0, Math.min(chunk.length, end - position), position);
|
|
524
|
+
if (!bytesRead) return false;
|
|
525
|
+
position += bytesRead;
|
|
526
|
+
last = chunk[bytesRead - 1];
|
|
527
|
+
}
|
|
528
|
+
return last === 10 || (await handle.read(byte, 0, 1, position)).bytesRead === 0;
|
|
529
|
+
} finally {
|
|
530
|
+
await handle.close();
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/** The rollout file with this rollout id under the live, then the archived, sessions folder (walking lists a plain file instead of its compressed sibling). */
|
|
535
|
+
async function findRolloutById(home, rolloutId) {
|
|
536
|
+
for (const folder of ['sessions', 'archived_sessions']) {
|
|
537
|
+
let rootReal;
|
|
538
|
+
try {
|
|
539
|
+
rootReal = await realpath(join(home, folder));
|
|
540
|
+
} catch {
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
const paths = [];
|
|
544
|
+
await walkCodex(rootReal, rootReal, paths, new Set([caseFold(rootReal)]));
|
|
545
|
+
for (const path of paths) {
|
|
546
|
+
if (rolloutIdOf(path) !== rolloutId) continue;
|
|
547
|
+
const real = await containedRealpath(rootReal, path);
|
|
548
|
+
const info = real ? await stat(real).catch(() => null) : null;
|
|
549
|
+
if (info?.isFile()) {
|
|
550
|
+
return { source: 'codex', root: rootReal, path: real, size: info.size, fileId: fileIdentity(info) };
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Size of a compressed file once decompressed, counting no further than `cap`.
|
|
558
|
+
async function decompressedLength(candidate, cap) {
|
|
559
|
+
const handle = await openCandidate(candidate);
|
|
560
|
+
try {
|
|
561
|
+
const chunk = Buffer.allocUnsafe(IMPORT_CHUNK_BYTES);
|
|
562
|
+
let length = 0;
|
|
563
|
+
while (length < cap) {
|
|
564
|
+
const { bytesRead } = await handle.read(chunk, 0, Math.min(chunk.length, cap - length), length);
|
|
565
|
+
if (!bytesRead) break;
|
|
566
|
+
length += bytesRead;
|
|
567
|
+
}
|
|
568
|
+
return length;
|
|
569
|
+
} finally {
|
|
570
|
+
await handle.close();
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Rollouts a `history_base` leads to, nearest first, each with the byte offset
|
|
576
|
+
* its contribution ends at, and `metaBytes`, what reading their metadata took.
|
|
577
|
+
* Cutoffs and byte counts are of the decompressed rollout. Stops (`partial`,
|
|
578
|
+
* the warning code saying why) at a base that is missing or unreadable
|
|
579
|
+
* (`historyUnavailable`), that would take the total read (the file, the
|
|
580
|
+
* `spent` metadata reads so far, every cutoff and every metadata read) beyond
|
|
581
|
+
* `maxFileBytes` (`historySizeLimit`), or is not paginated, already visited,
|
|
582
|
+
* past the depth cap, with a cutoff that excludes the session metadata,
|
|
583
|
+
* passes the end of the file or does not end a record (`historyPartial`).
|
|
584
|
+
*/
|
|
585
|
+
async function resolveHistoryChain(candidate, firstBase, { maxFileBytes, maxLineBytes, spent }) {
|
|
586
|
+
const home = dirname(candidate.root ?? sourceRoot('codex'));
|
|
587
|
+
const seen = new Set([rolloutIdOf(candidate.path)]);
|
|
588
|
+
const segments = [];
|
|
589
|
+
// The file itself is reserved at its decompressed size, which only reading it tells.
|
|
590
|
+
let used =
|
|
591
|
+
(isCompressed(candidate.path) ? await decompressedLength(candidate, maxFileBytes) : candidate.size) + spent;
|
|
592
|
+
let metaBytes = 0;
|
|
593
|
+
const partial = (warning = IMPORT_WARNING_HISTORY_PARTIAL) => ({ segments, partial: warning, metaBytes });
|
|
594
|
+
for (let base = firstBase; base; ) {
|
|
595
|
+
const id = typeof base.thread_id === 'string' ? base.thread_id.toLowerCase() : '';
|
|
596
|
+
const end = base.end_byte_offset;
|
|
597
|
+
const ordinal = base.end_ordinal_exclusive;
|
|
598
|
+
if (!UUID_RE.test(id) || seen.has(id) || segments.length >= HISTORY_CHAIN_MAX_DEPTH) return partial();
|
|
599
|
+
seen.add(id);
|
|
600
|
+
if (!Number.isSafeInteger(ordinal) || ordinal <= 0 || !Number.isSafeInteger(end) || end <= 0) return partial();
|
|
601
|
+
const found = await findRolloutById(home, id);
|
|
602
|
+
if (!found) return partial(IMPORT_WARNING_HISTORY_UNAVAILABLE);
|
|
603
|
+
if (!isCompressed(found.path) && end > found.size) return partial();
|
|
604
|
+
if (used + end > maxFileBytes) return partial(IMPORT_WARNING_HISTORY_SIZE_LIMIT);
|
|
605
|
+
let head;
|
|
606
|
+
try {
|
|
607
|
+
if (!(await endsOnRecord(found, end))) return partial();
|
|
608
|
+
head = await firstRecord(found, { maxBytes: Math.min(end, maxFileBytes - used - end), maxLineBytes });
|
|
609
|
+
} catch (error) {
|
|
610
|
+
// A runtime without zstd names its own fix; any other failure to open or read the base is "unavailable".
|
|
611
|
+
return partial(
|
|
612
|
+
error?.code === IMPORT_ERROR_NEEDS_NEWER_NODE
|
|
613
|
+
? IMPORT_ERROR_NEEDS_NEWER_NODE
|
|
614
|
+
: IMPORT_WARNING_HISTORY_UNAVAILABLE
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
used += end + head.bytes;
|
|
618
|
+
metaBytes += head.bytes;
|
|
619
|
+
if (head.record?.type !== 'session_meta' || head.record.payload?.history_mode !== 'paginated') return partial();
|
|
620
|
+
segments.push({ candidate: found, end });
|
|
621
|
+
const next = head.record.payload.history_base;
|
|
622
|
+
base = next && typeof next === 'object' ? next : null;
|
|
623
|
+
}
|
|
624
|
+
return { segments, partial: null, metaBytes };
|
|
625
|
+
}
|
|
626
|
+
|
|
358
627
|
/**
|
|
359
628
|
* Stream-convert a whole candidate file under the import limits. A file over
|
|
360
629
|
* `maxFileBytes`, a line over `maxLineBytes` or a conversion over the
|
|
361
630
|
* converter's limits fails the file with `tooLarge` without reading the rest.
|
|
362
|
-
* `
|
|
631
|
+
* A Codex rollout continuing a `history_base` is preceded by the records of
|
|
632
|
+
* each base up to its cutoff, oldest first (their session metadata dropped),
|
|
633
|
+
* all within the one `maxFileBytes` (counted in decompressed bytes for a
|
|
634
|
+
* compressed rollout); a base that cannot be followed leaves a `historyPartial`,
|
|
635
|
+
* `historySizeLimit` or `historyUnavailable` warning. `bytesRead` is how far
|
|
636
|
+
* the read got.
|
|
363
637
|
*/
|
|
364
638
|
export async function convertCandidateFile(
|
|
365
639
|
candidate,
|
|
@@ -377,32 +651,74 @@ export async function convertCandidateFile(
|
|
|
377
651
|
maxRetainedEntries,
|
|
378
652
|
maxMetadataBytes,
|
|
379
653
|
});
|
|
654
|
+
// Bytes read beyond the file itself: metadata pre-reads and the bases' cutoffs. All count against `maxFileBytes`.
|
|
655
|
+
let inheritedBytes = 0;
|
|
656
|
+
if (candidate.source === 'codex') {
|
|
657
|
+
const head = await firstRecord(candidate, { maxBytes: maxFileBytes, maxLineBytes });
|
|
658
|
+
const base = head.record?.payload?.history_base;
|
|
659
|
+
if (base && typeof base === 'object') {
|
|
660
|
+
inheritedBytes += head.bytes;
|
|
661
|
+
const chain =
|
|
662
|
+
head.record.payload.history_mode === 'paginated'
|
|
663
|
+
? await resolveHistoryChain(candidate, base, { maxFileBytes, maxLineBytes, spent: inheritedBytes })
|
|
664
|
+
: { segments: [], partial: IMPORT_WARNING_HISTORY_PARTIAL, metaBytes: 0 };
|
|
665
|
+
inheritedBytes += chain.metaBytes;
|
|
666
|
+
const { segments, partial } = chain;
|
|
667
|
+
if (partial) converter.markHistoryPartial(partial);
|
|
668
|
+
for (const { candidate: baseFile, end } of segments.reverse()) {
|
|
669
|
+
const baseHandle = await openCandidate(baseFile);
|
|
670
|
+
try {
|
|
671
|
+
const read = await streamJsonlLines(baseHandle, (line) => converter.pushLine(line, { inherited: true }), {
|
|
672
|
+
maxLineBytes,
|
|
673
|
+
chunkBytes,
|
|
674
|
+
maxBytes: end,
|
|
675
|
+
});
|
|
676
|
+
if (read.overflow) converter.markTooLarge();
|
|
677
|
+
inheritedBytes += read.bytesRead;
|
|
678
|
+
} finally {
|
|
679
|
+
await baseHandle.close();
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
380
684
|
const handle = await openCandidate(candidate);
|
|
381
685
|
try {
|
|
382
686
|
const read = await streamJsonlLines(handle, (line) => converter.pushLine(line), {
|
|
383
687
|
maxLineBytes,
|
|
384
688
|
chunkBytes,
|
|
385
|
-
maxBytes: maxFileBytes,
|
|
689
|
+
maxBytes: maxFileBytes - inheritedBytes,
|
|
386
690
|
});
|
|
387
691
|
if (read.overflow || read.truncated) converter.markTooLarge();
|
|
388
|
-
return { ...converter.result(), bytesRead: read.bytesRead };
|
|
692
|
+
return { ...converter.result(), bytesRead: read.bytesRead + inheritedBytes };
|
|
389
693
|
} finally {
|
|
390
694
|
await handle.close();
|
|
391
695
|
}
|
|
392
696
|
}
|
|
393
697
|
|
|
698
|
+
// On a runtime without zstd, a listed conversation whose history base is compressed says so on its row.
|
|
699
|
+
async function withCompressedBaseNotice(candidate, meta) {
|
|
700
|
+
if (!meta.historyBaseId || zstdSupported()) return meta;
|
|
701
|
+
const found = await findRolloutById(dirname(candidate.root ?? sourceRoot('codex')), meta.historyBaseId);
|
|
702
|
+
return found && isCompressed(found.path)
|
|
703
|
+
? { ...meta, warnings: [...meta.warnings, IMPORT_ERROR_NEEDS_NEWER_NODE] }
|
|
704
|
+
: meta;
|
|
705
|
+
}
|
|
706
|
+
|
|
394
707
|
function pumpMetaQueue() {
|
|
395
708
|
while (state.active < META_CONCURRENCY && state.queue.length) {
|
|
396
709
|
const job = state.queue.shift();
|
|
397
710
|
state.active += 1;
|
|
398
711
|
readPrefixMeta(job.candidate)
|
|
399
|
-
.
|
|
712
|
+
.then((meta) => withCompressedBaseNotice(job.candidate, meta))
|
|
713
|
+
.catch((error) => ({
|
|
400
714
|
foreignSessionId: null,
|
|
401
715
|
cwd: null,
|
|
402
716
|
title: null,
|
|
717
|
+
internalKind: null,
|
|
718
|
+
historyBaseId: null,
|
|
403
719
|
messageCount: null,
|
|
404
720
|
warnings: [],
|
|
405
|
-
errorCode: 'unreadable',
|
|
721
|
+
errorCode: error?.code === IMPORT_ERROR_NEEDS_NEWER_NODE ? IMPORT_ERROR_NEEDS_NEWER_NODE : 'unreadable',
|
|
406
722
|
}))
|
|
407
723
|
.then((value) => {
|
|
408
724
|
state.meta.set(job.candidate.path, { stampKey: stampKey(job.candidate), value });
|
|
@@ -470,14 +786,14 @@ export function importedKeyMap(summaries) {
|
|
|
470
786
|
return map;
|
|
471
787
|
}
|
|
472
788
|
|
|
473
|
-
function rowFor(candidate, meta, cwdInfo, imported) {
|
|
789
|
+
function rowFor(candidate, meta, cwdInfo, imported, titles) {
|
|
474
790
|
const warnings = new Set(meta?.warnings || []);
|
|
475
791
|
if (meta && !meta.errorCode && !cwdInfo?.exists) warnings.add(IMPORT_WARNING_CWD_MISSING);
|
|
476
792
|
if (meta?.errorCode) warnings.add(meta.errorCode);
|
|
477
793
|
return {
|
|
478
794
|
id: candidate.id,
|
|
479
795
|
source: candidate.source,
|
|
480
|
-
title: meta
|
|
796
|
+
title: displayTitle(meta, titles),
|
|
481
797
|
cwd: cwdInfo?.path ?? null,
|
|
482
798
|
modifiedAt: candidate.mtimeMs,
|
|
483
799
|
messageCount: meta?.messageCount ?? null,
|
|
@@ -486,10 +802,10 @@ function rowFor(candidate, meta, cwdInfo, imported) {
|
|
|
486
802
|
};
|
|
487
803
|
}
|
|
488
804
|
|
|
489
|
-
|
|
805
|
+
async function buildRow(candidate, imported, cwdCache = new Map(), titles = null) {
|
|
490
806
|
const meta = cachedMeta(candidate);
|
|
491
807
|
const cwdInfo = meta?.cwd ? await resolveSourceCwd(meta.cwd, cwdCache) : null;
|
|
492
|
-
return rowFor(candidate, meta, cwdInfo, imported);
|
|
808
|
+
return rowFor(candidate, meta, cwdInfo, imported, titles);
|
|
493
809
|
}
|
|
494
810
|
|
|
495
811
|
// ── views & paging ───────────────────────────────────────────────────────────
|
|
@@ -553,7 +869,7 @@ async function pageResult(view, offset, limit, imported) {
|
|
|
553
869
|
const rows = [];
|
|
554
870
|
for (const id of ids) {
|
|
555
871
|
const candidate = state.candidates.get(id);
|
|
556
|
-
if (candidate) rows.push(await buildRow(candidate, imported, cwdCache));
|
|
872
|
+
if (candidate) rows.push(await buildRow(candidate, imported, cwdCache, view.titles));
|
|
557
873
|
}
|
|
558
874
|
const nextOffset = offset + ids.length;
|
|
559
875
|
return {
|
|
@@ -568,31 +884,76 @@ async function pageResult(view, offset, limit, imported) {
|
|
|
568
884
|
};
|
|
569
885
|
}
|
|
570
886
|
|
|
887
|
+
/**
|
|
888
|
+
* Ids of Codex rollouts that an earlier revision of the same thread (same
|
|
889
|
+
* session id) names as its history base. Codex lists one rollout per thread,
|
|
890
|
+
* the current one, so these stay out of the list; a fork's parent is another
|
|
891
|
+
* thread and stays listed.
|
|
892
|
+
*/
|
|
893
|
+
function supersededBaseIds(candidates) {
|
|
894
|
+
const byRollout = new Map();
|
|
895
|
+
for (const candidate of candidates) {
|
|
896
|
+
const meta = cachedMeta(candidate);
|
|
897
|
+
const rolloutId = candidate.source === 'codex' ? rolloutIdOf(candidate.path) : null;
|
|
898
|
+
if (meta?.foreignSessionId && rolloutId) byRollout.set(`${meta.foreignSessionId}\0${rolloutId}`, candidate.id);
|
|
899
|
+
}
|
|
900
|
+
const hidden = new Set();
|
|
901
|
+
for (const candidate of candidates) {
|
|
902
|
+
const meta = cachedMeta(candidate);
|
|
903
|
+
const base = meta?.historyBaseId && byRollout.get(`${meta.foreignSessionId}\0${meta.historyBaseId}`);
|
|
904
|
+
if (base && base !== candidate.id) hidden.add(base);
|
|
905
|
+
}
|
|
906
|
+
// A cycle of revisions must not hide a thread entirely: its newest one (first in order) stays.
|
|
907
|
+
const newest = new Map();
|
|
908
|
+
const visible = new Set();
|
|
909
|
+
const indexed = new Set(byRollout.values());
|
|
910
|
+
for (const candidate of candidates) {
|
|
911
|
+
const sessionId = cachedMeta(candidate)?.foreignSessionId;
|
|
912
|
+
if (!sessionId || !indexed.has(candidate.id)) continue;
|
|
913
|
+
if (!newest.has(sessionId)) newest.set(sessionId, candidate.id);
|
|
914
|
+
if (!hidden.has(candidate.id)) visible.add(sessionId);
|
|
915
|
+
}
|
|
916
|
+
for (const [sessionId, id] of newest) if (!visible.has(sessionId)) hidden.delete(id);
|
|
917
|
+
return hidden;
|
|
918
|
+
}
|
|
919
|
+
|
|
571
920
|
/**
|
|
572
921
|
* Apply a view's filters (time range, query on title/cwd, project) to
|
|
573
922
|
* candidates using their cached metadata; a candidate whose metadata is still
|
|
574
|
-
* pending
|
|
923
|
+
* pending is neither a member nor counted and leaves the result incomplete.
|
|
924
|
+
* The source app's internal and empty records are never members.
|
|
575
925
|
*/
|
|
576
|
-
async function filterCandidates(candidates, filters, imported) {
|
|
926
|
+
async function filterCandidates(candidates, filters, imported, titles) {
|
|
577
927
|
const { needle, projectFilter, modifiedAfter, modifiedBefore } = filters;
|
|
578
928
|
const cwdCache = new Map();
|
|
579
929
|
const memberIds = [];
|
|
580
930
|
const selectableIds = [];
|
|
581
931
|
const projectCounts = new Map();
|
|
582
932
|
let complete = true;
|
|
933
|
+
const superseded = supersededBaseIds(candidates);
|
|
583
934
|
for (const candidate of candidates) {
|
|
935
|
+
if (superseded.has(candidate.id)) continue;
|
|
584
936
|
if (modifiedAfter !== null && candidate.mtimeMs < modifiedAfter) continue;
|
|
585
937
|
if (modifiedBefore !== null && candidate.mtimeMs > modifiedBefore) continue;
|
|
586
938
|
const meta = cachedMeta(candidate);
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
939
|
+
// Unclassified yet: neither a member nor counted until its metadata is read.
|
|
940
|
+
if (!meta) {
|
|
941
|
+
complete = false;
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
944
|
+
// The source app's own records and files read to the end without a message are not conversations.
|
|
945
|
+
if (meta.internalKind || meta.errorCode === 'empty') continue;
|
|
946
|
+
const cwdInfo = meta.cwd ? await resolveSourceCwd(meta.cwd, cwdCache) : null;
|
|
947
|
+
if (
|
|
948
|
+
needle &&
|
|
949
|
+
!(foldText(displayTitle(meta, titles)).includes(needle) || foldText(cwdInfo?.path).includes(needle))
|
|
950
|
+
) {
|
|
590
951
|
continue;
|
|
591
952
|
}
|
|
592
953
|
if (cwdInfo?.path) projectCounts.set(cwdInfo.path, (projectCounts.get(cwdInfo.path) || 0) + 1);
|
|
593
954
|
if (projectFilter && !samePath(cwdInfo?.path, projectFilter)) continue;
|
|
594
955
|
// Already-imported conversations are left out of the list entirely.
|
|
595
|
-
if (
|
|
956
|
+
if (imported.has(candidateKey(candidate, meta))) continue;
|
|
596
957
|
memberIds.push(candidate.id);
|
|
597
958
|
selectableIds.push(candidate.id);
|
|
598
959
|
}
|
|
@@ -624,7 +985,9 @@ export async function listConversations(params, { imported, env = process.env }
|
|
|
624
985
|
projectFilter: typeof project === 'string' && project.trim() ? project.trim() : null,
|
|
625
986
|
};
|
|
626
987
|
const { root, candidates } = await enumerateCandidates(source, env);
|
|
988
|
+
const titles = await loadStoredTitles(source, env);
|
|
627
989
|
// Background metadata for everything, newest first; the first page is awaited.
|
|
990
|
+
// Candidates still unclassified stay out of the view (it is incomplete) and join it on a later listing.
|
|
628
991
|
for (const candidate of candidates) void resolveMeta(candidate);
|
|
629
992
|
if (!filters.needle && !filters.projectFilter) await Promise.all(candidates.slice(0, limit).map(resolveMeta));
|
|
630
993
|
const view = {
|
|
@@ -632,8 +995,9 @@ export async function listConversations(params, { imported, env = process.env }
|
|
|
632
995
|
source,
|
|
633
996
|
root,
|
|
634
997
|
filters,
|
|
998
|
+
titles,
|
|
635
999
|
candidateIds: candidates.map((candidate) => candidate.id),
|
|
636
|
-
...(await filterCandidates(candidates, filters, imported)),
|
|
1000
|
+
...(await filterCandidates(candidates, filters, imported, titles)),
|
|
637
1001
|
touchedAt: Date.now(),
|
|
638
1002
|
};
|
|
639
1003
|
state.views.set(view.id, view);
|
|
@@ -651,21 +1015,22 @@ export async function resolveViewSelection(viewId, excludeIds, imported) {
|
|
|
651
1015
|
const view = requireView(viewId);
|
|
652
1016
|
const candidates = view.candidateIds.map((id) => state.candidates.get(id)).filter(Boolean);
|
|
653
1017
|
await Promise.all(candidates.map(resolveMeta));
|
|
654
|
-
const { selectableIds } = await filterCandidates(candidates, view.filters, imported);
|
|
1018
|
+
const { selectableIds } = await filterCandidates(candidates, view.filters, imported, view.titles);
|
|
655
1019
|
const excluded = new Set(Array.isArray(excludeIds) ? excludeIds : []);
|
|
656
1020
|
return selectableIds.filter((id) => !excluded.has(id));
|
|
657
1021
|
}
|
|
658
1022
|
|
|
659
1023
|
// ── picked files ─────────────────────────────────────────────────────────────
|
|
660
|
-
/** Validate and register a file chosen in a native picker (a local regular .jsonl). */
|
|
661
|
-
export async function registerFile({ source, path } = {}, { imported, limits } = {}) {
|
|
1024
|
+
/** Validate and register a file chosen in a native picker (a local regular .jsonl, or a Codex .jsonl.zst). */
|
|
1025
|
+
export async function registerFile({ source, path } = {}, { imported, limits, env = process.env } = {}) {
|
|
662
1026
|
requireSource(source);
|
|
663
1027
|
if (typeof path !== 'string' || !path.trim() || path.includes('\0')) throw importError('invalidPath');
|
|
664
1028
|
const requested = path.trim();
|
|
665
1029
|
// Network/device forms are refused before any filesystem call.
|
|
666
1030
|
if (isNetworkOrDevicePath(requested)) throw importError('unsupported');
|
|
667
1031
|
if (!isAbsolute(requested)) throw importError('invalidPath');
|
|
668
|
-
|
|
1032
|
+
const pickable = (path) => /\.jsonl$/i.test(path) || (source === 'codex' && isCompressed(path));
|
|
1033
|
+
if (!pickable(basename(requested))) throw importError('unsupported');
|
|
669
1034
|
let real;
|
|
670
1035
|
let info;
|
|
671
1036
|
try {
|
|
@@ -674,7 +1039,7 @@ export async function registerFile({ source, path } = {}, { imported, limits } =
|
|
|
674
1039
|
} catch {
|
|
675
1040
|
throw importError('unreadable');
|
|
676
1041
|
}
|
|
677
|
-
if (isNetworkOrDevicePath(real) || !info.isFile() ||
|
|
1042
|
+
if (isNetworkOrDevicePath(real) || !info.isFile() || !pickable(basename(real))) {
|
|
678
1043
|
throw importError('unsupported');
|
|
679
1044
|
}
|
|
680
1045
|
const candidate = registerCandidate({
|
|
@@ -689,10 +1054,10 @@ export async function registerFile({ source, path } = {}, { imported, limits } =
|
|
|
689
1054
|
let conversion;
|
|
690
1055
|
try {
|
|
691
1056
|
conversion = await convertCandidateFile(candidate, limits);
|
|
692
|
-
} catch {
|
|
693
|
-
throw importError('unreadable');
|
|
1057
|
+
} catch (error) {
|
|
1058
|
+
throw importError(error?.code === IMPORT_ERROR_NEEDS_NEWER_NODE ? IMPORT_ERROR_NEEDS_NEWER_NODE : 'unreadable');
|
|
694
1059
|
}
|
|
695
1060
|
if (conversion.errorCode) throw importError(conversion.errorCode);
|
|
696
1061
|
recordConversion(candidate, conversion);
|
|
697
|
-
return { row: await buildRow(candidate, imported) };
|
|
1062
|
+
return { row: await buildRow(candidate, imported, new Map(), await loadStoredTitles(source, env)) };
|
|
698
1063
|
}
|