mixdog 1.3.1 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/package.json +8 -8
- package/scripts/native-release-status.mjs +238 -0
- package/scripts/verify-runtime-dependency-versions.mjs +158 -0
- package/src/agents/reviewer/AGENT.md +5 -7
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +9 -1
- package/src/defaults/skills/browser-use/SKILL.md +6 -3
- package/src/defaults/skills/local-provider/SKILL.md +33 -6
- package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
- package/src/defaults/skills/setup/SKILL.md +23 -1
- package/src/defaults/skills/setup/references/actions.md +50 -3
- package/src/defaults/skills/setup/references/surfaces.md +23 -3
- package/src/defaults/skills/setup-guide/SKILL.md +7 -3
- package/src/defaults/skills/visualize/SKILL.md +9 -6
- package/src/defaults/skills/visualize/references/craft.md +8 -1
- package/src/help.mjs +1 -1
- package/src/lib/keychain-cjs.cjs +4 -1
- package/src/lib/rules-builder.cjs +14 -7
- package/src/rules/agent-mode/AGENT_MODE.md +8 -5
- package/src/rules/agent-mode/MIXY.md +6 -8
- package/src/rules/lead/LEAD.md +7 -10
- package/src/rules/shared/10-tool-workflow.md +4 -0
- package/src/rules/shared/20-research.md +2 -0
- package/src/rules/shared/45-destructive.md +5 -1
- package/src/rules/shared/60-verification.md +6 -4
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
- package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
- package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
- package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
- package/src/runtime/agent/orchestrator/config.mjs +11 -0
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +9 -2
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
- package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +84 -32
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +38 -11
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +15 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +84 -42
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +16 -0
- package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
- package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
- package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
- package/src/runtime/agent/orchestrator/providers/registry.mjs +19 -0
- package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +13 -1
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
- package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +5 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +10 -1
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -6
- package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +52 -6
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +35 -13
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +8 -3
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -2
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +196 -45
- package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compact/continuation.mjs +15 -6
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +45 -19
- package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +44 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +185 -37
- package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
- package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +240 -0
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +102 -35
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +19 -9
- package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +199 -109
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/empty-nudge.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +62 -13
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +22 -6
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +33 -8
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/call-prep.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/manager/agent-listing-notice.mjs +102 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +27 -14
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +32 -1
- package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +115 -0
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +36 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -9
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +4 -9
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +14 -1
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +7 -3
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
- package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +52 -11
- package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +126 -12
- package/src/runtime/agent/orchestrator/session/runtime-user-context.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +16 -3
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +5 -4
- package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +140 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +15 -5
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +49 -1
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +80 -1
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
- package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +33 -1
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.d.mts +5 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +37 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +35 -5
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +47 -14
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +127 -20
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +557 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +10 -12
- package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +42 -23
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
- package/src/runtime/attachments/attachment-reference.mjs +155 -0
- package/src/runtime/attachments/legacy-office.mjs +110 -0
- package/src/runtime/attachments/limits.mjs +5 -1
- package/src/runtime/attachments/office-extract.mjs +3 -6
- package/src/runtime/attachments/pdf-extract.mjs +84 -10
- package/src/runtime/attachments/prompt-restore.mjs +70 -0
- package/src/runtime/attachments/store.mjs +109 -33
- package/src/runtime/browser-bridge/client.mjs +73 -20
- package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
- package/src/runtime/channels/lib/scheduler.mjs +14 -7
- package/src/runtime/channels/lib/state-file.mjs +15 -15
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
- package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
- package/src/runtime/channels/lib/worker-main.mjs +3 -2
- package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
- package/src/runtime/effort-judge/judge-client.mjs +66 -16
- package/src/runtime/effort-judge/model-install.mjs +47 -7
- package/src/runtime/local-provider/asset-installer.mjs +10 -4
- package/src/runtime/local-provider/catalog.mjs +5 -2
- package/src/runtime/local-provider/registered-models.mjs +64 -27
- package/src/runtime/local-provider/server.mjs +2 -3
- package/src/runtime/media/adapters/image-api.mjs +89 -0
- package/src/runtime/media/auth.mjs +64 -1
- package/src/runtime/media/catalog.mjs +66 -4
- package/src/runtime/media/jobs.mjs +4 -0
- package/src/runtime/media/lanes.mjs +46 -5
- package/src/runtime/media/media-usage.mjs +17 -0
- package/src/runtime/media/renditions.mjs +15 -15
- package/src/runtime/media/store.mjs +36 -35
- package/src/runtime/media/tool.mjs +8 -1
- package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
- package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
- package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +14 -2
- package/src/runtime/memory/lib/pg/process.mjs +8 -4
- package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
- package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
- package/src/runtime/office/core/journal.mjs +7 -14
- package/src/runtime/office/design/library/design-library-core.mjs +4 -16
- package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
- package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
- package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
- package/src/runtime/office/pdf/pdf-render.mjs +30 -18
- package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
- package/src/runtime/office/portable/portable-soffice.mjs +135 -29
- package/src/runtime/shared/agent-avatar-sprites.mjs +23 -8
- package/src/runtime/shared/agent-avatar.mjs +3 -2
- package/src/runtime/shared/agent-kind-migration.mjs +200 -0
- package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
- package/src/runtime/shared/agent-message-envelope.mjs +44 -0
- package/src/runtime/shared/agent-reap-state.d.mts +4 -0
- package/src/runtime/shared/agent-room-host.d.mts +31 -0
- package/src/runtime/shared/agent-room-host.mjs +58 -0
- package/src/runtime/shared/agent-store.mjs +24 -7
- package/src/runtime/shared/agent-task-starts.mjs +27 -0
- package/src/runtime/shared/atomic-file.d.mts +50 -0
- package/src/runtime/shared/atomic-file.mjs +394 -132
- package/src/runtime/shared/background-tasks.mjs +7 -4
- package/src/runtime/shared/bot-hops.mjs +35 -0
- package/src/runtime/shared/bot-split-migration.mjs +1 -1
- package/src/runtime/shared/bounded-download.mjs +4 -2
- package/src/runtime/shared/config.mjs +39 -9
- package/src/runtime/shared/defaults-separation.mjs +6 -2
- package/src/runtime/shared/delegated-approval-host.mjs +74 -0
- package/src/runtime/shared/err-text.mjs +4 -0
- package/src/runtime/shared/error-presentation.mjs +22 -4
- package/src/runtime/shared/file-has-data.mjs +28 -0
- package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
- package/src/runtime/shared/pending-agent-work.mjs +54 -0
- package/src/runtime/shared/process-lifecycle.mjs +8 -12
- package/src/runtime/shared/projects.mjs +50 -41
- package/src/runtime/shared/provider-accounts.mjs +17 -12
- package/src/runtime/shared/redact-directories.mjs +18 -0
- package/src/runtime/shared/remove-directory.mjs +28 -0
- package/src/runtime/shared/routine-trigger.d.mts +6 -1
- package/src/runtime/shared/routine-trigger.mjs +14 -6
- package/src/runtime/shared/run-child.d.mts +3 -0
- package/src/runtime/shared/run-child.mjs +24 -3
- package/src/runtime/shared/session-baseline-store.mjs +8 -2
- package/src/runtime/shared/session-sidebar-host.d.mts +11 -2
- package/src/runtime/shared/session-sidebar-host.mjs +16 -3
- package/src/runtime/shared/singleton-owner.mjs +7 -6
- package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
- package/src/runtime/shared/staged-update.mjs +4 -2
- package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
- package/src/runtime/shared/tool-execution-contract.mjs +10 -0
- package/src/runtime/shared/tool-work-units.mjs +12 -9
- package/src/runtime/shared/transcript-metadata.mjs +28 -3
- package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
- package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
- package/src/runtime/shared/turn-snapshot.mjs +277 -124
- package/src/runtime/shared/user-cwd.mjs +9 -4
- package/src/runtime/shared/user-data-guard.mjs +92 -61
- package/src/runtime/shared/utf8-cut.mjs +10 -0
- package/src/runtime/shared/worktrees/activity-gate.mjs +251 -61
- package/src/runtime/shared/worktrees/contract.mjs +11 -3
- package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
- package/src/runtime/shared/worktrees/manager.mjs +419 -52
- package/src/runtime/shared/worktrees/registry.mjs +29 -27
- package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
- package/src/runtime/tidy/core-install.mjs +26 -4
- package/src/runtime/tidy/install.mjs +91 -23
- package/src/runtime/tidy/process.mjs +41 -8
- package/src/runtime/tidy/resolve.mjs +10 -7
- package/src/runtime/web-search/lib/config.mjs +4 -16
- package/src/session-runtime/agent-home-delivery.mjs +542 -204
- package/src/session-runtime/agent-profile-changes.mjs +16 -0
- package/src/session-runtime/agent-room-delivery.mjs +81 -35
- package/src/session-runtime/agent-task-sessions.mjs +811 -179
- package/src/session-runtime/agent-tool-routing.mjs +1 -0
- package/src/session-runtime/boot/apis.mjs +48 -24
- package/src/session-runtime/boot/config.mjs +1 -0
- package/src/session-runtime/boot/facade.mjs +16 -7
- package/src/session-runtime/boot/infrastructure.mjs +14 -2
- package/src/session-runtime/boot/providers.mjs +2 -2
- package/src/session-runtime/boot/shared.mjs +12 -6
- package/src/session-runtime/boot/tools.mjs +23 -3
- package/src/session-runtime/bot-status.mjs +432 -0
- package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
- package/src/session-runtime/config-lifecycle.mjs +60 -55
- package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
- package/src/session-runtime/held-requests.mjs +31 -33
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +24 -26
- package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
- package/src/session-runtime/lifecycle/teardown.mjs +11 -3
- package/src/session-runtime/mcp-glue/connect.mjs +100 -186
- package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
- package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
- package/src/session-runtime/media-api.mjs +5 -2
- package/src/session-runtime/model-recency.mjs +1 -0
- package/src/session-runtime/model-route/route-tuning.mjs +4 -1
- package/src/session-runtime/model-route/set-route.mjs +61 -1
- package/src/session-runtime/owned-automation-run.mjs +8 -1
- package/src/session-runtime/provider-models/row-hydration.mjs +8 -1
- package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
- package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
- package/src/session-runtime/route-resolution.mjs +16 -8
- package/src/session-runtime/route-state.mjs +1 -59
- package/src/session-runtime/routine-home-turn.mjs +5 -3
- package/src/session-runtime/runtime-bootstrap.mjs +5 -3
- package/src/session-runtime/runtime-paths.mjs +11 -5
- package/src/session-runtime/runtime-review-api.mjs +4 -1
- package/src/session-runtime/runtime-tunables.mjs +6 -0
- package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
- package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
- package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
- package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +34 -19
- package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +7 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +8 -5
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
- package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
- package/src/session-runtime/services/agent-tool.mjs +6 -0
- package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
- package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
- package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
- package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
- package/src/session-runtime/services/plugin-admin.mjs +26 -26
- package/src/session-runtime/services/provider-admin.mjs +26 -6
- package/src/session-runtime/services/schedule-access.mjs +33 -4
- package/src/session-runtime/services/seeds.mjs +4 -3
- package/src/session-runtime/services/usage-dashboard.mjs +1 -1
- package/src/session-runtime/services/usage-stats-model.mjs +2 -1
- package/src/session-runtime/services/worktree-access.mjs +27 -0
- package/src/session-runtime/session-create/materialize.mjs +2 -3
- package/src/session-runtime/session-create/session-options.mjs +15 -2
- package/src/session-runtime/session-hooks.mjs +9 -17
- package/src/session-runtime/settings-builtin-tools-api.mjs +65 -1
- package/src/session-runtime/settings-compaction-api.mjs +86 -39
- package/src/session-runtime/setup-tool/executor.mjs +12 -2
- package/src/session-runtime/setup-tool/settings-contract.mjs +34 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
- package/src/session-runtime/skills-api.mjs +12 -5
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +349 -0
- package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
- package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
- package/src/session-runtime/tool-policy/policy-stage.mjs +183 -0
- package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
- package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
- package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
- package/src/session-runtime/tool-policy-refresh.mjs +48 -369
- package/src/session-runtime/tool-surface.mjs +10 -4
- package/src/session-runtime/turn/session-ops.mjs +8 -0
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
- package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
- package/src/session-runtime/turn/turn-run.mjs +7 -2
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +527 -333
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +85 -63
- package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
- package/src/session-runtime/workspace/transition-steps.mjs +1 -18
- package/src/session-runtime/workspace/transitions.mjs +279 -67
- package/src/session-runtime/workspace/workspace-state.mjs +7 -6
- package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
- package/src/standalone/daemon-agent-control.mjs +4 -1
- package/src/standalone/daemon-lag-profiler.mjs +3 -1
- package/src/standalone/daemon-session-control.mjs +173 -15
- package/src/standalone/daemon-telemetry.mjs +9 -0
- package/src/standalone/daemon.mjs +33 -10
- package/src/standalone/local-session-runtime.mjs +33 -16
- package/src/standalone/session-client.mjs +7 -2
- package/src/standalone/session-protocol.mjs +8 -0
- package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
- package/src/standalone/session-service/agent-route-follow.mjs +69 -0
- package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
- package/src/standalone/session-service.mjs +21 -0
- package/src/standalone/session-state-projection.mjs +19 -9
- package/src/standalone/session-tool-output.mjs +36 -9
- package/src/standalone/staged-import.mjs +47 -0
- package/src/tui/App.jsx +2 -0
- package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
- package/src/tui/app/message-selector.mjs +15 -2
- package/src/tui/app/model-options.mjs +16 -2
- package/src/tui/app/model-picker.mjs +2 -1
- package/src/tui/app/route-pickers.mjs +3 -1
- package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
- package/src/tui/app/slash-commands.mjs +1 -1
- package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
- package/src/tui/dist/index.mjs +617 -369
- package/src/tui/paste-attachments.mjs +0 -1
- package/src/tui/paste-text-policy.mjs +4 -5
- package/src/tui/prompt-history-store.mjs +16 -21
- package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
- package/src/tui/session/context-state.mjs +4 -0
- package/src/tui/session/notice-surface.mjs +3 -0
- package/src/tui/session/session-api/intake/abort.mjs +52 -18
- package/src/tui/session/session-api/intake/rewind.mjs +39 -4
- package/src/tui/session/session-api/media.mjs +2 -1
- package/src/tui/session/session-api/routes-agent-definitions.mjs +76 -0
- package/src/tui/session/session-api/routes-write.mjs +203 -0
- package/src/tui/session/session-api/routes.mjs +23 -247
- package/src/tui/session/session-api/settings.mjs +14 -1
- package/src/tui/session/session-flow/drain.mjs +28 -3
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
- package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
- package/src/tui/session/session-flow/steering.mjs +8 -3
- package/src/tui/session/tool-approval.mjs +142 -14
- package/src/tui/session/transcript-intake.mjs +2 -0
- package/src/tui/session/turn.mjs +23 -14
- package/src/tui/transcript-tool-failures.mjs +8 -2
- package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
- package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
- package/src/rules/agent-mode/ROUTINE.md +0 -6
- package/src/session-runtime/room-member-gate.mjs +0 -48
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Bot-to-bot chain budget. Every send a bot makes carries `hop` (stored in the
|
|
2
|
+
// transcript meta of the message it delivers, and in the report that answers
|
|
3
|
+
// it). A turn knows the hop of the submission that started it (`turnHop`,
|
|
4
|
+
// stamped on the session when the turn is prepared); a send made in it goes
|
|
5
|
+
// out at turnHop + 1. Only a message another bot sent counts: a turn started by
|
|
6
|
+
// the user, a routine or a runtime notification (a task report, a wake) starts
|
|
7
|
+
// at 0, so a bot's reports of its own sends never use up its budget. Background
|
|
8
|
+
// work (a command, an agent task) launched in a bot-descended turn carries that
|
|
9
|
+
// turn's hop on its completion notification (`hop` in the completion meta and
|
|
10
|
+
// entry), and the turn the notification starts or joins resumes at it.
|
|
11
|
+
export const MAX_BOT_HOPS = 3;
|
|
12
|
+
export const BOT_HOP_LIMIT_ERROR = 'session: Bot-to-bot chain limit reached; ask the user before continuing.';
|
|
13
|
+
|
|
14
|
+
export const hopOf = (value) => (Number.isInteger(value) && value > 0 ? value : 0);
|
|
15
|
+
|
|
16
|
+
/** The hop of the turn that made a send, from the send's own place in the chain
|
|
17
|
+
* (`hop`, as a task report or watch carries it, with its `target`): a send to
|
|
18
|
+
* another session is one hop past its turn, one to a task session the bot
|
|
19
|
+
* created only continues it. A report wakes the sender's turn back at this
|
|
20
|
+
* depth: bot-descended work resumes where it was, independent work at 0. */
|
|
21
|
+
export const sendDepth = ({ hop, target } = {}) => Math.max(0, hopOf(hop) - (target === 'session' ? 1 : 0));
|
|
22
|
+
|
|
23
|
+
/** Stamp the hop of the submission that starts the turn (else 0) on `session`. */
|
|
24
|
+
export function stampTurnHop(session, transcriptMeta) {
|
|
25
|
+
session.turnHop = hopOf(transcriptMeta?.hop);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A bot's message steered into the running turn raises the turn's hop. */
|
|
29
|
+
export function raiseTurnHop(session, transcriptMeta) {
|
|
30
|
+
const hop = hopOf(transcriptMeta?.hop);
|
|
31
|
+
if (session && hop > hopOf(session.turnHop)) session.turnHop = hop;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The hop of the turn `session` is in. */
|
|
35
|
+
export const turnHopOf = (session) => hopOf(session?.turnHop);
|
|
@@ -83,7 +83,7 @@ function writeJson(file, value) {
|
|
|
83
83
|
|
|
84
84
|
// The definition file a bot's kind is read from: AGENT.md, or the archived
|
|
85
85
|
// copy of a deleted bot (its chat is kept, so it moves with the others).
|
|
86
|
-
function definitionFile(dir) {
|
|
86
|
+
export function definitionFile(dir) {
|
|
87
87
|
if (existsSync(join(dir, 'AGENT.md'))) return join(dir, 'AGENT.md');
|
|
88
88
|
if (existsSync(join(dir, DELETED_MARKER)) && existsSync(join(dir, ARCHIVED_FILE))) return join(dir, ARCHIVED_FILE);
|
|
89
89
|
return '';
|
|
@@ -55,7 +55,7 @@ export async function readResponseBuffer(response, { maxBytes, label = 'download
|
|
|
55
55
|
export async function streamResponseToFile(
|
|
56
56
|
response,
|
|
57
57
|
destPath,
|
|
58
|
-
{ maxBytes, expectedBytes = 0, label = 'download', onProgress = null } = {}
|
|
58
|
+
{ maxBytes, expectedBytes = 0, label = 'download', onProgress = null, signal = undefined } = {}
|
|
59
59
|
) {
|
|
60
60
|
const { maximum, expected } = downloadBounds(maxBytes, expectedBytes, label);
|
|
61
61
|
const advertised = responseByteLength(response, maximum, label);
|
|
@@ -94,7 +94,9 @@ export async function streamResponseToFile(
|
|
|
94
94
|
});
|
|
95
95
|
|
|
96
96
|
try {
|
|
97
|
-
|
|
97
|
+
// flush: the bytes reach disk before the caller installs the file, so a
|
|
98
|
+
// crash cannot leave an installed binary that is all NUL.
|
|
99
|
+
await pipeline(response.body, guard, createWriteStream(destPath, { flush: true }), { signal });
|
|
98
100
|
return downloaded;
|
|
99
101
|
} catch (error) {
|
|
100
102
|
try {
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
* Unified config reader/writer.
|
|
3
3
|
* Single file: mixdog-config.json with sections such as channels, agent, and memory.
|
|
4
4
|
*/
|
|
5
|
-
import { readFileSync, statSync, mkdirSync, existsSync, watch as fsWatch } from 'node:fs';
|
|
6
|
-
import { join, dirname } from 'node:path';
|
|
5
|
+
import { readFileSync, readdirSync, statSync, mkdirSync, existsSync, watch as fsWatch } from 'node:fs';
|
|
6
|
+
import { basename, join, dirname } from 'node:path';
|
|
7
7
|
import { createRequire } from 'node:module';
|
|
8
8
|
import { resolvePluginData } from './plugin-paths.mjs';
|
|
9
9
|
import { hasOwn, isPlainObject } from './object.mjs';
|
|
10
10
|
import {
|
|
11
|
+
parseJsonText,
|
|
12
|
+
quarantinePath,
|
|
11
13
|
renameWithRetrySync,
|
|
12
14
|
writeJsonAtomicSync,
|
|
13
15
|
writeJsonAtomicAsync,
|
|
@@ -226,7 +228,7 @@ function readConfigFile() {
|
|
|
226
228
|
const raw = readConfigRawCached();
|
|
227
229
|
if (raw == null) return null;
|
|
228
230
|
try {
|
|
229
|
-
return
|
|
231
|
+
return parseJsonText(raw);
|
|
230
232
|
} catch (err) {
|
|
231
233
|
// Quarantine a malformed mixdog-config.json so the next boot starts fresh
|
|
232
234
|
// instead of looping on a broken file. A cached raw string that fails to
|
|
@@ -346,21 +348,47 @@ function readAll() {
|
|
|
346
348
|
}
|
|
347
349
|
|
|
348
350
|
function quarantineMalformedConfig(parseErr) {
|
|
349
|
-
const corrupt =
|
|
351
|
+
const corrupt = quarantinePath(configPath());
|
|
352
|
+
let keptAside = false;
|
|
350
353
|
try {
|
|
351
354
|
if (existsSync(configPath())) renameWithRetrySync(configPath(), corrupt);
|
|
355
|
+
keptAside = existsSync(corrupt);
|
|
352
356
|
} catch {}
|
|
353
357
|
process.stderr.write(
|
|
354
358
|
`[config] mixdog-config.json is malformed (${parseErr.message}). Renamed to ${corrupt}. Restore it or delete to start fresh.\n`
|
|
355
359
|
);
|
|
360
|
+
return keptAside;
|
|
356
361
|
}
|
|
357
362
|
|
|
358
|
-
|
|
363
|
+
// A damaged config that was kept aside (its bytes preserved as
|
|
364
|
+
// mixdog-config.json.corrupt-<ts>) is not a lost config: those bytes are the
|
|
365
|
+
// safety net, so with no backup to restore a save may start from an empty
|
|
366
|
+
// config. Every save also snapshots a backup, so a stale copy from an earlier
|
|
367
|
+
// incident never reaches this point while a backup of a later config exists.
|
|
368
|
+
function hasQuarantinedCopy() {
|
|
369
|
+
const dir = dirname(configPath());
|
|
370
|
+
const prefix = `${basename(configPath())}.corrupt-`;
|
|
371
|
+
try {
|
|
372
|
+
return readdirSync(dir).some((name) => name.startsWith(prefix));
|
|
373
|
+
} catch {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// `keptAside`: the unusable config's bytes are preserved on disk, so with no
|
|
379
|
+
// backup to restore a save starts from an empty config instead of refusing.
|
|
380
|
+
function restoreAllForRmWOrThrow(reason, keptAside = false) {
|
|
359
381
|
const restored = loadLatestMixdogConfigFromBackup(resolvePluginData(), { fresh: true });
|
|
360
382
|
if (restored && isPlainObject(restored)) {
|
|
361
383
|
process.stderr.write(`[config] RMW read: restored mixdog-config.json from latest user-data backup (${reason})\n`);
|
|
362
384
|
return restored;
|
|
363
385
|
}
|
|
386
|
+
if (keptAside) {
|
|
387
|
+
process.stderr.write(
|
|
388
|
+
`[config] RMW read: mixdog-config.json is unusable (${reason}); its bytes are kept aside and no backup exists; starting empty\n`
|
|
389
|
+
);
|
|
390
|
+
return {};
|
|
391
|
+
}
|
|
364
392
|
const legacy = findLegacyUserDataEvidence(resolvePluginData(), { fresh: true });
|
|
365
393
|
if (legacy) {
|
|
366
394
|
const err = new Error(
|
|
@@ -381,6 +409,9 @@ function restoreAllForRmWOrThrow(reason) {
|
|
|
381
409
|
* Read-modify-write baseline. ENOENT on a never-initialized data dir → {}.
|
|
382
410
|
* Malformed on-disk config (or missing config after prior init) → restore
|
|
383
411
|
* from backup or throw; never silently collapse to a one-section overwrite.
|
|
412
|
+
* The exception is a damaged config whose bytes were kept aside as
|
|
413
|
+
* .corrupt-<ts>: with no backup to restore, the save starts from an empty
|
|
414
|
+
* config. A config that cannot be READ (I/O error) still throws.
|
|
384
415
|
*/
|
|
385
416
|
function readAllForRmW() {
|
|
386
417
|
let raw;
|
|
@@ -389,7 +420,7 @@ function readAllForRmW() {
|
|
|
389
420
|
} catch (err) {
|
|
390
421
|
if (err.code === 'ENOENT') {
|
|
391
422
|
if (isUserDataRecoveryExpected(resolvePluginData(), { fresh: true })) {
|
|
392
|
-
return restoreAllForRmWOrThrow('config file missing after user-data was initialized');
|
|
423
|
+
return restoreAllForRmWOrThrow('config file missing after user-data was initialized', hasQuarantinedCopy());
|
|
393
424
|
}
|
|
394
425
|
return {};
|
|
395
426
|
}
|
|
@@ -397,12 +428,11 @@ function readAllForRmW() {
|
|
|
397
428
|
throw err;
|
|
398
429
|
}
|
|
399
430
|
try {
|
|
400
|
-
const parsed =
|
|
431
|
+
const parsed = parseJsonText(raw);
|
|
401
432
|
if (!isPlainObject(parsed)) throw new SyntaxError('config root must be a JSON object');
|
|
402
433
|
return parsed;
|
|
403
434
|
} catch (parseErr) {
|
|
404
|
-
quarantineMalformedConfig(parseErr);
|
|
405
|
-
return restoreAllForRmWOrThrow(parseErr.message);
|
|
435
|
+
return restoreAllForRmWOrThrow(parseErr.message, quarantineMalformedConfig(parseErr));
|
|
406
436
|
}
|
|
407
437
|
}
|
|
408
438
|
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
* Left untouched on purpose: `desktop`, credentials, onboarding, detection and
|
|
12
12
|
* install flags, caches, and keys that are only ever hand-edited.
|
|
13
13
|
*/
|
|
14
|
-
import {
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, statSync } from 'node:fs';
|
|
15
15
|
import { join } from 'node:path';
|
|
16
16
|
import { isDeepStrictEqual } from 'node:util';
|
|
17
|
+
import { writeFileAtomicSync } from './atomic-file.mjs';
|
|
17
18
|
import { packDefinitionFromDir } from './shipped-definitions.mjs';
|
|
18
19
|
import { resolvePluginData } from './plugin-paths.mjs';
|
|
19
20
|
import { hasOwn, isPlainObject } from './object.mjs';
|
|
@@ -231,7 +232,10 @@ export function backupConfigBeforeDefaultsSeparation({ dataDir = resolvePluginDa
|
|
|
231
232
|
throw err;
|
|
232
233
|
}
|
|
233
234
|
const target = join(dir, 'mixdog-config.json');
|
|
234
|
-
|
|
235
|
+
// Written through an fsynced handle (a plain copy can leave this backup NUL
|
|
236
|
+
// exactly when a crash makes it needed); the folder was claimed above, so
|
|
237
|
+
// nothing can exist at `target`.
|
|
238
|
+
writeFileAtomicSync(target, readFileSync(source), { mode: statSync(source).mode & 0o777 });
|
|
235
239
|
return target;
|
|
236
240
|
}
|
|
237
241
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Delegated approvals: a sub-agent's approval request (tui/session/tool-approval.mjs)
|
|
2
|
+
// is also shown in the top-level conversation the user watches — the root bot's
|
|
3
|
+
// home chat, or the coding Lead's session (human-gate/gate.mjs approvalSurface).
|
|
4
|
+
// It stays ONE request: the sub-agent's queue owns it and its waiter; the
|
|
5
|
+
// top-level session's queue holds a copy whose answer is forwarded to that
|
|
6
|
+
// owner, and the copy is withdrawn as soon as the owner settles (answered in
|
|
7
|
+
// either place, timed out, its turn ended). Like held-request-host.mjs this
|
|
8
|
+
// registry is process-wide and dependency-free.
|
|
9
|
+
|
|
10
|
+
const KEY = Symbol.for('mixdog.delegatedApprovalHost.v1');
|
|
11
|
+
|
|
12
|
+
function registry() {
|
|
13
|
+
globalThis[KEY] ??= { queues: new Set(), pending: new Map(), opener: null };
|
|
14
|
+
return globalThis[KEY];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** `queue`: `{ sessionId() → string, disposed() → boolean, offer(delegated),
|
|
18
|
+
* withdraw(id, result) }` — one session runtime's approval queue. */
|
|
19
|
+
export function registerApprovalQueue(queue) {
|
|
20
|
+
registry().queues.add(queue);
|
|
21
|
+
return () => registry().queues.delete(queue);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** `open(sessionId) → Promise`: brings a session's runtime up (the daemon's
|
|
25
|
+
* session service), so a top-level chat that is not loaded can show the copy. */
|
|
26
|
+
export function registerDelegatedApprovalOpener(open) {
|
|
27
|
+
registry().opener = open;
|
|
28
|
+
return () => {
|
|
29
|
+
if (registry().opener === open) registry().opener = null;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function liveQueuesOf(sessionId) {
|
|
34
|
+
const { queues } = registry();
|
|
35
|
+
const live = [];
|
|
36
|
+
for (const queue of queues) {
|
|
37
|
+
if (queue.disposed()) queues.delete(queue);
|
|
38
|
+
else if (queue.sessionId() === sessionId) live.push(queue);
|
|
39
|
+
}
|
|
40
|
+
return live;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function offerTo(sessionId, delegated) {
|
|
44
|
+
const live = liveQueuesOf(sessionId);
|
|
45
|
+
for (const queue of live) queue.offer(delegated);
|
|
46
|
+
return live.length > 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Show `delegated` (`{ id, sessionId, request, answer(...finishArgs) → boolean }`,
|
|
50
|
+
* `sessionId` being the top-level conversation) there, now or once it loads. */
|
|
51
|
+
export function delegateApproval(delegated) {
|
|
52
|
+
const { pending, opener } = registry();
|
|
53
|
+
pending.set(delegated.id, delegated);
|
|
54
|
+
if (offerTo(delegated.sessionId, delegated) || typeof opener !== 'function') return;
|
|
55
|
+
void Promise.resolve()
|
|
56
|
+
.then(() => opener(delegated.sessionId))
|
|
57
|
+
.then(() => {
|
|
58
|
+
if (pending.get(delegated.id) === delegated) offerTo(delegated.sessionId, delegated);
|
|
59
|
+
})
|
|
60
|
+
.catch(() => {});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The owner settled `id`: every copy goes away. `result` (`{ approved, device }`)
|
|
64
|
+
* lets the copy say who answered. */
|
|
65
|
+
export function withdrawDelegatedApproval(id, result = null) {
|
|
66
|
+
const { pending, queues } = registry();
|
|
67
|
+
if (!pending.delete(id)) return;
|
|
68
|
+
for (const queue of queues) queue.withdraw(id, result);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The pending delegated approvals a top-level session should show. */
|
|
72
|
+
export function delegatedApprovalsFor(sessionId) {
|
|
73
|
+
return [...registry().pending.values()].filter((delegated) => delegated.sessionId === sessionId);
|
|
74
|
+
}
|
|
@@ -257,6 +257,10 @@ export function presentErrorText(error, options = {}) {
|
|
|
257
257
|
if (code === 'TOOL_SURFACE_EXCEEDS_CONTEXT' || /\(TOOL_SURFACE_EXCEEDS_CONTEXT\)/.test(text)) {
|
|
258
258
|
return stripErrorPrefix(text);
|
|
259
259
|
}
|
|
260
|
+
// Likewise a latest message that cannot fit names its size and what would
|
|
261
|
+
// help; a wrapper's prefix ("agent compact failed (...): ") is dropped.
|
|
262
|
+
const latestTurn = /The latest message cannot fit this model \(LATEST_TURN_EXCEEDS_CONTEXT\)[\s\S]*$/.exec(text);
|
|
263
|
+
if (latestTurn) return latestTurn[0].replace(' (LATEST_TURN_EXCEEDS_CONTEXT)', '');
|
|
260
264
|
const jsonError = extractJsonError(text);
|
|
261
265
|
if (jsonError) text = jsonError;
|
|
262
266
|
const embeddedError = extractEmbeddedError(text);
|
|
@@ -3,10 +3,14 @@ import { transportErrorText } from './transport-error-text.mjs';
|
|
|
3
3
|
|
|
4
4
|
const DETAILS_LIMIT = 12_000;
|
|
5
5
|
const SUMMARY_LIMIT = 180;
|
|
6
|
+
// "<wrapper>: The latest message cannot fit this model (CODE): <sizes>. <cause and remedy>"
|
|
7
|
+
const LATEST_TURN_RE =
|
|
8
|
+
/The latest message cannot fit this model \(LATEST_TURN_EXCEEDS_CONTEXT\)(: it needs about [\d,]+ tokens, but the [^.]* window leaves [\d,]+ for it\.)([\s\S]*)$/;
|
|
6
9
|
|
|
7
10
|
/** Diagnostic text must be safe before it reaches a view, copy button or log. */
|
|
8
11
|
export function safeErrorDetails(value) {
|
|
9
12
|
let text = String(value ?? '')
|
|
13
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: strips ANSI escape sequences (ESC) from diagnostic text
|
|
10
14
|
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '')
|
|
11
15
|
.replace(/\r\n?/g, '\n')
|
|
12
16
|
.replace(/\b((?:https?|ssh):\/\/)[^/\s@]+@/gi, '$1[redacted]@')
|
|
@@ -135,7 +139,9 @@ function resetLabel(resetAt) {
|
|
|
135
139
|
const reset = new Date(resetAt);
|
|
136
140
|
const pad = (n) => String(n).padStart(2, '0');
|
|
137
141
|
const time = `${pad(reset.getHours())}:${pad(reset.getMinutes())}`;
|
|
138
|
-
return reset.toDateString() === new Date().toDateString()
|
|
142
|
+
return reset.toDateString() === new Date().toDateString()
|
|
143
|
+
? time
|
|
144
|
+
: `${reset.getMonth() + 1}/${reset.getDate()} ${time}`;
|
|
139
145
|
}
|
|
140
146
|
|
|
141
147
|
// A refused request that names its model as unknown, retired or not offered
|
|
@@ -286,7 +292,9 @@ export function describeError(value) {
|
|
|
286
292
|
if (
|
|
287
293
|
value?.reauthRequired === true ||
|
|
288
294
|
(!typed &&
|
|
289
|
-
/invalid_grant|refresh token (?:expired|revoked|not available)|sign in again|sign-in expired|Reauth Required/i.test(
|
|
295
|
+
/invalid_grant|refresh token (?:expired|revoked|not available)|sign in again|sign-in expired|Reauth Required/i.test(
|
|
296
|
+
raw
|
|
297
|
+
))
|
|
290
298
|
) {
|
|
291
299
|
kind = 'reauth';
|
|
292
300
|
summary = 'Sign-in expired.';
|
|
@@ -294,7 +302,9 @@ export function describeError(value) {
|
|
|
294
302
|
} else if (
|
|
295
303
|
descriptor.kind === 'auth' ||
|
|
296
304
|
(descriptor.kind === 'unknown' &&
|
|
297
|
-
/\b(?:invalid|expired)\s+(?:api[ _-]?key|access token|credentials?)\b|Provider authentication failed/i.test(
|
|
305
|
+
/\b(?:invalid|expired)\s+(?:api[ _-]?key|access token|credentials?)\b|Provider authentication failed/i.test(
|
|
306
|
+
clean
|
|
307
|
+
))
|
|
298
308
|
) {
|
|
299
309
|
kind = 'authentication';
|
|
300
310
|
summary = 'Provider authentication failed';
|
|
@@ -331,6 +341,13 @@ export function describeError(value) {
|
|
|
331
341
|
kind = 'not-found';
|
|
332
342
|
summary = firstReadableLine(clean) || NOT_FOUND_SUMMARY;
|
|
333
343
|
recovery = MODEL_UNAVAILABLE_COPY[2];
|
|
344
|
+
} else if (LATEST_TURN_RE.test(clean)) {
|
|
345
|
+
// The latest message cannot fit the window (compact/runner.mjs): the
|
|
346
|
+
// sizes are the headline, the cause and remedy the next step.
|
|
347
|
+
const [, headline, rest] = LATEST_TURN_RE.exec(clean);
|
|
348
|
+
kind = 'latest-turn-size';
|
|
349
|
+
summary = `The latest message cannot fit this model${headline}`;
|
|
350
|
+
recovery = rest.trim();
|
|
334
351
|
} else if (
|
|
335
352
|
descriptor.kind === 'unknown' &&
|
|
336
353
|
/payload too large|request (?:body )?too large|The request exceeds the size limit/i.test(clean)
|
|
@@ -366,6 +383,7 @@ export function describeError(value) {
|
|
|
366
383
|
}
|
|
367
384
|
summary = summary.replace(/\s+/g, ' ').trim() || 'Something went wrong.';
|
|
368
385
|
if (summary.length > SUMMARY_LIMIT) summary = `${summary.slice(0, SUMMARY_LIMIT - 1).trimEnd()}…`;
|
|
386
|
+
const fingerprintStatus = kind === 'reauth' ? '' : status || '';
|
|
369
387
|
return {
|
|
370
388
|
kind,
|
|
371
389
|
summary,
|
|
@@ -378,6 +396,6 @@ export function describeError(value) {
|
|
|
378
396
|
details,
|
|
379
397
|
// Known structured failures ignore volatile indexes; unknown failures
|
|
380
398
|
// retain their diagnostic identity rather than merging unrelated work.
|
|
381
|
-
fingerprint: kind === 'unknown' ? details : `${kind}:${
|
|
399
|
+
fingerprint: kind === 'unknown' ? details : `${kind}:${fingerprintStatus}`,
|
|
382
400
|
};
|
|
383
401
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { closeSync, openSync, readSync } from 'node:fs';
|
|
2
|
+
import { holdsNoData } from './atomic-file.mjs';
|
|
3
|
+
|
|
4
|
+
const PROBE_BYTES = 4096;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Install check for a downloaded binary or model: false when `path` is absent
|
|
8
|
+
* or its first bytes hold no data (empty, or NUL-filled by a crash that
|
|
9
|
+
* committed the size but not the bytes). Cheaper than hashing a multi-GB file
|
|
10
|
+
* on every use. A file that exists but cannot be opened right now (locked by a
|
|
11
|
+
* scanner or by the process running it) still counts as present.
|
|
12
|
+
*/
|
|
13
|
+
export function fileHasData(path) {
|
|
14
|
+
let fd;
|
|
15
|
+
try {
|
|
16
|
+
fd = openSync(path, 'r');
|
|
17
|
+
const head = Buffer.alloc(PROBE_BYTES);
|
|
18
|
+
return !holdsNoData(head.subarray(0, readSync(fd, head, 0, PROBE_BYTES, 0)));
|
|
19
|
+
} catch (err) {
|
|
20
|
+
return err?.code !== 'ENOENT' && err?.code !== 'ENOTDIR';
|
|
21
|
+
} finally {
|
|
22
|
+
if (fd !== undefined) {
|
|
23
|
+
try {
|
|
24
|
+
closeSync(fd);
|
|
25
|
+
} catch {}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -114,7 +114,7 @@ export async function importUsageHistory(ledger, dataDir, { now = Date.now(), re
|
|
|
114
114
|
// send's diagnostic copy cannot also enter as historical usage.
|
|
115
115
|
const until = Number(ledger.get('liveSince')) || now;
|
|
116
116
|
if (Number(ledger.get('importedThrough')) >= until) return { skipped: true };
|
|
117
|
-
const result = { inserted: 0, sourceRows: 0, unrecordableRows: 0, files: [], traceRows: 0 };
|
|
117
|
+
const result = { inserted: 0, sourceRows: 0, unrecordableRows: 0, tornTraceLines: 0, files: [], traceRows: 0 };
|
|
118
118
|
const accept = (rows) => {
|
|
119
119
|
const normalized = [];
|
|
120
120
|
for (const raw of rows) {
|
|
@@ -140,15 +140,23 @@ export async function importUsageHistory(ledger, dataDir, { now = Date.now(), re
|
|
|
140
140
|
const original = await readFile(path, 'utf8');
|
|
141
141
|
const lines = original.split(/\r?\n/);
|
|
142
142
|
const rows = [];
|
|
143
|
+
let torn = 0;
|
|
143
144
|
for (let i = 0; i < lines.length; i++) {
|
|
144
145
|
if (!lines[i].trim()) continue;
|
|
145
146
|
let row;
|
|
146
147
|
try {
|
|
147
148
|
row = JSON.parse(lines[i]);
|
|
148
149
|
} catch {
|
|
149
|
-
|
|
150
|
+
// An append a crash tore (cut short, or NUL-filled) is no record: skip
|
|
151
|
+
// it, like the gateway store below, instead of failing every import.
|
|
152
|
+
torn += 1;
|
|
153
|
+
continue;
|
|
150
154
|
}
|
|
151
|
-
if (row
|
|
155
|
+
if (row?.kind === 'usage_raw' || row?.kind === 'usage') rows.push(row);
|
|
156
|
+
}
|
|
157
|
+
if (torn > 0) {
|
|
158
|
+
process.stderr.write(`[usage-ledger] warning: ${path} has ${torn} torn trace line(s); skipped\n`);
|
|
159
|
+
result.tornTraceLines += torn;
|
|
152
160
|
}
|
|
153
161
|
accept(rows);
|
|
154
162
|
result.files.push({ path, sha256: createHash('sha256').update(original).digest('hex'), rows: rows.length });
|
|
@@ -160,29 +168,40 @@ export async function importUsageHistory(ledger, dataDir, { now = Date.now(), re
|
|
|
160
168
|
// The JSONL store supersedes the legacy JSON file (it is migrated from it).
|
|
161
169
|
const gatewayJsonl = await optionalFile(join(dataDir, 'gateway-usage.local.jsonl'));
|
|
162
170
|
const gateway = gatewayJsonl === null ? await optionalFile(join(dataDir, 'gateway-usage.local.json')) : null;
|
|
163
|
-
|
|
171
|
+
// A missing file is absence. Empty, unparsable, or wrong-shaped JSON is not:
|
|
172
|
+
// skip only that legacy import and leave importedThrough unset so a repair
|
|
173
|
+
// is retried instead of short-circuiting forever.
|
|
174
|
+
let legacyReady = true;
|
|
175
|
+
let gatewayEvents = null;
|
|
176
|
+
if (gatewayJsonl !== null) {
|
|
177
|
+
gatewayEvents = gatewayJsonl.split('\n').flatMap((line) => {
|
|
178
|
+
try {
|
|
179
|
+
return line.trim() ? [JSON.parse(line)] : [];
|
|
180
|
+
} catch {
|
|
181
|
+
return []; // torn append
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
} else if (gateway !== null) {
|
|
185
|
+
try {
|
|
186
|
+
const events = JSON.parse(gateway)?.events;
|
|
187
|
+
gatewayEvents = Array.isArray(events) ? events : [];
|
|
188
|
+
} catch (error) {
|
|
189
|
+
legacyReady = false;
|
|
190
|
+
process.stderr.write(
|
|
191
|
+
`[usage-ledger] warning: legacy gateway usage file gateway-usage.local.json is empty or corrupt (${error.message}); skipping its import\n`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (gatewayEvents)
|
|
164
196
|
accept(
|
|
165
|
-
(
|
|
166
|
-
? gatewayJsonl.split('\n').flatMap((line) => {
|
|
167
|
-
try {
|
|
168
|
-
return line.trim() ? [JSON.parse(line)] : [];
|
|
169
|
-
} catch {
|
|
170
|
-
return []; // torn append
|
|
171
|
-
}
|
|
172
|
-
})
|
|
173
|
-
: JSON.parse(gateway).events || []
|
|
174
|
-
).map((event) => ({
|
|
197
|
+
gatewayEvents.map((event) => ({
|
|
175
198
|
...event,
|
|
176
199
|
kind: 'usage',
|
|
177
200
|
session_id: event.sessionId,
|
|
178
201
|
}))
|
|
179
202
|
);
|
|
180
|
-
// A missing file is absence. Empty, unparsable, or wrong-shaped JSON is not:
|
|
181
|
-
// skip only this legacy import and leave importedThrough unset so a repair
|
|
182
|
-
// is retried instead of short-circuiting forever.
|
|
183
203
|
const legacyPath = join(dataDir, 'usage-rollup.local.json');
|
|
184
204
|
const legacy = await optionalFile(legacyPath);
|
|
185
|
-
let legacyReady = true;
|
|
186
205
|
if (legacy !== null) {
|
|
187
206
|
let days;
|
|
188
207
|
try {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Which Lead sessions still wait on agent work they handed off: the rule the
|
|
2
|
+
// desktop turn notifier applies to the agent pool (apps/desktop
|
|
3
|
+
// shared/agent-activity.ts isActiveDesktopAgentEntry /
|
|
4
|
+
// isCancelUnconfirmedDesktopAgentEntry, main/push-turn-events.ts
|
|
5
|
+
// sessionsWithPendingWork), ported for the runtime so a bot's task-session
|
|
6
|
+
// report waits for the same final answer the desktop notifies. Pool rows are
|
|
7
|
+
// listStoredAgentWorkersAsync rows (`sessionId`, root `ownerSessionId`,
|
|
8
|
+
// `status`, `stage`); nested descendants keep the Lead as their owner.
|
|
9
|
+
|
|
10
|
+
const TERMINAL_STATUS = /idle|done|complete|success|closed|error|fail|cancel|killed|timeout/i;
|
|
11
|
+
const ACTIVE_STATUS =
|
|
12
|
+
/^(?:connecting|requesting|streaming|tool[-_\s]?running|running|queued|pending|starting|resource_wait)$/i;
|
|
13
|
+
const CANCELLED_STATUS = /cancel|killed|aborted|interrupted/i;
|
|
14
|
+
const CANCEL_UNCONFIRMED_STATUS = /cancel[-_\s]?(?:unconfirmed|pending)|cancell?ing/i;
|
|
15
|
+
|
|
16
|
+
const record = (value) => (value && typeof value === 'object' && !Array.isArray(value) ? value : {});
|
|
17
|
+
|
|
18
|
+
function statusValues(value) {
|
|
19
|
+
const entry = record(value);
|
|
20
|
+
return [entry.stage, entry.status]
|
|
21
|
+
.map((status) => String(status || '').trim())
|
|
22
|
+
.filter((status, index, values) => Boolean(status) && values.indexOf(status) === index);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** A running or queued row: `status` decides, `stage` only for a row without one. */
|
|
26
|
+
function isActiveAgentRow(value) {
|
|
27
|
+
const statuses = statusValues(value);
|
|
28
|
+
if (statuses.length === 0 || statuses.some((status) => CANCELLED_STATUS.test(status))) return false;
|
|
29
|
+
const lifecycle = String(record(value).status || '').trim() || statuses[0];
|
|
30
|
+
return !TERMINAL_STATUS.test(lifecycle) && ACTIVE_STATUS.test(lifecycle);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A cancel whose stop is not proven yet. */
|
|
34
|
+
function isCancelUnconfirmedAgentRow(value) {
|
|
35
|
+
return statusValues(value).some((status) => CANCEL_UNCONFIRMED_STATUS.test(status));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Agent work still in flight on one pool row. */
|
|
39
|
+
export function hasPendingAgentWork(value) {
|
|
40
|
+
return isActiveAgentRow(value) || isCancelUnconfirmedAgentRow(value);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The owner (Lead) session ids with a child row still in flight. The Lead's
|
|
44
|
+
* own row is skipped: its turn state comes from the session itself. */
|
|
45
|
+
export function sessionsWithPendingWork(rows) {
|
|
46
|
+
const pending = new Set();
|
|
47
|
+
for (const row of Array.isArray(rows) ? rows : []) {
|
|
48
|
+
const sessionId = String(row?.sessionId || '');
|
|
49
|
+
const ownerSessionId = String(row?.ownerSessionId || '') || sessionId;
|
|
50
|
+
if (!ownerSessionId || sessionId === ownerSessionId) continue;
|
|
51
|
+
if (hasPendingAgentWork(row)) pending.add(ownerSessionId);
|
|
52
|
+
}
|
|
53
|
+
return pending;
|
|
54
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
closeSync,
|
|
3
3
|
existsSync,
|
|
4
|
-
fsyncSync,
|
|
5
4
|
mkdirSync,
|
|
6
5
|
openSync,
|
|
7
6
|
readFileSync,
|
|
@@ -15,7 +14,7 @@ import {
|
|
|
15
14
|
import { join } from 'node:path';
|
|
16
15
|
import { execFile } from 'node:child_process';
|
|
17
16
|
import { isDeepStrictEqual, promisify } from 'node:util';
|
|
18
|
-
import { withFileLock, withFileLockSync } from './atomic-file.mjs';
|
|
17
|
+
import { withFileLock, withFileLockSync, writeJsonAtomicSync } from './atomic-file.mjs';
|
|
19
18
|
import { resolvePluginData } from './plugin-paths.mjs';
|
|
20
19
|
|
|
21
20
|
const execFileAsync = promisify(execFile);
|
|
@@ -338,24 +337,21 @@ function markerOwned(active) {
|
|
|
338
337
|
function writeMarker(active) {
|
|
339
338
|
try {
|
|
340
339
|
mkdirSync(active.markerDir, { recursive: true, mode: 0o700 });
|
|
341
|
-
|
|
340
|
+
// Flushed through the handle that wrote it: reopening the file read-only to
|
|
341
|
+
// fsync it is refused on Windows (EPERM), which reported a failure after
|
|
342
|
+
// the marker was already written.
|
|
343
|
+
writeJsonAtomicSync(
|
|
342
344
|
active.markerPath,
|
|
343
|
-
|
|
345
|
+
{
|
|
344
346
|
version: 1,
|
|
345
347
|
timestamp: new Date().toISOString(),
|
|
346
348
|
pid: process.pid,
|
|
347
349
|
ppid: process.ppid,
|
|
348
350
|
token: active.token,
|
|
349
351
|
processIdentity: active.processIdentity,
|
|
350
|
-
}
|
|
351
|
-
{
|
|
352
|
+
},
|
|
353
|
+
{ compact: true, mode: 0o600 }
|
|
352
354
|
);
|
|
353
|
-
const fd = openSync(active.markerPath, 'r');
|
|
354
|
-
try {
|
|
355
|
-
fsyncSync(fd);
|
|
356
|
-
} finally {
|
|
357
|
-
closeSync(fd);
|
|
358
|
-
}
|
|
359
355
|
return true;
|
|
360
356
|
} catch {
|
|
361
357
|
return false;
|