mixdog 1.3.1 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/package.json +2 -2
- package/scripts/native-release-status.mjs +238 -0
- package/src/defaults/shipped-history.json +5 -1
- 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 +2 -2
- package/src/rules/shared/10-tool-workflow.md +4 -0
- package/src/rules/shared/20-research.md +2 -0
- 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 +80 -31
- 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 +67 -38
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
- 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-wire.mjs +31 -4
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
- 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 +9 -0
- 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-storage.mjs +68 -22
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -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 +12 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -5
- 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 +2 -0
- 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 +17 -2
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +133 -38
- 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/execution-tail.mjs +14 -11
- package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +68 -18
- 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 +5 -1
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +1 -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 +230 -0
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +136 -32
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +21 -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 +189 -105
- 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 +3 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +38 -5
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +21 -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/ask-turn-auto-effort.mjs +13 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
- package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +99 -0
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -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/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-lifecycle.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +3 -8
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +12 -1
- 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 +42 -10
- 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 +120 -9
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -1
- 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/sweep-record.mjs +8 -3
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +36 -1
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -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/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 +35 -13
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -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/lib/shell-job-records.mjs +6 -14
- 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 +34 -21
- 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/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/store.mjs +49 -30
- 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 +5 -2
- package/src/runtime/effort-judge/model-install.mjs +16 -8
- 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/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 +4 -0
- 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-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 +30 -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/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/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 +6 -0
- 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 +44 -2
- package/src/runtime/shared/user-cwd.mjs +9 -4
- package/src/runtime/shared/user-data-guard.mjs +41 -42
- package/src/runtime/shared/utf8-cut.mjs +10 -0
- package/src/runtime/shared/worktrees/activity-gate.mjs +269 -61
- package/src/runtime/shared/worktrees/registry.mjs +26 -26
- package/src/runtime/tidy/core-install.mjs +26 -4
- package/src/runtime/tidy/install.mjs +91 -23
- package/src/runtime/web-search/lib/config.mjs +4 -16
- package/src/session-runtime/agent-home-delivery.mjs +555 -199
- package/src/session-runtime/agent-room-delivery.mjs +143 -36
- package/src/session-runtime/agent-task-sessions.mjs +667 -165
- package/src/session-runtime/agent-tool-routing.mjs +1 -0
- package/src/session-runtime/boot/apis.mjs +45 -24
- package/src/session-runtime/boot/config.mjs +1 -0
- package/src/session-runtime/boot/facade.mjs +11 -1
- 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 +2 -2
- 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 +36 -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 +19 -4
- 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-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/room-member-gate.mjs +16 -3
- 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-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/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 +4 -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 +14 -4
- 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/session-create/materialize.mjs +2 -3
- package/src/session-runtime/session-create/session-options.mjs +15 -2
- package/src/session-runtime/settings-builtin-tools-api.mjs +63 -1
- package/src/session-runtime/settings-compaction-api.mjs +86 -39
- package/src/session-runtime/setup-tool/executor.mjs +8 -2
- package/src/session-runtime/setup-tool/settings-contract.mjs +31 -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 +265 -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 +165 -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 +47 -369
- package/src/session-runtime/tool-surface.mjs +5 -3
- 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 +2 -1
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +519 -333
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +46 -46
- 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/workspace-state.mjs +7 -6
- package/src/standalone/daemon-agent-control.mjs +4 -1
- package/src/standalone/daemon-session-control.mjs +51 -10
- package/src/standalone/daemon.mjs +28 -10
- package/src/standalone/local-session-runtime.mjs +22 -14
- package/src/standalone/session-client.mjs +7 -2
- package/src/standalone/session-protocol.mjs +7 -0
- package/src/standalone/session-service.mjs +11 -0
- package/src/standalone/session-state-projection.mjs +19 -9
- package/src/standalone/session-tool-output.mjs +35 -9
- package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
- 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 +529 -333
- package/src/tui/prompt-history-store.mjs +16 -21
- package/src/tui/session/context-state.mjs +4 -0
- package/src/tui/session/session-api/intake/abort.mjs +19 -1
- package/src/tui/session/session-api/media.mjs +2 -1
- package/src/tui/session/session-api/routes-agent-definitions.mjs +75 -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 +185 -20
- 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
|
@@ -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
|
+
}
|
|
@@ -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,18 @@ 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
28
|
import { MAX_ATTACHMENT_BLOB_BYTES, MAX_PDF_BYTES, MAX_PDF_PAGES, isPdfBuffer } from './limits.mjs';
|
|
29
|
-
import { inspectPdfBuffer } from './pdf-extract.mjs';
|
|
29
|
+
import { inspectPdfBuffer, PDF_INTAKE_TIMEOUT_MS } from './pdf-extract.mjs';
|
|
30
30
|
|
|
31
31
|
const ATTACHMENT_REF_RE = /^[a-f0-9]{64}$/;
|
|
32
32
|
const TEXT_TOKEN_RE = /\[(?:Pasted text|File) #(\d+)[^\]\r\n]*\]/g;
|
|
@@ -140,17 +140,13 @@ function saveBuffer(buffer) {
|
|
|
140
140
|
}
|
|
141
141
|
if (!existingValid) {
|
|
142
142
|
mkdirSync(join(attachmentsDir(), attachmentRef.slice(0, 2)), { recursive: true, mode: 0o700 });
|
|
143
|
-
const temp = `${target}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`;
|
|
144
143
|
try {
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
// Durable, so a crash cannot leave this content address holding NUL
|
|
145
|
+
// bytes (a read would then fail its digest check).
|
|
146
|
+
writeFileAtomicSync(target, buffer, { mode: 0o600 });
|
|
147
147
|
} catch (error) {
|
|
148
148
|
// A concurrent writer may have published the same content first.
|
|
149
149
|
if (!storedMatches()) throw error;
|
|
150
|
-
} finally {
|
|
151
|
-
try {
|
|
152
|
-
unlinkSync(temp);
|
|
153
|
-
} catch {}
|
|
154
150
|
}
|
|
155
151
|
} else {
|
|
156
152
|
// Reusing old content creates a fresh reference before its queue/session
|
|
@@ -172,10 +168,23 @@ export function storeAttachmentBuffer(buffer) {
|
|
|
172
168
|
return saveBuffer(buffer);
|
|
173
169
|
}
|
|
174
170
|
|
|
171
|
+
/** Where the store keeps the bytes of `ref` on this machine (no extension:
|
|
172
|
+
* the read tool recognizes PDFs and images there by their content). */
|
|
173
|
+
export function attachmentFilePath(ref) {
|
|
174
|
+
return attachmentPath(ref);
|
|
175
|
+
}
|
|
176
|
+
|
|
175
177
|
export function isAttachmentReference(value) {
|
|
176
178
|
return Boolean(value && typeof value === 'object' && ATTACHMENT_REF_RE.test(String(value.attachmentRef || '')));
|
|
177
179
|
}
|
|
178
180
|
|
|
181
|
+
// The bytes of a base64 string, or null when they are empty or would not
|
|
182
|
+
// re-encode to the same string (a stored reference could not replay it exactly).
|
|
183
|
+
function canonicalBase64Buffer(data) {
|
|
184
|
+
const buffer = Buffer.from(data, 'base64');
|
|
185
|
+
return buffer.length === 0 || buffer.toString('base64') !== data ? null : buffer;
|
|
186
|
+
}
|
|
187
|
+
|
|
179
188
|
/**
|
|
180
189
|
* Stored-history form of an inline base64 image part (`{ type:'image', data }`
|
|
181
190
|
* or `{ type:'image', source:{ type:'base64' } }`): the same part as a
|
|
@@ -200,8 +209,8 @@ export function storeInlineImagePart(part) {
|
|
|
200
209
|
} else {
|
|
201
210
|
return null;
|
|
202
211
|
}
|
|
203
|
-
const buffer =
|
|
204
|
-
if (buffer
|
|
212
|
+
const buffer = canonicalBase64Buffer(data);
|
|
213
|
+
if (!buffer) return null;
|
|
205
214
|
return { ...metadata, ...saveBuffer(buffer) };
|
|
206
215
|
}
|
|
207
216
|
|
|
@@ -235,8 +244,8 @@ export function storeInlineDocumentPart(part) {
|
|
|
235
244
|
} else {
|
|
236
245
|
return null;
|
|
237
246
|
}
|
|
238
|
-
const buffer =
|
|
239
|
-
if (buffer
|
|
247
|
+
const buffer = canonicalBase64Buffer(data);
|
|
248
|
+
if (!buffer) return null;
|
|
240
249
|
// The magic header decides the kind before the label does (inlineFileKind),
|
|
241
250
|
// so a mislabelled PDF is stored under its real type: the reloaded part then
|
|
242
251
|
// estimates and lowers exactly like the live one.
|
|
@@ -645,11 +654,21 @@ export async function preparePromptSubmissionForProvider(intake, provider) {
|
|
|
645
654
|
if (bytes > MAX_PDF_BYTES) {
|
|
646
655
|
throw new RangeError(`PDF exceeds the ${MAX_PDF_BYTES / 1024 / 1024} MiB input limit`);
|
|
647
656
|
}
|
|
657
|
+
// The text-layer counts price the PDF (a scanned page costs its page image,
|
|
658
|
+
// a text page its text as well) and tell the model, when the PDF is sent
|
|
659
|
+
// as a reference, which pages read as text.
|
|
648
660
|
const inspected = await inspectPdfBuffer(readAttachmentBuffer(part), {
|
|
649
661
|
maxPages: nativePdf ? MAX_PDF_PAGES : Infinity,
|
|
662
|
+
measureText: true,
|
|
663
|
+
timeoutMs: PDF_INTAKE_TIMEOUT_MS,
|
|
650
664
|
});
|
|
651
665
|
changed = true;
|
|
652
|
-
prepared.push({
|
|
666
|
+
prepared.push({
|
|
667
|
+
...part,
|
|
668
|
+
pageCount: inspected.pageCount,
|
|
669
|
+
textPages: inspected.textPages,
|
|
670
|
+
textChars: inspected.textChars,
|
|
671
|
+
});
|
|
653
672
|
}
|
|
654
673
|
if (!changed) return intake;
|
|
655
674
|
return { ...intake, prompt: prepared };
|
|
@@ -739,8 +758,8 @@ function dropCachedAttachment(path) {
|
|
|
739
758
|
async function persistedAttachmentReferencePaths() {
|
|
740
759
|
const root = resolvePluginData();
|
|
741
760
|
const paths = [];
|
|
742
|
-
for (const
|
|
743
|
-
const dir = join(root,
|
|
761
|
+
for (const subdir of ['sessions', 'turn-checkpoints', 'session-pending']) {
|
|
762
|
+
const dir = join(root, subdir);
|
|
744
763
|
let entries;
|
|
745
764
|
try {
|
|
746
765
|
entries = await readdir(dir, { withFileTypes: true });
|
|
@@ -752,7 +771,7 @@ async function persistedAttachmentReferencePaths() {
|
|
|
752
771
|
if (!entry.isFile()) continue;
|
|
753
772
|
// The turn-checkpoint journal appends `<session>.jsonl` records that
|
|
754
773
|
// carry attachment refs of in-flight turns.
|
|
755
|
-
if (entry.name.endsWith('.json') || (
|
|
774
|
+
if (entry.name.endsWith('.json') || (subdir === 'turn-checkpoints' && entry.name.endsWith('.jsonl'))) {
|
|
756
775
|
paths.push(join(dir, entry.name));
|
|
757
776
|
}
|
|
758
777
|
}
|
|
@@ -819,16 +838,14 @@ async function loadReferenceCache(root) {
|
|
|
819
838
|
async function saveReferenceCache(root, files) {
|
|
820
839
|
referenceCache = { root, files };
|
|
821
840
|
const target = attachmentReferenceCachePath();
|
|
822
|
-
const temp = `${target}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`;
|
|
823
841
|
try {
|
|
824
|
-
|
|
825
|
-
await
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
}
|
|
842
|
+
// An unusable cache only costs a full re-read (loadReferenceCache), so no fsync.
|
|
843
|
+
await writeJsonAtomicAsync(
|
|
844
|
+
target,
|
|
845
|
+
{ version: 1, files: Object.fromEntries(files) },
|
|
846
|
+
{ compact: true, fsync: false }
|
|
847
|
+
);
|
|
848
|
+
} catch {}
|
|
832
849
|
}
|
|
833
850
|
|
|
834
851
|
export async function collectPromptAttachments({ now = Date.now(), minAgeMs = ATTACHMENT_GC_MIN_AGE_MS } = {}) {
|
|
@@ -885,8 +902,10 @@ export async function collectPromptAttachments({ now = Date.now(), minAgeMs = AT
|
|
|
885
902
|
continue;
|
|
886
903
|
}
|
|
887
904
|
for (const entry of entries) {
|
|
888
|
-
// A writer that died between create and rename leaves
|
|
889
|
-
|
|
905
|
+
// A writer that died between create and rename leaves
|
|
906
|
+
// `<ref>.mixdog-tmp-<rand>` (atomic writer), or the older
|
|
907
|
+
// `.<ref>.<rand>.tmp` / `<ref>.<pid>.<rand>.tmp`.
|
|
908
|
+
if (entry.isFile() && /^(?:\.?[a-f0-9]{64}\..+\.tmp|[a-f0-9]{64}\.mixdog-tmp-[0-9a-f]+)$/.test(entry.name)) {
|
|
890
909
|
const tempPath = join(dir, entry.name);
|
|
891
910
|
try {
|
|
892
911
|
if ((await stat(tempPath)).mtimeMs <= cutoff) await unlink(tempPath);
|
|
@@ -14,8 +14,12 @@ import { base64ByteLength, inlineFileKind } from '../shared/inline-file-kind.mjs
|
|
|
14
14
|
import { MAX_IMAGE_BYTES, SUPPORTED_IMAGE_MIME_TYPES } from '../attachments/limits.mjs';
|
|
15
15
|
|
|
16
16
|
const DISCOVERY_FILE = 'browser-bridge.json';
|
|
17
|
-
/**
|
|
18
|
-
*
|
|
17
|
+
/** How long a request may go without a single byte from the bridge. Above the
|
|
18
|
+
* bridge's own per-action timeouts (navigation settle, surface auto-open), so
|
|
19
|
+
* the bridge's specific error wins over a bare abort. It is a silence limit,
|
|
20
|
+
* not a total: a command that waits on the person (an approval card, which has
|
|
21
|
+
* no limit) is kept alive by the bridge with a space every few seconds
|
|
22
|
+
* (apps/desktop/src/main/browser/bridge-server.ts), and each byte restarts it. */
|
|
19
23
|
const REQUEST_TIMEOUT_MS = 45_000;
|
|
20
24
|
const MAX_REQUEST_BYTES = 256 * 1024;
|
|
21
25
|
const MAX_RESPONSE_BYTES = 150 * 1024 * 1024;
|
|
@@ -33,7 +37,7 @@ export async function finishBrowserTurn(sessionId, turnId, { aborted = false } =
|
|
|
33
37
|
if (!discovery) return;
|
|
34
38
|
turns.delete(turnId);
|
|
35
39
|
if (!turns.size) browserTurns.delete(sessionId);
|
|
36
|
-
const result = await
|
|
40
|
+
const result = await requestBridgeWithin(
|
|
37
41
|
discovery,
|
|
38
42
|
JSON.stringify({
|
|
39
43
|
action: 'finish_turn',
|
|
@@ -43,7 +47,7 @@ export async function finishBrowserTurn(sessionId, turnId, { aborted = false } =
|
|
|
43
47
|
// so its pages stay; only an aborted run reclaims them.
|
|
44
48
|
aborted: aborted === true,
|
|
45
49
|
}),
|
|
46
|
-
|
|
50
|
+
undefined,
|
|
47
51
|
{ sessionId, turnId, action: 'finish_turn' }
|
|
48
52
|
);
|
|
49
53
|
if (result.status !== 200 || result.body?.ok === false || result.body?.error) {
|
|
@@ -61,12 +65,10 @@ export async function browserSecretInput(sessionId, requestId, { signal } = {})
|
|
|
61
65
|
const discovery = readBridgeDiscovery(DISCOVERY_FILE);
|
|
62
66
|
if (!discovery || !sessionId || !requestId) return 'skipped';
|
|
63
67
|
try {
|
|
64
|
-
const result = await
|
|
68
|
+
const result = await requestBridgeWithin(
|
|
65
69
|
discovery,
|
|
66
70
|
JSON.stringify({ action: 'secret_input', session_id: String(sessionId), request_id: String(requestId) }),
|
|
67
|
-
signal
|
|
68
|
-
? AbortSignal.any([AbortSignal.timeout(REQUEST_TIMEOUT_MS), signal])
|
|
69
|
-
: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
71
|
+
signal,
|
|
70
72
|
{ sessionId, action: 'secret_input' }
|
|
71
73
|
);
|
|
72
74
|
return result.status === 200 && result.body?.ok !== false && result.body?.value?.text === 'provided'
|
|
@@ -155,8 +157,9 @@ function unavailableMessage() {
|
|
|
155
157
|
class BrowserBridgeResponseError extends Error {}
|
|
156
158
|
|
|
157
159
|
// Read the body incrementally so a chunked response (no content-length) still
|
|
158
|
-
// honours MAX_RESPONSE_BYTES.
|
|
159
|
-
|
|
160
|
+
// honours MAX_RESPONSE_BYTES. `onChunk` hears every chunk read (the bridge's
|
|
161
|
+
// keep-alive spaces included).
|
|
162
|
+
export async function readCappedJson(response, maximum = MAX_RESPONSE_BYTES, onChunk = undefined) {
|
|
160
163
|
if (!response.body) throw new Error('empty response');
|
|
161
164
|
const reader = response.body.getReader();
|
|
162
165
|
const chunks = [];
|
|
@@ -165,6 +168,7 @@ export async function readCappedJson(response, maximum = MAX_RESPONSE_BYTES) {
|
|
|
165
168
|
for (;;) {
|
|
166
169
|
const result = await reader.read();
|
|
167
170
|
if (result.done) break;
|
|
171
|
+
onChunk?.();
|
|
168
172
|
bytes += result.value.byteLength;
|
|
169
173
|
if (bytes > maximum) {
|
|
170
174
|
await reader.cancel().catch(() => {});
|
|
@@ -178,7 +182,50 @@ export async function readCappedJson(response, maximum = MAX_RESPONSE_BYTES) {
|
|
|
178
182
|
return JSON.parse(Buffer.concat(chunks, bytes).toString('utf8'));
|
|
179
183
|
}
|
|
180
184
|
|
|
181
|
-
|
|
185
|
+
/** One bridge request's clock: it times out after `timeoutMs` without a byte
|
|
186
|
+
* from the bridge (`touch` restarts it), with the same TimeoutError an
|
|
187
|
+
* AbortSignal.timeout gives, and ends with `callerSignal` (the caller's own
|
|
188
|
+
* reason). `stop` leaves no timer behind. */
|
|
189
|
+
export function bridgeDeadline(callerSignal, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
190
|
+
const controller = new AbortController();
|
|
191
|
+
let timer = null;
|
|
192
|
+
const stop = () => {
|
|
193
|
+
if (timer) clearTimeout(timer);
|
|
194
|
+
timer = null;
|
|
195
|
+
};
|
|
196
|
+
const touch = () => {
|
|
197
|
+
stop();
|
|
198
|
+
timer = setTimeout(
|
|
199
|
+
() => controller.abort(new DOMException('The operation was aborted due to timeout', 'TimeoutError')),
|
|
200
|
+
timeoutMs
|
|
201
|
+
);
|
|
202
|
+
timer.unref?.();
|
|
203
|
+
};
|
|
204
|
+
touch();
|
|
205
|
+
return {
|
|
206
|
+
signal: callerSignal ? AbortSignal.any([controller.signal, callerSignal]) : controller.signal,
|
|
207
|
+
touch,
|
|
208
|
+
stop,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** `requestBridge` under the silence limit of `bridgeDeadline`. */
|
|
213
|
+
export async function requestBridgeWithin(
|
|
214
|
+
discovery,
|
|
215
|
+
encodedPayload,
|
|
216
|
+
callerSignal,
|
|
217
|
+
timingContext,
|
|
218
|
+
timeoutMs = REQUEST_TIMEOUT_MS
|
|
219
|
+
) {
|
|
220
|
+
const deadline = bridgeDeadline(callerSignal, timeoutMs);
|
|
221
|
+
try {
|
|
222
|
+
return await requestBridge(discovery, encodedPayload, deadline.signal, timingContext, deadline.touch);
|
|
223
|
+
} finally {
|
|
224
|
+
deadline.stop();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function requestBridge(discovery, encodedPayload, signal, timingContext, onActivity = undefined) {
|
|
182
229
|
const started = performance.now();
|
|
183
230
|
let body;
|
|
184
231
|
let status;
|
|
@@ -192,6 +239,7 @@ async function requestBridge(discovery, encodedPayload, signal, timingContext) {
|
|
|
192
239
|
body: encodedPayload,
|
|
193
240
|
signal,
|
|
194
241
|
});
|
|
242
|
+
onActivity?.();
|
|
195
243
|
status = response.status;
|
|
196
244
|
const contentLength = Number(response.headers.get('content-length'));
|
|
197
245
|
if (Number.isFinite(contentLength) && contentLength > MAX_RESPONSE_BYTES) {
|
|
@@ -199,10 +247,12 @@ async function requestBridge(discovery, encodedPayload, signal, timingContext) {
|
|
|
199
247
|
throw new BrowserBridgeResponseError(`browser bridge response exceeds ${MAX_RESPONSE_BYTES} bytes`);
|
|
200
248
|
}
|
|
201
249
|
try {
|
|
202
|
-
body = await readCappedJson(response);
|
|
250
|
+
body = await readCappedJson(response, MAX_RESPONSE_BYTES, onActivity);
|
|
203
251
|
return { body, status: response.status };
|
|
204
252
|
} catch (error) {
|
|
205
253
|
if (error instanceof BrowserBridgeResponseError) throw error;
|
|
254
|
+
// Silence past the clock, or the caller stopping, is not a bad response.
|
|
255
|
+
if (signal?.aborted) throw signal.reason ?? error;
|
|
206
256
|
throw new BrowserBridgeResponseError(`browser bridge returned an invalid response (HTTP ${response.status})`);
|
|
207
257
|
}
|
|
208
258
|
} finally {
|
|
@@ -225,6 +275,12 @@ export async function executeBrowserTool(args, options = {}) {
|
|
|
225
275
|
action: validated.action,
|
|
226
276
|
...validated.input,
|
|
227
277
|
session_id: sessionId,
|
|
278
|
+
// Set by the runtime from the session, never from the model's arguments: a
|
|
279
|
+
// schedule/webhook run (or work it started) whose saved routine is the
|
|
280
|
+
// user's grant for submits, posts and uploads (human-gate/run-policy.mjs).
|
|
281
|
+
...(options.unattendedRun === true
|
|
282
|
+
? { unattended_run: true, unattended_agent: String(options.unattendedAgent || '') }
|
|
283
|
+
: {}),
|
|
228
284
|
...(Number.isFinite(Number(options.turnId)) && Number(options.turnId) > 0
|
|
229
285
|
? { turn_id: Math.trunc(Number(options.turnId)) }
|
|
230
286
|
: {}),
|
|
@@ -243,14 +299,11 @@ export async function executeBrowserTool(args, options = {}) {
|
|
|
243
299
|
let bridgeResult;
|
|
244
300
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
245
301
|
try {
|
|
246
|
-
bridgeResult = await
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
252
|
-
{ sessionId, turnId: payload.turn_id, action: validated.action }
|
|
253
|
-
);
|
|
302
|
+
bridgeResult = await requestBridgeWithin(discovery, encodedPayload, options.signal, {
|
|
303
|
+
sessionId,
|
|
304
|
+
turnId: payload.turn_id,
|
|
305
|
+
action: validated.action,
|
|
306
|
+
});
|
|
254
307
|
break;
|
|
255
308
|
} catch (error) {
|
|
256
309
|
if (error?.name === 'TimeoutError') {
|
|
@@ -329,7 +329,7 @@ function cleanupStaleRuntimeFiles(now = Date.now()) {
|
|
|
329
329
|
// tighter TTLs keep dead-process residue from accumulating.
|
|
330
330
|
let ttl = RUNTIME_STALE_TTL;
|
|
331
331
|
if (/^status-.*\.json$/.test(file)) ttl = STATUS_FILE_TTL;
|
|
332
|
-
else if (/\.tmp
|
|
332
|
+
else if (/\.tmp$|\.mixdog-tmp-[0-9a-f]+$/.test(file)) ttl = TMP_FILE_TTL;
|
|
333
333
|
if (age > ttl) removeFileIfExists(fullPath);
|
|
334
334
|
} catch {}
|
|
335
335
|
});
|
|
@@ -26,6 +26,19 @@ const SCHEDULE_LOG = join(DATA_DIR, 'schedule.log');
|
|
|
26
26
|
// here. The channels worker owns shutdown sequencing (drain queues, persist
|
|
27
27
|
// baselines, release the scheduler lock, etc.) and a library-level exit(0)
|
|
28
28
|
// preempts that drain. The buffered appender drains pending log lines on exit.
|
|
29
|
+
function skipReasonText(outcome) {
|
|
30
|
+
switch (outcome.reason) {
|
|
31
|
+
case 'backoff':
|
|
32
|
+
return 'backing off after failures';
|
|
33
|
+
case 'paused':
|
|
34
|
+
return 'switched off';
|
|
35
|
+
case 'owner':
|
|
36
|
+
return `paused: ${outcome.message}`;
|
|
37
|
+
default:
|
|
38
|
+
return 'previous run still in progress';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
29
42
|
function logSchedule(msg) {
|
|
30
43
|
msg = msg.replace(/\n$/, '');
|
|
31
44
|
process.stderr.write(`mixdog scheduler: ${msg}\n`);
|
|
@@ -686,13 +699,7 @@ ${prompt}`;
|
|
|
686
699
|
// already persisted the skip.
|
|
687
700
|
if (outcome?.skipped) {
|
|
688
701
|
schedule.lastSkippedAt = new Date().toISOString();
|
|
689
|
-
|
|
690
|
-
outcome.reason === 'backoff'
|
|
691
|
-
? 'backing off after failures'
|
|
692
|
-
: outcome.reason === 'owner'
|
|
693
|
-
? `paused: ${outcome.message}`
|
|
694
|
-
: 'previous run still in progress';
|
|
695
|
-
logSchedule(`${schedule.name}: skipped — ${why}\n`);
|
|
702
|
+
logSchedule(`${schedule.name}: skipped — ${skipReasonText(outcome)}\n`);
|
|
696
703
|
return false;
|
|
697
704
|
}
|
|
698
705
|
logSchedule(`${schedule.name} done\n`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, unlinkSync } from 'node:fs';
|
|
2
|
-
import { writeJsonAtomicSync } from '../../shared/atomic-file.mjs';
|
|
2
|
+
import { readJsonStateSync, updateJsonAtomicSync, writeJsonAtomicSync } from '../../shared/atomic-file.mjs';
|
|
3
3
|
function ensureDir(dirPath) {
|
|
4
4
|
mkdirSync(dirPath, { recursive: true });
|
|
5
5
|
}
|
|
@@ -19,10 +19,6 @@ function readJsonFile(filePath, fallback) {
|
|
|
19
19
|
return fallback;
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
-
function readJsonFileStrict(filePath) {
|
|
23
|
-
const raw = readFileSync(filePath, 'utf8');
|
|
24
|
-
return JSON.parse(raw);
|
|
25
|
-
}
|
|
26
22
|
function writeJsonFile(filePath, value) {
|
|
27
23
|
writeJsonAtomicSync(filePath, value, { compact: true, lock: true, fsync: false, fsyncDir: false });
|
|
28
24
|
}
|
|
@@ -38,19 +34,23 @@ class JsonStateFile {
|
|
|
38
34
|
writeJsonFile(this.filePath, value);
|
|
39
35
|
return value;
|
|
40
36
|
}
|
|
37
|
+
// A damaged file (empty, NUL-filled after a crash) is kept aside as
|
|
38
|
+
// <file>.corrupt-<ts> and the file restarts from the fallback; it is never
|
|
39
|
+
// overwritten in place and never blocks an update. That recovery is why the
|
|
40
|
+
// writes below can skip fsync.
|
|
41
41
|
ensure() {
|
|
42
|
-
writeJsonFile(this.filePath, this.
|
|
42
|
+
writeJsonFile(this.filePath, readJsonStateSync(this.filePath, { fallback: this.fallback }));
|
|
43
43
|
}
|
|
44
44
|
update(mutator) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
45
|
+
return updateJsonAtomicSync(
|
|
46
|
+
this.filePath,
|
|
47
|
+
(current) => {
|
|
48
|
+
const draft = current ?? structuredClone(this.fallback);
|
|
49
|
+
mutator(draft);
|
|
50
|
+
return draft;
|
|
51
|
+
},
|
|
52
|
+
{ compact: true, fsync: false, fsyncDir: false }
|
|
53
|
+
);
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
export { JsonStateFile, ensureDir, readJsonFile, removeFileIfExists, writeJsonFile };
|