mixdog 1.3.1 → 1.3.3
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 -4
- package/package.json +8 -8
- package/scripts/native-release-status.mjs +238 -0
- package/scripts/verify-runtime-dependency-versions.mjs +158 -0
- package/src/agents/reviewer/AGENT.md +5 -7
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +9 -1
- package/src/defaults/skills/browser-use/SKILL.md +6 -3
- package/src/defaults/skills/local-provider/SKILL.md +33 -6
- package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
- package/src/defaults/skills/setup/SKILL.md +23 -1
- package/src/defaults/skills/setup/references/actions.md +50 -3
- package/src/defaults/skills/setup/references/surfaces.md +23 -3
- package/src/defaults/skills/setup-guide/SKILL.md +7 -3
- package/src/defaults/skills/visualize/SKILL.md +9 -6
- package/src/defaults/skills/visualize/references/craft.md +8 -1
- package/src/help.mjs +1 -1
- package/src/lib/keychain-cjs.cjs +4 -1
- package/src/lib/rules-builder.cjs +14 -7
- package/src/rules/agent-mode/AGENT_MODE.md +8 -5
- package/src/rules/agent-mode/MIXY.md +6 -8
- package/src/rules/lead/LEAD.md +7 -10
- package/src/rules/shared/10-tool-workflow.md +4 -0
- package/src/rules/shared/20-research.md +2 -0
- package/src/rules/shared/45-destructive.md +5 -1
- package/src/rules/shared/60-verification.md +6 -4
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
- package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
- package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
- package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
- package/src/runtime/agent/orchestrator/config.mjs +11 -0
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +9 -2
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
- package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +84 -32
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +38 -11
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +15 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +84 -42
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +16 -0
- package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
- package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
- package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
- package/src/runtime/agent/orchestrator/providers/registry.mjs +19 -0
- package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +13 -1
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
- package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +5 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +10 -1
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -6
- package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +52 -6
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +35 -13
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +8 -3
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -2
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +196 -45
- package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compact/continuation.mjs +15 -6
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +45 -19
- package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +44 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +185 -37
- package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
- package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +240 -0
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +102 -35
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +19 -9
- package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +199 -109
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/empty-nudge.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +62 -13
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +22 -6
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +33 -8
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/call-prep.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/manager/agent-listing-notice.mjs +102 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +27 -14
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +32 -1
- package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +115 -0
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +36 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -9
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +4 -9
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +14 -1
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +7 -3
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
- package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +52 -11
- package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +126 -12
- package/src/runtime/agent/orchestrator/session/runtime-user-context.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +16 -3
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +5 -4
- package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +140 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +15 -5
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +49 -1
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +80 -1
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
- package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +33 -1
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.d.mts +5 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +37 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +35 -5
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +47 -14
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +127 -20
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +557 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +10 -12
- package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +42 -23
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
- package/src/runtime/attachments/attachment-reference.mjs +155 -0
- package/src/runtime/attachments/legacy-office.mjs +110 -0
- package/src/runtime/attachments/limits.mjs +5 -1
- package/src/runtime/attachments/office-extract.mjs +3 -6
- package/src/runtime/attachments/pdf-extract.mjs +84 -10
- package/src/runtime/attachments/prompt-restore.mjs +70 -0
- package/src/runtime/attachments/store.mjs +109 -33
- package/src/runtime/browser-bridge/client.mjs +73 -20
- package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
- package/src/runtime/channels/lib/scheduler.mjs +14 -7
- package/src/runtime/channels/lib/state-file.mjs +15 -15
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
- package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
- package/src/runtime/channels/lib/worker-main.mjs +3 -2
- package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
- package/src/runtime/effort-judge/judge-client.mjs +66 -16
- package/src/runtime/effort-judge/model-install.mjs +47 -7
- package/src/runtime/local-provider/asset-installer.mjs +10 -4
- package/src/runtime/local-provider/catalog.mjs +5 -2
- package/src/runtime/local-provider/registered-models.mjs +64 -27
- package/src/runtime/local-provider/server.mjs +2 -3
- package/src/runtime/media/adapters/image-api.mjs +89 -0
- package/src/runtime/media/auth.mjs +64 -1
- package/src/runtime/media/catalog.mjs +66 -4
- package/src/runtime/media/jobs.mjs +4 -0
- package/src/runtime/media/lanes.mjs +46 -5
- package/src/runtime/media/media-usage.mjs +17 -0
- package/src/runtime/media/renditions.mjs +15 -15
- package/src/runtime/media/store.mjs +36 -35
- package/src/runtime/media/tool.mjs +8 -1
- package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
- package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
- package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +14 -2
- package/src/runtime/memory/lib/pg/process.mjs +8 -4
- package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
- package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
- package/src/runtime/office/core/journal.mjs +7 -14
- package/src/runtime/office/design/library/design-library-core.mjs +4 -16
- package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
- package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
- package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
- package/src/runtime/office/pdf/pdf-render.mjs +30 -18
- package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
- package/src/runtime/office/portable/portable-soffice.mjs +135 -29
- package/src/runtime/shared/agent-avatar-sprites.mjs +23 -8
- package/src/runtime/shared/agent-avatar.mjs +3 -2
- package/src/runtime/shared/agent-kind-migration.mjs +200 -0
- package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
- package/src/runtime/shared/agent-message-envelope.mjs +44 -0
- package/src/runtime/shared/agent-reap-state.d.mts +4 -0
- package/src/runtime/shared/agent-room-host.d.mts +31 -0
- package/src/runtime/shared/agent-room-host.mjs +58 -0
- package/src/runtime/shared/agent-store.mjs +24 -7
- package/src/runtime/shared/agent-task-starts.mjs +27 -0
- package/src/runtime/shared/atomic-file.d.mts +50 -0
- package/src/runtime/shared/atomic-file.mjs +394 -132
- package/src/runtime/shared/background-tasks.mjs +7 -4
- package/src/runtime/shared/bot-hops.mjs +35 -0
- package/src/runtime/shared/bot-split-migration.mjs +1 -1
- package/src/runtime/shared/bounded-download.mjs +4 -2
- package/src/runtime/shared/config.mjs +39 -9
- package/src/runtime/shared/defaults-separation.mjs +6 -2
- package/src/runtime/shared/delegated-approval-host.mjs +74 -0
- package/src/runtime/shared/err-text.mjs +4 -0
- package/src/runtime/shared/error-presentation.mjs +22 -4
- package/src/runtime/shared/file-has-data.mjs +28 -0
- package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
- package/src/runtime/shared/pending-agent-work.mjs +54 -0
- package/src/runtime/shared/process-lifecycle.mjs +8 -12
- package/src/runtime/shared/projects.mjs +50 -41
- package/src/runtime/shared/provider-accounts.mjs +17 -12
- package/src/runtime/shared/redact-directories.mjs +18 -0
- package/src/runtime/shared/remove-directory.mjs +28 -0
- package/src/runtime/shared/routine-trigger.d.mts +6 -1
- package/src/runtime/shared/routine-trigger.mjs +14 -6
- package/src/runtime/shared/run-child.d.mts +3 -0
- package/src/runtime/shared/run-child.mjs +24 -3
- package/src/runtime/shared/session-baseline-store.mjs +8 -2
- package/src/runtime/shared/session-sidebar-host.d.mts +11 -2
- package/src/runtime/shared/session-sidebar-host.mjs +16 -3
- package/src/runtime/shared/singleton-owner.mjs +7 -6
- package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
- package/src/runtime/shared/staged-update.mjs +4 -2
- package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
- package/src/runtime/shared/tool-execution-contract.mjs +10 -0
- package/src/runtime/shared/tool-work-units.mjs +12 -9
- package/src/runtime/shared/transcript-metadata.mjs +28 -3
- package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
- package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
- package/src/runtime/shared/turn-snapshot.mjs +277 -124
- package/src/runtime/shared/user-cwd.mjs +9 -4
- package/src/runtime/shared/user-data-guard.mjs +92 -61
- package/src/runtime/shared/utf8-cut.mjs +10 -0
- package/src/runtime/shared/worktrees/activity-gate.mjs +251 -61
- package/src/runtime/shared/worktrees/contract.mjs +11 -3
- package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
- package/src/runtime/shared/worktrees/manager.mjs +419 -52
- package/src/runtime/shared/worktrees/registry.mjs +29 -27
- package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
- package/src/runtime/tidy/core-install.mjs +26 -4
- package/src/runtime/tidy/install.mjs +91 -23
- package/src/runtime/tidy/process.mjs +41 -8
- package/src/runtime/tidy/resolve.mjs +10 -7
- package/src/runtime/web-search/lib/config.mjs +4 -16
- package/src/session-runtime/agent-home-delivery.mjs +542 -204
- package/src/session-runtime/agent-profile-changes.mjs +16 -0
- package/src/session-runtime/agent-room-delivery.mjs +81 -35
- package/src/session-runtime/agent-task-sessions.mjs +811 -179
- package/src/session-runtime/agent-tool-routing.mjs +1 -0
- package/src/session-runtime/boot/apis.mjs +48 -24
- package/src/session-runtime/boot/config.mjs +1 -0
- package/src/session-runtime/boot/facade.mjs +16 -7
- package/src/session-runtime/boot/infrastructure.mjs +14 -2
- package/src/session-runtime/boot/providers.mjs +2 -2
- package/src/session-runtime/boot/shared.mjs +12 -6
- package/src/session-runtime/boot/tools.mjs +23 -3
- package/src/session-runtime/bot-status.mjs +432 -0
- package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
- package/src/session-runtime/config-lifecycle.mjs +60 -55
- package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
- package/src/session-runtime/held-requests.mjs +31 -33
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +24 -26
- package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
- package/src/session-runtime/lifecycle/teardown.mjs +11 -3
- package/src/session-runtime/mcp-glue/connect.mjs +100 -186
- package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
- package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
- package/src/session-runtime/media-api.mjs +5 -2
- package/src/session-runtime/model-recency.mjs +1 -0
- package/src/session-runtime/model-route/route-tuning.mjs +4 -1
- package/src/session-runtime/model-route/set-route.mjs +61 -1
- package/src/session-runtime/owned-automation-run.mjs +8 -1
- package/src/session-runtime/provider-models/row-hydration.mjs +8 -1
- package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
- package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
- package/src/session-runtime/route-resolution.mjs +16 -8
- package/src/session-runtime/route-state.mjs +1 -59
- package/src/session-runtime/routine-home-turn.mjs +5 -3
- package/src/session-runtime/runtime-bootstrap.mjs +5 -3
- package/src/session-runtime/runtime-paths.mjs +11 -5
- package/src/session-runtime/runtime-review-api.mjs +4 -1
- package/src/session-runtime/runtime-tunables.mjs +6 -0
- package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
- package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
- package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
- package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +34 -19
- package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +7 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +8 -5
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
- package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
- package/src/session-runtime/services/agent-tool.mjs +6 -0
- package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
- package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
- package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
- package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
- package/src/session-runtime/services/plugin-admin.mjs +26 -26
- package/src/session-runtime/services/provider-admin.mjs +26 -6
- package/src/session-runtime/services/schedule-access.mjs +33 -4
- package/src/session-runtime/services/seeds.mjs +4 -3
- package/src/session-runtime/services/usage-dashboard.mjs +1 -1
- package/src/session-runtime/services/usage-stats-model.mjs +2 -1
- package/src/session-runtime/services/worktree-access.mjs +27 -0
- package/src/session-runtime/session-create/materialize.mjs +2 -3
- package/src/session-runtime/session-create/session-options.mjs +15 -2
- package/src/session-runtime/session-hooks.mjs +9 -17
- package/src/session-runtime/settings-builtin-tools-api.mjs +65 -1
- package/src/session-runtime/settings-compaction-api.mjs +86 -39
- package/src/session-runtime/setup-tool/executor.mjs +12 -2
- package/src/session-runtime/setup-tool/settings-contract.mjs +34 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
- package/src/session-runtime/skills-api.mjs +12 -5
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +349 -0
- package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
- package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
- package/src/session-runtime/tool-policy/policy-stage.mjs +183 -0
- package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
- package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
- package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
- package/src/session-runtime/tool-policy-refresh.mjs +48 -369
- package/src/session-runtime/tool-surface.mjs +10 -4
- package/src/session-runtime/turn/session-ops.mjs +8 -0
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
- package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
- package/src/session-runtime/turn/turn-run.mjs +7 -2
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +527 -333
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +85 -63
- package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
- package/src/session-runtime/workspace/transition-steps.mjs +1 -18
- package/src/session-runtime/workspace/transitions.mjs +279 -67
- package/src/session-runtime/workspace/workspace-state.mjs +7 -6
- package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
- package/src/standalone/daemon-agent-control.mjs +4 -1
- package/src/standalone/daemon-lag-profiler.mjs +3 -1
- package/src/standalone/daemon-session-control.mjs +173 -15
- package/src/standalone/daemon-telemetry.mjs +9 -0
- package/src/standalone/daemon.mjs +33 -10
- package/src/standalone/local-session-runtime.mjs +33 -16
- package/src/standalone/session-client.mjs +7 -2
- package/src/standalone/session-protocol.mjs +8 -0
- package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
- package/src/standalone/session-service/agent-route-follow.mjs +69 -0
- package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
- package/src/standalone/session-service.mjs +21 -0
- package/src/standalone/session-state-projection.mjs +19 -9
- package/src/standalone/session-tool-output.mjs +36 -9
- package/src/standalone/staged-import.mjs +47 -0
- package/src/tui/App.jsx +2 -0
- package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
- package/src/tui/app/message-selector.mjs +15 -2
- package/src/tui/app/model-options.mjs +16 -2
- package/src/tui/app/model-picker.mjs +2 -1
- package/src/tui/app/route-pickers.mjs +3 -1
- package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
- package/src/tui/app/slash-commands.mjs +1 -1
- package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
- package/src/tui/dist/index.mjs +617 -369
- package/src/tui/paste-attachments.mjs +0 -1
- package/src/tui/paste-text-policy.mjs +4 -5
- package/src/tui/prompt-history-store.mjs +16 -21
- package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
- package/src/tui/session/context-state.mjs +4 -0
- package/src/tui/session/notice-surface.mjs +3 -0
- package/src/tui/session/session-api/intake/abort.mjs +52 -18
- package/src/tui/session/session-api/intake/rewind.mjs +39 -4
- package/src/tui/session/session-api/media.mjs +2 -1
- package/src/tui/session/session-api/routes-agent-definitions.mjs +76 -0
- package/src/tui/session/session-api/routes-write.mjs +203 -0
- package/src/tui/session/session-api/routes.mjs +23 -247
- package/src/tui/session/session-api/settings.mjs +14 -1
- package/src/tui/session/session-flow/drain.mjs +28 -3
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
- package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
- package/src/tui/session/session-flow/steering.mjs +8 -3
- package/src/tui/session/tool-approval.mjs +142 -14
- package/src/tui/session/transcript-intake.mjs +2 -0
- package/src/tui/session/turn.mjs +23 -14
- package/src/tui/transcript-tool-failures.mjs +8 -2
- package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
- package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
- package/src/rules/agent-mode/ROUTINE.md +0 -6
- package/src/session-runtime/room-member-gate.mjs +0 -48
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Legacy Office (.doc / .xls / .ppt, OLE compound) attachments. Text
|
|
2
|
+
// extraction reads OOXML packages only, so a legacy file is saved as its
|
|
3
|
+
// package by LibreOffice (convertLegacyOffice, the conversion `office open`
|
|
4
|
+
// uses) in a private scratch folder, never beside the user's file.
|
|
5
|
+
import { mkdtemp, readFile, writeFile } from 'node:fs/promises';
|
|
6
|
+
import { tmpdir } from 'node:os';
|
|
7
|
+
import { basename, extname, join } from 'node:path';
|
|
8
|
+
import { redactDirectories } from '../shared/redact-directories.mjs';
|
|
9
|
+
import { removeDirectoryWithRetry } from '../shared/remove-directory.mjs';
|
|
10
|
+
|
|
11
|
+
// The first eight bytes of every legacy Office (OLE compound) file.
|
|
12
|
+
const OLE_COMPOUND_MAGIC = Buffer.from([0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1]);
|
|
13
|
+
|
|
14
|
+
const PACKAGE_BY_LEGACY_EXTENSION = { doc: 'docx', xls: 'xlsx', ppt: 'pptx' };
|
|
15
|
+
const PACKAGE_BY_LEGACY_MIME = {
|
|
16
|
+
'application/msword': 'docx',
|
|
17
|
+
'application/vnd.ms-excel': 'xlsx',
|
|
18
|
+
'application/vnd.ms-powerpoint': 'pptx',
|
|
19
|
+
};
|
|
20
|
+
const PACKAGE_BY_OOXML_EXTENSION = { docx: 'docx', xlsx: 'xlsx', xlsm: 'xlsx', pptx: 'pptx' };
|
|
21
|
+
|
|
22
|
+
export const OOXML_MIME_BY_PACKAGE = {
|
|
23
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
24
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
25
|
+
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
26
|
+
};
|
|
27
|
+
const PACKAGE_BY_OOXML_MIME = {
|
|
28
|
+
...Object.fromEntries(Object.entries(OOXML_MIME_BY_PACKAGE).map(([kind, mime]) => [mime, kind])),
|
|
29
|
+
'application/vnd.ms-excel.sheet.macroenabled.12': 'xlsx',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function isLegacyOfficeBuffer(buffer) {
|
|
33
|
+
return buffer.length >= 8 && buffer.subarray(0, 8).equals(OLE_COMPOUND_MAGIC);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The package extension (`docx`/`xlsx`/`pptx`) an attachment labelled legacy Office
|
|
37
|
+
* (by MIME type or file extension) converts to, else ''. */
|
|
38
|
+
export function legacyOfficeLabelPackage(mimeType, filename) {
|
|
39
|
+
const mime = String(mimeType || '')
|
|
40
|
+
.split(';', 1)[0]
|
|
41
|
+
.trim()
|
|
42
|
+
.toLowerCase();
|
|
43
|
+
const extension = extname(String(filename || ''))
|
|
44
|
+
.slice(1)
|
|
45
|
+
.toLowerCase();
|
|
46
|
+
return PACKAGE_BY_LEGACY_MIME[mime] || PACKAGE_BY_LEGACY_EXTENSION[extension] || '';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The package an OLE file labelled as an OOXML Office file (renamed, or sent
|
|
50
|
+
* with the OOXML MIME type and no extension) converts to, else ''. */
|
|
51
|
+
export function ooxmlLabelPackage(mimeType, filename) {
|
|
52
|
+
const mime = String(mimeType || '')
|
|
53
|
+
.split(';', 1)[0]
|
|
54
|
+
.trim()
|
|
55
|
+
.toLowerCase();
|
|
56
|
+
const extension = extname(String(filename || ''))
|
|
57
|
+
.slice(1)
|
|
58
|
+
.toLowerCase();
|
|
59
|
+
return PACKAGE_BY_OOXML_MIME[mime] || PACKAGE_BY_OOXML_EXTENSION[extension] || '';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function defaultConvert(path, output, options) {
|
|
63
|
+
const { convertLegacyOffice } = await import('../office/portable/portable-soffice.mjs');
|
|
64
|
+
return convertLegacyOffice(path, output, options);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Saves the legacy Office `buffer` as the OOXML package `kind` and returns the
|
|
69
|
+
* package bytes and its file name. The scratch folder is always removed, once
|
|
70
|
+
* the converter has returned (it returns only after its process has closed;
|
|
71
|
+
* a removal that still meets a held file is retried, and a final failure is
|
|
72
|
+
* logged). A failure (LibreOffice missing, damaged or protected file, bytes
|
|
73
|
+
* that are not a legacy Office file) throws a message with the scratch path
|
|
74
|
+
* taken out; a cancellation rethrows the abort itself.
|
|
75
|
+
*/
|
|
76
|
+
export async function convertLegacyOfficeBuffer(
|
|
77
|
+
buffer,
|
|
78
|
+
filename,
|
|
79
|
+
kind,
|
|
80
|
+
{ signal = null, convert = defaultConvert, remove = undefined, log = undefined, retryMs = undefined } = {}
|
|
81
|
+
) {
|
|
82
|
+
signal?.throwIfAborted();
|
|
83
|
+
if (!isLegacyOfficeBuffer(buffer)) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`${filename || 'This attachment'} is labelled as a legacy Office file but its content is not one, so it cannot be converted. Open it in Microsoft Office and save it as .${kind}, then attach that copy.`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
const stem = basename(String(filename || 'attachment'), extname(String(filename || ''))) || 'attachment';
|
|
89
|
+
const safeStem = stem.replace(/[^\w.-]+/g, '_').slice(0, 80) || 'attachment';
|
|
90
|
+
const legacyExtension = Object.keys(PACKAGE_BY_LEGACY_EXTENSION).find(
|
|
91
|
+
(ext) => PACKAGE_BY_LEGACY_EXTENSION[ext] === kind
|
|
92
|
+
);
|
|
93
|
+
const dir = await mkdtemp(join(tmpdir(), 'mixdog-attachment-'));
|
|
94
|
+
let converted;
|
|
95
|
+
try {
|
|
96
|
+
const source = join(dir, `${safeStem}.${legacyExtension}`);
|
|
97
|
+
const output = join(dir, `${safeStem}.${kind}`);
|
|
98
|
+
await writeFile(source, buffer);
|
|
99
|
+
await convert(source, output, { signal });
|
|
100
|
+
converted = { buffer: await readFile(output), filename: `${stem}.${kind}` };
|
|
101
|
+
} catch (error) {
|
|
102
|
+
signal?.throwIfAborted();
|
|
103
|
+
throw new Error(redactDirectories(error instanceof Error ? error.message : String(error), [dir]));
|
|
104
|
+
} finally {
|
|
105
|
+
await removeDirectoryWithRetry(dir, { remove, log, retryMs });
|
|
106
|
+
}
|
|
107
|
+
// A cancel that arrived while the folder was being removed still cancels.
|
|
108
|
+
signal?.throwIfAborted();
|
|
109
|
+
return converted;
|
|
110
|
+
}
|
|
@@ -16,5 +16,9 @@ const PDF_MAGIC = Buffer.from('%PDF-', 'latin1');
|
|
|
16
16
|
|
|
17
17
|
/** True when the bytes start with the %PDF- header. */
|
|
18
18
|
export function isPdfBuffer(buffer) {
|
|
19
|
-
return
|
|
19
|
+
return (
|
|
20
|
+
Buffer.isBuffer(buffer) &&
|
|
21
|
+
buffer.length >= PDF_MAGIC.length &&
|
|
22
|
+
buffer.subarray(0, PDF_MAGIC.length).equals(PDF_MAGIC)
|
|
23
|
+
);
|
|
20
24
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// No external dependencies.
|
|
7
7
|
import { readFile, stat } from 'node:fs/promises';
|
|
8
8
|
import { inflateRawSync } from 'node:zlib';
|
|
9
|
+
import { stripTrailingReplacementChars } from '../shared/utf8-cut.mjs';
|
|
9
10
|
|
|
10
11
|
// Longest run of attributes inside one tag that a pattern will cross. Bounded
|
|
11
12
|
// so a tag with no closing bracket cannot make a pattern rescan the part.
|
|
@@ -675,12 +676,8 @@ function sheetFigures(buf, entries, sheetPart) {
|
|
|
675
676
|
function capOutput(text, maxOutputBytes) {
|
|
676
677
|
const buf = Buffer.from(text, 'utf8');
|
|
677
678
|
if (buf.length <= maxOutputBytes) return text;
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
// `/\uFFFD+$/` is quadratic on a long run of them).
|
|
681
|
-
let end = cut.length;
|
|
682
|
-
while (end > 0 && cut[end - 1] === '\uFFFD') end -= 1;
|
|
683
|
-
cut = cut.slice(0, end);
|
|
679
|
+
// Drop the replacement characters a split multi-byte sequence leaves.
|
|
680
|
+
const cut = stripTrailingReplacementChars(buf.subarray(0, maxOutputBytes).toString('utf8'));
|
|
684
681
|
return `${cut}\n... [office text truncated at ${maxOutputBytes} bytes]`;
|
|
685
682
|
}
|
|
686
683
|
|
|
@@ -5,19 +5,18 @@
|
|
|
5
5
|
* OpenAI-compatible providers without a document contract receive page-ordered
|
|
6
6
|
* text extracted here, so they never see an unsupported inline Base64 block.
|
|
7
7
|
*/
|
|
8
|
+
import { stripTrailingReplacementChars } from '../shared/utf8-cut.mjs';
|
|
8
9
|
import { MAX_PDF_PAGES } from './limits.mjs';
|
|
9
10
|
import { resolvedPdfJs } from './pdfjs-runtime.mjs';
|
|
10
11
|
|
|
11
12
|
const DEFAULT_MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
/** `value` cut to at most `maxBytes` of UTF-8, never inside a multi-byte sequence. */
|
|
15
|
+
export function boundedUtf8(value, maxBytes) {
|
|
14
16
|
const buffer = Buffer.from(String(value || ''), 'utf8');
|
|
15
17
|
if (buffer.length <= maxBytes) return { text: buffer.toString('utf8'), bytes: buffer.length, truncated: false };
|
|
16
18
|
if (maxBytes <= 0) return { text: '', bytes: 0, truncated: true };
|
|
17
|
-
const text = buffer
|
|
18
|
-
.subarray(0, maxBytes)
|
|
19
|
-
.toString('utf8')
|
|
20
|
-
.replace(/\uFFFD+$/g, '');
|
|
19
|
+
const text = stripTrailingReplacementChars(buffer.subarray(0, maxBytes).toString('utf8'));
|
|
21
20
|
return { text, bytes: Buffer.byteLength(text, 'utf8'), truncated: true };
|
|
22
21
|
}
|
|
23
22
|
|
|
@@ -74,38 +73,90 @@ function pageBodyText(content) {
|
|
|
74
73
|
.trim();
|
|
75
74
|
}
|
|
76
75
|
|
|
77
|
-
|
|
76
|
+
/** The deadline prompt intake passes: it runs inside a room's post queue, which
|
|
77
|
+
* a document that never loads would otherwise hold forever. */
|
|
78
|
+
export const PDF_INTAKE_TIMEOUT_MS = 60_000;
|
|
79
|
+
|
|
80
|
+
// A page counts as carrying a text layer when its extracted text holds at
|
|
81
|
+
// least this many non-whitespace characters. A scan often keeps a stamped page
|
|
82
|
+
// number or a few stray glyphs; those alone do not make its content readable
|
|
83
|
+
// as text, so such a page still needs its image (or OCR).
|
|
84
|
+
export const PDF_TEXT_LAYER_MIN_CHARS = 16;
|
|
85
|
+
|
|
86
|
+
/** Whether one page's extracted text is a real text layer (see above). */
|
|
87
|
+
export function pageHasTextLayer(text) {
|
|
88
|
+
return String(text || '').replace(/\s+/g, '').length >= PDF_TEXT_LAYER_MIN_CHARS;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function timeoutError(timeoutMs) {
|
|
92
|
+
return Object.assign(new Error(`PDF inspection timed out after ${timeoutMs} ms`), {
|
|
93
|
+
code: 'PDF_INSPECTION_TIMEOUT',
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Inspect a PDF. With `timeoutMs`, a document that has not finished by then
|
|
98
|
+
* is destroyed and the call rejects (`code: 'PDF_INSPECTION_TIMEOUT'`);
|
|
99
|
+
* without it there is no deadline. */
|
|
100
|
+
export async function inspectPdfBuffer(buffer, { timeoutMs = null, ...options } = {}) {
|
|
101
|
+
if (!Buffer.isBuffer(buffer) || buffer.length === 0) throw new TypeError('PDF payload is empty');
|
|
102
|
+
const hold = { pdf: null, expired: false };
|
|
103
|
+
if (!(timeoutMs > 0)) return inspectLoaded(buffer, options, hold);
|
|
104
|
+
let timer;
|
|
105
|
+
const deadline = new Promise((_, reject) => {
|
|
106
|
+
timer = setTimeout(() => {
|
|
107
|
+
hold.expired = true;
|
|
108
|
+
// A document still loading is destroyed as soon as it arrives (below).
|
|
109
|
+
Promise.resolve(hold.pdf?.destroy?.()).catch(() => {});
|
|
110
|
+
reject(timeoutError(timeoutMs));
|
|
111
|
+
}, timeoutMs);
|
|
112
|
+
});
|
|
113
|
+
try {
|
|
114
|
+
return await Promise.race([inspectLoaded(buffer, options, hold), deadline]);
|
|
115
|
+
} finally {
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function inspectLoaded(
|
|
78
121
|
buffer,
|
|
79
122
|
{
|
|
80
123
|
extractText = false,
|
|
124
|
+
measureText = false,
|
|
81
125
|
maxPages = MAX_PDF_PAGES,
|
|
82
126
|
maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES,
|
|
83
127
|
pageRange = null,
|
|
84
128
|
password = '',
|
|
85
|
-
}
|
|
129
|
+
},
|
|
130
|
+
hold
|
|
86
131
|
) {
|
|
87
|
-
if (!Buffer.isBuffer(buffer) || buffer.length === 0) throw new TypeError('PDF payload is empty');
|
|
88
132
|
await resolvedPdfJs();
|
|
89
133
|
const { getDocumentProxy } = await import('unpdf');
|
|
90
134
|
const pdf = await getDocumentProxy(new Uint8Array(buffer), password ? { password: String(password) } : {});
|
|
135
|
+
hold.pdf = pdf;
|
|
91
136
|
try {
|
|
137
|
+
if (hold.expired) throw timeoutError(0);
|
|
92
138
|
const pageCount = Math.max(0, Number(pdf?.numPages) || 0);
|
|
93
139
|
const pageLimit = Number.isFinite(Number(maxPages)) ? Math.max(1, Math.floor(Number(maxPages))) : Infinity;
|
|
94
140
|
if (pageCount > pageLimit) {
|
|
95
141
|
throw new RangeError(`PDF has ${pageCount} pages; maximum supported attachment is ${pageLimit} pages`);
|
|
96
142
|
}
|
|
143
|
+
if (measureText) return { pageCount, ...(await measureTextLayer(pdf, pageCount, hold)) };
|
|
97
144
|
if (!extractText) return { pageCount, text: '', truncated: false };
|
|
98
145
|
|
|
99
146
|
const from = Math.max(1, Number(pageRange?.from) || 1);
|
|
100
147
|
const to = Math.min(pageCount, Math.max(from, Number(pageRange?.to) || pageCount));
|
|
101
148
|
const byteLimit = Math.max(1, Number(maxOutputBytes) || DEFAULT_MAX_OUTPUT_BYTES);
|
|
102
149
|
const chunks = [];
|
|
150
|
+
// Each page's own text (before the output budget cuts it), so a caller can
|
|
151
|
+
// tell text pages from scanned ones.
|
|
152
|
+
const pages = [];
|
|
103
153
|
let bytes = 0;
|
|
104
154
|
let truncated = false;
|
|
105
155
|
for (let pageNumber = from; pageNumber <= to; pageNumber += 1) {
|
|
106
156
|
const page = await pdf.getPage(pageNumber);
|
|
107
157
|
try {
|
|
108
158
|
const body = pageBodyText(await page.getTextContent());
|
|
159
|
+
pages.push({ page: pageNumber, text: body });
|
|
109
160
|
const block = `--- Page ${pageNumber} ---\n${body || '(no extractable text on this page)'}`;
|
|
110
161
|
const separatorBytes = chunks.length ? 2 : 0;
|
|
111
162
|
const remaining = byteLimit - bytes - separatorBytes;
|
|
@@ -134,12 +185,35 @@ export async function inspectPdfBuffer(
|
|
|
134
185
|
const suffix = '\n\n... [PDF text truncated to the prompt input budget]';
|
|
135
186
|
const room = Math.max(0, byteLimit - Buffer.byteLength(suffix, 'utf8'));
|
|
136
187
|
const bounded = boundedUtf8(chunks.join(''), room);
|
|
137
|
-
return { pageCount, text: `${bounded.text}${suffix}`, truncated: true };
|
|
188
|
+
return { pageCount, text: `${bounded.text}${suffix}`, truncated: true, pages };
|
|
138
189
|
}
|
|
139
|
-
return { pageCount, text: chunks.join(''), truncated: false };
|
|
190
|
+
return { pageCount, text: chunks.join(''), truncated: false, pages };
|
|
140
191
|
} finally {
|
|
141
192
|
try {
|
|
142
193
|
await pdf.destroy?.();
|
|
143
194
|
} catch {}
|
|
144
195
|
}
|
|
145
196
|
}
|
|
197
|
+
|
|
198
|
+
// How much of the document reads as text: the pages that carry a text layer
|
|
199
|
+
// and the characters they hold. Nothing is kept but the counts.
|
|
200
|
+
async function measureTextLayer(pdf, pageCount, hold) {
|
|
201
|
+
let textPages = 0;
|
|
202
|
+
let textChars = 0;
|
|
203
|
+
for (let pageNumber = 1; pageNumber <= pageCount; pageNumber += 1) {
|
|
204
|
+
if (hold.expired) throw timeoutError(0);
|
|
205
|
+
const page = await pdf.getPage(pageNumber);
|
|
206
|
+
try {
|
|
207
|
+
const body = pageBodyText(await page.getTextContent());
|
|
208
|
+
if (pageHasTextLayer(body)) {
|
|
209
|
+
textPages += 1;
|
|
210
|
+
textChars += body.length;
|
|
211
|
+
}
|
|
212
|
+
} finally {
|
|
213
|
+
try {
|
|
214
|
+
page.cleanup?.();
|
|
215
|
+
} catch {}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return { textPages, textChars };
|
|
219
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Rebuilds the composer's restore payload (`pastedTexts`, `pastedImages`) from a
|
|
2
|
+
// stored, materialized user prompt. The model history holds the EXPANDED prompt:
|
|
3
|
+
// each `[Pasted text #n …]` / `[File #n …]` token became a `textKind` reference
|
|
4
|
+
// part, in token order, and each image an image reference part. The transcript
|
|
5
|
+
// row's own text still spells the tokens, so the n-th reference part belongs to
|
|
6
|
+
// the n-th token; the entries carry references only and are hydrated by the
|
|
7
|
+
// caller (`hydratePastedAttachments`) exactly as the abort/queue restores are.
|
|
8
|
+
import { isAttachmentReference } from './store.mjs';
|
|
9
|
+
|
|
10
|
+
const TEXT_TOKEN_RE = /\[(Pasted text|File) #(\d+)(?:: ([^\]\r\n]+))?[^\]\r\n]*\]/g;
|
|
11
|
+
const IMAGE_TOKEN_RE = /\[Image #(\d+)[^\]\r\n]*\]/g;
|
|
12
|
+
|
|
13
|
+
export function restorableAttachmentsFromPrompt(content, rowText) {
|
|
14
|
+
const parts = Array.isArray(content) ? content : [];
|
|
15
|
+
const text = String(rowText ?? '');
|
|
16
|
+
const textRefs = parts.filter(
|
|
17
|
+
(part) =>
|
|
18
|
+
part?.type === 'text' && isAttachmentReference(part) && (part.textKind === 'paste' || part.textKind === 'file')
|
|
19
|
+
);
|
|
20
|
+
const imageRefs = parts.filter((part) => part?.type === 'image' && isAttachmentReference(part));
|
|
21
|
+
|
|
22
|
+
// A `[File #n: name]` token is also spelled for binary files (PDF/Office),
|
|
23
|
+
// which never became a text reference: a token takes the next text reference
|
|
24
|
+
// only when it is the same kind (and, for files, the same name).
|
|
25
|
+
const pastedTexts = {};
|
|
26
|
+
let next = 0;
|
|
27
|
+
for (const match of text.matchAll(TEXT_TOKEN_RE)) {
|
|
28
|
+
const part = textRefs[next];
|
|
29
|
+
if (!part) break;
|
|
30
|
+
const isFile = match[1] === 'File';
|
|
31
|
+
if (isFile ? part.textKind !== 'file' || match[3] !== part.filename : part.textKind !== 'paste') continue;
|
|
32
|
+
next += 1;
|
|
33
|
+
const id = Number(match[2]);
|
|
34
|
+
if (!id || pastedTexts[id]) continue;
|
|
35
|
+
pastedTexts[id] = {
|
|
36
|
+
id,
|
|
37
|
+
source: part.textKind === 'file' ? 'file' : 'paste',
|
|
38
|
+
attachmentRef: part.attachmentRef,
|
|
39
|
+
sizeBytes: part.sizeBytes,
|
|
40
|
+
...(part.filename ? { filename: part.filename } : {}),
|
|
41
|
+
...(part.mimeType ? { mimeType: part.mimeType } : {}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const imageIds = [...text.matchAll(IMAGE_TOKEN_RE)].map((match) => Number(match[1]));
|
|
46
|
+
const usedIds = new Set([...Object.keys(pastedTexts).map(Number), ...imageIds]);
|
|
47
|
+
let nextId = 1;
|
|
48
|
+
const pastedImages = {};
|
|
49
|
+
imageRefs.forEach((part, index) => {
|
|
50
|
+
let id = Number(part.pasteId) || imageIds[index] || 0;
|
|
51
|
+
if (!id || pastedImages[id]) {
|
|
52
|
+
while (usedIds.has(nextId)) nextId += 1;
|
|
53
|
+
id = nextId;
|
|
54
|
+
usedIds.add(id);
|
|
55
|
+
}
|
|
56
|
+
pastedImages[id] = {
|
|
57
|
+
id,
|
|
58
|
+
type: 'image',
|
|
59
|
+
mediaType: String(part.mimeType || part.mediaType || 'image/png'),
|
|
60
|
+
attachmentRef: part.attachmentRef,
|
|
61
|
+
sizeBytes: part.sizeBytes,
|
|
62
|
+
...(part.filename ? { filename: part.filename } : {}),
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
pastedTexts: Object.keys(pastedTexts).length ? pastedTexts : null,
|
|
68
|
+
pastedImages: Object.keys(pastedImages).length ? pastedImages : null,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* snapshots carry only a sha256 reference plus byte-free metadata.
|
|
7
7
|
* Provider lowering resolves the reference at the last possible boundary.
|
|
8
8
|
*/
|
|
9
|
-
import { createHash
|
|
9
|
+
import { createHash } from 'node:crypto';
|
|
10
10
|
import {
|
|
11
11
|
closeSync,
|
|
12
12
|
existsSync,
|
|
@@ -15,18 +15,25 @@ import {
|
|
|
15
15
|
openSync,
|
|
16
16
|
readFileSync,
|
|
17
17
|
readSync,
|
|
18
|
-
renameSync,
|
|
19
18
|
statSync,
|
|
20
19
|
unlinkSync,
|
|
21
20
|
utimesSync,
|
|
22
21
|
writeFileSync,
|
|
23
22
|
} from 'node:fs';
|
|
24
|
-
import {
|
|
23
|
+
import { readdir, readFile as readFileAsync, stat, unlink } from 'node:fs/promises';
|
|
25
24
|
import { dirname, join } from 'node:path';
|
|
26
25
|
|
|
26
|
+
import { writeFileAtomicSync, writeJsonAtomicAsync } from '../shared/atomic-file.mjs';
|
|
27
27
|
import { resolvePluginData } from '../shared/plugin-paths.mjs';
|
|
28
|
+
import {
|
|
29
|
+
convertLegacyOfficeBuffer,
|
|
30
|
+
isLegacyOfficeBuffer,
|
|
31
|
+
legacyOfficeLabelPackage,
|
|
32
|
+
OOXML_MIME_BY_PACKAGE,
|
|
33
|
+
ooxmlLabelPackage,
|
|
34
|
+
} from './legacy-office.mjs';
|
|
28
35
|
import { MAX_ATTACHMENT_BLOB_BYTES, MAX_PDF_BYTES, MAX_PDF_PAGES, isPdfBuffer } from './limits.mjs';
|
|
29
|
-
import { inspectPdfBuffer } from './pdf-extract.mjs';
|
|
36
|
+
import { inspectPdfBuffer, PDF_INTAKE_TIMEOUT_MS } from './pdf-extract.mjs';
|
|
30
37
|
|
|
31
38
|
const ATTACHMENT_REF_RE = /^[a-f0-9]{64}$/;
|
|
32
39
|
const TEXT_TOKEN_RE = /\[(?:Pasted text|File) #(\d+)[^\]\r\n]*\]/g;
|
|
@@ -140,17 +147,13 @@ function saveBuffer(buffer) {
|
|
|
140
147
|
}
|
|
141
148
|
if (!existingValid) {
|
|
142
149
|
mkdirSync(join(attachmentsDir(), attachmentRef.slice(0, 2)), { recursive: true, mode: 0o700 });
|
|
143
|
-
const temp = `${target}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`;
|
|
144
150
|
try {
|
|
145
|
-
|
|
146
|
-
|
|
151
|
+
// Durable, so a crash cannot leave this content address holding NUL
|
|
152
|
+
// bytes (a read would then fail its digest check).
|
|
153
|
+
writeFileAtomicSync(target, buffer, { mode: 0o600 });
|
|
147
154
|
} catch (error) {
|
|
148
155
|
// A concurrent writer may have published the same content first.
|
|
149
156
|
if (!storedMatches()) throw error;
|
|
150
|
-
} finally {
|
|
151
|
-
try {
|
|
152
|
-
unlinkSync(temp);
|
|
153
|
-
} catch {}
|
|
154
157
|
}
|
|
155
158
|
} else {
|
|
156
159
|
// Reusing old content creates a fresh reference before its queue/session
|
|
@@ -172,10 +175,23 @@ export function storeAttachmentBuffer(buffer) {
|
|
|
172
175
|
return saveBuffer(buffer);
|
|
173
176
|
}
|
|
174
177
|
|
|
178
|
+
/** Where the store keeps the bytes of `ref` on this machine (no extension:
|
|
179
|
+
* the read tool recognizes PDFs and images there by their content). */
|
|
180
|
+
export function attachmentFilePath(ref) {
|
|
181
|
+
return attachmentPath(ref);
|
|
182
|
+
}
|
|
183
|
+
|
|
175
184
|
export function isAttachmentReference(value) {
|
|
176
185
|
return Boolean(value && typeof value === 'object' && ATTACHMENT_REF_RE.test(String(value.attachmentRef || '')));
|
|
177
186
|
}
|
|
178
187
|
|
|
188
|
+
// The bytes of a base64 string, or null when they are empty or would not
|
|
189
|
+
// re-encode to the same string (a stored reference could not replay it exactly).
|
|
190
|
+
function canonicalBase64Buffer(data) {
|
|
191
|
+
const buffer = Buffer.from(data, 'base64');
|
|
192
|
+
return buffer.length === 0 || buffer.toString('base64') !== data ? null : buffer;
|
|
193
|
+
}
|
|
194
|
+
|
|
179
195
|
/**
|
|
180
196
|
* Stored-history form of an inline base64 image part (`{ type:'image', data }`
|
|
181
197
|
* or `{ type:'image', source:{ type:'base64' } }`): the same part as a
|
|
@@ -200,8 +216,8 @@ export function storeInlineImagePart(part) {
|
|
|
200
216
|
} else {
|
|
201
217
|
return null;
|
|
202
218
|
}
|
|
203
|
-
const buffer =
|
|
204
|
-
if (buffer
|
|
219
|
+
const buffer = canonicalBase64Buffer(data);
|
|
220
|
+
if (!buffer) return null;
|
|
205
221
|
return { ...metadata, ...saveBuffer(buffer) };
|
|
206
222
|
}
|
|
207
223
|
|
|
@@ -235,8 +251,8 @@ export function storeInlineDocumentPart(part) {
|
|
|
235
251
|
} else {
|
|
236
252
|
return null;
|
|
237
253
|
}
|
|
238
|
-
const buffer =
|
|
239
|
-
if (buffer
|
|
254
|
+
const buffer = canonicalBase64Buffer(data);
|
|
255
|
+
if (!buffer) return null;
|
|
240
256
|
// The magic header decides the kind before the label does (inlineFileKind),
|
|
241
257
|
// so a mislabelled PDF is stored under its real type: the reloaded part then
|
|
242
258
|
// estimates and lowers exactly like the live one.
|
|
@@ -380,7 +396,7 @@ function materializePastedTexts(pastedTexts) {
|
|
|
380
396
|
type: 'text',
|
|
381
397
|
attachmentRef: raw.attachmentRef,
|
|
382
398
|
sizeBytes: attachmentBlobSize(raw, raw.sizeBytes),
|
|
383
|
-
|
|
399
|
+
textKind: raw.source === 'file' ? 'file' : 'paste',
|
|
384
400
|
...(raw.filename ? { filename: raw.filename } : {}),
|
|
385
401
|
...(raw.mimeType ? { mimeType: raw.mimeType } : {}),
|
|
386
402
|
};
|
|
@@ -621,6 +637,50 @@ export function providerTakesNativePdf(provider) {
|
|
|
621
637
|
return provider?.nativePdf !== false;
|
|
622
638
|
}
|
|
623
639
|
|
|
640
|
+
/**
|
|
641
|
+
* A legacy Office attachment (.doc/.xls/.ppt: by label, or an OLE file renamed
|
|
642
|
+
* to an OOXML name) as the OOXML package LibreOffice saves it as, stored under
|
|
643
|
+
* its own reference, so text extraction and every later lowering see an
|
|
644
|
+
* ordinary package. A conversion that cannot run (LibreOffice missing, damaged
|
|
645
|
+
* file, or bytes that are not a legacy file under a legacy label) becomes a text
|
|
646
|
+
* part carrying the converter's message, so the model and the user are told
|
|
647
|
+
* instead of the file vanishing. A cancellation (`signal`) is rethrown, never
|
|
648
|
+
* turned into text. Any other part is returned as is.
|
|
649
|
+
*/
|
|
650
|
+
async function packageLegacyOfficePart(part, { convertLegacy, signal }) {
|
|
651
|
+
if (part?.type !== 'file' || !isAttachmentReference(part)) return part;
|
|
652
|
+
const mimeType = part.mimeType || part.mediaType;
|
|
653
|
+
const legacyKind = legacyOfficeLabelPackage(mimeType, part.filename);
|
|
654
|
+
const kind = legacyKind || ooxmlLabelPackage(mimeType, part.filename);
|
|
655
|
+
if (!kind) return part;
|
|
656
|
+
const buffer = readAttachmentBuffer(part);
|
|
657
|
+
// An OOXML-labelled part converts only when its bytes are legacy; a legacy
|
|
658
|
+
// label is always converted (or explained).
|
|
659
|
+
if (!legacyKind && !isLegacyOfficeBuffer(buffer)) return part;
|
|
660
|
+
const name = String(part.filename || '')
|
|
661
|
+
.split(/[\\/]/)
|
|
662
|
+
.pop();
|
|
663
|
+
try {
|
|
664
|
+
const converted = await convertLegacyOfficeBuffer(buffer, part.filename, kind, {
|
|
665
|
+
signal,
|
|
666
|
+
...(convertLegacy ? { convert: convertLegacy } : {}),
|
|
667
|
+
});
|
|
668
|
+
const { mediaType: _mediaType, ...rest } = part;
|
|
669
|
+
return {
|
|
670
|
+
...rest,
|
|
671
|
+
...saveBuffer(converted.buffer),
|
|
672
|
+
mimeType: OOXML_MIME_BY_PACKAGE[kind],
|
|
673
|
+
filename: converted.filename,
|
|
674
|
+
};
|
|
675
|
+
} catch (error) {
|
|
676
|
+
if (signal?.aborted) throw error;
|
|
677
|
+
return {
|
|
678
|
+
type: 'text',
|
|
679
|
+
text: `--- ${name || 'attachment'} ---\n${error instanceof Error ? error.message : String(error)}`,
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
624
684
|
/**
|
|
625
685
|
* PDF intake validation. Every PDF keeps its content-addressed reference — the
|
|
626
686
|
* one conversion to text happens at provider lowering — so this only checks what
|
|
@@ -629,13 +689,19 @@ export function providerTakesNativePdf(provider) {
|
|
|
629
689
|
* PDF as text reads its first MAX_PDF_PAGES pages, so a longer PDF is accepted.
|
|
630
690
|
* The page count is recorded on the part for estimates.
|
|
631
691
|
*/
|
|
632
|
-
export async function preparePromptSubmissionForProvider(
|
|
692
|
+
export async function preparePromptSubmissionForProvider(
|
|
693
|
+
intake,
|
|
694
|
+
provider,
|
|
695
|
+
{ convertLegacy = undefined, signal = null } = {}
|
|
696
|
+
) {
|
|
633
697
|
const prompt = intake?.prompt;
|
|
634
698
|
if (!Array.isArray(prompt)) return intake;
|
|
635
699
|
const nativePdf = providerTakesNativePdf(provider);
|
|
636
700
|
let changed = false;
|
|
637
701
|
const prepared = [];
|
|
638
|
-
for (const
|
|
702
|
+
for (const candidate of prompt) {
|
|
703
|
+
const part = await packageLegacyOfficePart(candidate, { convertLegacy, signal });
|
|
704
|
+
if (part !== candidate) changed = true;
|
|
639
705
|
const mimeType = String(part?.mimeType || part?.mediaType || '').toLowerCase();
|
|
640
706
|
if (part?.type !== 'file' || mimeType !== 'application/pdf' || !isAttachmentReference(part)) {
|
|
641
707
|
prepared.push(part);
|
|
@@ -645,11 +711,21 @@ export async function preparePromptSubmissionForProvider(intake, provider) {
|
|
|
645
711
|
if (bytes > MAX_PDF_BYTES) {
|
|
646
712
|
throw new RangeError(`PDF exceeds the ${MAX_PDF_BYTES / 1024 / 1024} MiB input limit`);
|
|
647
713
|
}
|
|
714
|
+
// The text-layer counts price the PDF (a scanned page costs its page image,
|
|
715
|
+
// a text page its text as well) and tell the model, when the PDF is sent
|
|
716
|
+
// as a reference, which pages read as text.
|
|
648
717
|
const inspected = await inspectPdfBuffer(readAttachmentBuffer(part), {
|
|
649
718
|
maxPages: nativePdf ? MAX_PDF_PAGES : Infinity,
|
|
719
|
+
measureText: true,
|
|
720
|
+
timeoutMs: PDF_INTAKE_TIMEOUT_MS,
|
|
650
721
|
});
|
|
651
722
|
changed = true;
|
|
652
|
-
prepared.push({
|
|
723
|
+
prepared.push({
|
|
724
|
+
...part,
|
|
725
|
+
pageCount: inspected.pageCount,
|
|
726
|
+
textPages: inspected.textPages,
|
|
727
|
+
textChars: inspected.textChars,
|
|
728
|
+
});
|
|
653
729
|
}
|
|
654
730
|
if (!changed) return intake;
|
|
655
731
|
return { ...intake, prompt: prepared };
|
|
@@ -739,8 +815,8 @@ function dropCachedAttachment(path) {
|
|
|
739
815
|
async function persistedAttachmentReferencePaths() {
|
|
740
816
|
const root = resolvePluginData();
|
|
741
817
|
const paths = [];
|
|
742
|
-
for (const
|
|
743
|
-
const dir = join(root,
|
|
818
|
+
for (const subdir of ['sessions', 'turn-checkpoints', 'session-pending']) {
|
|
819
|
+
const dir = join(root, subdir);
|
|
744
820
|
let entries;
|
|
745
821
|
try {
|
|
746
822
|
entries = await readdir(dir, { withFileTypes: true });
|
|
@@ -752,7 +828,7 @@ async function persistedAttachmentReferencePaths() {
|
|
|
752
828
|
if (!entry.isFile()) continue;
|
|
753
829
|
// The turn-checkpoint journal appends `<session>.jsonl` records that
|
|
754
830
|
// carry attachment refs of in-flight turns.
|
|
755
|
-
if (entry.name.endsWith('.json') || (
|
|
831
|
+
if (entry.name.endsWith('.json') || (subdir === 'turn-checkpoints' && entry.name.endsWith('.jsonl'))) {
|
|
756
832
|
paths.push(join(dir, entry.name));
|
|
757
833
|
}
|
|
758
834
|
}
|
|
@@ -819,16 +895,14 @@ async function loadReferenceCache(root) {
|
|
|
819
895
|
async function saveReferenceCache(root, files) {
|
|
820
896
|
referenceCache = { root, files };
|
|
821
897
|
const target = attachmentReferenceCachePath();
|
|
822
|
-
const temp = `${target}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`;
|
|
823
898
|
try {
|
|
824
|
-
|
|
825
|
-
await
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
}
|
|
899
|
+
// An unusable cache only costs a full re-read (loadReferenceCache), so no fsync.
|
|
900
|
+
await writeJsonAtomicAsync(
|
|
901
|
+
target,
|
|
902
|
+
{ version: 1, files: Object.fromEntries(files) },
|
|
903
|
+
{ compact: true, fsync: false }
|
|
904
|
+
);
|
|
905
|
+
} catch {}
|
|
832
906
|
}
|
|
833
907
|
|
|
834
908
|
export async function collectPromptAttachments({ now = Date.now(), minAgeMs = ATTACHMENT_GC_MIN_AGE_MS } = {}) {
|
|
@@ -885,8 +959,10 @@ export async function collectPromptAttachments({ now = Date.now(), minAgeMs = AT
|
|
|
885
959
|
continue;
|
|
886
960
|
}
|
|
887
961
|
for (const entry of entries) {
|
|
888
|
-
// A writer that died between create and rename leaves
|
|
889
|
-
|
|
962
|
+
// A writer that died between create and rename leaves
|
|
963
|
+
// `<ref>.mixdog-tmp-<rand>` (atomic writer), or the older
|
|
964
|
+
// `.<ref>.<rand>.tmp` / `<ref>.<pid>.<rand>.tmp`.
|
|
965
|
+
if (entry.isFile() && /^(?:\.?[a-f0-9]{64}\..+\.tmp|[a-f0-9]{64}\.mixdog-tmp-[0-9a-f]+)$/.test(entry.name)) {
|
|
890
966
|
const tempPath = join(dir, entry.name);
|
|
891
967
|
try {
|
|
892
968
|
if ((await stat(tempPath)).mtimeMs <= cutoff) await unlink(tempPath);
|