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
|
@@ -43,7 +43,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
43
43
|
import { pipeline } from 'node:stream/promises';
|
|
44
44
|
import { spawnSync } from 'node:child_process';
|
|
45
45
|
import { createGunzip } from 'node:zlib';
|
|
46
|
-
import { renameWithRetrySync, writeFileAtomicSync } from '../../shared/atomic-file.mjs';
|
|
46
|
+
import { renameWithRetrySync, writeFileAtomicSync, writeJsonAtomicSync } from '../../shared/atomic-file.mjs';
|
|
47
|
+
import { fileHasData } from '../../shared/file-has-data.mjs';
|
|
47
48
|
import { streamResponseToFile } from '../../shared/bounded-download.mjs';
|
|
48
49
|
import { platformEntryKey, sha256File, verifySha256File } from '../../shared/native-asset.mjs';
|
|
49
50
|
import { windowsProgramRoots, windowsSystemRoot } from '../../agent/orchestrator/tools/builtin/windows-roots.mjs';
|
|
@@ -264,7 +265,7 @@ async function loadManifest(dataDir) {
|
|
|
264
265
|
if (!res.ok) throw new Error(`[voice-runtime] manifest fetch failed: ${res.status} ${res.statusText}`);
|
|
265
266
|
const manifest = await res.json();
|
|
266
267
|
mkdirSync(join(dataDir, 'voice-runtime'), { recursive: true });
|
|
267
|
-
|
|
268
|
+
writeJsonAtomicSync(cachedPath, manifest);
|
|
268
269
|
return manifest;
|
|
269
270
|
}
|
|
270
271
|
|
|
@@ -439,7 +440,7 @@ function gcPublishedRuntimeSiblings(rootDir, prefix) {
|
|
|
439
440
|
}
|
|
440
441
|
|
|
441
442
|
async function extractGz(gzPath, destPath) {
|
|
442
|
-
await pipeline(createReadStream(gzPath), createGunzip(), createWriteStream(destPath));
|
|
443
|
+
await pipeline(createReadStream(gzPath), createGunzip(), createWriteStream(destPath, { flush: true }));
|
|
443
444
|
}
|
|
444
445
|
|
|
445
446
|
// Sweep .staging-* partials left by killed/crashed install attempts.
|
|
@@ -541,13 +542,13 @@ export async function ensureWhisperRuntime(dataDir, onProgress = null) {
|
|
|
541
542
|
const activeDir = join(rootDir, activeName);
|
|
542
543
|
const whisperCmd = join(activeDir, variant.executable);
|
|
543
544
|
|
|
544
|
-
if (
|
|
545
|
+
if (fileHasData(whisperCmd)) {
|
|
545
546
|
gcStaleVersions(rootDir, activeName, 'whisper-');
|
|
546
547
|
return { whisperCmd, version: ver, variantId: variant.id };
|
|
547
548
|
}
|
|
548
549
|
|
|
549
550
|
return _withInstallLock(rootDir, 'install', async () => {
|
|
550
|
-
if (
|
|
551
|
+
if (fileHasData(whisperCmd)) {
|
|
551
552
|
gcStaleVersions(rootDir, activeName, 'whisper-');
|
|
552
553
|
return { whisperCmd, version: ver, variantId: variant.id };
|
|
553
554
|
}
|
|
@@ -641,14 +642,14 @@ function resolveManagedWhisperCmd(dataDir) {
|
|
|
641
642
|
const variant = variants.find((v) => v.id === variantId);
|
|
642
643
|
if (variant?.executable) {
|
|
643
644
|
const p = join(activeDir, variant.executable);
|
|
644
|
-
if (
|
|
645
|
+
if (fileHasData(p)) return p;
|
|
645
646
|
}
|
|
646
647
|
}
|
|
647
648
|
} catch {}
|
|
648
649
|
}
|
|
649
650
|
for (const c of ['Release/whisper-cli.exe', 'Release/whisper-cli']) {
|
|
650
651
|
const p = join(activeDir, c);
|
|
651
|
-
if (
|
|
652
|
+
if (fileHasData(p)) return p;
|
|
652
653
|
}
|
|
653
654
|
return null;
|
|
654
655
|
}
|
|
@@ -659,7 +660,7 @@ function resolveManagedWhisperModelById(dataDir, modelId = 'standard') {
|
|
|
659
660
|
if (!entry?.filename) return null;
|
|
660
661
|
const modelDir = join(dataDir, 'voice', 'models');
|
|
661
662
|
const p = join(modelDir, entry.filename);
|
|
662
|
-
return
|
|
663
|
+
return fileHasData(p) ? p : null;
|
|
663
664
|
}
|
|
664
665
|
|
|
665
666
|
// Clean-install policy: the models dir holds ONLY filenames declared by the
|
|
@@ -712,13 +713,13 @@ export async function ensureFfmpegRuntime(dataDir, onProgress = null) {
|
|
|
712
713
|
const activeDir = join(rootDir, activeName);
|
|
713
714
|
const ffmpegPath = join(activeDir, platformEntry.executable);
|
|
714
715
|
|
|
715
|
-
if (
|
|
716
|
+
if (fileHasData(ffmpegPath)) {
|
|
716
717
|
gcStaleVersions(rootDir, activeName, 'ffmpeg-');
|
|
717
718
|
return { ffmpegPath, version: ver };
|
|
718
719
|
}
|
|
719
720
|
|
|
720
721
|
return _withInstallLock(rootDir, 'install', async () => {
|
|
721
|
-
if (
|
|
722
|
+
if (fileHasData(ffmpegPath)) {
|
|
722
723
|
gcStaleVersions(rootDir, activeName, 'ffmpeg-');
|
|
723
724
|
return { ffmpegPath, version: ver };
|
|
724
725
|
}
|
|
@@ -783,13 +784,13 @@ function resolveManagedFfmpegPath(dataDir) {
|
|
|
783
784
|
const platformEntry = manifest.ffmpeg?.platforms?.[key];
|
|
784
785
|
if (platformEntry?.executable) {
|
|
785
786
|
const p = join(activeDir, platformEntry.executable);
|
|
786
|
-
if (
|
|
787
|
+
if (fileHasData(p)) return p;
|
|
787
788
|
}
|
|
788
789
|
} catch {}
|
|
789
790
|
}
|
|
790
791
|
for (const c of ['ffmpeg.exe', 'ffmpeg']) {
|
|
791
792
|
const p = join(activeDir, c);
|
|
792
|
-
if (
|
|
793
|
+
if (fileHasData(p)) return p;
|
|
793
794
|
}
|
|
794
795
|
return null;
|
|
795
796
|
}
|
|
@@ -835,7 +836,7 @@ export function resolveVoiceRuntime(dataDir, { modelId = 'standard', cleanup = t
|
|
|
835
836
|
const managedFfmpegPath = resolveManagedFfmpegPath(dataDir);
|
|
836
837
|
const ext = process.platform === 'win32' ? '.exe' : '';
|
|
837
838
|
const managedServerCmd = managedWhisperCmd ? join(dirname(managedWhisperCmd), `whisper-server${ext}`) : null;
|
|
838
|
-
const serverCmd = managedServerCmd &&
|
|
839
|
+
const serverCmd = managedServerCmd && fileHasData(managedServerCmd) ? managedServerCmd : null;
|
|
839
840
|
return {
|
|
840
841
|
kind: 'managed-whisper.cpp',
|
|
841
842
|
label: 'whisper.cpp',
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
// verification stays local — the relay never inspects payloads. Works out
|
|
9
9
|
// of the box: no binary, no authtoken, no reserved domain.
|
|
10
10
|
import { randomBytes, randomUUID } from 'node:crypto';
|
|
11
|
-
import { readFileSync,
|
|
11
|
+
import { readFileSync, chmodSync } from 'node:fs';
|
|
12
12
|
import { join } from 'node:path';
|
|
13
|
+
import { readJsonStateSync, writeJsonAtomicSync } from '../../../shared/atomic-file.mjs';
|
|
13
14
|
import { DATA_DIR } from '../config.mjs';
|
|
14
15
|
import { logWebhook } from './log.mjs';
|
|
15
16
|
import { MAX_HOOK_FRAME_BYTES, MAX_HOOK_HEADER_BYTES, MAX_TUNNEL_BODY_BYTES } from './relay-tunnel/limits.mjs';
|
|
@@ -55,19 +56,28 @@ function hookIdentityPath() {
|
|
|
55
56
|
return join(DATA_DIR, 'relay-hook-device.json');
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
function validHookIdentity(parsed) {
|
|
60
|
+
if (
|
|
61
|
+
typeof parsed?.deviceId !== 'string' ||
|
|
62
|
+
!/^[0-9a-f-]{8,64}$/.test(parsed.deviceId) ||
|
|
63
|
+
typeof parsed.deviceSecret !== 'string' ||
|
|
64
|
+
parsed.deviceSecret.length < 16
|
|
65
|
+
) {
|
|
66
|
+
throw new Error('invalid hook tunnel identity');
|
|
67
|
+
}
|
|
68
|
+
return { deviceId: parsed.deviceId, deviceSecret: parsed.deviceSecret };
|
|
69
|
+
}
|
|
70
|
+
|
|
58
71
|
// Stable per-install identity (trust-on-first-use at the relay, mirroring
|
|
59
72
|
// the desktop leg). The secret never leaves this machine except toward the
|
|
60
73
|
// relay; the deviceId doubles as the public URL path segment.
|
|
61
74
|
function loadOrCreateHookIdentity() {
|
|
62
75
|
const path = hookIdentityPath();
|
|
63
76
|
try {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
typeof parsed.deviceSecret === 'string' &&
|
|
69
|
-
parsed.deviceSecret.length >= 16
|
|
70
|
-
) {
|
|
77
|
+
// A damaged identity (empty, NUL-filled, malformed) is kept aside and the
|
|
78
|
+
// last good copy restored; only when there is none is a new identity made.
|
|
79
|
+
const stored = readJsonStateSync(path, { validate: validHookIdentity, secret: true, fallback: null });
|
|
80
|
+
if (stored) {
|
|
71
81
|
// The secret authenticates the webhook tunnel leg (whoever holds it can
|
|
72
82
|
// take over inbound deliveries), so clamp legacy world-readable files.
|
|
73
83
|
try {
|
|
@@ -75,20 +85,14 @@ function loadOrCreateHookIdentity() {
|
|
|
75
85
|
} catch {
|
|
76
86
|
/* windows/fs quirk */
|
|
77
87
|
}
|
|
78
|
-
return
|
|
88
|
+
return stored;
|
|
79
89
|
}
|
|
80
|
-
} catch {
|
|
81
|
-
|
|
90
|
+
} catch (err) {
|
|
91
|
+
logWebhook(`hook tunnel: identity read failed — ${err?.message || err}`);
|
|
82
92
|
}
|
|
83
93
|
const identity = { deviceId: randomUUID(), deviceSecret: randomBytes(24).toString('hex') };
|
|
84
94
|
try {
|
|
85
|
-
|
|
86
|
-
writeFileSync(path, JSON.stringify(identity, null, 2), { encoding: 'utf8', mode: 0o600 });
|
|
87
|
-
try {
|
|
88
|
-
chmodSync(path, 0o600);
|
|
89
|
-
} catch {
|
|
90
|
-
/* windows/fs quirk */
|
|
91
|
-
}
|
|
95
|
+
writeJsonAtomicSync(path, identity, { secret: true, backup: true });
|
|
92
96
|
} catch (err) {
|
|
93
97
|
logWebhook(`hook tunnel: identity persist failed — ${err?.message || err}`);
|
|
94
98
|
}
|
|
@@ -2,8 +2,6 @@ import { logWebhook } from './log.mjs';
|
|
|
2
2
|
import { recordEndpointEvent, updateDeliveryStatus } from '../webhooks-db.mjs';
|
|
3
3
|
import { UNTRUSTED_MARKER, scrubFenceMarker } from './untrusted-fence.mjs';
|
|
4
4
|
|
|
5
|
-
const DISPATCH_TIMEOUT_MS = 10 * 60 * 1000;
|
|
6
|
-
|
|
7
5
|
/** The headers a run's prompt shows (and a stored event keeps). */
|
|
8
6
|
function promptHeaders(headers) {
|
|
9
7
|
return Object.fromEntries(Object.entries(headers || {}).filter(([k]) => k.startsWith('x-') || k === 'content-type'));
|
|
@@ -47,33 +45,16 @@ function webhookEventSummary(headers, body) {
|
|
|
47
45
|
return [type, field(body?.action)].filter(Boolean).join(' · ').slice(0, 120);
|
|
48
46
|
}
|
|
49
47
|
|
|
50
|
-
function trackDispatch({ name, deliveryId, dispatchP,
|
|
48
|
+
function trackDispatch({ name, deliveryId, dispatchP, updateDeliveryStatus }) {
|
|
51
49
|
let settled = false;
|
|
52
|
-
const timeoutHandle = setTimeout(
|
|
53
|
-
() => controller.abort(new Error(`bridge dispatch timed out after ${DISPATCH_TIMEOUT_MS}ms`)),
|
|
54
|
-
DISPATCH_TIMEOUT_MS
|
|
55
|
-
);
|
|
56
50
|
const finish = (status, fields, message) => {
|
|
57
51
|
if (settled) return;
|
|
58
52
|
settled = true;
|
|
59
|
-
clearTimeout(timeoutHandle);
|
|
60
53
|
void updateDeliveryStatus(name, deliveryId, status, fields).catch((e) =>
|
|
61
54
|
logWebhook(`${name}: delivery status update failed: ${e?.message || e}`)
|
|
62
55
|
);
|
|
63
56
|
logWebhook(message);
|
|
64
57
|
};
|
|
65
|
-
controller.signal.addEventListener(
|
|
66
|
-
'abort',
|
|
67
|
-
() => {
|
|
68
|
-
const reason = controller.signal.reason;
|
|
69
|
-
finish(
|
|
70
|
-
'failed',
|
|
71
|
-
{ error: String(reason?.message || reason) },
|
|
72
|
-
`${name}: webhook session run aborted: ${reason?.message || reason}`
|
|
73
|
-
);
|
|
74
|
-
},
|
|
75
|
-
{ once: true }
|
|
76
|
-
);
|
|
77
58
|
dispatchP.then(
|
|
78
59
|
(run) =>
|
|
79
60
|
run?.skipped
|
|
@@ -88,23 +69,20 @@ function trackDispatch({ name, deliveryId, dispatchP, controller }) {
|
|
|
88
69
|
);
|
|
89
70
|
}
|
|
90
71
|
|
|
91
|
-
|
|
72
|
+
// `store` is the delivery record (injectable for tests).
|
|
73
|
+
function createWebhookSessionDispatcher({ getBridgeDispatch, store = { recordEndpointEvent, updateDeliveryStatus } }) {
|
|
74
|
+
const { recordEndpointEvent: recordEvent, updateDeliveryStatus: setStatus } = store;
|
|
92
75
|
async function dispatchSessionRun(name, fullPrompt, deliveryId, res, extra = {}) {
|
|
93
|
-
await
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
// the run itself kept going: the delivery row was already `failed` when the
|
|
100
|
-
// late result arrived and flipped it again. The timeout now aborts the
|
|
101
|
-
// dispatch through a signal the dispatcher receives, and the first outcome
|
|
102
|
-
// to land is the only one that writes a terminal status.
|
|
103
|
-
const controller = new AbortController();
|
|
76
|
+
await setStatus(name, deliveryId, 'processing');
|
|
77
|
+
// The HTTP caller is answered 202 at once; the run is a turn in the bot's
|
|
78
|
+
// chat and has no deadline (a run may wait on the user's approval for as
|
|
79
|
+
// long as it takes), so nothing here cancels it. The delivery stays
|
|
80
|
+
// `processing` until the run ends, then `done` / `skipped` / `failed`; a
|
|
81
|
+
// second delivery meanwhile is skipped by the run's own overlap rule.
|
|
104
82
|
const dispatchP = Promise.resolve(
|
|
105
83
|
getBridgeDispatch()({
|
|
106
84
|
prompt: fullPrompt,
|
|
107
|
-
signal:
|
|
85
|
+
signal: null,
|
|
108
86
|
// The endpoint's project folder, named to the bot in the turn.
|
|
109
87
|
cwd: extra.cwd || null,
|
|
110
88
|
attachments: extra.attachments || null,
|
|
@@ -123,7 +101,7 @@ function createWebhookSessionDispatcher({ getBridgeDispatch }) {
|
|
|
123
101
|
},
|
|
124
102
|
})
|
|
125
103
|
);
|
|
126
|
-
trackDispatch({ name, deliveryId, dispatchP,
|
|
104
|
+
trackDispatch({ name, deliveryId, dispatchP, updateDeliveryStatus: setStatus });
|
|
127
105
|
res.writeHead(202, { 'Content-Type': 'application/json' });
|
|
128
106
|
res.end(JSON.stringify({ status: 'accepted', handler: 'session', id: deliveryId }));
|
|
129
107
|
}
|
|
@@ -135,7 +113,7 @@ function createWebhookSessionDispatcher({ getBridgeDispatch }) {
|
|
|
135
113
|
if (!getBridgeDispatch()) throw new Error(`[webhook] session dispatch requires bridgeDispatch`);
|
|
136
114
|
// Kept for the app's "Run now", which re-runs the latest event. Losing
|
|
137
115
|
// the record never blocks the delivery itself.
|
|
138
|
-
await
|
|
116
|
+
await recordEvent(name, { body, headers: promptHeaders(headers) }).catch((e) =>
|
|
139
117
|
logWebhook(`${name}: event record failed: ${e?.message || e}`)
|
|
140
118
|
);
|
|
141
119
|
await dispatchSessionRun(name, run.prompt, deliveryId, res, {
|
|
@@ -148,7 +126,7 @@ function createWebhookSessionDispatcher({ getBridgeDispatch }) {
|
|
|
148
126
|
});
|
|
149
127
|
return true;
|
|
150
128
|
} catch (err) {
|
|
151
|
-
await
|
|
129
|
+
await setStatus(name, deliveryId, 'failed', { error: String(err?.message || err) });
|
|
152
130
|
logWebhook(`${name}: folder handler error: ${err}`);
|
|
153
131
|
return false;
|
|
154
132
|
}
|
|
@@ -10,6 +10,7 @@ import { Scheduler } from './scheduler.mjs';
|
|
|
10
10
|
import { hasPending as dispatchHasPending } from '../../agent/orchestrator/dispatch-persist.mjs';
|
|
11
11
|
import { stripSoftWarns } from '../../agent/orchestrator/tool-loop-guard.mjs';
|
|
12
12
|
import { JsonStateFile } from './state-file.mjs';
|
|
13
|
+
import { writeJsonAtomicSync } from '../../shared/atomic-file.mjs';
|
|
13
14
|
import {
|
|
14
15
|
makeInstanceId,
|
|
15
16
|
getStatusPath,
|
|
@@ -115,7 +116,7 @@ function writeBridgeState(active) {
|
|
|
115
116
|
try {
|
|
116
117
|
const stateFile = path.join(ensurePrivateRuntimeRoot(resolveRuntimeRoot()), 'bridge-state.json');
|
|
117
118
|
fs.mkdirSync(path.dirname(stateFile), { recursive: true });
|
|
118
|
-
|
|
119
|
+
writeJsonAtomicSync(stateFile, { active, ts: Date.now() }, { compact: true });
|
|
119
120
|
} catch {}
|
|
120
121
|
}
|
|
121
122
|
function isChannelBridgeActive() {
|
|
@@ -225,7 +226,7 @@ function wireWebhookHandlers() {
|
|
|
225
226
|
cwd: cwd || null,
|
|
226
227
|
attachments: attachments || null,
|
|
227
228
|
event: context?.event || '',
|
|
228
|
-
//
|
|
229
|
+
// The dispatch has no deadline; a caller-supplied signal still reaches the run.
|
|
229
230
|
signal: signal || null,
|
|
230
231
|
prompt,
|
|
231
232
|
skillSelection,
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
// arrays (~30 MB). The compact form is built once from tokenizer.json and
|
|
8
8
|
// cached next to it as tokenizer.bin, keyed by the source file's size and
|
|
9
9
|
// modification time.
|
|
10
|
-
import { readFileSync, statSync
|
|
10
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
|
+
import { writeFileAtomicSync } from '../shared/atomic-file.mjs';
|
|
12
13
|
|
|
13
14
|
const MAGIC = 0x4d585442; // "MXTB"
|
|
14
15
|
const VERSION = 3;
|
|
@@ -93,6 +94,9 @@ function readCached(dir) {
|
|
|
93
94
|
const h = new Int32Array(buf.buffer, buf.byteOffset, 7);
|
|
94
95
|
const [size, mtime] = sourceStamp(dir);
|
|
95
96
|
if (h[0] !== MAGIC || h[1] !== VERSION || h[2] !== size || h[3] !== mtime % 2 ** 31) return null;
|
|
97
|
+
// header (28) + byte table (1024) + char pairs + merges + meta; a shorter
|
|
98
|
+
// file is torn and is rebuilt.
|
|
99
|
+
if (buf.length < 1052 + h[4] * 8 + h[5] * 12 + h[6]) return null;
|
|
96
100
|
return buf;
|
|
97
101
|
} catch {
|
|
98
102
|
return null;
|
|
@@ -104,7 +108,9 @@ export function loadCompactTokenizer(dir) {
|
|
|
104
108
|
let buf = readCached(dir);
|
|
105
109
|
if (!buf) {
|
|
106
110
|
buf = build(dir);
|
|
107
|
-
|
|
111
|
+
// Atomic: a torn cache (valid header, short body) would pass readCached's
|
|
112
|
+
// header check and then fail on every load.
|
|
113
|
+
writeFileAtomicSync(join(dir, 'tokenizer.bin'), buf);
|
|
108
114
|
}
|
|
109
115
|
// Copy into an aligned buffer so typed-array views are valid.
|
|
110
116
|
const ab = new ArrayBuffer(buf.length);
|
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
// effort. Decisions are logged locally (lengths and probabilities only, never
|
|
9
9
|
// the request text).
|
|
10
10
|
import { Worker } from 'node:worker_threads';
|
|
11
|
-
import { appendFileSync,
|
|
11
|
+
import { appendFileSync, mkdirSync, readFileSync, statSync } from 'node:fs';
|
|
12
12
|
import { dirname, join } from 'node:path';
|
|
13
13
|
import { fileURLToPath } from 'node:url';
|
|
14
14
|
import { resolvePluginData } from '../shared/plugin-paths.mjs';
|
|
15
15
|
import { onnxRuntimeSupported } from '../shared/onnx-runtime-support.mjs';
|
|
16
|
+
import { fileHasData } from '../shared/file-has-data.mjs';
|
|
16
17
|
import { effortJudgeInstallCurrent, effortJudgeInstallStamped, installEffortJudgeModel } from './model-install.mjs';
|
|
17
18
|
|
|
18
19
|
export const EFFORT_JUDGE_UNSUPPORTED = 'Auto effort is unsupported on darwin-x64 (no onnxruntime-node binding)';
|
|
@@ -86,7 +87,9 @@ export function effortJudgeModelDir() {
|
|
|
86
87
|
// it is. Unstamped leftovers (hand-copied packs) never load.
|
|
87
88
|
export function effortJudgeAvailable(dir = effortJudgeModelDir()) {
|
|
88
89
|
if (!onnxRuntimeSupported()) return false;
|
|
89
|
-
|
|
90
|
+
// A model file a crash left empty or NUL-filled is not installed: the refresh
|
|
91
|
+
// then re-verifies the folder against the manifest hashes and fetches it again.
|
|
92
|
+
if (!MODEL_FILES.every((file) => fileHasData(join(dir, file)))) return false;
|
|
90
93
|
return Boolean(process.env.MIXDOG_EFFORT_JUDGE_DIR) || effortJudgeInstallStamped(dir);
|
|
91
94
|
}
|
|
92
95
|
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
// download never replaces a working file. Files that already match are kept.
|
|
5
5
|
// install.json records the installed release; a newer bundled manifest makes
|
|
6
6
|
// the install stale and the next refresh downloads only what changed.
|
|
7
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync } from 'node:fs';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { parseJsonText, writeJsonAtomicSync } from '../shared/atomic-file.mjs';
|
|
10
11
|
import { downloadToFileWithRetry } from '../shared/bounded-download.mjs';
|
|
11
12
|
import { sha256File, validSha256 } from '../shared/native-asset.mjs';
|
|
12
13
|
|
|
@@ -30,18 +31,25 @@ export function effortJudgeManifest() {
|
|
|
30
31
|
return manifest;
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
// Release named by the stamp; '' when it is missing or damaged (empty,
|
|
35
|
+
// NUL-filled), so a damaged stamp reads as not installed and the next refresh
|
|
36
|
+
// re-verifies every file against its sha256 and stamps again.
|
|
37
|
+
function stampedRelease(dir) {
|
|
38
|
+
try {
|
|
39
|
+
return String(parseJsonText(readFileSync(join(dir, STAMP), 'utf8')).release || '');
|
|
40
|
+
} catch {
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
33
45
|
/** True when `dir` holds a complete install of some release (stamped). */
|
|
34
46
|
export function effortJudgeInstallStamped(dir) {
|
|
35
|
-
return
|
|
47
|
+
return stampedRelease(dir) !== '';
|
|
36
48
|
}
|
|
37
49
|
|
|
38
50
|
/** True when `dir` holds the release the manifest names. */
|
|
39
51
|
export function effortJudgeInstallCurrent(dir, manifest = effortJudgeManifest()) {
|
|
40
|
-
|
|
41
|
-
return JSON.parse(readFileSync(join(dir, STAMP), 'utf8')).release === manifest.release;
|
|
42
|
-
} catch {
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
52
|
+
return stampedRelease(dir) === manifest.release;
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
/** Download what is missing or different, then stamp the release. */
|
|
@@ -76,5 +84,5 @@ export async function installEffortJudgeModel(
|
|
|
76
84
|
}
|
|
77
85
|
}
|
|
78
86
|
if (replaced) for (const name of DERIVED) rmSync(join(dir, name), { force: true });
|
|
79
|
-
|
|
87
|
+
writeJsonAtomicSync(join(dir, STAMP), { release: manifest.release }, { compact: true });
|
|
80
88
|
}
|
|
@@ -4,6 +4,7 @@ import { dirname, join } from 'node:path';
|
|
|
4
4
|
import { Readable, Transform } from 'node:stream';
|
|
5
5
|
import { pipeline } from 'node:stream/promises';
|
|
6
6
|
|
|
7
|
+
import { fileHasData } from '../shared/file-has-data.mjs';
|
|
7
8
|
import { sha256File } from '../shared/native-asset.mjs';
|
|
8
9
|
import { resolvePluginData } from '../shared/plugin-paths.mjs';
|
|
9
10
|
import {
|
|
@@ -125,9 +126,14 @@ export async function downloadVerifiedLocalAsset(
|
|
|
125
126
|
callback(null, chunk);
|
|
126
127
|
},
|
|
127
128
|
});
|
|
128
|
-
await pipeline(
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
await pipeline(
|
|
130
|
+
Readable.fromWeb(response.body),
|
|
131
|
+
meter,
|
|
132
|
+
createWriteStream(partial, { flags: resumed ? 'a' : 'w', flush: true }),
|
|
133
|
+
{
|
|
134
|
+
signal: operationSignal,
|
|
135
|
+
}
|
|
136
|
+
);
|
|
131
137
|
if (total !== asset.size) {
|
|
132
138
|
throw new Error(`[local-provider] incomplete download for ${asset.name || destination}: ${total}/${asset.size}`);
|
|
133
139
|
}
|
|
@@ -198,7 +204,7 @@ async function installRuntimeInternal({ dataDir, onProgress, fetchFn, signal })
|
|
|
198
204
|
}
|
|
199
205
|
const target = localProviderRuntimeDirectory(dataDir);
|
|
200
206
|
const executable = join(target, entry.executable);
|
|
201
|
-
if (
|
|
207
|
+
if (fileHasData(executable)) return localProviderCatalogStatus({ dataDir, hardware });
|
|
202
208
|
ensureDiskSpace(localProviderRuntimeRoot(dataDir), entry.downloadBytes);
|
|
203
209
|
const staging = join(localProviderRuntimeRoot(dataDir), `.staging-${process.pid}`);
|
|
204
210
|
const downloads = join(localProviderRuntimeRoot(dataDir), '.downloads');
|
|
@@ -2,6 +2,7 @@ import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
|
|
5
|
+
import { fileHasData } from '../shared/file-has-data.mjs';
|
|
5
6
|
import { resolvePluginData } from '../shared/plugin-paths.mjs';
|
|
6
7
|
import { localProviderDiskStatus, partialAssetBytes } from './asset-storage.mjs';
|
|
7
8
|
import { localProviderHardwareStatus, localProviderPlatformKey } from './hardware.mjs';
|
|
@@ -54,7 +55,9 @@ export function localProviderModelPath(entry, dataDir = resolvePluginData()) {
|
|
|
54
55
|
export function exactLocalProviderFile(path, size) {
|
|
55
56
|
try {
|
|
56
57
|
const stat = statSync(path);
|
|
57
|
-
|
|
58
|
+
// A crash can commit the size of a renamed download without its bytes: a
|
|
59
|
+
// file whose head is all NUL is not installed (it is fetched again).
|
|
60
|
+
return stat.isFile() && stat.size === size && fileHasData(path);
|
|
58
61
|
} catch {
|
|
59
62
|
return false;
|
|
60
63
|
}
|
|
@@ -108,7 +111,7 @@ export function localProviderCatalogStatus({
|
|
|
108
111
|
platformSupported: Boolean(platformEntry),
|
|
109
112
|
available: Boolean(platformEntry && hardware?.supported && models.some((entry) => entry.compatible)),
|
|
110
113
|
runtime: {
|
|
111
|
-
installed: Boolean(executable &&
|
|
114
|
+
installed: Boolean(executable && fileHasData(executable)),
|
|
112
115
|
version: LOCAL_PROVIDER_MANIFEST.runtime.version,
|
|
113
116
|
downloadBytes: platformEntry?.downloadBytes || 0,
|
|
114
117
|
source: LOCAL_PROVIDER_MANIFEST.runtime.source,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, statSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { resolvePluginData } from '../shared/plugin-paths.mjs';
|
|
4
|
-
import {
|
|
4
|
+
import { readJsonStateSync, unsupportedState, updateJsonAtomicSync } from '../shared/atomic-file.mjs';
|
|
5
5
|
|
|
6
6
|
const MAX_REGISTRY_BYTES = 1024 * 1024;
|
|
7
7
|
const cache = new Map();
|
|
@@ -42,44 +42,81 @@ function validateModel(entry) {
|
|
|
42
42
|
return entry;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
// Each candidate (the index or its backup) is bounded by its own bytes.
|
|
46
|
+
function registryText(content) {
|
|
47
|
+
if (content.length > MAX_REGISTRY_BYTES) throw new Error('[local-provider] registered model index is too large');
|
|
48
|
+
return content.toString('utf8');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function parseRegistry(value) {
|
|
52
|
+
// A newer build's index is left in place (reads as no models), not discarded.
|
|
53
|
+
if (Number.isSafeInteger(value?.schema) && value.schema > 1) {
|
|
54
|
+
throw unsupportedState(`[local-provider] registered model index schema ${value.schema} is newer than this build`);
|
|
55
|
+
}
|
|
53
56
|
if (value?.schema !== 1 || !Array.isArray(value.models) || value.models.length > 256) {
|
|
54
57
|
throw new Error('[local-provider] invalid registered model index');
|
|
55
58
|
}
|
|
56
59
|
const models = value.models.map(validateModel);
|
|
57
60
|
if (new Set(models.map((entry) => entry.id)).size !== models.length)
|
|
58
61
|
throw new Error('[local-provider] duplicate registered model ids');
|
|
62
|
+
return models;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function registeredLocalModels(dataDir = resolvePluginData()) {
|
|
66
|
+
const path = registryPath(dataDir);
|
|
67
|
+
if (!existsSync(path)) return [];
|
|
68
|
+
const stat = statSync(path);
|
|
69
|
+
const stamp = `${stat.mtimeMs}:${stat.size}`;
|
|
70
|
+
if (cache.get(path)?.stamp === stamp) return cache.get(path).models.map((entry) => ({ ...entry }));
|
|
71
|
+
// A damaged index (empty, NUL-filled, truncated, invalid) never blocks the
|
|
72
|
+
// provider: its bytes are kept as <file>.corrupt-<ts>, the last good copy
|
|
73
|
+
// (<file>.bak) is restored when there is one, else no model is registered.
|
|
74
|
+
// The downloaded weights are untouched.
|
|
75
|
+
const models = readJsonStateSync(path, { validate: parseRegistry, decode: registryText, fallback: [] });
|
|
59
76
|
cache.set(path, { stamp, models });
|
|
60
77
|
return models.map((entry) => ({ ...entry }));
|
|
61
78
|
}
|
|
62
79
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (existing.contextWindow !== entry.contextWindow)
|
|
69
|
-
throw new Error('[local-provider] this model is already registered with another context size');
|
|
70
|
-
return existing;
|
|
71
|
-
}
|
|
72
|
-
if (models.length >= 256) throw new Error('[local-provider] registered model limit reached');
|
|
80
|
+
// Read-modify-write under the index lock. A damaged index was recovered by the
|
|
81
|
+
// read first; a newer build's index makes the write refuse instead of
|
|
82
|
+
// replacing it (and its backup) with this build's format.
|
|
83
|
+
function mutateRegistry(dataDir, change) {
|
|
84
|
+
registeredLocalModels(dataDir);
|
|
73
85
|
const path = registryPath(dataDir);
|
|
74
|
-
|
|
86
|
+
let result;
|
|
87
|
+
updateJsonAtomicSync(
|
|
88
|
+
path,
|
|
89
|
+
(raw) => {
|
|
90
|
+
const next = change(raw == null ? [] : parseRegistry(raw));
|
|
91
|
+
if (!next) return undefined;
|
|
92
|
+
result = next.result;
|
|
93
|
+
return { schema: 1, models: next.models };
|
|
94
|
+
},
|
|
95
|
+
{ backup: true }
|
|
96
|
+
);
|
|
75
97
|
cache.delete(path);
|
|
76
|
-
return
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function registerLocalModel(entry, dataDir = resolvePluginData()) {
|
|
102
|
+
validateModel(entry);
|
|
103
|
+
let existing;
|
|
104
|
+
const registered = mutateRegistry(dataDir, (models) => {
|
|
105
|
+
existing = models.find((model) => model.id === entry.id);
|
|
106
|
+
if (existing) return null;
|
|
107
|
+
if (models.length >= 256) throw new Error('[local-provider] registered model limit reached');
|
|
108
|
+
return { models: [...models, entry], result: entry };
|
|
109
|
+
});
|
|
110
|
+
if (!existing) return registered;
|
|
111
|
+
if (existing.contextWindow !== entry.contextWindow)
|
|
112
|
+
throw new Error('[local-provider] this model is already registered with another context size');
|
|
113
|
+
return existing;
|
|
77
114
|
}
|
|
78
115
|
|
|
79
116
|
export function forgetRegisteredLocalModel(modelId, dataDir = resolvePluginData()) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
117
|
+
mutateRegistry(dataDir, (models) =>
|
|
118
|
+
models.some((entry) => entry.id === modelId)
|
|
119
|
+
? { models: models.filter((entry) => entry.id !== modelId), result: undefined }
|
|
120
|
+
: null
|
|
121
|
+
);
|
|
85
122
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { fileHasData } from '../shared/file-has-data.mjs';
|
|
3
2
|
import { resolvePluginData } from '../shared/plugin-paths.mjs';
|
|
4
3
|
import {
|
|
5
4
|
exactLocalProviderFile,
|
|
@@ -65,7 +64,7 @@ export async function ensureLocalProviderServer(modelId, { dataDir = resolvePlug
|
|
|
65
64
|
const contextWindow = localContextSettings(entry, dataDir).contextWindow;
|
|
66
65
|
const executable = localProviderRuntimeExecutable(dataDir);
|
|
67
66
|
const weights = localProviderModelPath(entry, dataDir);
|
|
68
|
-
if (!executable || !
|
|
67
|
+
if (!executable || !fileHasData(executable)) {
|
|
69
68
|
throw new Error('[local-provider] runtime is not installed');
|
|
70
69
|
}
|
|
71
70
|
if (!exactLocalProviderFile(weights, entry.size)) {
|