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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { join, dirname, resolve } from 'node:path';
|
|
3
3
|
import { explicitSessionCwd } from '../../shared/user-cwd.mjs';
|
|
4
|
+
import { holdsNoData } from '../../shared/atomic-file.mjs';
|
|
4
5
|
|
|
5
6
|
/** @type {Map<string, string|null>} */
|
|
6
7
|
const cache = new Map();
|
|
@@ -51,7 +52,10 @@ export function resolveProjectId(cwd) {
|
|
|
51
52
|
const idFile = join(mixdogRoot, '.mixdog', 'project.id');
|
|
52
53
|
let content;
|
|
53
54
|
try {
|
|
54
|
-
|
|
55
|
+
const raw = readFileSync(idFile, 'utf8');
|
|
56
|
+
// A crash can leave the marker NUL-filled; trim() keeps NULs, so it would
|
|
57
|
+
// otherwise become the project id. No data means no marker (COMMON).
|
|
58
|
+
content = holdsNoData(raw) ? '' : raw.trim();
|
|
55
59
|
} catch {
|
|
56
60
|
// TOCTOU: the file vanished between findProjectIdRoot's existsSync and
|
|
57
61
|
// this read. Treat as no marker; do not cache so a transient miss can
|
|
@@ -34,6 +34,7 @@ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'nod
|
|
|
34
34
|
import { fileURLToPath } from 'node:url';
|
|
35
35
|
import { runChild } from './pg/run-child.mjs';
|
|
36
36
|
import { renameWithRetrySync, writeFileAtomicSync, writeJsonAtomicSync } from '../../shared/atomic-file.mjs';
|
|
37
|
+
import { fileHasData } from '../../shared/file-has-data.mjs';
|
|
37
38
|
import { downloadToFileWithRetry } from '../../shared/bounded-download.mjs';
|
|
38
39
|
import { platformKey, platformKeyCandidates, verifySha256File } from '../../shared/native-asset.mjs';
|
|
39
40
|
|
|
@@ -497,8 +498,12 @@ function cachedRuntimeDir(runtimeBaseDir, version, sha256) {
|
|
|
497
498
|
}
|
|
498
499
|
|
|
499
500
|
// The asset digest stamped into a complete runtime tree, '' when absent.
|
|
501
|
+
// A tree whose postgres executable holds no data (a crash after the extract
|
|
502
|
+
// left it NUL-filled) is not complete, whatever its stamp says.
|
|
500
503
|
function readRuntimeStamp(verDir) {
|
|
501
504
|
try {
|
|
505
|
+
const postgres = join(verDir, 'bin', process.platform === 'win32' ? 'postgres.exe' : 'postgres');
|
|
506
|
+
if (existsSync(postgres) && !fileHasData(postgres)) return '';
|
|
502
507
|
return readFileSync(join(verDir, '.version-sha256'), 'utf8').trim();
|
|
503
508
|
} catch {
|
|
504
509
|
return '';
|
|
@@ -588,7 +593,7 @@ async function installRuntimeVersion({ runtimeBaseDir, url, sha256, size, versio
|
|
|
588
593
|
}
|
|
589
594
|
|
|
590
595
|
// Stamp sha256 inside staging dir.
|
|
591
|
-
|
|
596
|
+
writeFileAtomicSync(join(stagingDir, '.version-sha256'), sha256);
|
|
592
597
|
normalizeBinExecBit(stagingDir);
|
|
593
598
|
|
|
594
599
|
// Atomic swap:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { mkdir, readFile, readdir,
|
|
1
|
+
import { mkdir, readFile, readdir, rm, stat } from 'node:fs/promises';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import { parseJsonText, writeJsonAtomicAsync } from '../../shared/atomic-file.mjs';
|
|
5
5
|
|
|
6
6
|
const RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
|
|
7
7
|
|
|
@@ -18,17 +18,8 @@ function journalPath(id, dataDir) {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export async function writeOfficeJournal(record, dataDir = '') {
|
|
21
|
-
const directory = officeJournalDirectory(dataDir);
|
|
22
|
-
await mkdir(directory, { recursive: true });
|
|
23
21
|
const target = journalPath(record.id, dataDir);
|
|
24
|
-
|
|
25
|
-
await writeFile(temporary, `${JSON.stringify(record, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
26
|
-
try {
|
|
27
|
-
await rename(temporary, target);
|
|
28
|
-
} catch {
|
|
29
|
-
await rm(target, { force: true });
|
|
30
|
-
await rename(temporary, target);
|
|
31
|
-
}
|
|
22
|
+
await writeJsonAtomicAsync(target, record);
|
|
32
23
|
return target;
|
|
33
24
|
}
|
|
34
25
|
|
|
@@ -38,9 +29,11 @@ export async function removeOfficeJournal(id, dataDir = '') {
|
|
|
38
29
|
|
|
39
30
|
export async function readOfficeJournal(id, dataDir = '') {
|
|
40
31
|
try {
|
|
41
|
-
return
|
|
32
|
+
return parseJsonText(await readFile(journalPath(id, dataDir), 'utf8'));
|
|
42
33
|
} catch (error) {
|
|
43
|
-
|
|
34
|
+
// A journal a crash left empty or NUL-filled records nothing to recover;
|
|
35
|
+
// it stays on disk for the retention sweep and never blocks the caller.
|
|
36
|
+
if (error?.code === 'ENOENT' || error instanceof SyntaxError) return null;
|
|
44
37
|
throw error;
|
|
45
38
|
}
|
|
46
39
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { writeJsonAtomicAsync } from '../../../shared/atomic-file.mjs';
|
|
3
3
|
import { delimiter, dirname, isAbsolute, join, resolve, win32 } from 'node:path';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
@@ -126,20 +126,8 @@ export async function readJson(path, fallback = null) {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
export async function writeJsonAtomic(path, value) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, {
|
|
132
|
-
encoding: 'utf8',
|
|
133
|
-
mode: 0o600,
|
|
134
|
-
});
|
|
135
|
-
try {
|
|
136
|
-
await rename(temporary, path);
|
|
137
|
-
} catch {
|
|
138
|
-
await rm(path, { force: true });
|
|
139
|
-
await rename(temporary, path);
|
|
140
|
-
} finally {
|
|
141
|
-
await rm(temporary, { force: true }).catch(() => {});
|
|
142
|
-
}
|
|
129
|
+
// Durable atomic replace; the replaced version stays as <file>.bak.
|
|
130
|
+
await writeJsonAtomicAsync(path, value, { backup: true });
|
|
143
131
|
}
|
|
144
132
|
|
|
145
133
|
function resolveConfigPath(path, base) {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// The OCR engine's own thread. Starting the engine includes the first-use
|
|
2
|
+
// download of each language's data and gives no handle until it finishes, so
|
|
3
|
+
// it runs here, where ending this thread ends the engine and the download with
|
|
4
|
+
// it (ocr-engine.mjs).
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import { mkdir } from 'node:fs/promises';
|
|
7
|
+
import { parentPort, workerData } from 'node:worker_threads';
|
|
8
|
+
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
const { languages, cachePath, langPath } = workerData;
|
|
11
|
+
|
|
12
|
+
let engine = null;
|
|
13
|
+
try {
|
|
14
|
+
await mkdir(cachePath, { recursive: true });
|
|
15
|
+
const tesseract = require('tesseract.js');
|
|
16
|
+
engine = await tesseract.createWorker(languages, tesseract.OEM.LSTM_ONLY, {
|
|
17
|
+
cachePath,
|
|
18
|
+
gzip: true,
|
|
19
|
+
...(langPath ? { langPath } : {}),
|
|
20
|
+
});
|
|
21
|
+
} catch (error) {
|
|
22
|
+
parentPort.postMessage({ startError: String(error?.message || error) });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (engine) {
|
|
26
|
+
// One page at a time, in arrival order: the engine itself is serial.
|
|
27
|
+
parentPort.on('message', async ({ id, image, output }) => {
|
|
28
|
+
try {
|
|
29
|
+
const input = typeof image === 'string' ? image : Buffer.from(image.buffer, image.byteOffset, image.byteLength);
|
|
30
|
+
const recognized = await engine.recognize(input, {}, output || { text: true });
|
|
31
|
+
parentPort.postMessage({ id, data: recognized.data });
|
|
32
|
+
} catch (error) {
|
|
33
|
+
parentPort.postMessage({ id, error: String(error?.message || error) });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
parentPort.postMessage({ ready: true });
|
|
37
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// One OCR engine per language set and cache, on a thread of its own so that a
|
|
2
|
+
// stalled start (the first-use language download) or a cancelled page can be
|
|
3
|
+
// ended at once: ending the thread ends the engine and its download. Callers
|
|
4
|
+
// arriving while it starts share that start; the engine ends when the last
|
|
5
|
+
// caller releases it.
|
|
6
|
+
import { Worker } from 'node:worker_threads';
|
|
7
|
+
import { workerExecArgv } from '../../shared/worker-exec-argv.mjs';
|
|
8
|
+
|
|
9
|
+
const ENGINE_URL = new URL('./ocr-engine-worker.mjs', import.meta.url);
|
|
10
|
+
|
|
11
|
+
/** The start includes the first-use download; without network it fails or hangs. */
|
|
12
|
+
export const OCR_ENGINE_START_TIMEOUT_MS = 90_000;
|
|
13
|
+
|
|
14
|
+
const engines = new Map();
|
|
15
|
+
|
|
16
|
+
const cancelledError = () => new Error('PDF OCR was cancelled');
|
|
17
|
+
const unavailableError = (message) => Object.assign(new Error(message), { code: 'OCR_UNAVAILABLE' });
|
|
18
|
+
|
|
19
|
+
const defaultSpawn = (url, options) => new Worker(url, options);
|
|
20
|
+
|
|
21
|
+
/** How many engine threads are running (for diagnostics and tests). */
|
|
22
|
+
export function liveOcrEngines() {
|
|
23
|
+
return engines.size;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Ends the engine: its thread, its start and every page it still owes.
|
|
27
|
+
function closeEngine(entry, error) {
|
|
28
|
+
if (entry.closed) return;
|
|
29
|
+
entry.closed = true;
|
|
30
|
+
entry.error = error;
|
|
31
|
+
clearTimeout(entry.timer);
|
|
32
|
+
if (engines.get(entry.key) === entry) engines.delete(entry.key);
|
|
33
|
+
entry.failStart(error);
|
|
34
|
+
const jobs = [...entry.jobs.values()];
|
|
35
|
+
entry.jobs.clear();
|
|
36
|
+
for (const job of jobs) job.reject(error);
|
|
37
|
+
void entry.thread.terminate().catch(() => {});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function startEngine(key, { languages, cachePath, langPath, timeoutMs, spawn }) {
|
|
41
|
+
const entry = { key, holders: 0, jobs: new Map(), nextJob: 0, closed: false, error: null };
|
|
42
|
+
entry.started = new Promise((resolve, reject) => {
|
|
43
|
+
entry.markStarted = resolve;
|
|
44
|
+
entry.failStart = reject;
|
|
45
|
+
});
|
|
46
|
+
entry.started.catch(() => {});
|
|
47
|
+
entry.thread = spawn(ENGINE_URL, {
|
|
48
|
+
execArgv: workerExecArgv(),
|
|
49
|
+
workerData: { languages, cachePath, langPath: langPath || null },
|
|
50
|
+
});
|
|
51
|
+
entry.timer = setTimeout(
|
|
52
|
+
() => closeEngine(entry, unavailableError('OCR language data did not load in time (it downloads on first use)')),
|
|
53
|
+
timeoutMs
|
|
54
|
+
);
|
|
55
|
+
entry.timer.unref?.();
|
|
56
|
+
entry.thread.on('message', (message) => {
|
|
57
|
+
if (message?.ready) {
|
|
58
|
+
clearTimeout(entry.timer);
|
|
59
|
+
entry.markStarted();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (message?.startError) {
|
|
63
|
+
closeEngine(entry, unavailableError(`OCR could not start: ${message.startError}`));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const job = entry.jobs.get(message?.id);
|
|
67
|
+
if (!job) return;
|
|
68
|
+
entry.jobs.delete(message.id);
|
|
69
|
+
if (message.error) job.reject(new Error(`OCR failed: ${message.error}`));
|
|
70
|
+
else job.resolve(message.data);
|
|
71
|
+
});
|
|
72
|
+
entry.thread.on('error', (error) => closeEngine(entry, error));
|
|
73
|
+
entry.thread.on('exit', (code) => closeEngine(entry, new Error(`OCR engine stopped (exit ${code})`)));
|
|
74
|
+
engines.set(key, entry);
|
|
75
|
+
return entry;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// `promise`, or a cancellation as soon as `signal` aborts.
|
|
79
|
+
function untilAborted(promise, signal) {
|
|
80
|
+
if (!signal) return promise;
|
|
81
|
+
if (signal.aborted) return Promise.reject(cancelledError());
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
const onAbort = () => reject(cancelledError());
|
|
84
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
85
|
+
promise.then(resolve, reject).finally(() => signal.removeEventListener('abort', onAbort));
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function recognizeOn(entry, image, { output, signal } = {}) {
|
|
90
|
+
if (entry.closed) return Promise.reject(entry.error);
|
|
91
|
+
if (signal?.aborted) return Promise.reject(cancelledError());
|
|
92
|
+
const id = ++entry.nextJob;
|
|
93
|
+
const answer = new Promise((resolve, reject) => {
|
|
94
|
+
entry.jobs.set(id, { resolve, reject });
|
|
95
|
+
});
|
|
96
|
+
entry.thread.postMessage({ id, image, output: output || null });
|
|
97
|
+
if (!signal) return answer;
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const onAbort = () => {
|
|
100
|
+
entry.jobs.delete(id);
|
|
101
|
+
// A page in recognition cannot be stopped inside the engine: the engine
|
|
102
|
+
// ends with it unless another caller still uses it, which then gets the
|
|
103
|
+
// page's (discarded) answer before its own.
|
|
104
|
+
if (entry.holders <= 1) closeEngine(entry, cancelledError());
|
|
105
|
+
reject(cancelledError());
|
|
106
|
+
};
|
|
107
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
108
|
+
answer.then(resolve, reject).finally(() => signal.removeEventListener('abort', onAbort));
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The engine for `languages` ("eng+kor") with its data cached at `cachePath`,
|
|
114
|
+
* started on first use. Resolves `{ recognize(image, { output, signal }),
|
|
115
|
+
* release() }` once it is ready; the caller releases it when done. Rejects
|
|
116
|
+
* with code OCR_UNAVAILABLE when it does not start within `timeoutMs`, and
|
|
117
|
+
* with a cancellation when `signal` aborts, ending the start if no one else
|
|
118
|
+
* waits on it.
|
|
119
|
+
*/
|
|
120
|
+
export async function acquireOcrEngine({
|
|
121
|
+
languages,
|
|
122
|
+
cachePath,
|
|
123
|
+
langPath = null,
|
|
124
|
+
signal = null,
|
|
125
|
+
timeoutMs = OCR_ENGINE_START_TIMEOUT_MS,
|
|
126
|
+
spawn = defaultSpawn,
|
|
127
|
+
}) {
|
|
128
|
+
if (signal?.aborted) throw cancelledError();
|
|
129
|
+
const key = `${cachePath}\0${languages}\0${langPath || ''}`;
|
|
130
|
+
const entry = engines.get(key) || startEngine(key, { languages, cachePath, langPath, timeoutMs, spawn });
|
|
131
|
+
entry.holders += 1;
|
|
132
|
+
let released = false;
|
|
133
|
+
const release = () => {
|
|
134
|
+
if (released) return;
|
|
135
|
+
released = true;
|
|
136
|
+
entry.holders -= 1;
|
|
137
|
+
if (entry.holders <= 0) closeEngine(entry, new Error('OCR engine released'));
|
|
138
|
+
};
|
|
139
|
+
try {
|
|
140
|
+
await untilAborted(entry.started, signal);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
release();
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
return { recognize: (image, options) => recognizeOn(entry, image, options), release };
|
|
146
|
+
}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
// PDF OCR: tesseract TSV/block parsing, readiness probe and the ocr operation.
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import {
|
|
3
|
+
import { readFile, readdir, rm, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { PDFDocument, rgb } from 'pdf-lib';
|
|
6
6
|
import { SAVE_OPTIONS } from './pdf-draw.mjs';
|
|
7
7
|
import { embedDocumentFont, fontCovers } from './pdf-fonts.mjs';
|
|
8
|
-
import { renderPdfPages } from './pdf-render.mjs';
|
|
8
|
+
import { PDF_RENDER_MAX_EXPLICIT_PAGES, renderPdfPages } from './pdf-render.mjs';
|
|
9
9
|
import { selectedPages } from './pdf-document.mjs';
|
|
10
|
+
import { acquireOcrEngine } from './ocr-engine.mjs';
|
|
10
11
|
|
|
11
12
|
const require = createRequire(import.meta.url);
|
|
12
13
|
|
|
14
|
+
const DEFAULT_MIN_CONFIDENCE = 40;
|
|
15
|
+
|
|
13
16
|
// The engine's own column order. Tesseract writes this table with a header
|
|
14
17
|
// row through its command line and without one through the worker API, so the
|
|
15
18
|
// reader accepts both: taking the first data row as a header dropped that word
|
|
@@ -208,7 +211,7 @@ export async function pdfOcrReadiness(dataDir) {
|
|
|
208
211
|
|
|
209
212
|
// Renders and recognizes one page; the rendered image path is recorded on
|
|
210
213
|
// `temporaryImages` for cleanup.
|
|
211
|
-
async function recognizePage(
|
|
214
|
+
async function recognizePage(engine, path, pageNumber, operation, signal, temporaryImages) {
|
|
212
215
|
const rendered = await renderPdfPages(path, {
|
|
213
216
|
pages: [pageNumber],
|
|
214
217
|
maxWidth: Math.max(1200, Math.min(3200, Number(operation.maxWidth) || 2400)),
|
|
@@ -216,13 +219,13 @@ async function recognizePage(worker, path, pageNumber, operation, signal, tempor
|
|
|
216
219
|
});
|
|
217
220
|
const image = rendered.images[0];
|
|
218
221
|
temporaryImages.push(image.path);
|
|
219
|
-
const recognized = await
|
|
220
|
-
const lines = ocrTextLines(recognized.
|
|
221
|
-
const minConfidence = Number(operation.minConfidence ??
|
|
222
|
-
const words = (lines.length ? lines.flatMap((line) => line.words) : parseOcrBlocks(recognized.
|
|
222
|
+
const recognized = await engine.recognize(image.path, { output: { text: true, tsv: true, blocks: true }, signal });
|
|
223
|
+
const lines = ocrTextLines(recognized.tsv, recognized.text);
|
|
224
|
+
const minConfidence = Number(operation.minConfidence ?? DEFAULT_MIN_CONFIDENCE);
|
|
225
|
+
const words = (lines.length ? lines.flatMap((line) => line.words) : parseOcrBlocks(recognized.blocks)).filter(
|
|
223
226
|
(word) => word.confidence >= minConfidence
|
|
224
227
|
);
|
|
225
|
-
return { pageNumber, image, lines, words, text: recognized.
|
|
228
|
+
return { pageNumber, image, lines, words, text: recognized.text || '' };
|
|
226
229
|
}
|
|
227
230
|
|
|
228
231
|
// One font for all the recognized text: Helvetica when it is Latin, an
|
|
@@ -285,18 +288,15 @@ function placeRecognizedText(document, { pageNumber, image, lines, words }, font
|
|
|
285
288
|
}
|
|
286
289
|
}
|
|
287
290
|
|
|
288
|
-
|
|
291
|
+
// The shared engine for the operation's languages (ocr-engine.mjs): it starts
|
|
292
|
+
// on its own thread, so a stalled first-use download or a cancellation ends
|
|
293
|
+
// it at once.
|
|
294
|
+
async function openOcrEngine(operation, dataDir, signal) {
|
|
289
295
|
const languages = Array.isArray(operation.languages)
|
|
290
296
|
? operation.languages.map(String).join('+')
|
|
291
297
|
: String(operation.languages || 'eng+kor');
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
const tesseract = require('tesseract.js');
|
|
295
|
-
const worker = await tesseract.createWorker(languages, tesseract.OEM.LSTM_ONLY, {
|
|
296
|
-
cachePath,
|
|
297
|
-
gzip: true,
|
|
298
|
-
});
|
|
299
|
-
return { worker, languages };
|
|
298
|
+
const engine = await acquireOcrEngine({ languages, cachePath: ocrCachePath(dataDir), signal });
|
|
299
|
+
return { engine, languages };
|
|
300
300
|
}
|
|
301
301
|
|
|
302
302
|
function ocrResult(operation, { pages, languages, text, tally, fontPath, embedded }) {
|
|
@@ -318,6 +318,36 @@ function ocrResult(operation, { pages, languages, text, tally, fontPath, embedde
|
|
|
318
318
|
};
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
+
/**
|
|
322
|
+
* Read-only OCR: the recognized text of `pages` (1-based) of the PDF at
|
|
323
|
+
* `path`, as `[{ page, text }]`. Pages are rendered in memory and nothing is
|
|
324
|
+
* written next to the PDF or into it. Throws when the engine or its language
|
|
325
|
+
* data is unavailable (no network on first use).
|
|
326
|
+
*/
|
|
327
|
+
export async function recognizePdfPageTexts(path, pages, { dataDir, signal = null, languages } = {}) {
|
|
328
|
+
const { engine } = await openOcrEngine({ languages }, dataDir, signal);
|
|
329
|
+
const out = [];
|
|
330
|
+
try {
|
|
331
|
+
for (let offset = 0; offset < pages.length; offset += PDF_RENDER_MAX_EXPLICIT_PAGES) {
|
|
332
|
+
if (signal?.aborted) throw new Error('PDF OCR was cancelled');
|
|
333
|
+
const rendered = await renderPdfPages(path, {
|
|
334
|
+
pages: pages.slice(offset, offset + PDF_RENDER_MAX_EXPLICIT_PAGES),
|
|
335
|
+
maxWidth: 2000,
|
|
336
|
+
signal,
|
|
337
|
+
writeImages: false,
|
|
338
|
+
});
|
|
339
|
+
for (const image of rendered.images) {
|
|
340
|
+
if (signal?.aborted) throw new Error('PDF OCR was cancelled');
|
|
341
|
+
const recognized = await engine.recognize(Buffer.from(image.data, 'base64'), { signal });
|
|
342
|
+
out.push({ page: image.page, text: String(recognized?.text || '').trim() });
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return out;
|
|
346
|
+
} finally {
|
|
347
|
+
engine.release();
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
321
351
|
export async function ocrPdf(path, operation, { dataDir, signal = null } = {}) {
|
|
322
352
|
const source = await readFile(path);
|
|
323
353
|
const document = await PDFDocument.load(source, {
|
|
@@ -325,7 +355,7 @@ export async function ocrPdf(path, operation, { dataDir, signal = null } = {}) {
|
|
|
325
355
|
updateMetadata: false,
|
|
326
356
|
});
|
|
327
357
|
const pages = selectedPages(document.getPageCount(), operation.pages || (operation.page ? [operation.page] : null));
|
|
328
|
-
const {
|
|
358
|
+
const { engine, languages } = await openOcrEngine(operation, dataDir, signal);
|
|
329
359
|
const tally = { wordCount: 0, skippedWords: 0, totalConfidence: 0 };
|
|
330
360
|
let text = '';
|
|
331
361
|
const temporaryImages = [];
|
|
@@ -335,19 +365,19 @@ export async function ocrPdf(path, operation, { dataDir, signal = null } = {}) {
|
|
|
335
365
|
// it produced.
|
|
336
366
|
for (const pageNumber of pages) {
|
|
337
367
|
if (signal?.aborted) throw new Error('PDF OCR was cancelled');
|
|
338
|
-
const recognized = await recognizePage(
|
|
368
|
+
const recognized = await recognizePage(engine, path, pageNumber, operation, signal, temporaryImages);
|
|
339
369
|
recognizedPages.push(recognized);
|
|
340
370
|
text += `${text ? '\n\n' : ''}--- Page ${pageNumber} ---\n${recognized.text}`;
|
|
341
371
|
}
|
|
342
372
|
const { font, fontPath, embedded } = await selectOcrFont(document, operation, recognizedPages);
|
|
343
|
-
const minConfidence = Number(operation.minConfidence ??
|
|
373
|
+
const minConfidence = Number(operation.minConfidence ?? DEFAULT_MIN_CONFIDENCE);
|
|
344
374
|
for (const entry of recognizedPages) placeRecognizedText(document, entry, font, minConfidence, tally);
|
|
345
375
|
if (tally.wordCount > 0) {
|
|
346
376
|
await writeFile(path, await document.save(SAVE_OPTIONS));
|
|
347
377
|
}
|
|
348
378
|
return ocrResult(operation, { pages, languages, text, tally, fontPath, embedded });
|
|
349
379
|
} finally {
|
|
350
|
-
|
|
380
|
+
engine.release();
|
|
351
381
|
for (const image of temporaryImages) await rm(image, { force: true }).catch(() => {});
|
|
352
382
|
}
|
|
353
383
|
}
|
|
@@ -51,8 +51,8 @@ function requestedPages(pageCount, pages) {
|
|
|
51
51
|
const selected =
|
|
52
52
|
Array.isArray(pages) && pages.length ? pages : Array.from({ length: pageCount }, (_, index) => index + 1);
|
|
53
53
|
const unique = [...new Set(selected.map(Number))];
|
|
54
|
-
if (Array.isArray(pages) && pages.length && unique.length >
|
|
55
|
-
throw new Error(
|
|
54
|
+
if (Array.isArray(pages) && pages.length && unique.length > PDF_RENDER_MAX_EXPLICIT_PAGES) {
|
|
55
|
+
throw new Error(`render accepts at most ${PDF_RENDER_MAX_EXPLICIT_PAGES} explicit pages per call`);
|
|
56
56
|
}
|
|
57
57
|
for (const page of unique) {
|
|
58
58
|
if (!Number.isInteger(page) || page < 1 || page > pageCount) {
|
|
@@ -62,6 +62,8 @@ function requestedPages(pageCount, pages) {
|
|
|
62
62
|
return unique;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/** The most explicit pages one render call accepts. */
|
|
66
|
+
export const PDF_RENDER_MAX_EXPLICIT_PAGES = 12;
|
|
65
67
|
const PDF_RENDER_WORKER_KIND = 'mixdog-office-pdf-render';
|
|
66
68
|
const PDF_RENDER_PAGE_WORKER_KIND = 'mixdog-office-pdf-render-page';
|
|
67
69
|
|
|
@@ -104,9 +106,21 @@ async function renderPdfPageDirect(path, pageNumber, targetWidth, minimumScale =
|
|
|
104
106
|
data: canvas.toBuffer('image/png').toString('base64'),
|
|
105
107
|
};
|
|
106
108
|
} finally {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
await destroyQuietly(loadingTask);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const cancelledError = () => new Error('PDF rendering was cancelled');
|
|
114
|
+
|
|
115
|
+
function throwIfCancelled(signal) {
|
|
116
|
+
if (signal?.aborted) throw cancelledError();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function destroyQuietly(loadingTask) {
|
|
120
|
+
try {
|
|
121
|
+
await loadingTask.destroy?.();
|
|
122
|
+
} catch {
|
|
123
|
+
/* a document that will not close cleanly must not mask the render's own outcome */
|
|
110
124
|
}
|
|
111
125
|
}
|
|
112
126
|
|
|
@@ -125,7 +139,7 @@ async function runPdfRenderWorker(data, signal = null) {
|
|
|
125
139
|
};
|
|
126
140
|
const onAbort = () => {
|
|
127
141
|
void worker.terminate();
|
|
128
|
-
finish(reject,
|
|
142
|
+
finish(reject, cancelledError());
|
|
129
143
|
};
|
|
130
144
|
signal?.addEventListener('abort', onAbort, { once: true });
|
|
131
145
|
worker.once('message', (message) => {
|
|
@@ -216,7 +230,7 @@ async function renderPdfPagesDirect(
|
|
|
216
230
|
path,
|
|
217
231
|
{ pages = null, maxWidth = 1400, maximumScale = 2, signal = null, writeImages = true } = {}
|
|
218
232
|
) {
|
|
219
|
-
|
|
233
|
+
throwIfCancelled(signal);
|
|
220
234
|
installPdfGlobals();
|
|
221
235
|
const { getDocument, VerbosityLevel } = await resolvedPdfJs();
|
|
222
236
|
const pdfData = await readFile(path);
|
|
@@ -225,9 +239,7 @@ async function renderPdfPagesDirect(
|
|
|
225
239
|
try {
|
|
226
240
|
return { loadingTask, document: await loadingTask.promise };
|
|
227
241
|
} catch (error) {
|
|
228
|
-
|
|
229
|
-
await loadingTask.destroy?.();
|
|
230
|
-
} catch {}
|
|
242
|
+
await destroyQuietly(loadingTask);
|
|
231
243
|
throw error;
|
|
232
244
|
}
|
|
233
245
|
};
|
|
@@ -237,7 +249,7 @@ async function renderPdfPagesDirect(
|
|
|
237
249
|
const stem = basename(path, extname(path));
|
|
238
250
|
const output = [];
|
|
239
251
|
const renderPage = async (pageNumber, targetWidth, minimumScale = 0.25) => {
|
|
240
|
-
|
|
252
|
+
throwIfCancelled(signal);
|
|
241
253
|
const rendered = await runPdfRenderWorker(
|
|
242
254
|
{
|
|
243
255
|
kind: PDF_RENDER_PAGE_WORKER_KIND,
|
|
@@ -249,7 +261,7 @@ async function renderPdfPagesDirect(
|
|
|
249
261
|
},
|
|
250
262
|
signal
|
|
251
263
|
);
|
|
252
|
-
|
|
264
|
+
throwIfCancelled(signal);
|
|
253
265
|
return {
|
|
254
266
|
canvas: { width: rendered.width, height: rendered.height },
|
|
255
267
|
data: Buffer.from(rendered.data, 'base64'),
|
|
@@ -294,18 +306,18 @@ async function renderPdfPagesDirect(
|
|
|
294
306
|
},
|
|
295
307
|
};
|
|
296
308
|
} finally {
|
|
297
|
-
|
|
298
|
-
await loadingTask.destroy?.();
|
|
299
|
-
} catch {}
|
|
309
|
+
await destroyQuietly(loadingTask);
|
|
300
310
|
}
|
|
301
311
|
}
|
|
302
312
|
|
|
303
313
|
export async function renderPdfPages(path, options = {}) {
|
|
304
314
|
const signal = options.signal || null;
|
|
305
|
-
|
|
315
|
+
throwIfCancelled(signal);
|
|
306
316
|
if (!isMainThread) return await renderPdfPagesDirect(path, options);
|
|
307
317
|
// The worker rasterizes; this thread writes the images, where the call's
|
|
308
|
-
// write ledger can see each one before it lands.
|
|
318
|
+
// write ledger can see each one before it lands. `writeImages: false`
|
|
319
|
+
// returns the images in memory only (a reader that must not write next to
|
|
320
|
+
// the user's file).
|
|
309
321
|
const rendered = await runPdfRenderWorker(
|
|
310
322
|
{
|
|
311
323
|
kind: PDF_RENDER_WORKER_KIND,
|
|
@@ -319,7 +331,7 @@ export async function renderPdfPages(path, options = {}) {
|
|
|
319
331
|
},
|
|
320
332
|
signal
|
|
321
333
|
);
|
|
322
|
-
await writePageImages(rendered.images);
|
|
334
|
+
if (options.writeImages !== false) await writePageImages(rendered.images);
|
|
323
335
|
return rendered;
|
|
324
336
|
}
|
|
325
337
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import { createHash,
|
|
2
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
3
3
|
import { access, chmod, mkdir, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { gunzipSync } from 'node:zlib';
|
|
5
5
|
import { join } from 'node:path';
|
|
@@ -202,7 +202,7 @@ export async function ensureOoxmlValidator({ dataDir, download = true, signal =
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
async function installValidator(paths, entry, signal) {
|
|
205
|
-
const temporary = join(paths.root,
|
|
205
|
+
const temporary = join(paths.root, `${entry.binary}.mixdog-tmp-${randomBytes(8).toString('hex')}`);
|
|
206
206
|
try {
|
|
207
207
|
const archive = await fetchArchive(packageUrl(entry.name), { signal });
|
|
208
208
|
verifyIntegrity(archive, entry.integrity);
|