mixdog 1.1.2 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -5
- package/package.json +2 -1
- package/scripts/lib/run-node-tests.mjs +9 -0
- package/scripts/prune-desktop-runtime.mjs +3 -0
- package/scripts/test-direct.mjs +4 -2
- package/src/defaults/shipped-history.json +10 -0
- package/src/defaults/skills/pptx/SKILL.md +6 -6
- package/src/defaults/skills/pptx/references/review.md +6 -4
- package/src/defaults/skills/pptx/scripts/source-extract.mjs +1 -1
- package/src/defaults/skills/setup/references/actions.md +18 -5
- package/src/defaults/skills/visualize/SKILL.md +160 -0
- package/src/defaults/skills/visualize/references/craft.md +88 -0
- package/src/headless-command.mjs +56 -3
- package/src/headless-exec.mjs +17 -1
- package/src/help.mjs +2 -0
- package/src/rules/shared/60-verification.md +3 -1
- package/src/rules/shared/70-delivery.md +4 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +1 -0
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +2 -0
- package/src/runtime/agent/orchestrator/cache-mtime.mjs +26 -5
- package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
- package/src/runtime/agent/orchestrator/config-providers-merge.mjs +17 -11
- package/src/runtime/agent/orchestrator/config-storage.mjs +1 -0
- package/src/runtime/agent/orchestrator/config.mjs +19 -5
- package/src/runtime/agent/orchestrator/context/collect.mjs +2 -1
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +11 -5
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +74 -21
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +133 -15
- package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +55 -8
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +86 -8
- package/src/runtime/agent/orchestrator/mcp/client.mjs +474 -164
- package/src/runtime/agent/orchestrator/mcp/lazy-output-validator.mjs +25 -0
- package/src/runtime/agent/orchestrator/mcp/plugin-placeholders.mjs +54 -0
- package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +108 -17
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -12
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +337 -0
- package/src/runtime/agent/orchestrator/providers/command-code-oauth-tokens.mjs +159 -0
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +444 -0
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -4
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +21 -6
- package/src/runtime/agent/orchestrator/providers/effort-rejection.mjs +244 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +41 -28
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +8 -40
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +51 -12
- package/src/runtime/agent/orchestrator/providers/model-cache.mjs +37 -2
- package/src/runtime/agent/orchestrator/providers/model-proxy-bridge.mjs +1080 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +82 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +556 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +251 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +69 -102
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +15 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +6 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +11 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +15 -4
- package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +4 -4
- package/src/runtime/agent/orchestrator/providers/registry.mjs +47 -7
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +10 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +36 -3
- package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -11
- package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +15 -55
- package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +15 -2
- package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +16 -1
- package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +278 -84
- package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +4 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +21 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +54 -3
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +10 -3
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +9 -5
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +18 -8
- package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +284 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +268 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/sqlite-gate.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +628 -0
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +24 -13
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +13 -5
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +362 -0
- package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +253 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +184 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +136 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +128 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +127 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +101 -0
- package/src/runtime/agent/orchestrator/session/explicit-skills.mjs +83 -5
- package/src/runtime/agent/orchestrator/session/imported-message.mjs +11 -0
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/diagnostic.mjs +1 -7
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/refusal.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -4
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +21 -10
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +15 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +22 -12
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +9 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +9 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +37 -16
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +17 -4
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +90 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +22 -12
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +32 -3
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +0 -1
- package/src/runtime/agent/orchestrator/session/message-ids.mjs +123 -0
- package/src/runtime/agent/orchestrator/session/message-ids.test-support.mjs +29 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +33 -22
- package/src/runtime/agent/orchestrator/session/store/listing.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +11 -0
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +817 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +36 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +23 -12
- package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +158 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +202 -374
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +20 -3
- package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +48 -9
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +161 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +17 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -0
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +52 -2
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +32 -22
- package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +10 -3
- package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/builtin/read-cache-policy.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +203 -33
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +32 -35
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +8 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +11 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +9 -6
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +83 -68
- package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +8 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +20 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +16 -7
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +137 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +11 -10
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +21 -11
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +13 -10
- package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +17 -12
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +56 -13
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +58 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +25 -1
- package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/patch/apply-patch/route.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/read-redirects.mjs +34 -30
- package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +7 -7
- package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +78 -5
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -0
- package/src/runtime/attachments/store.d.mts +3 -0
- package/src/runtime/attachments/store.mjs +38 -0
- package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +1 -1
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +16 -4
- package/src/runtime/channels/lib/worker-main.mjs +17 -14
- package/src/runtime/computer-bridge/linux-readiness.mjs +0 -2
- package/src/runtime/effort-judge/judge-client.mjs +56 -18
- package/src/runtime/memory/lib/memory.mjs +1 -1
- package/src/runtime/memory/lib/session-ingest.mjs +2 -2
- package/src/runtime/shared/atomic-file.mjs +33 -6
- package/src/runtime/shared/best-effort-stderr.mjs +8 -0
- package/src/runtime/shared/code-graph-state.mjs +12 -0
- package/src/runtime/shared/defaults-separation.mjs +2 -0
- package/src/runtime/shared/extension-scopes.mjs +5 -3
- package/src/runtime/shared/file-lock.mjs +37 -11
- package/src/runtime/shared/git-runner-worker.mjs +3 -2
- package/src/runtime/shared/git-runner.mjs +4 -3
- package/src/runtime/shared/llm/anthropic-effort.mjs +14 -0
- package/src/runtime/shared/llm/default-context-window.mjs +6 -0
- package/src/runtime/shared/llm/model-catalog-projection.mjs +5 -4
- package/src/runtime/shared/llm/model-catalog.mjs +240 -130
- package/src/runtime/shared/llm/provider-catalog-cache.mjs +36 -28
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +162 -0
- package/src/runtime/shared/llm/provider-seed-rosters.mjs +130 -0
- package/src/runtime/shared/model-proxy-mode.d.mts +3 -0
- package/src/runtime/shared/model-proxy-mode.mjs +19 -0
- package/src/runtime/shared/plugin-manifest.mjs +18 -9
- package/src/runtime/shared/pristine-execution-contract.json +9 -2
- package/src/runtime/shared/pristine-execution.mjs +28 -4
- package/src/runtime/shared/provider-accounts.mjs +125 -21
- package/src/runtime/shared/provider-api-key.mjs +33 -1
- package/src/runtime/shared/schedule-model-ref.mjs +11 -2
- package/src/runtime/shared/session-usage-totals.mjs +19 -0
- package/src/runtime/shared/sharp-electron-linux.mjs +25 -0
- package/src/runtime/shared/shell-display.mjs +9 -3
- package/src/runtime/shared/singleton-owner.mjs +36 -6
- package/src/runtime/shared/skill-mentions.d.mts +10 -0
- package/src/runtime/shared/skill-mentions.mjs +133 -0
- package/src/runtime/shared/skill-script-runtime.mjs +264 -0
- package/src/runtime/shared/statusline/statusline-segments.mjs +68 -14
- package/src/runtime/shared/tool-primitives.mjs +35 -2
- package/src/runtime/shared/tool-surface.mjs +7 -0
- package/src/runtime/shared/tool-work-units.mjs +14 -0
- package/src/runtime/shared/transcript-metadata.mjs +17 -1
- package/src/runtime/shared/turn-changes.mjs +791 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +346 -12
- package/src/runtime/shared/turn-snapshot.mjs +690 -81
- package/src/runtime/shared/turn-worktree-snapshot.mjs +185 -15
- package/src/runtime/shared/worker-requests.mjs +15 -2
- package/src/runtime/shared/worktrees/cache-cleanups.mjs +52 -0
- package/src/runtime/shared/worktrees/contract.mjs +551 -0
- package/src/runtime/shared/worktrees/disk.mjs +57 -0
- package/src/runtime/shared/worktrees/git.mjs +247 -0
- package/src/runtime/shared/worktrees/include.mjs +480 -0
- package/src/runtime/shared/worktrees/lock.mjs +46 -0
- package/src/runtime/shared/worktrees/maintenance.mjs +96 -0
- package/src/runtime/shared/worktrees/manager.mjs +1215 -0
- package/src/runtime/shared/worktrees/merge.mjs +182 -0
- package/src/runtime/shared/worktrees/owner.mjs +46 -0
- package/src/runtime/shared/worktrees/paths.mjs +62 -0
- package/src/runtime/shared/worktrees/registry.mjs +110 -0
- package/src/runtime/shared/worktrees/settings.mjs +84 -0
- package/src/runtime/shared/worktrees/slug.mjs +62 -0
- package/src/runtime/shared/worktrees/test-support.mjs +96 -0
- package/src/runtime/visualize-bridge/client.mjs +181 -0
- package/src/runtime/visualize-bridge/tool-defs.mjs +97 -0
- package/src/session-runtime/automation-agents.mjs +8 -2
- package/src/session-runtime/boot/apis.mjs +90 -0
- package/src/session-runtime/boot/config.mjs +7 -2
- package/src/session-runtime/boot/facade.mjs +21 -0
- package/src/session-runtime/boot/infrastructure.mjs +30 -18
- package/src/session-runtime/boot/session.mjs +1 -0
- package/src/session-runtime/boot/tools.mjs +4 -0
- package/src/session-runtime/config-lifecycle.mjs +10 -1
- package/src/session-runtime/conversation-import/discovery.mjs +698 -0
- package/src/session-runtime/conversation-import/service.mjs +369 -0
- package/src/session-runtime/cwd-plugins/cwd-apply.mjs +21 -6
- package/src/session-runtime/cwd-plugins/plugin-status.mjs +47 -42
- package/src/session-runtime/global-extensions.mjs +2 -2
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +27 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +32 -1
- package/src/session-runtime/internal-tool-executor.mjs +9 -1
- package/src/session-runtime/lifecycle/session-catalog.mjs +8 -1
- package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +5 -0
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +21 -3
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +16 -0
- package/src/session-runtime/mcp-glue/connect.mjs +68 -16
- package/src/session-runtime/mcp-glue/plugin-servers.mjs +499 -0
- package/src/session-runtime/mcp-glue/server-catalog.mjs +122 -33
- package/src/session-runtime/mcp-glue/server-input.mjs +29 -0
- package/src/session-runtime/mcp-glue.mjs +22 -2
- package/src/session-runtime/model-route/route-persist.mjs +3 -0
- package/src/session-runtime/model-route/set-route.mjs +15 -2
- package/src/session-runtime/new-session-config.mjs +3 -2
- package/src/session-runtime/notification-bus.mjs +18 -3
- package/src/session-runtime/provider-auth/accounts.mjs +33 -15
- package/src/session-runtime/provider-models/row-hydration.mjs +2 -0
- package/src/session-runtime/provider-models/web-search-catalog.mjs +5 -1
- package/src/session-runtime/provider-readiness.mjs +16 -10
- package/src/session-runtime/resource-extension-sync.mjs +45 -7
- package/src/session-runtime/resource-mcp-api.mjs +121 -84
- package/src/session-runtime/resource-plugins-api.mjs +29 -41
- package/src/session-runtime/resource-scope-decorate.mjs +6 -8
- package/src/session-runtime/resource-skills-api.mjs +23 -2
- package/src/session-runtime/route-resolution.mjs +10 -1
- package/src/session-runtime/route-state.mjs +16 -2
- package/src/session-runtime/runtime-core.mjs +5 -0
- package/src/session-runtime/runtime-facade.mjs +8 -0
- package/src/session-runtime/runtime-review-api.mjs +9 -0
- package/src/session-runtime/schedule-session-run.mjs +1 -1
- package/src/session-runtime/services/agent-tool/helpers.mjs +5 -0
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +3 -0
- package/src/session-runtime/services/agent-tool/job-views/job-lookup.mjs +1 -0
- package/src/session-runtime/services/agent-tool/job-views/spawn-meta.mjs +1 -0
- package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +15 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +1 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +10 -2
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -7
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +2 -1
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -0
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +1 -0
- package/src/session-runtime/services/agent-tool.mjs +1 -0
- package/src/session-runtime/services/channel-session-router.mjs +4 -1
- package/src/session-runtime/services/daemon-candidate.mjs +3 -2
- package/src/session-runtime/services/daemon-crash-capture/spawn-capture.mjs +55 -0
- package/src/session-runtime/services/hook-bus/command-handler.mjs +4 -1
- package/src/session-runtime/services/hook-bus/handlers.mjs +7 -3
- package/src/session-runtime/services/provider-admin.mjs +64 -18
- package/src/session-runtime/services/usage-dashboard-model.mjs +3 -0
- package/src/session-runtime/session-create/session-options.mjs +10 -1
- package/src/session-runtime/session-turn-api.mjs +1 -0
- package/src/session-runtime/settings-api.mjs +4 -2
- package/src/session-runtime/settings-builtin-tools-api.mjs +9 -2
- package/src/session-runtime/settings-worktree-api.mjs +17 -0
- package/src/session-runtime/setup-tool/executor.mjs +23 -5
- package/src/session-runtime/setup-tool/extended-actions.mjs +1 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +4 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +38 -4
- package/src/session-runtime/skills-api.mjs +7 -3
- package/src/session-runtime/turn/agent-ops.mjs +12 -1
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +14 -3
- package/src/session-runtime/turn/turn-run/snapshot-tracker.mjs +20 -15
- package/src/session-runtime/turn/turn-run/turn-open.mjs +4 -5
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +29 -1
- package/src/session-runtime/turn/turn-run/turn-settle.mjs +6 -1
- package/src/session-runtime/turn/turn-run.mjs +6 -7
- package/src/session-runtime/webhook-session-run.mjs +16 -4
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -3
- package/src/session-runtime/workspace/admission-gate.mjs +197 -0
- package/src/session-runtime/workspace/project-branches.mjs +136 -0
- package/src/session-runtime/workspace/transition-steps.mjs +364 -0
- package/src/session-runtime/workspace/transitions.mjs +651 -0
- package/src/session-runtime/workspace/workspace-state.mjs +112 -0
- package/src/skill-script-entry.mjs +36 -0
- package/src/standalone/channel-transport/http-routes.mjs +17 -0
- package/src/standalone/channel-transport.mjs +13 -1
- package/src/standalone/daemon-desktop-runtime.mjs +1 -0
- package/src/standalone/daemon-owner.mjs +372 -0
- package/src/standalone/daemon-stored-session-views.mjs +15 -4
- package/src/standalone/daemon.mjs +179 -8
- package/src/standalone/desktop-service-registry.mjs +20 -0
- package/src/standalone/local-session-runtime.mjs +11 -0
- package/src/standalone/session-client/attachment-calls.mjs +3 -1
- package/src/standalone/session-client/daemon-unavailable.mjs +62 -0
- package/src/standalone/session-client/lease-heartbeat.mjs +47 -0
- package/src/standalone/session-client.mjs +104 -13
- package/src/standalone/session-protocol.mjs +21 -0
- package/src/standalone/session-proxy.mjs +3 -0
- package/src/standalone/session-service/agent-tree/agent-child.mjs +4 -0
- package/src/standalone/session-service/agent-tree/agent-rehydrate.mjs +1 -0
- package/src/standalone/session-service/entries/lifecycle.mjs +6 -0
- package/src/standalone/session-service/projection/revisions.mjs +2 -0
- package/src/standalone/session-service/projection/transcript-window.mjs +149 -19
- package/src/standalone/session-service/projection/workspace.mjs +14 -0
- package/src/standalone/session-service/session-calls/session-action.mjs +17 -4
- package/src/standalone/session-transport/call-route.mjs +13 -0
- package/src/standalone/session-transport/client-registry/client-grace.mjs +45 -3
- package/src/standalone/session-transport/client-registry/registration-replays.mjs +1 -0
- package/src/standalone/session-transport/client-registry.mjs +20 -3
- package/src/standalone/session-transport/routes.mjs +79 -2
- package/src/standalone/session-transport/stack.mjs +2 -0
- package/src/standalone/session-transport.mjs +24 -1
- package/src/tui/App.jsx +9 -1
- package/src/tui/app/app-view/floating-panel.jsx +9 -1
- package/src/tui/app/app-view.jsx +6 -0
- package/src/tui/app/extension-pickers/plugins-pickers.mjs +35 -16
- package/src/tui/app/message-selector.mjs +2 -1
- package/src/tui/app/model-options.mjs +37 -5
- package/src/tui/app/model-picker/provider-models-list.mjs +2 -2
- package/src/tui/app/model-picker.mjs +4 -2
- package/src/tui/app/skill-mention-palette.mjs +88 -0
- package/src/tui/app/slash-palette.mjs +23 -7
- package/src/tui/app/transcript-row-estimate.mjs +11 -3
- package/src/tui/app/use-prompt-draft-flow.mjs +7 -2
- package/src/tui/app/use-prompt-state.mjs +5 -0
- package/src/tui/components/Message.jsx +22 -1
- package/src/tui/components/PromptInput.jsx +21 -3
- package/src/tui/components/SlashCommandPalette.jsx +12 -3
- package/src/tui/components/TranscriptItem.jsx +9 -1
- package/src/tui/components/prompt-input/key-handler.mjs +21 -8
- package/src/tui/components/user-message-skills.mjs +38 -0
- package/src/tui/dist/index.mjs +839 -207
- package/src/tui/index.jsx +10 -2
- package/src/tui/session/compaction-view.mjs +147 -0
- package/src/tui/session/context-state.mjs +6 -0
- package/src/tui/session/initial-state.mjs +3 -0
- package/src/tui/session/queue-helpers.mjs +7 -2
- package/src/tui/session/quota-resume.mjs +2 -1
- package/src/tui/session/session-api/commands.mjs +10 -2
- package/src/tui/session/session-api/controls.mjs +4 -0
- package/src/tui/session/session-api/extensions.mjs +4 -2
- package/src/tui/session/session-api/intake/rewind.mjs +2 -1
- package/src/tui/session/session-api/integrations.mjs +17 -0
- package/src/tui/session/session-api/lifecycle/dispose.mjs +6 -1
- package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -0
- package/src/tui/session/session-api/lifecycle/resume.mjs +3 -0
- package/src/tui/session/session-api/routes.mjs +10 -1
- package/src/tui/session/session-api/settings.mjs +9 -0
- package/src/tui/session/session-api/worktree.mjs +60 -0
- package/src/tui/session/session-api.mjs +2 -0
- package/src/tui/session/session-flow/auto-clear.mjs +31 -12
- package/src/tui/session/session-flow/drain.mjs +27 -3
- package/src/tui/session/session-flow/session-reset.mjs +4 -3
- package/src/tui/session/session-flow.mjs +2 -0
- package/src/tui/session/transcript-intake.mjs +14 -5
- package/src/tui/session/tui-steering-persist.mjs +25 -9
- package/src/tui/session/turn-stream.mjs +32 -4
- package/src/tui/session/turn-tool-cards.mjs +10 -2
- package/src/tui/session/turn.mjs +56 -10
- package/src/runtime/effort-judge/previous-human-request.mjs +0 -162
- package/src/runtime/effort-judge/verified-context-input.mjs +0 -10
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discovery of importable foreign conversations on disk (read-only).
|
|
3
|
+
*
|
|
4
|
+
* codex $CODEX_HOME/sessions/**\/rollout-*.jsonl (default ~/.codex)
|
|
5
|
+
* claude $CLAUDE_CONFIG_DIR/projects/*\/*.jsonl (default ~/.claude)
|
|
6
|
+
*
|
|
7
|
+
* Enumeration is stat-only; per-file metadata (foreign id, cwd, title, and
|
|
8
|
+
* the message count when the whole file fit the read) comes from a bounded
|
|
9
|
+
* prefix read with bounded concurrency and is cached by canonical path +
|
|
10
|
+
* size + mtime. State is process-wide so every session runtime in the daemon
|
|
11
|
+
* shares one cache, one candidate registry and one set of listing views.
|
|
12
|
+
*/
|
|
13
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
14
|
+
import { open, readdir, realpath, stat } from 'node:fs/promises';
|
|
15
|
+
import { homedir } from 'node:os';
|
|
16
|
+
import { basename, extname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
17
|
+
import {
|
|
18
|
+
IMPORT_MAX_FILE_BYTES,
|
|
19
|
+
IMPORT_MAX_LINE_BYTES,
|
|
20
|
+
IMPORT_MAX_METADATA_BYTES,
|
|
21
|
+
IMPORT_MAX_RETAINED_ENTRIES,
|
|
22
|
+
IMPORT_MAX_TRANSCRIPT_CHARS,
|
|
23
|
+
createConversationConverter,
|
|
24
|
+
isImportSource,
|
|
25
|
+
} from '../../runtime/agent/orchestrator/session/conversation-import/convert.mjs';
|
|
26
|
+
import { codexMetaFromTruncatedLine } from '../../runtime/agent/orchestrator/session/conversation-import/codex.mjs';
|
|
27
|
+
import { IMPORT_WARNING_CWD_MISSING } from '../../runtime/agent/orchestrator/session/conversation-import/turns.mjs';
|
|
28
|
+
import { importOriginKey } from '../../runtime/agent/orchestrator/session/conversation-import/imported-session.mjs';
|
|
29
|
+
|
|
30
|
+
export const PREFIX_CHUNK_BYTES = 64 * 1024;
|
|
31
|
+
export const PREFIX_MAX_BYTES = 512 * 1024;
|
|
32
|
+
const IMPORT_CHUNK_BYTES = 256 * 1024;
|
|
33
|
+
const META_CONCURRENCY = 8;
|
|
34
|
+
const STAT_CONCURRENCY = 32;
|
|
35
|
+
const VIEW_TTL_MS = 30 * 60 * 1000;
|
|
36
|
+
const MAX_VIEWS = 32;
|
|
37
|
+
const DEFAULT_LIMIT = 50;
|
|
38
|
+
const MAX_LIMIT = 100;
|
|
39
|
+
|
|
40
|
+
/** Error with a language-neutral `code`; the message is the code itself. */
|
|
41
|
+
export function importError(code) {
|
|
42
|
+
return Object.assign(new Error(code), { code });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const state = {
|
|
46
|
+
candidates: new Map(), // id → { id, source, path, size, mtimeMs }
|
|
47
|
+
meta: new Map(), // canonical path → { stampKey, value }
|
|
48
|
+
pendingMeta: new Map(), // canonical path → Promise
|
|
49
|
+
queue: [],
|
|
50
|
+
active: 0,
|
|
51
|
+
views: new Map(), // viewId → view
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** Test seam: forget every cached candidate, metadata entry and view. */
|
|
55
|
+
export function resetConversationDiscovery() {
|
|
56
|
+
state.candidates.clear();
|
|
57
|
+
state.meta.clear();
|
|
58
|
+
state.pendingMeta.clear();
|
|
59
|
+
state.queue.length = 0;
|
|
60
|
+
state.views.clear();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const envDir = (value) => (typeof value === 'string' && value.trim() ? resolve(value.trim()) : null);
|
|
64
|
+
|
|
65
|
+
/** Root searched for a source (blank env override = unset). */
|
|
66
|
+
export function sourceRoot(source, env = process.env) {
|
|
67
|
+
if (source === 'codex') return join(envDir(env.CODEX_HOME) || join(homedir(), '.codex'), 'sessions');
|
|
68
|
+
if (source === 'claude') return join(envDir(env.CLAUDE_CONFIG_DIR) || join(homedir(), '.claude'), 'projects');
|
|
69
|
+
throw importError('unsupported');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function requireSource(source) {
|
|
73
|
+
if (!isImportSource(source)) throw new TypeError('source must be codex or claude.');
|
|
74
|
+
return source;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const caseFold = (value) => (process.platform === 'win32' ? value.toLowerCase() : value);
|
|
78
|
+
|
|
79
|
+
function isInside(root, path) {
|
|
80
|
+
const rel = relative(caseFold(root), caseFold(path));
|
|
81
|
+
return rel === '' || (!!rel && !rel.startsWith('..') && !isAbsolute(rel));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A path that may reach the network or a raw device, by string alone (no
|
|
86
|
+
* filesystem call): UNC `\\server\share` / `//server/share`, and the Win32
|
|
87
|
+
* namespaces `\\?\…` (including `\\?\UNC\…`) and `\\.\…`. Two leading
|
|
88
|
+
* separators also name a network root on POSIX (`//host/path`).
|
|
89
|
+
*/
|
|
90
|
+
export function isNetworkOrDevicePath(path) {
|
|
91
|
+
return /^[\\/]{2}/.test(String(path));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const fileIdentity = (info) => `${info.dev}:${info.ino}`;
|
|
95
|
+
|
|
96
|
+
export function candidateId(source, canonicalPath) {
|
|
97
|
+
return createHash('sha256')
|
|
98
|
+
.update(`${source}\0${caseFold(canonicalPath)}`)
|
|
99
|
+
.digest('base64url')
|
|
100
|
+
.slice(0, 32);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Canonical path of a directory entry, or null when it escapes the root.
|
|
104
|
+
async function containedRealpath(rootReal, path) {
|
|
105
|
+
try {
|
|
106
|
+
const real = await realpath(path);
|
|
107
|
+
return isInside(rootReal, real) ? real : null;
|
|
108
|
+
} catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function entriesOf(dir) {
|
|
114
|
+
try {
|
|
115
|
+
return await readdir(dir, { withFileTypes: true });
|
|
116
|
+
} catch {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function walkCodex(rootReal, dir, files, seenDirs) {
|
|
122
|
+
for (const entry of await entriesOf(dir)) {
|
|
123
|
+
const path = join(dir, entry.name);
|
|
124
|
+
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
125
|
+
const real = await containedRealpath(rootReal, path);
|
|
126
|
+
if (!real) continue;
|
|
127
|
+
if (entry.isDirectory() || (await stat(real).catch(() => null))?.isDirectory()) {
|
|
128
|
+
if (seenDirs.has(caseFold(real))) continue;
|
|
129
|
+
seenDirs.add(caseFold(real));
|
|
130
|
+
await walkCodex(rootReal, path, files, seenDirs);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (/^rollout-.*\.jsonl$/i.test(entry.name)) files.push(path);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function walkClaude(rootReal, files) {
|
|
139
|
+
for (const entry of await entriesOf(rootReal)) {
|
|
140
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
141
|
+
const dir = join(rootReal, entry.name);
|
|
142
|
+
if (!(await containedRealpath(rootReal, dir))) continue;
|
|
143
|
+
for (const file of await entriesOf(dir)) {
|
|
144
|
+
if ((file.isFile() || file.isSymbolicLink()) && /\.jsonl$/i.test(file.name)) files.push(join(dir, file.name));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function mapBounded(items, limit, fn) {
|
|
150
|
+
const out = new Array(items.length);
|
|
151
|
+
let next = 0;
|
|
152
|
+
const worker = async () => {
|
|
153
|
+
while (next < items.length) {
|
|
154
|
+
const index = next++;
|
|
155
|
+
out[index] = await fn(items[index], index);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Regular files under the source root, canonical and contained, as candidates. */
|
|
163
|
+
export async function enumerateCandidates(source, env = process.env) {
|
|
164
|
+
const root = sourceRoot(source, env);
|
|
165
|
+
let rootReal;
|
|
166
|
+
try {
|
|
167
|
+
rootReal = await realpath(root);
|
|
168
|
+
} catch {
|
|
169
|
+
return { root, candidates: [] };
|
|
170
|
+
}
|
|
171
|
+
const paths = [];
|
|
172
|
+
if (source === 'codex') await walkCodex(rootReal, rootReal, paths, new Set([caseFold(rootReal)]));
|
|
173
|
+
else await walkClaude(rootReal, paths);
|
|
174
|
+
const stamped = await mapBounded(paths, STAT_CONCURRENCY, async (path) => {
|
|
175
|
+
const real = await containedRealpath(rootReal, path);
|
|
176
|
+
if (!real) return null;
|
|
177
|
+
const info = await stat(real).catch(() => null);
|
|
178
|
+
return info?.isFile() ? { path: real, size: info.size, mtimeMs: info.mtimeMs, fileId: fileIdentity(info) } : null;
|
|
179
|
+
});
|
|
180
|
+
const unique = new Map();
|
|
181
|
+
for (const file of stamped) {
|
|
182
|
+
if (!file) continue;
|
|
183
|
+
const id = candidateId(source, file.path);
|
|
184
|
+
unique.set(id, registerCandidate({ id, source, root: rootReal, ...file }));
|
|
185
|
+
}
|
|
186
|
+
const candidates = [...unique.values()].sort(byModifiedThenId);
|
|
187
|
+
return { root, candidates };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function compareCodeUnits(left, right) {
|
|
191
|
+
if (left === right) return 0;
|
|
192
|
+
return left < right ? -1 : 1;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function byModifiedThenId(left, right) {
|
|
196
|
+
if (right.mtimeMs !== left.mtimeMs) return right.mtimeMs - left.mtimeMs;
|
|
197
|
+
return compareCodeUnits(left.id, right.id);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function registerCandidate(candidate) {
|
|
201
|
+
state.candidates.set(candidate.id, candidate);
|
|
202
|
+
return candidate;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function getCandidate(id) {
|
|
206
|
+
return typeof id === 'string' ? state.candidates.get(id) || null : null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const stampKey = (candidate) => `${candidate.size}:${candidate.mtimeMs}`;
|
|
210
|
+
|
|
211
|
+
/** Cached metadata of a candidate at its current stamp, or null. */
|
|
212
|
+
export function cachedMeta(candidate) {
|
|
213
|
+
const entry = state.meta.get(candidate.path);
|
|
214
|
+
return entry && entry.stampKey === stampKey(candidate) ? entry.value : null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Record a full conversion's message count against the stamp it was read at. */
|
|
218
|
+
export function recordConversion(candidate, conversion) {
|
|
219
|
+
state.meta.set(candidate.path, {
|
|
220
|
+
stampKey: stampKey(candidate),
|
|
221
|
+
value: metaFromConversion(conversion, true),
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function metaFromConversion(conversion, whole) {
|
|
226
|
+
return {
|
|
227
|
+
foreignSessionId: conversion.foreignSessionId,
|
|
228
|
+
cwd: conversion.cwd,
|
|
229
|
+
title: conversion.title,
|
|
230
|
+
messageCount: whole && !conversion.errorCode ? conversion.messageCount : null,
|
|
231
|
+
warnings: whole ? conversion.warnings : conversion.warnings.filter((code) => code !== 'malformed'),
|
|
232
|
+
errorCode: whole ? conversion.errorCode : null,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Open a candidate for reading and prove the opened file is still the one
|
|
238
|
+
* enumerated: its current real path is the candidate path (inside the source
|
|
239
|
+
* root for an enumerated candidate, never a network/device path) and names
|
|
240
|
+
* the same file as the open handle, which is the file enumerated. A link or
|
|
241
|
+
* junction swapped in after enumeration fails `unreadable`.
|
|
242
|
+
*/
|
|
243
|
+
async function openCandidate(candidate) {
|
|
244
|
+
const handle = await open(candidate.path, 'r');
|
|
245
|
+
try {
|
|
246
|
+
const opened = await handle.stat();
|
|
247
|
+
const real = await realpath(candidate.path);
|
|
248
|
+
const current = await stat(real);
|
|
249
|
+
const identity = fileIdentity(opened);
|
|
250
|
+
if (
|
|
251
|
+
!opened.isFile() ||
|
|
252
|
+
isNetworkOrDevicePath(real) ||
|
|
253
|
+
caseFold(real) !== caseFold(candidate.path) ||
|
|
254
|
+
(candidate.root && !isInside(candidate.root, real)) ||
|
|
255
|
+
fileIdentity(current) !== identity ||
|
|
256
|
+
(candidate.fileId && candidate.fileId !== identity)
|
|
257
|
+
) {
|
|
258
|
+
throw importError('unreadable');
|
|
259
|
+
}
|
|
260
|
+
return handle;
|
|
261
|
+
} catch (error) {
|
|
262
|
+
await handle.close();
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Stream a file's lines (split on LF; the converter strips BOM and CR) into
|
|
269
|
+
* `onLine`, holding at most one line plus one chunk in memory. Stops when
|
|
270
|
+
* `onLine` or `onChunk` returns false, when a line exceeds `maxLineBytes`
|
|
271
|
+
* (`overflow`), or after `maxBytes` with more to read (`truncated`;
|
|
272
|
+
* `firstPartial` = the cut-off first line).
|
|
273
|
+
*/
|
|
274
|
+
async function streamJsonlLines(
|
|
275
|
+
handle,
|
|
276
|
+
onLine,
|
|
277
|
+
{ maxLineBytes, chunkBytes, maxBytes = Number.POSITIVE_INFINITY, onChunk }
|
|
278
|
+
) {
|
|
279
|
+
const chunk = Buffer.allocUnsafe(chunkBytes);
|
|
280
|
+
let pending = [];
|
|
281
|
+
let pendingBytes = 0;
|
|
282
|
+
let position = 0;
|
|
283
|
+
let lines = 0;
|
|
284
|
+
const emit = (piece) => {
|
|
285
|
+
const line = (pending.length ? Buffer.concat([...pending, piece]) : piece).toString('utf8');
|
|
286
|
+
pending = [];
|
|
287
|
+
pendingBytes = 0;
|
|
288
|
+
lines += 1;
|
|
289
|
+
return onLine(line) !== false;
|
|
290
|
+
};
|
|
291
|
+
const outcome = (fields) => ({
|
|
292
|
+
bytesRead: position,
|
|
293
|
+
eof: false,
|
|
294
|
+
overflow: false,
|
|
295
|
+
truncated: false,
|
|
296
|
+
firstPartial: null,
|
|
297
|
+
...fields,
|
|
298
|
+
});
|
|
299
|
+
for (;;) {
|
|
300
|
+
const room = Math.min(chunkBytes, maxBytes - position);
|
|
301
|
+
// At the byte budget: probe one byte to tell a cut-off from the end of file.
|
|
302
|
+
const { bytesRead } = await handle.read(chunk, 0, room > 0 ? room : 1, position);
|
|
303
|
+
if (!bytesRead) break;
|
|
304
|
+
if (room <= 0) {
|
|
305
|
+
const firstPartial = lines === 0 && pending.length ? Buffer.concat(pending).toString('utf8') : null;
|
|
306
|
+
return outcome({ truncated: true, firstPartial });
|
|
307
|
+
}
|
|
308
|
+
position += bytesRead;
|
|
309
|
+
const view = chunk.subarray(0, bytesRead);
|
|
310
|
+
let start = 0;
|
|
311
|
+
for (let newline = view.indexOf(10, start); newline !== -1; newline = view.indexOf(10, start)) {
|
|
312
|
+
if (pendingBytes + newline - start > maxLineBytes) return outcome({ overflow: true });
|
|
313
|
+
if (!emit(view.subarray(start, newline))) return outcome({});
|
|
314
|
+
start = newline + 1;
|
|
315
|
+
}
|
|
316
|
+
if (start < bytesRead) {
|
|
317
|
+
pendingBytes += bytesRead - start;
|
|
318
|
+
if (pendingBytes > maxLineBytes) return outcome({ overflow: true });
|
|
319
|
+
pending.push(Buffer.from(view.subarray(start)));
|
|
320
|
+
}
|
|
321
|
+
if (onChunk && onChunk(position) === false) return outcome({});
|
|
322
|
+
}
|
|
323
|
+
if (pending.length && !emit(Buffer.alloc(0))) return outcome({});
|
|
324
|
+
return outcome({ eof: true });
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async function readPrefixMeta(candidate) {
|
|
328
|
+
const converter = createConversationConverter(candidate.source);
|
|
329
|
+
const handle = await openCandidate(candidate);
|
|
330
|
+
let read;
|
|
331
|
+
try {
|
|
332
|
+
read = await streamJsonlLines(handle, (line) => converter.pushLine(line), {
|
|
333
|
+
maxLineBytes: PREFIX_MAX_BYTES,
|
|
334
|
+
chunkBytes: PREFIX_CHUNK_BYTES,
|
|
335
|
+
maxBytes: PREFIX_MAX_BYTES,
|
|
336
|
+
onChunk: (position) => {
|
|
337
|
+
// A file read to its known size keeps going to EOF (whole-file metadata).
|
|
338
|
+
if (position >= candidate.size) return true;
|
|
339
|
+
const partial = converter.result();
|
|
340
|
+
return !(partial.foreignSessionId && partial.cwd && partial.title);
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
} finally {
|
|
344
|
+
await handle.close();
|
|
345
|
+
}
|
|
346
|
+
const conversion = converter.result();
|
|
347
|
+
if (read.eof) return metaFromConversion(conversion, true);
|
|
348
|
+
const meta = metaFromConversion(conversion, false);
|
|
349
|
+
if (candidate.source === 'codex' && (!meta.foreignSessionId || !meta.cwd) && read.firstPartial) {
|
|
350
|
+
// A session_meta line longer than the prefix still names its id/cwd.
|
|
351
|
+
const fromLine = codexMetaFromTruncatedLine(read.firstPartial.replace(/^\uFEFF/, ''));
|
|
352
|
+
meta.foreignSessionId ||= fromLine?.foreignSessionId || null;
|
|
353
|
+
meta.cwd ||= fromLine?.cwd || null;
|
|
354
|
+
}
|
|
355
|
+
return meta;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Stream-convert a whole candidate file under the import limits. A file over
|
|
360
|
+
* `maxFileBytes`, a line over `maxLineBytes` or a conversion over the
|
|
361
|
+
* converter's limits fails the file with `tooLarge` without reading the rest.
|
|
362
|
+
* `bytesRead` is how far the read got.
|
|
363
|
+
*/
|
|
364
|
+
export async function convertCandidateFile(
|
|
365
|
+
candidate,
|
|
366
|
+
{
|
|
367
|
+
maxFileBytes = IMPORT_MAX_FILE_BYTES,
|
|
368
|
+
maxLineBytes = IMPORT_MAX_LINE_BYTES,
|
|
369
|
+
maxTranscriptChars = IMPORT_MAX_TRANSCRIPT_CHARS,
|
|
370
|
+
maxRetainedEntries = IMPORT_MAX_RETAINED_ENTRIES,
|
|
371
|
+
maxMetadataBytes = IMPORT_MAX_METADATA_BYTES,
|
|
372
|
+
chunkBytes = IMPORT_CHUNK_BYTES,
|
|
373
|
+
} = {}
|
|
374
|
+
) {
|
|
375
|
+
const converter = createConversationConverter(candidate.source, {
|
|
376
|
+
maxTranscriptChars,
|
|
377
|
+
maxRetainedEntries,
|
|
378
|
+
maxMetadataBytes,
|
|
379
|
+
});
|
|
380
|
+
const handle = await openCandidate(candidate);
|
|
381
|
+
try {
|
|
382
|
+
const read = await streamJsonlLines(handle, (line) => converter.pushLine(line), {
|
|
383
|
+
maxLineBytes,
|
|
384
|
+
chunkBytes,
|
|
385
|
+
maxBytes: maxFileBytes,
|
|
386
|
+
});
|
|
387
|
+
if (read.overflow || read.truncated) converter.markTooLarge();
|
|
388
|
+
return { ...converter.result(), bytesRead: read.bytesRead };
|
|
389
|
+
} finally {
|
|
390
|
+
await handle.close();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function pumpMetaQueue() {
|
|
395
|
+
while (state.active < META_CONCURRENCY && state.queue.length) {
|
|
396
|
+
const job = state.queue.shift();
|
|
397
|
+
state.active += 1;
|
|
398
|
+
readPrefixMeta(job.candidate)
|
|
399
|
+
.catch(() => ({
|
|
400
|
+
foreignSessionId: null,
|
|
401
|
+
cwd: null,
|
|
402
|
+
title: null,
|
|
403
|
+
messageCount: null,
|
|
404
|
+
warnings: [],
|
|
405
|
+
errorCode: 'unreadable',
|
|
406
|
+
}))
|
|
407
|
+
.then((value) => {
|
|
408
|
+
state.meta.set(job.candidate.path, { stampKey: stampKey(job.candidate), value });
|
|
409
|
+
job.resolve(value);
|
|
410
|
+
})
|
|
411
|
+
.finally(() => {
|
|
412
|
+
state.pendingMeta.delete(job.key);
|
|
413
|
+
state.active -= 1;
|
|
414
|
+
pumpMetaQueue();
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** Metadata of a candidate, read through the bounded queue when not cached. */
|
|
420
|
+
export function resolveMeta(candidate) {
|
|
421
|
+
const cached = cachedMeta(candidate);
|
|
422
|
+
if (cached) return Promise.resolve(cached);
|
|
423
|
+
const key = `${candidate.path}\0${stampKey(candidate)}`;
|
|
424
|
+
const pending = state.pendingMeta.get(key);
|
|
425
|
+
if (pending) return pending;
|
|
426
|
+
const promise = new Promise((resolveJob) => {
|
|
427
|
+
state.queue.push({ candidate, key, resolve: resolveJob });
|
|
428
|
+
});
|
|
429
|
+
state.pendingMeta.set(key, promise);
|
|
430
|
+
pumpMetaQueue();
|
|
431
|
+
return promise;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ── cwd canonicalization ─────────────────────────────────────────────────────
|
|
435
|
+
/**
|
|
436
|
+
* `{ exists, path }`: the canonical directory when it exists, else the raw
|
|
437
|
+
* value. A relative or network/device cwd is display text only: it is never
|
|
438
|
+
* handed to the filesystem (probing a UNC share can negotiate credentials).
|
|
439
|
+
*/
|
|
440
|
+
export async function resolveSourceCwd(raw, cache = null) {
|
|
441
|
+
if (!raw) return { exists: false, path: null };
|
|
442
|
+
if (cache?.has(raw)) return cache.get(raw);
|
|
443
|
+
let result = { exists: false, path: raw };
|
|
444
|
+
try {
|
|
445
|
+
if (!isAbsolute(raw) || isNetworkOrDevicePath(raw)) throw importError('unsupported');
|
|
446
|
+
const real = await realpath(raw);
|
|
447
|
+
if (!isNetworkOrDevicePath(real) && (await stat(real)).isDirectory()) result = { exists: true, path: real };
|
|
448
|
+
} catch {
|
|
449
|
+
/* missing or unreadable: kept as provenance only */
|
|
450
|
+
}
|
|
451
|
+
cache?.set(raw, result);
|
|
452
|
+
return result;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// ── rows ─────────────────────────────────────────────────────────────────────
|
|
456
|
+
export function candidateKey(candidate, meta) {
|
|
457
|
+
return importOriginKey(candidate.source, meta?.foreignSessionId || null, candidate.path);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/** Session ids per import key, from session summaries (`importKey`). */
|
|
461
|
+
export function importedKeyMap(summaries) {
|
|
462
|
+
const map = new Map();
|
|
463
|
+
for (const row of Array.isArray(summaries) ? summaries : []) {
|
|
464
|
+
if (row?.closed === true || typeof row?.importKey !== 'string' || !row.importKey) continue;
|
|
465
|
+
if (typeof row.id !== 'string' || !row.id) continue;
|
|
466
|
+
const ids = map.get(row.importKey);
|
|
467
|
+
if (ids) ids.push(row.id);
|
|
468
|
+
else map.set(row.importKey, [row.id]);
|
|
469
|
+
}
|
|
470
|
+
return map;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function rowFor(candidate, meta, cwdInfo, imported) {
|
|
474
|
+
const warnings = new Set(meta?.warnings || []);
|
|
475
|
+
if (meta && !meta.errorCode && !cwdInfo?.exists) warnings.add(IMPORT_WARNING_CWD_MISSING);
|
|
476
|
+
if (meta?.errorCode) warnings.add(meta.errorCode);
|
|
477
|
+
return {
|
|
478
|
+
id: candidate.id,
|
|
479
|
+
source: candidate.source,
|
|
480
|
+
title: meta?.title ?? null,
|
|
481
|
+
cwd: cwdInfo?.path ?? null,
|
|
482
|
+
modifiedAt: candidate.mtimeMs,
|
|
483
|
+
messageCount: meta?.messageCount ?? null,
|
|
484
|
+
importedSessionId: meta ? imported.get(candidateKey(candidate, meta)) || null : null,
|
|
485
|
+
warnings: [...warnings],
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export async function buildRow(candidate, imported, cwdCache = new Map()) {
|
|
490
|
+
const meta = cachedMeta(candidate);
|
|
491
|
+
const cwdInfo = meta?.cwd ? await resolveSourceCwd(meta.cwd, cwdCache) : null;
|
|
492
|
+
return rowFor(candidate, meta, cwdInfo, imported);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// ── views & paging ───────────────────────────────────────────────────────────
|
|
496
|
+
function pruneViews(now = Date.now()) {
|
|
497
|
+
for (const [id, view] of state.views) {
|
|
498
|
+
if (now - view.touchedAt > VIEW_TTL_MS) state.views.delete(id);
|
|
499
|
+
}
|
|
500
|
+
while (state.views.size > MAX_VIEWS) state.views.delete(state.views.keys().next().value);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** A live listing view, or an error when it expired (membership is never re-derived). */
|
|
504
|
+
export function requireView(viewId) {
|
|
505
|
+
pruneViews();
|
|
506
|
+
const view = typeof viewId === 'string' ? state.views.get(viewId) : null;
|
|
507
|
+
if (!view) throw importError('viewExpired');
|
|
508
|
+
view.touchedAt = Date.now();
|
|
509
|
+
return view;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function encodeCursor(viewId, offset) {
|
|
513
|
+
return Buffer.from(JSON.stringify([viewId, offset]), 'utf8').toString('base64url');
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function decodeCursor(cursor) {
|
|
517
|
+
try {
|
|
518
|
+
const [viewId, offset] = JSON.parse(Buffer.from(String(cursor), 'base64url').toString('utf8'));
|
|
519
|
+
if (typeof viewId === 'string' && Number.isSafeInteger(offset) && offset >= 0) return { viewId, offset };
|
|
520
|
+
} catch {
|
|
521
|
+
/* fall through */
|
|
522
|
+
}
|
|
523
|
+
throw importError('invalidCursor');
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function pageLimit(limit) {
|
|
527
|
+
if (limit === undefined || limit === null) return DEFAULT_LIMIT;
|
|
528
|
+
const value = Math.floor(Number(limit));
|
|
529
|
+
if (!Number.isFinite(value) || value < 1) throw new TypeError('limit must be a positive integer.');
|
|
530
|
+
return Math.min(value, MAX_LIMIT);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const foldText = (value) =>
|
|
534
|
+
String(value || '')
|
|
535
|
+
.normalize('NFKC')
|
|
536
|
+
.toLowerCase();
|
|
537
|
+
|
|
538
|
+
function samePath(left, right) {
|
|
539
|
+
if (!left || !right) return false;
|
|
540
|
+
return caseFold(resolve(left)) === caseFold(resolve(right));
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function optionalTime(value, name) {
|
|
544
|
+
if (value === undefined || value === null) return null;
|
|
545
|
+
const ms = Number(value);
|
|
546
|
+
if (!Number.isFinite(ms)) throw new TypeError(`${name} must be a timestamp in milliseconds.`);
|
|
547
|
+
return ms;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async function pageResult(view, offset, limit, imported) {
|
|
551
|
+
const cwdCache = new Map();
|
|
552
|
+
const ids = view.memberIds.slice(offset, offset + limit);
|
|
553
|
+
const rows = [];
|
|
554
|
+
for (const id of ids) {
|
|
555
|
+
const candidate = state.candidates.get(id);
|
|
556
|
+
if (candidate) rows.push(await buildRow(candidate, imported, cwdCache));
|
|
557
|
+
}
|
|
558
|
+
const nextOffset = offset + ids.length;
|
|
559
|
+
return {
|
|
560
|
+
searchedRoot: view.root,
|
|
561
|
+
viewId: view.id,
|
|
562
|
+
complete: view.complete,
|
|
563
|
+
matchedCount: view.memberIds.length,
|
|
564
|
+
selectableCount: view.selectableIds.length,
|
|
565
|
+
projects: view.projects,
|
|
566
|
+
rows,
|
|
567
|
+
nextCursor: nextOffset < view.memberIds.length ? encodeCursor(view.id, nextOffset) : null,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Apply a view's filters (time range, query on title/cwd, project) to
|
|
573
|
+
* candidates using their cached metadata; a candidate whose metadata is still
|
|
574
|
+
* pending cannot match a query or project and leaves the result incomplete.
|
|
575
|
+
*/
|
|
576
|
+
async function filterCandidates(candidates, filters, imported) {
|
|
577
|
+
const { needle, projectFilter, modifiedAfter, modifiedBefore } = filters;
|
|
578
|
+
const cwdCache = new Map();
|
|
579
|
+
const memberIds = [];
|
|
580
|
+
const selectableIds = [];
|
|
581
|
+
const projectCounts = new Map();
|
|
582
|
+
let complete = true;
|
|
583
|
+
for (const candidate of candidates) {
|
|
584
|
+
if (modifiedAfter !== null && candidate.mtimeMs < modifiedAfter) continue;
|
|
585
|
+
if (modifiedBefore !== null && candidate.mtimeMs > modifiedBefore) continue;
|
|
586
|
+
const meta = cachedMeta(candidate);
|
|
587
|
+
if (!meta) complete = false;
|
|
588
|
+
const cwdInfo = meta?.cwd ? await resolveSourceCwd(meta.cwd, cwdCache) : null;
|
|
589
|
+
if (needle && (!meta || !(foldText(meta.title).includes(needle) || foldText(cwdInfo?.path).includes(needle)))) {
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
if (cwdInfo?.path) projectCounts.set(cwdInfo.path, (projectCounts.get(cwdInfo.path) || 0) + 1);
|
|
593
|
+
if (projectFilter && !samePath(cwdInfo?.path, projectFilter)) continue;
|
|
594
|
+
// Already-imported conversations are left out of the list entirely.
|
|
595
|
+
if (meta && imported.has(candidateKey(candidate, meta))) continue;
|
|
596
|
+
memberIds.push(candidate.id);
|
|
597
|
+
selectableIds.push(candidate.id);
|
|
598
|
+
}
|
|
599
|
+
const projects = [...projectCounts]
|
|
600
|
+
.map(([cwd, count]) => ({ cwd, count }))
|
|
601
|
+
.sort((a, b) => b.count - a.count || compareCodeUnits(a.cwd, b.cwd));
|
|
602
|
+
return { memberIds, selectableIds, projects, complete };
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* One page of importable conversations. Without `cursor` a new view (fixed
|
|
607
|
+
* membership) is built from a fresh enumeration; a cursor pages that view.
|
|
608
|
+
* `imported` maps an import key to its live session id (`get`/`has`).
|
|
609
|
+
*/
|
|
610
|
+
export async function listConversations(params, { imported, env = process.env } = {}) {
|
|
611
|
+
const { source, query, project, cursor } = params || {};
|
|
612
|
+
requireSource(source);
|
|
613
|
+
const limit = pageLimit(params?.limit);
|
|
614
|
+
if (cursor !== undefined && cursor !== null && cursor !== '') {
|
|
615
|
+
const { viewId, offset } = decodeCursor(cursor);
|
|
616
|
+
const view = requireView(viewId);
|
|
617
|
+
if (view.source !== source) throw importError('invalidCursor');
|
|
618
|
+
return pageResult(view, offset, limit, imported);
|
|
619
|
+
}
|
|
620
|
+
const filters = {
|
|
621
|
+
modifiedAfter: optionalTime(params?.modifiedAfter, 'modifiedAfter'),
|
|
622
|
+
modifiedBefore: optionalTime(params?.modifiedBefore, 'modifiedBefore'),
|
|
623
|
+
needle: foldText(query).trim(),
|
|
624
|
+
projectFilter: typeof project === 'string' && project.trim() ? project.trim() : null,
|
|
625
|
+
};
|
|
626
|
+
const { root, candidates } = await enumerateCandidates(source, env);
|
|
627
|
+
// Background metadata for everything, newest first; the first page is awaited.
|
|
628
|
+
for (const candidate of candidates) void resolveMeta(candidate);
|
|
629
|
+
if (!filters.needle && !filters.projectFilter) await Promise.all(candidates.slice(0, limit).map(resolveMeta));
|
|
630
|
+
const view = {
|
|
631
|
+
id: randomUUID(),
|
|
632
|
+
source,
|
|
633
|
+
root,
|
|
634
|
+
filters,
|
|
635
|
+
candidateIds: candidates.map((candidate) => candidate.id),
|
|
636
|
+
...(await filterCandidates(candidates, filters, imported)),
|
|
637
|
+
touchedAt: Date.now(),
|
|
638
|
+
};
|
|
639
|
+
state.views.set(view.id, view);
|
|
640
|
+
pruneViews();
|
|
641
|
+
return pageResult(view, 0, limit, imported);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Complete selectable membership of a view for select-all: waits until the
|
|
646
|
+
* metadata of every candidate the view enumerated is resolved, re-applies the
|
|
647
|
+
* view's filters, then drops `excludeIds` and already-imported conversations.
|
|
648
|
+
* An expired view is rejected.
|
|
649
|
+
*/
|
|
650
|
+
export async function resolveViewSelection(viewId, excludeIds, imported) {
|
|
651
|
+
const view = requireView(viewId);
|
|
652
|
+
const candidates = view.candidateIds.map((id) => state.candidates.get(id)).filter(Boolean);
|
|
653
|
+
await Promise.all(candidates.map(resolveMeta));
|
|
654
|
+
const { selectableIds } = await filterCandidates(candidates, view.filters, imported);
|
|
655
|
+
const excluded = new Set(Array.isArray(excludeIds) ? excludeIds : []);
|
|
656
|
+
return selectableIds.filter((id) => !excluded.has(id));
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// ── 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 } = {}) {
|
|
662
|
+
requireSource(source);
|
|
663
|
+
if (typeof path !== 'string' || !path.trim() || path.includes('\0')) throw importError('invalidPath');
|
|
664
|
+
const requested = path.trim();
|
|
665
|
+
// Network/device forms are refused before any filesystem call.
|
|
666
|
+
if (isNetworkOrDevicePath(requested)) throw importError('unsupported');
|
|
667
|
+
if (!isAbsolute(requested)) throw importError('invalidPath');
|
|
668
|
+
if (extname(basename(requested)).toLowerCase() !== '.jsonl') throw importError('unsupported');
|
|
669
|
+
let real;
|
|
670
|
+
let info;
|
|
671
|
+
try {
|
|
672
|
+
real = await realpath(requested);
|
|
673
|
+
info = await stat(real);
|
|
674
|
+
} catch {
|
|
675
|
+
throw importError('unreadable');
|
|
676
|
+
}
|
|
677
|
+
if (isNetworkOrDevicePath(real) || !info.isFile() || extname(real).toLowerCase() !== '.jsonl') {
|
|
678
|
+
throw importError('unsupported');
|
|
679
|
+
}
|
|
680
|
+
const candidate = registerCandidate({
|
|
681
|
+
id: candidateId(source, real),
|
|
682
|
+
source,
|
|
683
|
+
root: null,
|
|
684
|
+
path: real,
|
|
685
|
+
size: info.size,
|
|
686
|
+
mtimeMs: info.mtimeMs,
|
|
687
|
+
fileId: fileIdentity(info),
|
|
688
|
+
});
|
|
689
|
+
let conversion;
|
|
690
|
+
try {
|
|
691
|
+
conversion = await convertCandidateFile(candidate, limits);
|
|
692
|
+
} catch {
|
|
693
|
+
throw importError('unreadable');
|
|
694
|
+
}
|
|
695
|
+
if (conversion.errorCode) throw importError(conversion.errorCode);
|
|
696
|
+
recordConversion(candidate, conversion);
|
|
697
|
+
return { row: await buildRow(candidate, imported) };
|
|
698
|
+
}
|