mixdog 1.3.5 → 1.4.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/NOTICE.md +2 -2
- package/README.md +2 -2
- package/package.json +2 -11
- package/scripts/check-voice-runtime-inputs.mjs +25 -0
- package/scripts/daemon-stub.mjs +1 -1
- package/scripts/gate-local.mjs +16 -5
- package/scripts/latest-guard.mjs +21 -9
- package/scripts/lib/isolated-test-env.mjs +10 -5
- package/scripts/lib/run-node-tests.mjs +37 -37
- package/scripts/lib/scoped-temp.d.mts +16 -0
- package/scripts/lib/scoped-temp.mjs +123 -0
- package/scripts/lib/stale-temp.mjs +44 -0
- package/scripts/native-release-status.mjs +2 -2
- package/scripts/post-release-check.mjs +1 -6
- package/scripts/pptx-kit-index.mjs +17 -4
- package/scripts/recall-history-cases.json +1 -1
- package/scripts/suite-health.mjs +20 -2
- package/scripts/test-data-dir.mjs +10 -7
- package/scripts/test.mjs +7 -11
- package/src/agents/reviewer/AGENT.md +7 -11
- package/src/agents/reviewer/agent.json +1 -1
- package/src/app.mjs +4 -0
- package/src/defaults/shipped-history.json +22 -1
- package/src/defaults/skills/browser-use/SKILL.md +62 -12
- package/src/defaults/skills/code-tidy/SKILL.md +142 -270
- package/src/defaults/skills/code-tidy/references/agent-cleanup.md +4 -4
- package/src/defaults/skills/code-tidy/references/cleanup-report.md +31 -76
- package/src/defaults/skills/code-tidy/references/dead-code.md +6 -12
- package/src/defaults/skills/computer-use/SKILL.md +35 -9
- package/src/defaults/skills/setup/SKILL.md +22 -0
- package/src/defaults/skills/setup/references/actions.md +39 -5
- package/src/defaults/skills/setup/references/surfaces.md +11 -2
- package/src/defaults/skills/visualize/SKILL.md +2 -1
- package/src/lib/rules-builder.cjs +39 -20
- package/src/output-styles/common.md +2 -2
- package/src/output-styles/simple.md +1 -1
- package/src/rules/agent/AGENT.md +11 -10
- package/src/rules/lead/LEAD.md +23 -20
- package/src/rules/shared/05-tool-calls.md +5 -6
- package/src/rules/shared/10-tool-workflow.md +4 -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 +7 -5
- package/src/rules/shared/70-delivery.md +7 -9
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -6
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
- package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -1
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +2 -0
- package/src/runtime/agent/orchestrator/config.mjs +17 -1
- package/src/runtime/agent/orchestrator/context/collect.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +73 -68
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +57 -2
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -3
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +176 -72
- package/src/runtime/agent/orchestrator/mcp/client.mjs +19 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +192 -133
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +42 -6
- package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +7 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +8 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
- 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 +4 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +14 -14
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +159 -69
- package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +70 -33
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +135 -87
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +123 -102
- package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
- package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +1 -10
- package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +25 -13
- package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/devin.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +210 -20
- 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-token-utils.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +52 -10
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +1 -10
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +8 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +11 -3
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -1
- 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 +136 -5
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -9
- package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +22 -3
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +43 -5
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +6 -3
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +20 -12
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +48 -22
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +65 -1
- package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +38 -12
- package/src/runtime/agent/orchestrator/providers/registry.mjs +38 -0
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +39 -7
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +6 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +49 -16
- package/src/runtime/agent/orchestrator/runtime-core/compaction-config-keys.mjs +62 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +37 -16
- package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +100 -23
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +7 -6
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +9 -3
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +19 -4
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +125 -30
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +3 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +798 -0
- 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 +280 -45
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +475 -58
- package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +42 -17
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +163 -22
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +132 -17
- 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 +90 -22
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +0 -2
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +51 -25
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +37 -3
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +1 -1
- 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 +31 -3
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +20 -3
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/after-hook.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +5 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +49 -21
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -10
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +12 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +19 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +133 -90
- 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/ask-turn-state.mjs +31 -4
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -18
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +24 -14
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +130 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +27 -9
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +81 -8
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +27 -8
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +12 -5
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +9 -4
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +21 -5
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +24 -9
- package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
- package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +150 -0
- package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +154 -0
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +36 -9
- package/src/runtime/agent/orchestrator/session/sensitive-projection.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/sensitive-values.mjs +257 -0
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +20 -7
- package/src/runtime/agent/orchestrator/session/side-question.mjs +18 -0
- package/src/runtime/agent/orchestrator/session/skill-update-notice.mjs +60 -0
- package/src/runtime/agent/orchestrator/session/store/live-state.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/store/save-delta.mjs +9 -5
- package/src/runtime/agent/orchestrator/session/store/serialize.mjs +9 -8
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/store/session-persistence-events.mjs +29 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +8 -6
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +16 -2
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +81 -22
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +92 -3
- package/src/runtime/agent/orchestrator/session/stored-attachment-history.mjs +32 -0
- package/src/runtime/agent/orchestrator/session/thinking-binding.mjs +108 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +79 -3
- package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +9 -7
- package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-text.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/turn-failure.mjs +5 -1
- package/src/runtime/agent/orchestrator/stall-policy.mjs +23 -0
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +240 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/shell-cwd.mjs +38 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +31 -6
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +110 -26
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +31 -5
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +22 -8
- package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +9 -5
- 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/shell-runtime.mjs +7 -1
- 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/shell-warm-standby.mjs +26 -4
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +36 -13
- package/src/runtime/browser-bridge/action-schema.mjs +13 -2
- package/src/runtime/browser-bridge/client.mjs +73 -8
- package/src/runtime/browser-bridge/input-fields.mjs +8 -9
- package/src/runtime/browser-bridge/mail-links.mjs +107 -0
- package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
- package/src/runtime/channels/lib/config.mjs +6 -61
- package/src/runtime/channels/lib/crash-log.mjs +1 -1
- package/src/runtime/channels/lib/event-pipeline.mjs +4 -19
- package/src/runtime/channels/lib/event-queue.mjs +3 -18
- package/src/runtime/channels/lib/owned-runtime/automation-services.mjs +2 -2
- package/src/runtime/channels/lib/owned-runtime/automation-start.mjs +3 -4
- package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +4 -59
- package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +33 -115
- package/src/runtime/channels/lib/owned-runtime/owned-stop.mjs +5 -22
- package/src/runtime/channels/lib/owned-runtime/state.mjs +2 -8
- package/src/runtime/channels/lib/owned-runtime.mjs +6 -23
- package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
- package/src/runtime/channels/lib/runtime-paths.mjs +4 -43
- package/src/runtime/channels/lib/scheduler.mjs +33 -147
- package/src/runtime/channels/lib/schedules-db.mjs +34 -11
- package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
- package/src/runtime/channels/lib/tool-dispatch.mjs +1 -2
- package/src/runtime/channels/lib/webhook/signature.mjs +4 -1
- package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
- package/src/runtime/channels/lib/whisper-port.mjs +10 -1
- package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -1
- package/src/runtime/channels/lib/worker-ipc.mjs +0 -1
- package/src/runtime/channels/lib/worker-main.mjs +10 -34
- package/src/runtime/computer-bridge/action-schema.mjs +42 -1
- package/src/runtime/computer-bridge/actions.mjs +9 -1
- package/src/runtime/computer-bridge/client.mjs +180 -8
- package/src/runtime/computer-bridge/core-actions.mjs +2 -2
- package/src/runtime/computer-bridge/error-recovery.mjs +28 -2
- 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/computer-bridge/linux-readiness.mjs +65 -23
- package/src/runtime/computer-bridge/pending-continuation.mjs +1 -1
- package/src/runtime/connectors/connector-accounts-mcp.mjs +230 -0
- package/src/runtime/connectors/gmail-app-password.mjs +172 -39
- package/src/runtime/connectors/google-mcp.mjs +10 -12
- 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/lib/embedding-worker.mjs +6 -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/memory/lib/pg/adapter.mjs +77 -70
- package/src/runtime/memory/lib/pg/supervisor.mjs +24 -12
- package/src/runtime/memory/lib/runtime-fetcher.mjs +23 -5
- 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 +3 -1
- 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/session-search/session-search-db.mjs +73 -9
- package/src/runtime/session-search/session-search-ingest.mjs +110 -20
- package/src/runtime/shared/abort-race.mjs +89 -0
- package/src/runtime/shared/agent-avatar.d.mts +0 -1
- package/src/runtime/shared/agent-kind.mjs +4 -3
- package/src/runtime/shared/bot-owned-work.mjs +56 -0
- package/src/runtime/shared/bounded-download.mjs +8 -3
- package/src/runtime/shared/config.mjs +2 -13
- package/src/runtime/shared/connector-accounts.mjs +20 -10
- package/src/runtime/shared/connector-catalog.d.mts +29 -0
- package/src/runtime/shared/connector-catalog.mjs +150 -0
- package/src/runtime/shared/connector-triggers.mjs +22 -19
- package/src/runtime/shared/defaults-separation.mjs +2 -17
- package/src/runtime/shared/delegated-approval-host.mjs +103 -11
- package/src/runtime/shared/err-text.mjs +24 -15
- 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-permissions.d.mts +0 -8
- package/src/runtime/shared/file-permissions.mjs +0 -11
- package/src/runtime/shared/git-direct.test-support.mjs +19 -0
- package/src/runtime/shared/handoff-artifacts.d.mts +2 -1
- package/src/runtime/shared/handoff-artifacts.mjs +18 -8
- package/src/runtime/shared/heartbeat-interval.d.mts +4 -0
- package/src/runtime/shared/heartbeat-interval.mjs +19 -0
- package/src/runtime/shared/idle-auto-clear-gate.mjs +126 -0
- package/src/runtime/shared/llm/cost.mjs +5 -1
- package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
- package/src/runtime/shared/loopback-listener.mjs +25 -2
- package/src/runtime/shared/mixy-presence.mjs +22 -0
- 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/orchestration.d.mts +2 -0
- package/src/runtime/shared/pg-schema-db.mjs +27 -16
- package/src/runtime/shared/profile-config.cjs +1 -1
- package/src/runtime/shared/prompt-origin.d.mts +4 -0
- package/src/runtime/shared/prompt-origin.mjs +21 -0
- package/src/runtime/shared/provider-accounts.mjs +7 -1
- package/src/runtime/shared/provider-display-name.mjs +29 -0
- package/src/runtime/shared/provider-timeout-phase.mjs +2 -0
- package/src/runtime/shared/routine-trigger.d.mts +2 -0
- package/src/runtime/shared/routine-trigger.mjs +10 -0
- package/src/runtime/shared/sensitive-path-rules.d.mts +2 -10
- package/src/runtime/shared/sensitive-path-rules.mjs +19 -41
- package/src/runtime/shared/session-activity-signal.mjs +48 -1
- package/src/runtime/shared/session-scratch.mjs +37 -0
- package/src/runtime/shared/session-sidebar-host.d.mts +1 -1
- package/src/runtime/shared/session-sidebar-host.mjs +7 -3
- package/src/runtime/shared/session-turn-prelude.mjs +18 -0
- 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 +2 -15
- package/src/runtime/shared/tool-surface.mjs +3 -0
- package/src/runtime/shared/tool-work-units.mjs +2 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +13 -6
- package/src/runtime/shared/worktrees/contract.mjs +4 -1
- package/src/runtime/shared/worktrees/git.mjs +15 -0
- package/src/runtime/shared/worktrees/manager.mjs +87 -38
- package/src/runtime/shared/worktrees/test-support.mjs +14 -13
- package/src/runtime/terminal-bridge/client.mjs +10 -1
- package/src/runtime/visualize-bridge/client.mjs +29 -8
- package/src/runtime/visualize-bridge/tool-defs.mjs +22 -8
- package/src/runtime/visualize-bridge/tool-meta.d.mts +5 -0
- package/src/runtime/visualize-bridge/tool-meta.mjs +22 -0
- package/src/runtime/web-search/lib/fetch-pipeline.mjs +2 -2
- package/src/session-runtime/agent-home-delivery.mjs +489 -185
- package/src/session-runtime/agent-task-sessions.mjs +467 -229
- package/src/session-runtime/agent-tool-routing.mjs +8 -41
- package/src/session-runtime/boot/apis.mjs +9 -0
- package/src/session-runtime/boot/infrastructure.mjs +4 -0
- package/src/session-runtime/boot/providers.mjs +3 -0
- package/src/session-runtime/boot/tools.mjs +11 -2
- package/src/session-runtime/bot-status.mjs +12 -6
- package/src/session-runtime/bot-target.mjs +38 -0
- package/src/session-runtime/channel-config-api.mjs +6 -1
- package/src/session-runtime/config-lifecycle.mjs +6 -0
- package/src/session-runtime/connector-merge-migration.mjs +305 -0
- package/src/session-runtime/conversation-import/codex-titles.mjs +88 -0
- package/src/session-runtime/conversation-import/discovery.mjs +271 -25
- package/src/session-runtime/conversation-import/service.mjs +26 -5
- package/src/session-runtime/handoff-attachments.mjs +82 -31
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +144 -41
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -0
- package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +4 -1
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +8 -2
- package/src/session-runtime/lifecycle/cold-heir.mjs +11 -6
- package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
- package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
- package/src/session-runtime/lifecycle/teardown/runtime-stops.mjs +50 -4
- package/src/session-runtime/lifecycle/teardown.mjs +23 -5
- package/src/session-runtime/mcp-glue/server-catalog.mjs +55 -3
- package/src/session-runtime/mcp-oauth-login.mjs +5 -3
- package/src/session-runtime/mixy-heartbeat.mjs +153 -33
- package/src/session-runtime/model-route/set-route.mjs +11 -2
- package/src/session-runtime/notification-bus.mjs +31 -117
- package/src/session-runtime/notification-completion-queue.mjs +54 -0
- package/src/session-runtime/notification-listeners.mjs +76 -0
- package/src/session-runtime/owned-automation-run.mjs +2 -1
- package/src/session-runtime/prewarm/channel-start.mjs +3 -7
- 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 +143 -224
- package/src/session-runtime/resource-mcp-connector-accounts.mjs +163 -0
- package/src/session-runtime/resource-mcp-oauth-api.mjs +217 -0
- package/src/session-runtime/resource-mcp-secrets.mjs +82 -0
- package/src/session-runtime/resource-mcp-servers.mjs +78 -0
- package/src/session-runtime/resource-mcp-store.mjs +49 -0
- package/src/session-runtime/resource-recall-api.mjs +6 -1
- package/src/session-runtime/routine-owner.mjs +40 -9
- package/src/session-runtime/runtime-feature-gates.mjs +5 -1
- package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
- 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/spawn-preset.mjs +13 -4
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
- package/src/session-runtime/services/agent-tool/tool-def.mjs +9 -9
- package/src/session-runtime/services/agent-tool/worker-index/awaiting-user-sync.mjs +51 -0
- package/src/session-runtime/services/agent-tool/worker-index/disposal.mjs +55 -0
- package/src/session-runtime/services/agent-tool/worker-index/released-store.mjs +123 -0
- package/src/session-runtime/services/agent-tool/worker-index/restart-settlement.mjs +112 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-reads.mjs +36 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +17 -4
- package/src/session-runtime/services/agent-tool/worker-index/row-writes.mjs +58 -0
- package/src/session-runtime/services/agent-tool/worker-index.mjs +41 -241
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +3 -0
- package/src/session-runtime/services/channel-admin.mjs +7 -13
- package/src/session-runtime/services/channel-session-router.mjs +4 -2
- package/src/session-runtime/services/provider-admin.mjs +2 -1
- package/src/session-runtime/services/seeds.mjs +0 -28
- package/src/session-runtime/session-compaction-sync.mjs +27 -0
- package/src/session-runtime/session-create/session-options.mjs +1 -0
- package/src/session-runtime/settings-builtin-tools-api.mjs +11 -0
- package/src/session-runtime/settings-compaction-api.mjs +2 -40
- package/src/session-runtime/settings-system-api.mjs +13 -1
- package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
- package/src/session-runtime/setup-tool/executor.mjs +145 -22
- package/src/session-runtime/setup-tool/extended-actions.mjs +8 -3
- package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +4 -3
- package/src/session-runtime/setup-tool/tool-defs.mjs +64 -5
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
- package/src/session-runtime/tool-policy/policy-stage.mjs +17 -2
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -4
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -3
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
- package/src/session-runtime/workspace/transitions.mjs +25 -0
- package/src/session-runtime/workspace/worktree-tool.mjs +26 -9
- package/src/standalone/channel-transport/client-registry/client-records.mjs +65 -0
- package/src/standalone/channel-transport/client-registry/client-registration.mjs +118 -0
- package/src/standalone/channel-transport/client-registry.mjs +24 -163
- package/src/standalone/channel-transport.mjs +1 -1
- package/src/standalone/daemon-channels-loader.mjs +10 -8
- package/src/standalone/daemon-session-control/list-page.mjs +72 -0
- package/src/standalone/daemon-session-control/request.mjs +51 -0
- package/src/standalone/daemon-session-control/room-host.mjs +57 -0
- package/src/standalone/daemon-session-control/route-control.mjs +48 -0
- package/src/standalone/daemon-session-control/send-turn.mjs +60 -0
- package/src/standalone/daemon-session-control/worktree-control.mjs +40 -0
- package/src/standalone/daemon-session-control.mjs +142 -190
- package/src/standalone/daemon.mjs +26 -3
- package/src/standalone/session-client.mjs +7 -3
- package/src/standalone/session-protocol.mjs +5 -0
- package/src/standalone/session-service/agent-tree/agent-child.mjs +1 -0
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +16 -12
- package/src/standalone/session-service/quota-resume.mjs +3 -0
- package/src/standalone/session-service/retention/busy.mjs +65 -1
- package/src/standalone/session-service/session-calls/session-action.mjs +30 -2
- package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
- package/src/standalone/session-tool-output.mjs +15 -7
- package/src/standalone/session-transport/client-registry/client-grace.mjs +12 -9
- package/src/tui/app/app-view/floating-panel.jsx +7 -4
- package/src/tui/app/model-options.mjs +3 -28
- 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/provider-setup/oauth-actions.mjs +38 -20
- package/src/tui/app/provider-setup/provider-items.mjs +8 -2
- package/src/tui/app/provider-setup-picker.mjs +9 -5
- package/src/tui/components/QuestionPanel.jsx +13 -4
- package/src/tui/dist/index.mjs +133 -92
- package/src/tui/session/agent-job-feed/model-notification.mjs +3 -0
- package/src/tui/session/initial-state.mjs +3 -0
- package/src/tui/session/notice-surface.mjs +7 -1
- package/src/tui/session/queue-helpers.mjs +3 -42
- package/src/tui/session/session-api/routes-agent-definitions.mjs +14 -2
- package/src/tui/session/session-api/routes-write.mjs +5 -1
- package/src/tui/session/session-api/routes.mjs +1 -0
- package/src/tui/session/session-api/settings.mjs +17 -1
- package/src/tui/session/session-flow/auto-clear.mjs +4 -33
- package/src/tui/session/session-flow/drain-closed-queue.mjs +38 -0
- package/src/tui/session/session-flow/drain-enqueue.mjs +86 -0
- package/src/tui/session/session-flow/drain-retry.mjs +35 -0
- package/src/tui/session/session-flow/drain.mjs +118 -149
- package/src/tui/session/session-flow/queue/entry-shape.mjs +5 -0
- package/src/tui/session/session-flow/queue/take-entries.mjs +16 -2
- package/src/tui/session/tool-approval-delegation.mjs +64 -0
- package/src/tui/session/tool-approval-request.mjs +39 -0
- package/src/tui/session/tool-approval-settle.mjs +62 -0
- package/src/tui/session/tool-approval.mjs +69 -115
- package/src/tui/session/tui-steering-persist.mjs +8 -1
- package/src/tui/session/turn-stream.mjs +19 -2
- package/src/tui/session/turn.mjs +1 -0
- package/src/ui/statusline-agents.mjs +1 -3
- package/src/vendor/statusline/bin/statusline-route.mjs +1 -219
- package/src/vendor/statusline/src/gateway/route-meta.mjs +35 -13
- package/scripts/code-graph-description-contract.mjs +0 -188
- package/src/runtime/channels/lib/owned-runtime/ownership-refresh.mjs +0 -36
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The thread titles Codex itself stores, by thread id (read-only):
|
|
3
|
+
*
|
|
4
|
+
* state_<N>.sqlite `threads.title`, overridden by `threads.name` (a rename)
|
|
5
|
+
* session_index.jsonl append-only `{ id, thread_name }`; the latest
|
|
6
|
+
* non-empty name of an id wins and overrides the DB
|
|
7
|
+
*
|
|
8
|
+
* Anything unreadable is skipped: a missing or locked store simply leaves the
|
|
9
|
+
* conversation to the title derived from its messages.
|
|
10
|
+
*/
|
|
11
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
12
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { titleFromText } from '../../runtime/agent/orchestrator/session/conversation-import/turns.mjs';
|
|
15
|
+
|
|
16
|
+
const STATE_DB_RE = /^state_(\d+)\.sqlite$/i;
|
|
17
|
+
|
|
18
|
+
async function newestStateDb(home) {
|
|
19
|
+
let newest = null;
|
|
20
|
+
for (const name of await readdir(home).catch(() => [])) {
|
|
21
|
+
const version = STATE_DB_RE.exec(name)?.[1];
|
|
22
|
+
if (version !== undefined && (!newest || Number(version) > newest.version)) {
|
|
23
|
+
newest = { name, version: Number(version) };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return newest ? join(home, newest.name) : null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readStateRows(path) {
|
|
30
|
+
const db = new DatabaseSync(path, { readOnly: true });
|
|
31
|
+
try {
|
|
32
|
+
const columns = new Set(
|
|
33
|
+
db
|
|
34
|
+
.prepare('PRAGMA table_info(threads)')
|
|
35
|
+
.all()
|
|
36
|
+
.map((column) => column.name)
|
|
37
|
+
);
|
|
38
|
+
const wanted = ['title', 'name'].filter((column) => columns.has(column));
|
|
39
|
+
if (!wanted.length) return [];
|
|
40
|
+
return db.prepare(`SELECT id, ${wanted.join(', ')} FROM threads`).all();
|
|
41
|
+
} finally {
|
|
42
|
+
db.close();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function readStateTitles(home, titles) {
|
|
47
|
+
try {
|
|
48
|
+
const path = await newestStateDb(home);
|
|
49
|
+
if (!path) return;
|
|
50
|
+
const rows = readStateRows(path);
|
|
51
|
+
for (const column of ['title', 'name']) {
|
|
52
|
+
for (const row of rows) {
|
|
53
|
+
const title = titleFromText(row[column]);
|
|
54
|
+
if (title && typeof row.id === 'string') titles.set(row.id, title);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
/* unreadable store: fall back to the session index and derived titles */
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function readIndexTitles(home, titles) {
|
|
63
|
+
let text;
|
|
64
|
+
try {
|
|
65
|
+
text = await readFile(join(home, 'session_index.jsonl'), 'utf8');
|
|
66
|
+
} catch {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
for (const line of text.split('\n')) {
|
|
70
|
+
if (!line.trim()) continue;
|
|
71
|
+
let entry;
|
|
72
|
+
try {
|
|
73
|
+
entry = JSON.parse(line);
|
|
74
|
+
} catch {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const title = titleFromText(entry?.thread_name);
|
|
78
|
+
if (title && typeof entry.id === 'string') titles.set(entry.id, title);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Map of Codex thread id → its stored title (whitespace-normalized, ≤100 chars). */
|
|
83
|
+
export async function loadCodexThreadTitles(home) {
|
|
84
|
+
const titles = new Map();
|
|
85
|
+
await readStateTitles(home, titles);
|
|
86
|
+
await readIndexTitles(home, titles);
|
|
87
|
+
return titles;
|
|
88
|
+
}
|
|
@@ -13,7 +13,7 @@
|
|
|
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, extname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
16
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
17
17
|
import {
|
|
18
18
|
IMPORT_MAX_FILE_BYTES,
|
|
19
19
|
IMPORT_MAX_LINE_BYTES,
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
import { codexMetaFromTruncatedLine } from '../../runtime/agent/orchestrator/session/conversation-import/codex.mjs';
|
|
27
27
|
import { IMPORT_WARNING_CWD_MISSING } from '../../runtime/agent/orchestrator/session/conversation-import/turns.mjs';
|
|
28
28
|
import { importOriginKey } from '../../runtime/agent/orchestrator/session/conversation-import/imported-session.mjs';
|
|
29
|
+
import { loadCodexThreadTitles } from './codex-titles.mjs';
|
|
29
30
|
|
|
30
31
|
export const PREFIX_CHUNK_BYTES = 64 * 1024;
|
|
31
32
|
export const PREFIX_MAX_BYTES = 512 * 1024;
|
|
@@ -49,10 +50,12 @@ const state = {
|
|
|
49
50
|
queue: [],
|
|
50
51
|
active: 0,
|
|
51
52
|
views: new Map(), // viewId → view
|
|
53
|
+
scanMaxBytes: IMPORT_MAX_FILE_BYTES, // per-file budget of a sidechain-only scan
|
|
52
54
|
};
|
|
53
55
|
|
|
54
|
-
/** Test seam: forget every cached candidate, metadata entry and view. */
|
|
55
|
-
export function resetConversationDiscovery() {
|
|
56
|
+
/** Test seam: forget every cached candidate, metadata entry and view; `scanMaxBytes` overrides the scan budget. */
|
|
57
|
+
export function resetConversationDiscovery({ scanMaxBytes = IMPORT_MAX_FILE_BYTES } = {}) {
|
|
58
|
+
state.scanMaxBytes = scanMaxBytes;
|
|
56
59
|
state.candidates.clear();
|
|
57
60
|
state.meta.clear();
|
|
58
61
|
state.pendingMeta.clear();
|
|
@@ -74,6 +77,16 @@ export function requireSource(source) {
|
|
|
74
77
|
return source;
|
|
75
78
|
}
|
|
76
79
|
|
|
80
|
+
/** Titles the source app stores per foreign session id (Codex only), read fresh; else null. */
|
|
81
|
+
export function loadStoredTitles(source, env = process.env) {
|
|
82
|
+
return source === 'codex' ? loadCodexThreadTitles(dirname(sourceRoot(source, env))) : null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The app's stored title of a conversation, else the one derived from its messages. */
|
|
86
|
+
export function displayTitle(meta, titles) {
|
|
87
|
+
return (meta?.foreignSessionId && titles?.get(meta.foreignSessionId)) || meta?.title || null;
|
|
88
|
+
}
|
|
89
|
+
|
|
77
90
|
const caseFold = (value) => (process.platform === 'win32' ? value.toLowerCase() : value);
|
|
78
91
|
|
|
79
92
|
function isInside(root, path) {
|
|
@@ -227,6 +240,11 @@ function metaFromConversion(conversion, whole) {
|
|
|
227
240
|
foreignSessionId: conversion.foreignSessionId,
|
|
228
241
|
cwd: conversion.cwd,
|
|
229
242
|
title: conversion.title,
|
|
243
|
+
// A Codex marker sits in the first line; a Claude transcript is sidechain-only only once read to the end.
|
|
244
|
+
internalKind: whole || conversion.source === 'codex' ? (conversion.internalKind ?? null) : null,
|
|
245
|
+
// Rollout id of the history base this file continues, else null.
|
|
246
|
+
historyBaseId:
|
|
247
|
+
typeof conversion.historyBase?.thread_id === 'string' ? conversion.historyBase.thread_id.toLowerCase() : null,
|
|
230
248
|
messageCount: whole && !conversion.errorCode ? conversion.messageCount : null,
|
|
231
249
|
warnings: whole ? conversion.warnings : conversion.warnings.filter((code) => code !== 'malformed'),
|
|
232
250
|
errorCode: whole ? conversion.errorCode : null,
|
|
@@ -324,42 +342,190 @@ async function streamJsonlLines(
|
|
|
324
342
|
return outcome({ eof: true });
|
|
325
343
|
}
|
|
326
344
|
|
|
345
|
+
// Complete lines of the last prefix-chunk of a file (the cut-off first line dropped).
|
|
346
|
+
async function readTailLines(handle, size) {
|
|
347
|
+
const length = Math.min(size, PREFIX_CHUNK_BYTES);
|
|
348
|
+
const buffer = Buffer.allocUnsafe(length);
|
|
349
|
+
const { bytesRead } = await handle.read(buffer, 0, length, size - length);
|
|
350
|
+
const text = buffer.subarray(0, bytesRead).toString('utf8');
|
|
351
|
+
return (size > length ? text.slice(text.indexOf('\n') + 1) : text).split('\n');
|
|
352
|
+
}
|
|
353
|
+
|
|
327
354
|
async function readPrefixMeta(candidate) {
|
|
328
355
|
const converter = createConversationConverter(candidate.source);
|
|
329
356
|
const handle = await openCandidate(candidate);
|
|
357
|
+
// Only the end of a Claude transcript proves it is sidechain-only, so while
|
|
358
|
+
// nothing but sidechain messages has been seen the scan streams on, line by
|
|
359
|
+
// line, to EOF or to the import file-size limit. A file over that limit stays
|
|
360
|
+
// unclassified (listed); importing it fails as tooLarge.
|
|
361
|
+
const claude = candidate.source === 'claude';
|
|
330
362
|
let read;
|
|
331
363
|
try {
|
|
332
364
|
read = await streamJsonlLines(handle, (line) => converter.pushLine(line), {
|
|
333
|
-
maxLineBytes: PREFIX_MAX_BYTES,
|
|
365
|
+
maxLineBytes: claude ? IMPORT_MAX_LINE_BYTES : PREFIX_MAX_BYTES,
|
|
334
366
|
chunkBytes: PREFIX_CHUNK_BYTES,
|
|
335
|
-
maxBytes: PREFIX_MAX_BYTES,
|
|
367
|
+
maxBytes: claude ? state.scanMaxBytes : PREFIX_MAX_BYTES,
|
|
336
368
|
onChunk: (position) => {
|
|
337
369
|
// A file read to its known size keeps going to EOF (whole-file metadata).
|
|
338
370
|
if (position >= candidate.size) return true;
|
|
371
|
+
if (claude) {
|
|
372
|
+
if (converter.sidechainOnly) return true;
|
|
373
|
+
if (position >= PREFIX_MAX_BYTES) return false;
|
|
374
|
+
}
|
|
339
375
|
const partial = converter.result();
|
|
340
376
|
return !(partial.foreignSessionId && partial.cwd && partial.title);
|
|
341
377
|
},
|
|
342
378
|
});
|
|
379
|
+
// The stored title records sit near the end of a transcript.
|
|
380
|
+
if (claude && !read.eof) {
|
|
381
|
+
for (const line of await readTailLines(handle, candidate.size)) converter.pushTitleLine(line);
|
|
382
|
+
}
|
|
343
383
|
} finally {
|
|
344
384
|
await handle.close();
|
|
345
385
|
}
|
|
346
386
|
const conversion = converter.result();
|
|
347
|
-
|
|
348
|
-
const
|
|
387
|
+
// A thread continuing a history base is only counted, named and judged empty once the base is followed (on import).
|
|
388
|
+
const listing = (meta) =>
|
|
389
|
+
conversion.historyBase
|
|
390
|
+
? { ...meta, title: null, messageCount: null, errorCode: meta.errorCode === 'empty' ? null : meta.errorCode }
|
|
391
|
+
: meta;
|
|
392
|
+
if (read.eof) return listing(metaFromConversion(conversion, true));
|
|
393
|
+
const meta = listing(metaFromConversion(conversion, false));
|
|
349
394
|
if (candidate.source === 'codex' && (!meta.foreignSessionId || !meta.cwd) && read.firstPartial) {
|
|
350
395
|
// A session_meta line longer than the prefix still names its id/cwd.
|
|
351
396
|
const fromLine = codexMetaFromTruncatedLine(read.firstPartial.replace(/^\uFEFF/, ''));
|
|
352
397
|
meta.foreignSessionId ||= fromLine?.foreignSessionId || null;
|
|
353
398
|
meta.cwd ||= fromLine?.cwd || null;
|
|
399
|
+
meta.internalKind ||= fromLine?.internalKind || null;
|
|
354
400
|
}
|
|
355
401
|
return meta;
|
|
356
402
|
}
|
|
357
403
|
|
|
404
|
+
// ── Codex history base chain ─────────────────────────────────────────────────
|
|
405
|
+
const HISTORY_CHAIN_MAX_DEPTH = 32;
|
|
406
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
407
|
+
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$/i;
|
|
408
|
+
|
|
409
|
+
/** Rollout id a rollout file name ends with (`…-<thread>.jsonl` or `…-<thread>_<rollout>.jsonl`). */
|
|
410
|
+
const rolloutIdOf = (path) => basename(path).match(ROLLOUT_ID_RE)?.[1].toLowerCase() ?? null;
|
|
411
|
+
|
|
412
|
+
const META_CHUNK_BYTES = 8 * 1024;
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* First JSON record of a candidate file (null when none completes within
|
|
416
|
+
* `maxBytes`, blank lines included, or it is not JSON) and the bytes read for it.
|
|
417
|
+
*/
|
|
418
|
+
async function firstRecord(candidate, { maxBytes, maxLineBytes }) {
|
|
419
|
+
const handle = await openCandidate(candidate);
|
|
420
|
+
try {
|
|
421
|
+
let first = null;
|
|
422
|
+
const read = await streamJsonlLines(
|
|
423
|
+
handle,
|
|
424
|
+
(line) => {
|
|
425
|
+
first = line.replace(/^\uFEFF/, '').trim();
|
|
426
|
+
return !first;
|
|
427
|
+
},
|
|
428
|
+
{ maxLineBytes, chunkBytes: META_CHUNK_BYTES, maxBytes }
|
|
429
|
+
);
|
|
430
|
+
let record = null;
|
|
431
|
+
try {
|
|
432
|
+
record = first ? JSON.parse(first) : null;
|
|
433
|
+
} catch {
|
|
434
|
+
/* not a record */
|
|
435
|
+
}
|
|
436
|
+
return { record, bytes: read.bytesRead };
|
|
437
|
+
} finally {
|
|
438
|
+
await handle.close();
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Whether a cutoff falls on a record boundary: the end of the file or just after a line feed.
|
|
443
|
+
async function endsOnRecord(candidate, end) {
|
|
444
|
+
if (end === candidate.size) return true;
|
|
445
|
+
const handle = await openCandidate(candidate);
|
|
446
|
+
try {
|
|
447
|
+
const byte = Buffer.alloc(1);
|
|
448
|
+
const { bytesRead } = await handle.read(byte, 0, 1, end - 1);
|
|
449
|
+
return bytesRead === 1 && byte[0] === 10;
|
|
450
|
+
} finally {
|
|
451
|
+
await handle.close();
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/** The plain rollout file with this rollout id under the live, then the archived, sessions folder. */
|
|
456
|
+
async function findRolloutById(home, rolloutId) {
|
|
457
|
+
for (const folder of ['sessions', 'archived_sessions']) {
|
|
458
|
+
let rootReal;
|
|
459
|
+
try {
|
|
460
|
+
rootReal = await realpath(join(home, folder));
|
|
461
|
+
} catch {
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
const paths = [];
|
|
465
|
+
await walkCodex(rootReal, rootReal, paths, new Set([caseFold(rootReal)]));
|
|
466
|
+
for (const path of paths) {
|
|
467
|
+
if (rolloutIdOf(path) !== rolloutId) continue;
|
|
468
|
+
const real = await containedRealpath(rootReal, path);
|
|
469
|
+
const info = real ? await stat(real).catch(() => null) : null;
|
|
470
|
+
if (info?.isFile()) {
|
|
471
|
+
return { source: 'codex', root: rootReal, path: real, size: info.size, fileId: fileIdentity(info) };
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Rollouts a `history_base` leads to, nearest first, each with the byte offset
|
|
480
|
+
* its contribution ends at, and `metaBytes`, what reading their metadata took.
|
|
481
|
+
* Stops (`partial`) at a base that is missing, unreadable (compressed rollouts
|
|
482
|
+
* are not read), not paginated, already visited, past the depth cap, with a
|
|
483
|
+
* cutoff that excludes the session metadata, passes the end of the file or
|
|
484
|
+
* does not end a record, or that would take the total read (the file, the
|
|
485
|
+
* `spent` metadata reads so far, every cutoff and every metadata read) beyond
|
|
486
|
+
* `maxFileBytes`.
|
|
487
|
+
*/
|
|
488
|
+
async function resolveHistoryChain(candidate, firstBase, { maxFileBytes, maxLineBytes, spent }) {
|
|
489
|
+
const home = dirname(candidate.root ?? sourceRoot('codex'));
|
|
490
|
+
const seen = new Set([rolloutIdOf(candidate.path)]);
|
|
491
|
+
const segments = [];
|
|
492
|
+
let used = candidate.size + spent;
|
|
493
|
+
let metaBytes = 0;
|
|
494
|
+
const partial = () => ({ segments, partial: true, metaBytes });
|
|
495
|
+
for (let base = firstBase; base; ) {
|
|
496
|
+
const id = typeof base.thread_id === 'string' ? base.thread_id.toLowerCase() : '';
|
|
497
|
+
const end = base.end_byte_offset;
|
|
498
|
+
const ordinal = base.end_ordinal_exclusive;
|
|
499
|
+
if (!UUID_RE.test(id) || seen.has(id) || segments.length >= HISTORY_CHAIN_MAX_DEPTH) return partial();
|
|
500
|
+
seen.add(id);
|
|
501
|
+
if (!Number.isSafeInteger(ordinal) || ordinal <= 0 || !Number.isSafeInteger(end) || end <= 0) return partial();
|
|
502
|
+
const found = await findRolloutById(home, id);
|
|
503
|
+
if (!found || end > found.size || used + end > maxFileBytes) return partial();
|
|
504
|
+
let head;
|
|
505
|
+
try {
|
|
506
|
+
if (!(await endsOnRecord(found, end))) return partial();
|
|
507
|
+
head = await firstRecord(found, { maxBytes: Math.min(end, maxFileBytes - used - end), maxLineBytes });
|
|
508
|
+
} catch {
|
|
509
|
+
return partial();
|
|
510
|
+
}
|
|
511
|
+
used += end + head.bytes;
|
|
512
|
+
metaBytes += head.bytes;
|
|
513
|
+
if (head.record?.type !== 'session_meta' || head.record.payload?.history_mode !== 'paginated') return partial();
|
|
514
|
+
segments.push({ candidate: found, end });
|
|
515
|
+
const next = head.record.payload.history_base;
|
|
516
|
+
base = next && typeof next === 'object' ? next : null;
|
|
517
|
+
}
|
|
518
|
+
return { segments, partial: false, metaBytes };
|
|
519
|
+
}
|
|
520
|
+
|
|
358
521
|
/**
|
|
359
522
|
* Stream-convert a whole candidate file under the import limits. A file over
|
|
360
523
|
* `maxFileBytes`, a line over `maxLineBytes` or a conversion over the
|
|
361
524
|
* converter's limits fails the file with `tooLarge` without reading the rest.
|
|
362
|
-
* `
|
|
525
|
+
* A Codex rollout continuing a `history_base` is preceded by the records of
|
|
526
|
+
* each base up to its cutoff, oldest first (their session metadata dropped),
|
|
527
|
+
* all within the one `maxFileBytes`; a base that cannot be followed leaves the
|
|
528
|
+
* `historyPartial` warning. `bytesRead` is how far the read got.
|
|
363
529
|
*/
|
|
364
530
|
export async function convertCandidateFile(
|
|
365
531
|
candidate,
|
|
@@ -377,15 +543,45 @@ export async function convertCandidateFile(
|
|
|
377
543
|
maxRetainedEntries,
|
|
378
544
|
maxMetadataBytes,
|
|
379
545
|
});
|
|
546
|
+
// Bytes read beyond the file itself: metadata pre-reads and the bases' cutoffs. All count against `maxFileBytes`.
|
|
547
|
+
let inheritedBytes = 0;
|
|
548
|
+
if (candidate.source === 'codex') {
|
|
549
|
+
const head = await firstRecord(candidate, { maxBytes: maxFileBytes, maxLineBytes });
|
|
550
|
+
const base = head.record?.payload?.history_base;
|
|
551
|
+
if (base && typeof base === 'object') {
|
|
552
|
+
inheritedBytes += head.bytes;
|
|
553
|
+
const chain =
|
|
554
|
+
head.record.payload.history_mode === 'paginated'
|
|
555
|
+
? await resolveHistoryChain(candidate, base, { maxFileBytes, maxLineBytes, spent: inheritedBytes })
|
|
556
|
+
: { segments: [], partial: true, metaBytes: 0 };
|
|
557
|
+
inheritedBytes += chain.metaBytes;
|
|
558
|
+
const { segments, partial } = chain;
|
|
559
|
+
if (partial) converter.markHistoryPartial();
|
|
560
|
+
for (const { candidate: baseFile, end } of segments.reverse()) {
|
|
561
|
+
const baseHandle = await openCandidate(baseFile);
|
|
562
|
+
try {
|
|
563
|
+
const read = await streamJsonlLines(baseHandle, (line) => converter.pushLine(line, { inherited: true }), {
|
|
564
|
+
maxLineBytes,
|
|
565
|
+
chunkBytes,
|
|
566
|
+
maxBytes: end,
|
|
567
|
+
});
|
|
568
|
+
if (read.overflow) converter.markTooLarge();
|
|
569
|
+
inheritedBytes += read.bytesRead;
|
|
570
|
+
} finally {
|
|
571
|
+
await baseHandle.close();
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
380
576
|
const handle = await openCandidate(candidate);
|
|
381
577
|
try {
|
|
382
578
|
const read = await streamJsonlLines(handle, (line) => converter.pushLine(line), {
|
|
383
579
|
maxLineBytes,
|
|
384
580
|
chunkBytes,
|
|
385
|
-
maxBytes: maxFileBytes,
|
|
581
|
+
maxBytes: maxFileBytes - inheritedBytes,
|
|
386
582
|
});
|
|
387
583
|
if (read.overflow || read.truncated) converter.markTooLarge();
|
|
388
|
-
return { ...converter.result(), bytesRead: read.bytesRead };
|
|
584
|
+
return { ...converter.result(), bytesRead: read.bytesRead + inheritedBytes };
|
|
389
585
|
} finally {
|
|
390
586
|
await handle.close();
|
|
391
587
|
}
|
|
@@ -400,6 +596,8 @@ function pumpMetaQueue() {
|
|
|
400
596
|
foreignSessionId: null,
|
|
401
597
|
cwd: null,
|
|
402
598
|
title: null,
|
|
599
|
+
internalKind: null,
|
|
600
|
+
historyBaseId: null,
|
|
403
601
|
messageCount: null,
|
|
404
602
|
warnings: [],
|
|
405
603
|
errorCode: 'unreadable',
|
|
@@ -470,14 +668,14 @@ export function importedKeyMap(summaries) {
|
|
|
470
668
|
return map;
|
|
471
669
|
}
|
|
472
670
|
|
|
473
|
-
function rowFor(candidate, meta, cwdInfo, imported) {
|
|
671
|
+
function rowFor(candidate, meta, cwdInfo, imported, titles) {
|
|
474
672
|
const warnings = new Set(meta?.warnings || []);
|
|
475
673
|
if (meta && !meta.errorCode && !cwdInfo?.exists) warnings.add(IMPORT_WARNING_CWD_MISSING);
|
|
476
674
|
if (meta?.errorCode) warnings.add(meta.errorCode);
|
|
477
675
|
return {
|
|
478
676
|
id: candidate.id,
|
|
479
677
|
source: candidate.source,
|
|
480
|
-
title: meta
|
|
678
|
+
title: displayTitle(meta, titles),
|
|
481
679
|
cwd: cwdInfo?.path ?? null,
|
|
482
680
|
modifiedAt: candidate.mtimeMs,
|
|
483
681
|
messageCount: meta?.messageCount ?? null,
|
|
@@ -486,10 +684,10 @@ function rowFor(candidate, meta, cwdInfo, imported) {
|
|
|
486
684
|
};
|
|
487
685
|
}
|
|
488
686
|
|
|
489
|
-
export async function buildRow(candidate, imported, cwdCache = new Map()) {
|
|
687
|
+
export async function buildRow(candidate, imported, cwdCache = new Map(), titles = null) {
|
|
490
688
|
const meta = cachedMeta(candidate);
|
|
491
689
|
const cwdInfo = meta?.cwd ? await resolveSourceCwd(meta.cwd, cwdCache) : null;
|
|
492
|
-
return rowFor(candidate, meta, cwdInfo, imported);
|
|
690
|
+
return rowFor(candidate, meta, cwdInfo, imported, titles);
|
|
493
691
|
}
|
|
494
692
|
|
|
495
693
|
// ── views & paging ───────────────────────────────────────────────────────────
|
|
@@ -553,7 +751,7 @@ async function pageResult(view, offset, limit, imported) {
|
|
|
553
751
|
const rows = [];
|
|
554
752
|
for (const id of ids) {
|
|
555
753
|
const candidate = state.candidates.get(id);
|
|
556
|
-
if (candidate) rows.push(await buildRow(candidate, imported, cwdCache));
|
|
754
|
+
if (candidate) rows.push(await buildRow(candidate, imported, cwdCache, view.titles));
|
|
557
755
|
}
|
|
558
756
|
const nextOffset = offset + ids.length;
|
|
559
757
|
return {
|
|
@@ -568,31 +766,76 @@ async function pageResult(view, offset, limit, imported) {
|
|
|
568
766
|
};
|
|
569
767
|
}
|
|
570
768
|
|
|
769
|
+
/**
|
|
770
|
+
* Ids of Codex rollouts that an earlier revision of the same thread (same
|
|
771
|
+
* session id) names as its history base. Codex lists one rollout per thread,
|
|
772
|
+
* the current one, so these stay out of the list; a fork's parent is another
|
|
773
|
+
* thread and stays listed.
|
|
774
|
+
*/
|
|
775
|
+
function supersededBaseIds(candidates) {
|
|
776
|
+
const byRollout = new Map();
|
|
777
|
+
for (const candidate of candidates) {
|
|
778
|
+
const meta = cachedMeta(candidate);
|
|
779
|
+
const rolloutId = candidate.source === 'codex' ? rolloutIdOf(candidate.path) : null;
|
|
780
|
+
if (meta?.foreignSessionId && rolloutId) byRollout.set(`${meta.foreignSessionId}\0${rolloutId}`, candidate.id);
|
|
781
|
+
}
|
|
782
|
+
const hidden = new Set();
|
|
783
|
+
for (const candidate of candidates) {
|
|
784
|
+
const meta = cachedMeta(candidate);
|
|
785
|
+
const base = meta?.historyBaseId && byRollout.get(`${meta.foreignSessionId}\0${meta.historyBaseId}`);
|
|
786
|
+
if (base && base !== candidate.id) hidden.add(base);
|
|
787
|
+
}
|
|
788
|
+
// A cycle of revisions must not hide a thread entirely: its newest one (first in order) stays.
|
|
789
|
+
const newest = new Map();
|
|
790
|
+
const visible = new Set();
|
|
791
|
+
const indexed = new Set(byRollout.values());
|
|
792
|
+
for (const candidate of candidates) {
|
|
793
|
+
const sessionId = cachedMeta(candidate)?.foreignSessionId;
|
|
794
|
+
if (!sessionId || !indexed.has(candidate.id)) continue;
|
|
795
|
+
if (!newest.has(sessionId)) newest.set(sessionId, candidate.id);
|
|
796
|
+
if (!hidden.has(candidate.id)) visible.add(sessionId);
|
|
797
|
+
}
|
|
798
|
+
for (const [sessionId, id] of newest) if (!visible.has(sessionId)) hidden.delete(id);
|
|
799
|
+
return hidden;
|
|
800
|
+
}
|
|
801
|
+
|
|
571
802
|
/**
|
|
572
803
|
* Apply a view's filters (time range, query on title/cwd, project) to
|
|
573
804
|
* candidates using their cached metadata; a candidate whose metadata is still
|
|
574
|
-
* pending
|
|
805
|
+
* pending is neither a member nor counted and leaves the result incomplete.
|
|
806
|
+
* The source app's internal and empty records are never members.
|
|
575
807
|
*/
|
|
576
|
-
async function filterCandidates(candidates, filters, imported) {
|
|
808
|
+
async function filterCandidates(candidates, filters, imported, titles) {
|
|
577
809
|
const { needle, projectFilter, modifiedAfter, modifiedBefore } = filters;
|
|
578
810
|
const cwdCache = new Map();
|
|
579
811
|
const memberIds = [];
|
|
580
812
|
const selectableIds = [];
|
|
581
813
|
const projectCounts = new Map();
|
|
582
814
|
let complete = true;
|
|
815
|
+
const superseded = supersededBaseIds(candidates);
|
|
583
816
|
for (const candidate of candidates) {
|
|
817
|
+
if (superseded.has(candidate.id)) continue;
|
|
584
818
|
if (modifiedAfter !== null && candidate.mtimeMs < modifiedAfter) continue;
|
|
585
819
|
if (modifiedBefore !== null && candidate.mtimeMs > modifiedBefore) continue;
|
|
586
820
|
const meta = cachedMeta(candidate);
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
821
|
+
// Unclassified yet: neither a member nor counted until its metadata is read.
|
|
822
|
+
if (!meta) {
|
|
823
|
+
complete = false;
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
// The source app's own records and files read to the end without a message are not conversations.
|
|
827
|
+
if (meta.internalKind || meta.errorCode === 'empty') continue;
|
|
828
|
+
const cwdInfo = meta.cwd ? await resolveSourceCwd(meta.cwd, cwdCache) : null;
|
|
829
|
+
if (
|
|
830
|
+
needle &&
|
|
831
|
+
!(foldText(displayTitle(meta, titles)).includes(needle) || foldText(cwdInfo?.path).includes(needle))
|
|
832
|
+
) {
|
|
590
833
|
continue;
|
|
591
834
|
}
|
|
592
835
|
if (cwdInfo?.path) projectCounts.set(cwdInfo.path, (projectCounts.get(cwdInfo.path) || 0) + 1);
|
|
593
836
|
if (projectFilter && !samePath(cwdInfo?.path, projectFilter)) continue;
|
|
594
837
|
// Already-imported conversations are left out of the list entirely.
|
|
595
|
-
if (
|
|
838
|
+
if (imported.has(candidateKey(candidate, meta))) continue;
|
|
596
839
|
memberIds.push(candidate.id);
|
|
597
840
|
selectableIds.push(candidate.id);
|
|
598
841
|
}
|
|
@@ -624,7 +867,9 @@ export async function listConversations(params, { imported, env = process.env }
|
|
|
624
867
|
projectFilter: typeof project === 'string' && project.trim() ? project.trim() : null,
|
|
625
868
|
};
|
|
626
869
|
const { root, candidates } = await enumerateCandidates(source, env);
|
|
870
|
+
const titles = await loadStoredTitles(source, env);
|
|
627
871
|
// Background metadata for everything, newest first; the first page is awaited.
|
|
872
|
+
// Candidates still unclassified stay out of the view (it is incomplete) and join it on a later listing.
|
|
628
873
|
for (const candidate of candidates) void resolveMeta(candidate);
|
|
629
874
|
if (!filters.needle && !filters.projectFilter) await Promise.all(candidates.slice(0, limit).map(resolveMeta));
|
|
630
875
|
const view = {
|
|
@@ -632,8 +877,9 @@ export async function listConversations(params, { imported, env = process.env }
|
|
|
632
877
|
source,
|
|
633
878
|
root,
|
|
634
879
|
filters,
|
|
880
|
+
titles,
|
|
635
881
|
candidateIds: candidates.map((candidate) => candidate.id),
|
|
636
|
-
...(await filterCandidates(candidates, filters, imported)),
|
|
882
|
+
...(await filterCandidates(candidates, filters, imported, titles)),
|
|
637
883
|
touchedAt: Date.now(),
|
|
638
884
|
};
|
|
639
885
|
state.views.set(view.id, view);
|
|
@@ -651,14 +897,14 @@ export async function resolveViewSelection(viewId, excludeIds, imported) {
|
|
|
651
897
|
const view = requireView(viewId);
|
|
652
898
|
const candidates = view.candidateIds.map((id) => state.candidates.get(id)).filter(Boolean);
|
|
653
899
|
await Promise.all(candidates.map(resolveMeta));
|
|
654
|
-
const { selectableIds } = await filterCandidates(candidates, view.filters, imported);
|
|
900
|
+
const { selectableIds } = await filterCandidates(candidates, view.filters, imported, view.titles);
|
|
655
901
|
const excluded = new Set(Array.isArray(excludeIds) ? excludeIds : []);
|
|
656
902
|
return selectableIds.filter((id) => !excluded.has(id));
|
|
657
903
|
}
|
|
658
904
|
|
|
659
905
|
// ── picked files ─────────────────────────────────────────────────────────────
|
|
660
906
|
/** Validate and register a file chosen in a native picker (a local regular .jsonl). */
|
|
661
|
-
export async function registerFile({ source, path } = {}, { imported, limits } = {}) {
|
|
907
|
+
export async function registerFile({ source, path } = {}, { imported, limits, env = process.env } = {}) {
|
|
662
908
|
requireSource(source);
|
|
663
909
|
if (typeof path !== 'string' || !path.trim() || path.includes('\0')) throw importError('invalidPath');
|
|
664
910
|
const requested = path.trim();
|
|
@@ -694,5 +940,5 @@ export async function registerFile({ source, path } = {}, { imported, limits } =
|
|
|
694
940
|
}
|
|
695
941
|
if (conversion.errorCode) throw importError(conversion.errorCode);
|
|
696
942
|
recordConversion(candidate, conversion);
|
|
697
|
-
return { row: await buildRow(candidate, imported) };
|
|
943
|
+
return { row: await buildRow(candidate, imported, new Map(), await loadStoredTitles(source, env)) };
|
|
698
944
|
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { randomUUID } from 'node:crypto';
|
|
16
16
|
import { access, stat } from 'node:fs/promises';
|
|
17
|
+
import { basename } from 'node:path';
|
|
17
18
|
import {
|
|
18
19
|
buildImportOrigin,
|
|
19
20
|
createImportedSession,
|
|
@@ -24,10 +25,12 @@ import { sessionPath } from '../../runtime/agent/orchestrator/session/store/sess
|
|
|
24
25
|
import {
|
|
25
26
|
cachedMeta,
|
|
26
27
|
convertCandidateFile,
|
|
28
|
+
displayTitle,
|
|
27
29
|
getCandidate,
|
|
28
30
|
importError,
|
|
29
31
|
importedKeyMap,
|
|
30
32
|
listConversations,
|
|
33
|
+
loadStoredTitles,
|
|
31
34
|
recordConversion,
|
|
32
35
|
registerFile,
|
|
33
36
|
resolveSourceCwd,
|
|
@@ -131,6 +134,17 @@ function touch(job, patch = {}) {
|
|
|
131
134
|
|
|
132
135
|
const COUNTER = { imported: 'imported', alreadyImported: 'skippedAlreadyImported', failed: 'failed' };
|
|
133
136
|
|
|
137
|
+
const titlesFor = (candidate, context) => (candidate.source === 'codex' ? context.titles : null);
|
|
138
|
+
|
|
139
|
+
/** Which record a result is about: its title and source file name, for the failure report. */
|
|
140
|
+
function describeCandidate(candidateId, context) {
|
|
141
|
+
const candidate = getCandidate(candidateId);
|
|
142
|
+
return {
|
|
143
|
+
title: candidate ? displayTitle(cachedMeta(candidate), titlesFor(candidate, context)) : null,
|
|
144
|
+
sourceName: candidate ? basename(candidate.path) : null,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
134
148
|
function record(job, { projectPath = null, ...result }) {
|
|
135
149
|
const counter = COUNTER[result.status];
|
|
136
150
|
job[counter] += 1;
|
|
@@ -162,6 +176,7 @@ async function importOne(candidateId, context) {
|
|
|
162
176
|
if (!candidate) return failed(candidateId, 'unknownCandidate');
|
|
163
177
|
// The chat keeps the title the user picked it by: a long file's listing
|
|
164
178
|
// title comes from its prefix and can differ from the converted branch.
|
|
179
|
+
// Codex's own stored title, when it has one, names the chat first.
|
|
165
180
|
const listedTitle = cachedMeta(candidate)?.title ?? null;
|
|
166
181
|
let conversion;
|
|
167
182
|
try {
|
|
@@ -173,7 +188,11 @@ async function importOne(candidateId, context) {
|
|
|
173
188
|
} catch {
|
|
174
189
|
return failed(candidateId, 'unreadable');
|
|
175
190
|
}
|
|
176
|
-
|
|
191
|
+
const title = displayTitle(
|
|
192
|
+
{ foreignSessionId: conversion.foreignSessionId, title: listedTitle || conversion.title },
|
|
193
|
+
titlesFor(candidate, context)
|
|
194
|
+
);
|
|
195
|
+
if (title) conversion = { ...conversion, title };
|
|
177
196
|
recordConversion(candidate, conversion);
|
|
178
197
|
const cwdInfo = await resolveSourceCwd(conversion.cwd);
|
|
179
198
|
const projectPath = cwdInfo.exists ? cwdInfo.path : null;
|
|
@@ -221,6 +240,7 @@ async function runJob(job, candidateIds, context) {
|
|
|
221
240
|
try {
|
|
222
241
|
// One catalog read per batch; later commits are seen through committedKeys.
|
|
223
242
|
context.imported = await context.importedLookup();
|
|
243
|
+
context.titles = await context.loadTitles();
|
|
224
244
|
for (const candidateId of candidateIds) {
|
|
225
245
|
if (job.cancelRequested) break;
|
|
226
246
|
let result;
|
|
@@ -228,8 +248,7 @@ async function runJob(job, candidateIds, context) {
|
|
|
228
248
|
result = await admit(async () => {
|
|
229
249
|
// A cancel that arrived while queued behind other jobs wins here.
|
|
230
250
|
if (job.cancelRequested) return null;
|
|
231
|
-
|
|
232
|
-
touch(job, { current: { candidateId, title: (candidate && cachedMeta(candidate)?.title) ?? null } });
|
|
251
|
+
touch(job, { current: { candidateId, title: describeCandidate(candidateId, context).title } });
|
|
233
252
|
try {
|
|
234
253
|
return await importOne(candidateId, context);
|
|
235
254
|
} finally {
|
|
@@ -241,7 +260,7 @@ async function runJob(job, candidateIds, context) {
|
|
|
241
260
|
result = failed(candidateId, 'importFailed');
|
|
242
261
|
}
|
|
243
262
|
if (result === null) break;
|
|
244
|
-
record(job, result);
|
|
263
|
+
record(job, { ...result, ...describeCandidate(candidateId, context) });
|
|
245
264
|
}
|
|
246
265
|
touch(job, { state: job.cancelRequested ? 'stopped' : 'completed' });
|
|
247
266
|
} catch (error) {
|
|
@@ -296,7 +315,7 @@ export function createConversationImportApi({
|
|
|
296
315
|
return listConversations(requireParams(params), { imported: await imported(), env });
|
|
297
316
|
},
|
|
298
317
|
async registerConversationImportFile(params) {
|
|
299
|
-
return registerFile(requireParams(params), { imported: await imported(), limits });
|
|
318
|
+
return registerFile(requireParams(params), { imported: await imported(), limits, env });
|
|
300
319
|
},
|
|
301
320
|
getConversationImportJobs(params = {}) {
|
|
302
321
|
const { jobId } = requireParams(params ?? {});
|
|
@@ -334,6 +353,8 @@ export function createConversationImportApi({
|
|
|
334
353
|
void runJob(job, candidateIds, {
|
|
335
354
|
importedLookup: imported,
|
|
336
355
|
imported: null,
|
|
356
|
+
titles: null,
|
|
357
|
+
loadTitles: () => loadStoredTitles('codex', env),
|
|
337
358
|
sessionExists,
|
|
338
359
|
limits,
|
|
339
360
|
prepareLeadOptions,
|