mixdog 1.3.1 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/package.json +8 -8
- package/scripts/native-release-status.mjs +238 -0
- package/scripts/verify-runtime-dependency-versions.mjs +158 -0
- package/src/agents/reviewer/AGENT.md +5 -7
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +9 -1
- package/src/defaults/skills/browser-use/SKILL.md +6 -3
- package/src/defaults/skills/local-provider/SKILL.md +33 -6
- package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
- package/src/defaults/skills/setup/SKILL.md +23 -1
- package/src/defaults/skills/setup/references/actions.md +50 -3
- package/src/defaults/skills/setup/references/surfaces.md +23 -3
- package/src/defaults/skills/setup-guide/SKILL.md +7 -3
- package/src/defaults/skills/visualize/SKILL.md +9 -6
- package/src/defaults/skills/visualize/references/craft.md +8 -1
- package/src/help.mjs +1 -1
- package/src/lib/keychain-cjs.cjs +4 -1
- package/src/lib/rules-builder.cjs +14 -7
- package/src/rules/agent-mode/AGENT_MODE.md +8 -5
- package/src/rules/agent-mode/MIXY.md +6 -8
- package/src/rules/lead/LEAD.md +7 -10
- package/src/rules/shared/10-tool-workflow.md +4 -0
- package/src/rules/shared/20-research.md +2 -0
- package/src/rules/shared/45-destructive.md +5 -1
- package/src/rules/shared/60-verification.md +6 -4
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
- package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
- package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
- package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
- package/src/runtime/agent/orchestrator/config.mjs +11 -0
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +9 -2
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
- package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +84 -32
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +38 -11
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +15 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +84 -42
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +16 -0
- package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
- package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
- package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
- package/src/runtime/agent/orchestrator/providers/registry.mjs +19 -0
- package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +13 -1
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
- package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +5 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +10 -1
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -6
- package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +52 -6
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +35 -13
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +8 -3
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -2
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +196 -45
- package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compact/continuation.mjs +15 -6
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +45 -19
- package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +44 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +185 -37
- package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
- package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +240 -0
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +102 -35
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +19 -9
- package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +199 -109
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/empty-nudge.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +62 -13
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +22 -6
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +33 -8
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/call-prep.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/manager/agent-listing-notice.mjs +102 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +27 -14
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +32 -1
- package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +115 -0
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +36 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -9
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +4 -9
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +14 -1
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +7 -3
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
- package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +52 -11
- package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +126 -12
- package/src/runtime/agent/orchestrator/session/runtime-user-context.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +16 -3
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +5 -4
- package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +140 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +15 -5
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +49 -1
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +80 -1
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
- package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +33 -1
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.d.mts +5 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +37 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +35 -5
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +47 -14
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +127 -20
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +557 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +10 -12
- package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +42 -23
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
- package/src/runtime/attachments/attachment-reference.mjs +155 -0
- package/src/runtime/attachments/legacy-office.mjs +110 -0
- package/src/runtime/attachments/limits.mjs +5 -1
- package/src/runtime/attachments/office-extract.mjs +3 -6
- package/src/runtime/attachments/pdf-extract.mjs +84 -10
- package/src/runtime/attachments/prompt-restore.mjs +70 -0
- package/src/runtime/attachments/store.mjs +109 -33
- package/src/runtime/browser-bridge/client.mjs +73 -20
- package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
- package/src/runtime/channels/lib/scheduler.mjs +14 -7
- package/src/runtime/channels/lib/state-file.mjs +15 -15
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
- package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
- package/src/runtime/channels/lib/worker-main.mjs +3 -2
- package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
- package/src/runtime/effort-judge/judge-client.mjs +66 -16
- package/src/runtime/effort-judge/model-install.mjs +47 -7
- package/src/runtime/local-provider/asset-installer.mjs +10 -4
- package/src/runtime/local-provider/catalog.mjs +5 -2
- package/src/runtime/local-provider/registered-models.mjs +64 -27
- package/src/runtime/local-provider/server.mjs +2 -3
- package/src/runtime/media/adapters/image-api.mjs +89 -0
- package/src/runtime/media/auth.mjs +64 -1
- package/src/runtime/media/catalog.mjs +66 -4
- package/src/runtime/media/jobs.mjs +4 -0
- package/src/runtime/media/lanes.mjs +46 -5
- package/src/runtime/media/media-usage.mjs +17 -0
- package/src/runtime/media/renditions.mjs +15 -15
- package/src/runtime/media/store.mjs +36 -35
- package/src/runtime/media/tool.mjs +8 -1
- package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
- package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
- package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +14 -2
- package/src/runtime/memory/lib/pg/process.mjs +8 -4
- package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
- package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
- package/src/runtime/office/core/journal.mjs +7 -14
- package/src/runtime/office/design/library/design-library-core.mjs +4 -16
- package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
- package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
- package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
- package/src/runtime/office/pdf/pdf-render.mjs +30 -18
- package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
- package/src/runtime/office/portable/portable-soffice.mjs +135 -29
- package/src/runtime/shared/agent-avatar-sprites.mjs +23 -8
- package/src/runtime/shared/agent-avatar.mjs +3 -2
- package/src/runtime/shared/agent-kind-migration.mjs +200 -0
- package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
- package/src/runtime/shared/agent-message-envelope.mjs +44 -0
- package/src/runtime/shared/agent-reap-state.d.mts +4 -0
- package/src/runtime/shared/agent-room-host.d.mts +31 -0
- package/src/runtime/shared/agent-room-host.mjs +58 -0
- package/src/runtime/shared/agent-store.mjs +24 -7
- package/src/runtime/shared/agent-task-starts.mjs +27 -0
- package/src/runtime/shared/atomic-file.d.mts +50 -0
- package/src/runtime/shared/atomic-file.mjs +394 -132
- package/src/runtime/shared/background-tasks.mjs +7 -4
- package/src/runtime/shared/bot-hops.mjs +35 -0
- package/src/runtime/shared/bot-split-migration.mjs +1 -1
- package/src/runtime/shared/bounded-download.mjs +4 -2
- package/src/runtime/shared/config.mjs +39 -9
- package/src/runtime/shared/defaults-separation.mjs +6 -2
- package/src/runtime/shared/delegated-approval-host.mjs +74 -0
- package/src/runtime/shared/err-text.mjs +4 -0
- package/src/runtime/shared/error-presentation.mjs +22 -4
- package/src/runtime/shared/file-has-data.mjs +28 -0
- package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
- package/src/runtime/shared/pending-agent-work.mjs +54 -0
- package/src/runtime/shared/process-lifecycle.mjs +8 -12
- package/src/runtime/shared/projects.mjs +50 -41
- package/src/runtime/shared/provider-accounts.mjs +17 -12
- package/src/runtime/shared/redact-directories.mjs +18 -0
- package/src/runtime/shared/remove-directory.mjs +28 -0
- package/src/runtime/shared/routine-trigger.d.mts +6 -1
- package/src/runtime/shared/routine-trigger.mjs +14 -6
- package/src/runtime/shared/run-child.d.mts +3 -0
- package/src/runtime/shared/run-child.mjs +24 -3
- package/src/runtime/shared/session-baseline-store.mjs +8 -2
- package/src/runtime/shared/session-sidebar-host.d.mts +11 -2
- package/src/runtime/shared/session-sidebar-host.mjs +16 -3
- package/src/runtime/shared/singleton-owner.mjs +7 -6
- package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
- package/src/runtime/shared/staged-update.mjs +4 -2
- package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
- package/src/runtime/shared/tool-execution-contract.mjs +10 -0
- package/src/runtime/shared/tool-work-units.mjs +12 -9
- package/src/runtime/shared/transcript-metadata.mjs +28 -3
- package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
- package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
- package/src/runtime/shared/turn-snapshot.mjs +277 -124
- package/src/runtime/shared/user-cwd.mjs +9 -4
- package/src/runtime/shared/user-data-guard.mjs +92 -61
- package/src/runtime/shared/utf8-cut.mjs +10 -0
- package/src/runtime/shared/worktrees/activity-gate.mjs +251 -61
- package/src/runtime/shared/worktrees/contract.mjs +11 -3
- package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
- package/src/runtime/shared/worktrees/manager.mjs +419 -52
- package/src/runtime/shared/worktrees/registry.mjs +29 -27
- package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
- package/src/runtime/tidy/core-install.mjs +26 -4
- package/src/runtime/tidy/install.mjs +91 -23
- package/src/runtime/tidy/process.mjs +41 -8
- package/src/runtime/tidy/resolve.mjs +10 -7
- package/src/runtime/web-search/lib/config.mjs +4 -16
- package/src/session-runtime/agent-home-delivery.mjs +542 -204
- package/src/session-runtime/agent-profile-changes.mjs +16 -0
- package/src/session-runtime/agent-room-delivery.mjs +81 -35
- package/src/session-runtime/agent-task-sessions.mjs +811 -179
- package/src/session-runtime/agent-tool-routing.mjs +1 -0
- package/src/session-runtime/boot/apis.mjs +48 -24
- package/src/session-runtime/boot/config.mjs +1 -0
- package/src/session-runtime/boot/facade.mjs +16 -7
- package/src/session-runtime/boot/infrastructure.mjs +14 -2
- package/src/session-runtime/boot/providers.mjs +2 -2
- package/src/session-runtime/boot/shared.mjs +12 -6
- package/src/session-runtime/boot/tools.mjs +23 -3
- package/src/session-runtime/bot-status.mjs +432 -0
- package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
- package/src/session-runtime/config-lifecycle.mjs +60 -55
- package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
- package/src/session-runtime/held-requests.mjs +31 -33
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +24 -26
- package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
- package/src/session-runtime/lifecycle/teardown.mjs +11 -3
- package/src/session-runtime/mcp-glue/connect.mjs +100 -186
- package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
- package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
- package/src/session-runtime/media-api.mjs +5 -2
- package/src/session-runtime/model-recency.mjs +1 -0
- package/src/session-runtime/model-route/route-tuning.mjs +4 -1
- package/src/session-runtime/model-route/set-route.mjs +61 -1
- package/src/session-runtime/owned-automation-run.mjs +8 -1
- package/src/session-runtime/provider-models/row-hydration.mjs +8 -1
- package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
- package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
- package/src/session-runtime/route-resolution.mjs +16 -8
- package/src/session-runtime/route-state.mjs +1 -59
- package/src/session-runtime/routine-home-turn.mjs +5 -3
- package/src/session-runtime/runtime-bootstrap.mjs +5 -3
- package/src/session-runtime/runtime-paths.mjs +11 -5
- package/src/session-runtime/runtime-review-api.mjs +4 -1
- package/src/session-runtime/runtime-tunables.mjs +6 -0
- package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
- package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
- package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
- package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +34 -19
- package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +7 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +8 -5
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
- package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
- package/src/session-runtime/services/agent-tool.mjs +6 -0
- package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
- package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
- package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
- package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
- package/src/session-runtime/services/plugin-admin.mjs +26 -26
- package/src/session-runtime/services/provider-admin.mjs +26 -6
- package/src/session-runtime/services/schedule-access.mjs +33 -4
- package/src/session-runtime/services/seeds.mjs +4 -3
- package/src/session-runtime/services/usage-dashboard.mjs +1 -1
- package/src/session-runtime/services/usage-stats-model.mjs +2 -1
- package/src/session-runtime/services/worktree-access.mjs +27 -0
- package/src/session-runtime/session-create/materialize.mjs +2 -3
- package/src/session-runtime/session-create/session-options.mjs +15 -2
- package/src/session-runtime/session-hooks.mjs +9 -17
- package/src/session-runtime/settings-builtin-tools-api.mjs +65 -1
- package/src/session-runtime/settings-compaction-api.mjs +86 -39
- package/src/session-runtime/setup-tool/executor.mjs +12 -2
- package/src/session-runtime/setup-tool/settings-contract.mjs +34 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
- package/src/session-runtime/skills-api.mjs +12 -5
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +349 -0
- package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
- package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
- package/src/session-runtime/tool-policy/policy-stage.mjs +183 -0
- package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
- package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
- package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
- package/src/session-runtime/tool-policy-refresh.mjs +48 -369
- package/src/session-runtime/tool-surface.mjs +10 -4
- package/src/session-runtime/turn/session-ops.mjs +8 -0
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
- package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
- package/src/session-runtime/turn/turn-run.mjs +7 -2
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +527 -333
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +85 -63
- package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
- package/src/session-runtime/workspace/transition-steps.mjs +1 -18
- package/src/session-runtime/workspace/transitions.mjs +279 -67
- package/src/session-runtime/workspace/workspace-state.mjs +7 -6
- package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
- package/src/standalone/daemon-agent-control.mjs +4 -1
- package/src/standalone/daemon-lag-profiler.mjs +3 -1
- package/src/standalone/daemon-session-control.mjs +173 -15
- package/src/standalone/daemon-telemetry.mjs +9 -0
- package/src/standalone/daemon.mjs +33 -10
- package/src/standalone/local-session-runtime.mjs +33 -16
- package/src/standalone/session-client.mjs +7 -2
- package/src/standalone/session-protocol.mjs +8 -0
- package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
- package/src/standalone/session-service/agent-route-follow.mjs +69 -0
- package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
- package/src/standalone/session-service.mjs +21 -0
- package/src/standalone/session-state-projection.mjs +19 -9
- package/src/standalone/session-tool-output.mjs +36 -9
- package/src/standalone/staged-import.mjs +47 -0
- package/src/tui/App.jsx +2 -0
- package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
- package/src/tui/app/message-selector.mjs +15 -2
- package/src/tui/app/model-options.mjs +16 -2
- package/src/tui/app/model-picker.mjs +2 -1
- package/src/tui/app/route-pickers.mjs +3 -1
- package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
- package/src/tui/app/slash-commands.mjs +1 -1
- package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
- package/src/tui/dist/index.mjs +617 -369
- package/src/tui/paste-attachments.mjs +0 -1
- package/src/tui/paste-text-policy.mjs +4 -5
- package/src/tui/prompt-history-store.mjs +16 -21
- package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
- package/src/tui/session/context-state.mjs +4 -0
- package/src/tui/session/notice-surface.mjs +3 -0
- package/src/tui/session/session-api/intake/abort.mjs +52 -18
- package/src/tui/session/session-api/intake/rewind.mjs +39 -4
- package/src/tui/session/session-api/media.mjs +2 -1
- package/src/tui/session/session-api/routes-agent-definitions.mjs +76 -0
- package/src/tui/session/session-api/routes-write.mjs +203 -0
- package/src/tui/session/session-api/routes.mjs +23 -247
- package/src/tui/session/session-api/settings.mjs +14 -1
- package/src/tui/session/session-flow/drain.mjs +28 -3
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
- package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
- package/src/tui/session/session-flow/steering.mjs +8 -3
- package/src/tui/session/tool-approval.mjs +142 -14
- package/src/tui/session/transcript-intake.mjs +2 -0
- package/src/tui/session/turn.mjs +23 -14
- package/src/tui/transcript-tool-failures.mjs +8 -2
- package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
- package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
- package/src/rules/agent-mode/ROUTINE.md +0 -6
- package/src/session-runtime/room-member-gate.mjs +0 -48
|
@@ -1,23 +1,15 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync } from 'node:fs';
|
|
1
2
|
import {
|
|
2
|
-
copyFileSync,
|
|
3
|
-
existsSync,
|
|
4
|
-
mkdirSync,
|
|
5
|
-
readdirSync,
|
|
6
|
-
readFileSync,
|
|
7
|
-
rmSync,
|
|
8
|
-
statSync,
|
|
9
|
-
writeFileSync,
|
|
10
|
-
} from 'node:fs';
|
|
11
|
-
import {
|
|
12
|
-
copyFile as copyFileP,
|
|
13
3
|
mkdir as mkdirP,
|
|
4
|
+
readFile as readFileP,
|
|
14
5
|
readdir as readdirP,
|
|
15
6
|
rm as rmP,
|
|
16
7
|
stat as statP,
|
|
17
8
|
} from 'node:fs/promises';
|
|
18
|
-
import { basename,
|
|
9
|
+
import { basename, join, resolve } from 'node:path';
|
|
19
10
|
import { homedir } from 'node:os';
|
|
20
11
|
import { createHash, randomUUID } from 'node:crypto';
|
|
12
|
+
import { writeFileAtomicAsync, writeFileAtomicSync, writeJsonAtomicSync } from './atomic-file.mjs';
|
|
21
13
|
import { isPlainObject } from './object.mjs';
|
|
22
14
|
import { mixdogHome } from './plugin-paths.mjs';
|
|
23
15
|
|
|
@@ -155,15 +147,27 @@ function safeReason(reason) {
|
|
|
155
147
|
);
|
|
156
148
|
}
|
|
157
149
|
|
|
158
|
-
/** instanceId of this data dir's init marker; null when absent
|
|
150
|
+
/** instanceId of this data dir's init marker; null only when absent. */
|
|
159
151
|
function readInitInstanceId(dataDir) {
|
|
160
152
|
if (!dataDir) return null;
|
|
153
|
+
const path = join(dataDir, INIT_MARKER);
|
|
154
|
+
let raw;
|
|
161
155
|
try {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
156
|
+
raw = readFileSync(path, 'utf8');
|
|
157
|
+
} catch (error) {
|
|
158
|
+
if (error?.code === 'ENOENT') return null;
|
|
159
|
+
throw error;
|
|
166
160
|
}
|
|
161
|
+
let marker;
|
|
162
|
+
try {
|
|
163
|
+
marker = JSON.parse(raw);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
throw new SyntaxError(`[user-data] invalid initialization marker at ${path}`, { cause: error });
|
|
166
|
+
}
|
|
167
|
+
if (typeof marker?.instanceId !== 'string' || !marker.instanceId) {
|
|
168
|
+
throw new SyntaxError(`[user-data] initialization marker at ${path} must have a non-empty instanceId`);
|
|
169
|
+
}
|
|
170
|
+
return marker.instanceId;
|
|
167
171
|
}
|
|
168
172
|
|
|
169
173
|
export function hasUserDataInitMarker(dataDir) {
|
|
@@ -379,14 +383,26 @@ function copyTree(src, dst, copied) {
|
|
|
379
383
|
return;
|
|
380
384
|
}
|
|
381
385
|
if (!st.isFile()) return;
|
|
382
|
-
|
|
383
|
-
|
|
386
|
+
// A copy goes through an fsynced handle: a plain copy can leave a backup that
|
|
387
|
+
// is NUL exactly when a crash makes it needed.
|
|
388
|
+
writeFileAtomicSync(dst, readFileSync(src), { mode: st.mode & 0o777 });
|
|
384
389
|
copied.push(dst);
|
|
385
390
|
}
|
|
386
391
|
|
|
387
|
-
|
|
392
|
+
// Only a published manifest makes a backup count toward retention.
|
|
393
|
+
const manifestPath = (scopeDir, name) => join(scopeDir, name, BACKUP_MANIFEST);
|
|
394
|
+
|
|
395
|
+
/** Prune one data dir's scope only; other scopes, legacy entries and
|
|
396
|
+
* unfinished reservations stay. */
|
|
388
397
|
function pruneBackups(scopeDir, names, own) {
|
|
389
|
-
|
|
398
|
+
const completed = names.filter((name) => {
|
|
399
|
+
try {
|
|
400
|
+
return statSync(manifestPath(scopeDir, name)).isFile();
|
|
401
|
+
} catch {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
for (const name of backupsToPrune(completed, own)) {
|
|
390
406
|
try {
|
|
391
407
|
rmSync(join(scopeDir, name), { recursive: true, force: true });
|
|
392
408
|
} catch {}
|
|
@@ -394,22 +410,20 @@ function pruneBackups(scopeDir, names, own) {
|
|
|
394
410
|
}
|
|
395
411
|
|
|
396
412
|
/**
|
|
397
|
-
* Create
|
|
398
|
-
*
|
|
413
|
+
* Create a missing init marker; return its instanceId, or null if the data dir
|
|
414
|
+
* is gone or the missing marker cannot be created. Never creates the data dir.
|
|
415
|
+
* Read/validation failures throw; an existing marker is never replaced.
|
|
399
416
|
*/
|
|
400
417
|
export function markUserDataInitialized(dataDir) {
|
|
401
418
|
const existing = readInitInstanceId(dataDir);
|
|
402
419
|
if (existing || !dataDir || !existsSync(dataDir)) return existing;
|
|
403
420
|
try {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
)}\n`,
|
|
411
|
-
// An unreadable marker is replaced; a concurrent creator's valid one wins.
|
|
412
|
-
{ encoding: 'utf8', flag: existsSync(join(dataDir, INIT_MARKER)) ? 'w' : 'wx' }
|
|
421
|
+
const marker = join(dataDir, INIT_MARKER);
|
|
422
|
+
writeJsonAtomicSync(
|
|
423
|
+
marker,
|
|
424
|
+
{ instanceId: randomUUID(), dataDir: resolve(dataDir), createdAt: new Date().toISOString() },
|
|
425
|
+
// Another creator may have published since the absent read; it wins.
|
|
426
|
+
{ createOnly: true }
|
|
413
427
|
);
|
|
414
428
|
} catch {}
|
|
415
429
|
return readInitInstanceId(dataDir);
|
|
@@ -427,8 +441,8 @@ export function ensureUserDataInitialized(dataDir) {
|
|
|
427
441
|
if (!dataDir) return;
|
|
428
442
|
const key = normalizeDataDir(dataDir);
|
|
429
443
|
if (ensuredDataDirs.has(key)) return;
|
|
430
|
-
ensuredDataDirs.add(key);
|
|
431
444
|
if (!hasUserDataInitMarker(dataDir)) backupUserData(dataDir, 'initial-snapshot');
|
|
445
|
+
ensuredDataDirs.add(key);
|
|
432
446
|
}
|
|
433
447
|
|
|
434
448
|
function skipBackups() {
|
|
@@ -439,15 +453,12 @@ function skipBackups() {
|
|
|
439
453
|
// manifest goes last, so an interrupted copy is never restorable.
|
|
440
454
|
function finishBackup(dataDir, backupDir, reason) {
|
|
441
455
|
const instanceId = markUserDataInitialized(dataDir);
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
)}\n`,
|
|
449
|
-
'utf8'
|
|
450
|
-
);
|
|
456
|
+
writeJsonAtomicSync(join(backupDir, BACKUP_MANIFEST), {
|
|
457
|
+
dataDir: resolve(dataDir),
|
|
458
|
+
instanceId,
|
|
459
|
+
reason,
|
|
460
|
+
createdAt: new Date().toISOString(),
|
|
461
|
+
});
|
|
451
462
|
restoreLookupCache.delete(normalizeDataDir(dataDir));
|
|
452
463
|
legacyEvidenceCache.delete(normalizeDataDir(dataDir));
|
|
453
464
|
}
|
|
@@ -461,16 +472,24 @@ export function backupUserData(dataDir, reason = 'snapshot', { dirs = [] } = {})
|
|
|
461
472
|
const scopeDir = scopeDirFor(dataDir);
|
|
462
473
|
const { backupDir, names } = reserveBackupDir(scopeDir, reason);
|
|
463
474
|
const copied = [];
|
|
464
|
-
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
const
|
|
470
|
-
|
|
475
|
+
try {
|
|
476
|
+
for (const rel of USER_DATA_FILES) {
|
|
477
|
+
const src = join(dataDir, rel);
|
|
478
|
+
if (existsSync(src)) copyTree(src, join(backupDir, rel), copied);
|
|
479
|
+
}
|
|
480
|
+
for (const rel of [...USER_DATA_DIRS, ...dirs]) {
|
|
481
|
+
const src = join(dataDir, rel);
|
|
482
|
+
if (existsSync(src)) copyTree(src, join(backupDir, rel), copied);
|
|
483
|
+
}
|
|
484
|
+
if (copied.length > 0) finishBackup(dataDir, backupDir, reason);
|
|
485
|
+
} catch (error) {
|
|
486
|
+
// Drop only this call's reservation; a failed cleanup must not mask the cause.
|
|
487
|
+
try {
|
|
488
|
+
rmSync(backupDir, { recursive: true, force: true });
|
|
489
|
+
} catch {}
|
|
490
|
+
throw error;
|
|
471
491
|
}
|
|
472
492
|
if (copied.length > 0) {
|
|
473
|
-
finishBackup(dataDir, backupDir, reason);
|
|
474
493
|
pruneBackups(scopeDir, names, basename(backupDir));
|
|
475
494
|
if (process.env.MIXDOG_SETUP_QUIET !== '1') {
|
|
476
495
|
process.stderr.write(`[user-data-backup] ${reason}: copied ${copied.length} file(s) to ${backupDir}\n`);
|
|
@@ -494,13 +513,18 @@ async function copyTreeAsync(src, dst, copied) {
|
|
|
494
513
|
return;
|
|
495
514
|
}
|
|
496
515
|
if (!st.isFile()) return;
|
|
497
|
-
await
|
|
498
|
-
await copyFileP(src, dst);
|
|
516
|
+
await writeFileAtomicAsync(dst, await readFileP(src), { mode: st.mode & 0o777 });
|
|
499
517
|
copied.push(dst);
|
|
500
518
|
}
|
|
501
519
|
|
|
502
520
|
async function pruneBackupsAsync(scopeDir, names, own) {
|
|
503
|
-
|
|
521
|
+
const completed = [];
|
|
522
|
+
for (const name of names) {
|
|
523
|
+
try {
|
|
524
|
+
if ((await statP(manifestPath(scopeDir, name))).isFile()) completed.push(name);
|
|
525
|
+
} catch {}
|
|
526
|
+
}
|
|
527
|
+
for (const name of backupsToPrune(completed, own)) {
|
|
504
528
|
try {
|
|
505
529
|
await rmP(join(scopeDir, name), { recursive: true, force: true });
|
|
506
530
|
} catch {}
|
|
@@ -513,16 +537,23 @@ export async function backupUserDataAsync(dataDir, reason = 'snapshot') {
|
|
|
513
537
|
const scopeDir = scopeDirFor(dataDir);
|
|
514
538
|
const { backupDir, names } = await reserveBackupDirAsync(scopeDir, reason);
|
|
515
539
|
const copied = [];
|
|
516
|
-
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
const
|
|
522
|
-
|
|
540
|
+
try {
|
|
541
|
+
for (const rel of USER_DATA_FILES) {
|
|
542
|
+
const src = join(dataDir, rel);
|
|
543
|
+
if (existsSync(src)) await copyTreeAsync(src, join(backupDir, rel), copied);
|
|
544
|
+
}
|
|
545
|
+
for (const rel of USER_DATA_DIRS) {
|
|
546
|
+
const src = join(dataDir, rel);
|
|
547
|
+
if (existsSync(src)) await copyTreeAsync(src, join(backupDir, rel), copied);
|
|
548
|
+
}
|
|
549
|
+
if (copied.length > 0) finishBackup(dataDir, backupDir, reason);
|
|
550
|
+
} catch (error) {
|
|
551
|
+
try {
|
|
552
|
+
await rmP(backupDir, { recursive: true, force: true });
|
|
553
|
+
} catch {}
|
|
554
|
+
throw error;
|
|
523
555
|
}
|
|
524
556
|
if (copied.length > 0) {
|
|
525
|
-
finishBackup(dataDir, backupDir, reason);
|
|
526
557
|
await pruneBackupsAsync(scopeDir, names, basename(backupDir));
|
|
527
558
|
if (process.env.MIXDOG_SETUP_QUIET !== '1') {
|
|
528
559
|
process.stderr.write(`[user-data-backup] ${reason}: copied ${copied.length} file(s) to ${backupDir}\n`);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `text` without its trailing U+FFFD characters: what a byte cut through a
|
|
3
|
+
* multi-byte sequence leaves behind. A loop, not `/\uFFFD+$/`, which is
|
|
4
|
+
* quadratic on a long run of them.
|
|
5
|
+
*/
|
|
6
|
+
export function stripTrailingReplacementChars(text) {
|
|
7
|
+
let end = text.length;
|
|
8
|
+
while (end > 0 && text[end - 1] === '\uFFFD') end -= 1;
|
|
9
|
+
return end === text.length ? text : text.slice(0, end);
|
|
10
|
+
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
2
3
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
3
|
-
import { readFileSync, renameSync } from 'node:fs';
|
|
4
4
|
import { isAbsolute, join } from 'node:path';
|
|
5
|
-
import { withFileLockSync,
|
|
5
|
+
import { parseJsonText, quarantinePath, withFileLockSync, writeFileAtomicSync } from '../atomic-file.mjs';
|
|
6
6
|
import { isPidAliveOrUnknown } from '../pid-liveness.mjs';
|
|
7
7
|
import { findGitLayout } from './git-head.mjs';
|
|
8
8
|
import { canonicalPath, isWithin, samePath } from './paths.mjs';
|
|
9
9
|
import { worktreeError, worktreeRefusal } from './contract.mjs';
|
|
10
|
-
import {
|
|
10
|
+
import { readRegistrySync } from './registry.mjs';
|
|
11
11
|
|
|
12
12
|
const FILE = 'mixdog-workspace-admission.json';
|
|
13
13
|
const overlaps = (a, b) => isWithin(a, b) || isWithin(b, a);
|
|
@@ -24,97 +24,277 @@ export function isExternalCheckout(folder) {
|
|
|
24
24
|
if (!layout || samePath(layout.gitDir, layout.commonDir)) return false;
|
|
25
25
|
let entries;
|
|
26
26
|
try {
|
|
27
|
-
entries =
|
|
28
|
-
} catch
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
entries = readRegistrySync().entries;
|
|
28
|
+
} catch {
|
|
29
|
+
// A damaged registry is kept aside and reads as a restored or empty one;
|
|
30
|
+
// only a real I/O failure (access denied, in use) lands here.
|
|
31
|
+
throw worktreeRefusal('workspace-admission-unreadable');
|
|
31
32
|
}
|
|
32
33
|
return !Object.values(entries).some(
|
|
33
34
|
(record) => typeof record?.path === 'string' && samePath(record.path, layout.topLevel)
|
|
34
35
|
);
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
/*
|
|
39
|
+
* The admission ledger lists every turn, command and management lease of a
|
|
40
|
+
* repository. It is kept in two copies with the same content in the Git
|
|
41
|
+
* common directory: `mixdog-workspace-admission.json` and its `.management`
|
|
42
|
+
* mirror. A grant returns its release only once both copies hold it, and a
|
|
43
|
+
* release is forgotten only once both copies dropped it, so damage to either
|
|
44
|
+
* copy alone never loses an acknowledged lease. `recoveredAt` marks a ledger
|
|
45
|
+
* that may be missing leases because no complete copy survived: readers still
|
|
46
|
+
* run, management refuses, and only a quiescent reset that removes both
|
|
47
|
+
* copies clears it (docs/git-github-integration.md). Time, healing and
|
|
48
|
+
* releases never do.
|
|
49
|
+
*/
|
|
50
|
+
const VERSION = 2;
|
|
51
|
+
const isLease = (lease) =>
|
|
52
|
+
!!lease &&
|
|
53
|
+
['turn', 'command', 'management'].includes(lease.kind) &&
|
|
54
|
+
typeof lease.token === 'string' &&
|
|
55
|
+
Number.isSafeInteger(lease.pid) &&
|
|
56
|
+
lease.pid > 0 &&
|
|
57
|
+
typeof lease.path === 'string' &&
|
|
58
|
+
isAbsolute(lease.path);
|
|
59
|
+
// Only a pid PROVEN dead (ESRCH) drops its lease, whatever its kind; an
|
|
60
|
+
// unprobeable pid still counts as running. A live runtime keeps a command
|
|
61
|
+
// whose tree it cannot verify reserved under its own (live) pid until the
|
|
62
|
+
// tree is confirmed gone, so a lease that outlives every pid belongs to a
|
|
63
|
+
// restarted runtime that can no longer release it.
|
|
64
|
+
const aliveLease = (lease) => isPidAliveOrUnknown(lease.pid);
|
|
65
|
+
// A management hold shares one token across its folders, so a lease is
|
|
66
|
+
// identified by token and path.
|
|
67
|
+
const leaseKey = (lease) => `${lease.token}\0${lease.path}`;
|
|
68
|
+
const unverifiable = () => worktreeRefusal('workspace-admission-unreadable');
|
|
69
|
+
const copyPaths = (commonDir) => {
|
|
70
|
+
const primary = join(commonDir, FILE);
|
|
71
|
+
return [primary, `${primary}.management`];
|
|
72
|
+
};
|
|
73
|
+
const envMs = (name, fallback) => {
|
|
74
|
+
const value = Number(process.env[name]);
|
|
75
|
+
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
76
|
+
};
|
|
77
|
+
const healIntervalMs = () => envMs('MIXDOG_ADMISSION_HEAL_MS', 2_000);
|
|
78
|
+
|
|
79
|
+
// The leases this process holds, put back into a ledger that lost them, and
|
|
80
|
+
// the tokens it released or failed to grant whose removal from both copies is
|
|
81
|
+
// not confirmed yet (token -> common dir), retried until it is.
|
|
82
|
+
const held = new Map();
|
|
83
|
+
const pending = new Map();
|
|
84
|
+
let healTimer = null;
|
|
85
|
+
|
|
37
86
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* its place) is not an empty one and still refuses, as does one that moves
|
|
46
|
-
* aside unsuccessfully: an unreadable file is never overwritten.
|
|
87
|
+
* One copy as read under the ledger lock. A copy that cannot be read (access
|
|
88
|
+
* denied, in use, a directory) or that a newer build wrote refuses everything
|
|
89
|
+
* and is never written: neither may pass for an empty ledger. Anything else
|
|
90
|
+
* that is not a ledger, or holds an invalid lease record, is damaged; its
|
|
91
|
+
* valid records still count. A copy is complete when it accounts for every
|
|
92
|
+
* acknowledged lease: intact, not uncertain, and version 2 — or the version 1
|
|
93
|
+
* primary, since the version 1 mirror held management leases only.
|
|
47
94
|
*/
|
|
48
|
-
function
|
|
49
|
-
let
|
|
95
|
+
function readCopy(path, primary) {
|
|
96
|
+
let bytes;
|
|
50
97
|
try {
|
|
51
|
-
|
|
98
|
+
bytes = readFileSync(path);
|
|
52
99
|
} catch (error) {
|
|
53
|
-
if (error?.code === 'ENOENT') return {
|
|
54
|
-
throw
|
|
100
|
+
if (error?.code === 'ENOENT') return { path, leases: [] };
|
|
101
|
+
throw unverifiable();
|
|
55
102
|
}
|
|
103
|
+
let ledger = null;
|
|
56
104
|
try {
|
|
57
|
-
|
|
105
|
+
ledger = parseJsonText(bytes.toString('utf8'));
|
|
58
106
|
} catch {
|
|
107
|
+
// Unparseable: damaged below.
|
|
108
|
+
}
|
|
109
|
+
if (Number.isSafeInteger(ledger?.version) && ledger.version > VERSION) throw unverifiable();
|
|
110
|
+
if (![1, VERSION].includes(ledger?.version) || !Array.isArray(ledger.leases)) {
|
|
111
|
+
return { path, bytes, leases: [], damaged: true };
|
|
112
|
+
}
|
|
113
|
+
const leases = ledger.leases.filter(isLease);
|
|
114
|
+
const damaged = leases.length !== ledger.leases.length;
|
|
115
|
+
const uncertain = Object.hasOwn(ledger, 'recoveredAt');
|
|
116
|
+
const complete = !damaged && !uncertain && (ledger.version === VERSION || primary);
|
|
117
|
+
return { path, bytes, leases, damaged, uncertain, complete, recoveredAt: ledger.recoveredAt };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The ledger recovered from both copies: the union of their valid lease
|
|
122
|
+
* records (one copy lacking a lease never drops it), less proven-dead pids
|
|
123
|
+
* and this process's pending removals, plus the leases this process holds.
|
|
124
|
+
* It stays uncertain when either copy is, and becomes so when a copy exists
|
|
125
|
+
* but none is complete. Both copies missing reads as a new repository.
|
|
126
|
+
*/
|
|
127
|
+
function recover(commonDir, copies) {
|
|
128
|
+
const own = [...held.values()].filter((entry) => entry.commonDir === commonDir).map((entry) => entry.lease);
|
|
129
|
+
const leases = new Map();
|
|
130
|
+
for (const lease of [...copies.flatMap((copy) => copy.leases), ...own]) {
|
|
131
|
+
const key = leaseKey(lease);
|
|
132
|
+
if (!leases.has(key) && pending.get(lease.token) !== commonDir && aliveLease(lease)) leases.set(key, lease);
|
|
133
|
+
}
|
|
134
|
+
const ledger = { version: VERSION, leases: [...leases.values()] };
|
|
135
|
+
const uncertain =
|
|
136
|
+
copies.some((copy) => copy.uncertain) ||
|
|
137
|
+
(copies.some((copy) => copy.bytes) && !copies.some((copy) => copy.complete));
|
|
138
|
+
if (uncertain) ledger.recoveredAt = copies.map((copy) => copy.recoveredAt).find(Number.isFinite) ?? Date.now();
|
|
139
|
+
return ledger;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* #117: the bytes of a damaged copy are copied aside as `<copy>.corrupt-<ts>`
|
|
144
|
+
* before it is rewritten. If they cannot be kept, nothing is written and
|
|
145
|
+
* everything refuses.
|
|
146
|
+
*/
|
|
147
|
+
function keepDamagedAside(copies) {
|
|
148
|
+
for (const copy of copies.filter((each) => each.damaged)) {
|
|
149
|
+
const aside = quarantinePath(copy.path);
|
|
59
150
|
try {
|
|
60
|
-
|
|
151
|
+
writeFileAtomicSync(aside, copy.bytes);
|
|
61
152
|
} catch {
|
|
62
|
-
throw
|
|
153
|
+
throw unverifiable();
|
|
63
154
|
}
|
|
64
|
-
|
|
155
|
+
process.stderr.write(`[state] ${copy.path} is damaged; kept as ${aside}\n`);
|
|
65
156
|
}
|
|
66
157
|
}
|
|
67
158
|
|
|
68
|
-
|
|
69
|
-
|
|
159
|
+
/** Writes the primary, then the mirror; a copy that already holds the ledger is left alone. */
|
|
160
|
+
function publish(copies, ledger) {
|
|
161
|
+
const text = Buffer.from(`${JSON.stringify(ledger, null, 2)}\n`);
|
|
162
|
+
for (const copy of copies) {
|
|
163
|
+
if (copy.bytes?.equals(text)) continue;
|
|
164
|
+
copy.bytes = null; // Unknown until the write lands.
|
|
165
|
+
writeFileAtomicSync(copy.path, text);
|
|
166
|
+
copy.bytes = text;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function settle(tokens) {
|
|
171
|
+
for (const token of tokens) pending.delete(token);
|
|
172
|
+
syncHealTimer();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Runs `update(leases, { unknown })` on the recovered ledger under the
|
|
177
|
+
* primary copy's lock and publishes the result to both copies before
|
|
178
|
+
* returning. `unknown`: the ledger may be missing leases. A refused update
|
|
179
|
+
* still publishes the repaired ledger. A grant that cannot be published is
|
|
180
|
+
* refused: its leases become pending removals and the ledger without them is
|
|
181
|
+
* published again; whatever that leaves behind is withdrawn by the next
|
|
182
|
+
* mutation or heal tick, which also blocks every later grant until it lands.
|
|
183
|
+
*/
|
|
184
|
+
function mutate(commonDir, update = () => {}) {
|
|
185
|
+
const paths = copyPaths(commonDir);
|
|
70
186
|
return withFileLockSync(
|
|
71
|
-
`${
|
|
187
|
+
`${paths[0]}.lock`,
|
|
72
188
|
() => {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
189
|
+
const copies = paths.map((path, index) => readCopy(path, index === 0));
|
|
190
|
+
keepDamagedAside(copies);
|
|
191
|
+
const withdrawn = [...pending].filter(([, dir]) => dir === commonDir).map(([token]) => token);
|
|
192
|
+
const base = recover(commonDir, copies);
|
|
193
|
+
const leases = [...base.leases];
|
|
194
|
+
let result;
|
|
195
|
+
let refusal = null;
|
|
196
|
+
try {
|
|
197
|
+
result = update(leases, { unknown: Object.hasOwn(base, 'recoveredAt') });
|
|
198
|
+
} catch (error) {
|
|
199
|
+
refusal = error;
|
|
200
|
+
}
|
|
201
|
+
const granted = refusal ? [] : leases.filter((lease) => !base.leases.includes(lease));
|
|
202
|
+
try {
|
|
203
|
+
publish(copies, refusal ? base : { ...base, leases });
|
|
204
|
+
settle(withdrawn);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
process.stderr.write(`[state] ${paths[0]} could not be published (${error?.message})\n`);
|
|
207
|
+
if (refusal) throw refusal;
|
|
208
|
+
for (const lease of granted) pending.set(lease.token, commonDir);
|
|
209
|
+
if (granted.length) {
|
|
210
|
+
try {
|
|
211
|
+
publish(copies, base);
|
|
212
|
+
settle([...withdrawn, ...granted.map((lease) => lease.token)]);
|
|
213
|
+
} catch {
|
|
214
|
+
syncHealTimer();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
throw unverifiable();
|
|
89
218
|
}
|
|
90
|
-
|
|
91
|
-
// unprobeable pid still counts as running. A live runtime keeps a command
|
|
92
|
-
// whose tree it cannot verify reserved under its own (live) pid until the
|
|
93
|
-
// tree is confirmed gone, so a lease that outlives every pid belongs to a
|
|
94
|
-
// restarted runtime that can no longer release it.
|
|
95
|
-
state.leases = state.leases.filter((lease) => isPidAliveOrUnknown(lease.pid));
|
|
96
|
-
const result = update(state.leases);
|
|
97
|
-
writeJsonAtomicSync(path, state, { lock: false });
|
|
219
|
+
if (refusal) throw refusal;
|
|
98
220
|
return result;
|
|
99
221
|
},
|
|
100
222
|
{ strictOwner: true }
|
|
101
223
|
);
|
|
102
224
|
}
|
|
103
225
|
|
|
226
|
+
// Without the lock, a cheap check that both copies are the same current
|
|
227
|
+
// ledger holding every live lease this process holds there and none of the
|
|
228
|
+
// tokens it is withdrawing. Repair, never proof that the ledger is complete.
|
|
229
|
+
function inSync(commonDir) {
|
|
230
|
+
let ledger;
|
|
231
|
+
try {
|
|
232
|
+
const [primary, mirror] = copyPaths(commonDir).map((path) => readFileSync(path));
|
|
233
|
+
if (!primary.equals(mirror)) return false;
|
|
234
|
+
ledger = parseJsonText(primary.toString('utf8'));
|
|
235
|
+
} catch {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
if (ledger?.version !== VERSION || !Array.isArray(ledger.leases)) return false;
|
|
239
|
+
const stored = new Set(ledger.leases.filter(isLease).map(leaseKey));
|
|
240
|
+
return (
|
|
241
|
+
[...held].every(([key, entry]) => entry.commonDir !== commonDir || stored.has(key) || !aliveLease(entry.lease)) &&
|
|
242
|
+
!ledger.leases.some((lease) => pending.get(lease?.token) === commonDir)
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function healHeld() {
|
|
247
|
+
const dirs = new Set([...[...held.values()].map((entry) => entry.commonDir), ...pending.values()]);
|
|
248
|
+
for (const commonDir of dirs) {
|
|
249
|
+
if (inSync(commonDir)) continue;
|
|
250
|
+
try {
|
|
251
|
+
mutate(commonDir);
|
|
252
|
+
} catch {
|
|
253
|
+
// Retried on the next tick.
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
syncHealTimer();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function syncHealTimer() {
|
|
260
|
+
if (held.size || pending.size) {
|
|
261
|
+
healTimer ??= setInterval(healHeld, healIntervalMs());
|
|
262
|
+
healTimer.unref?.();
|
|
263
|
+
} else if (healTimer) {
|
|
264
|
+
clearInterval(healTimer);
|
|
265
|
+
healTimer = null;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function track(commonDir, lease) {
|
|
270
|
+
held.set(leaseKey(lease), { commonDir, lease });
|
|
271
|
+
syncHealTimer();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Forgotten as held first, so nothing puts the lease back; its token stays
|
|
275
|
+
// pending until both copies dropped it.
|
|
104
276
|
function release(commonDir, token) {
|
|
105
277
|
let done = false;
|
|
106
278
|
return () => {
|
|
107
279
|
if (done) return;
|
|
108
|
-
mutate(commonDir, (leases) => {
|
|
109
|
-
for (let i = leases.length - 1; i >= 0; i--) if (leases[i].token === token) leases.splice(i, 1);
|
|
110
|
-
});
|
|
111
280
|
done = true;
|
|
281
|
+
for (const [key, entry] of held) if (entry.lease.token === token) held.delete(key);
|
|
282
|
+
pending.set(token, commonDir);
|
|
283
|
+
try {
|
|
284
|
+
mutate(commonDir);
|
|
285
|
+
} catch {
|
|
286
|
+
// Retried by the next mutation or heal tick.
|
|
287
|
+
}
|
|
288
|
+
syncHealTimer();
|
|
112
289
|
};
|
|
113
290
|
}
|
|
114
291
|
|
|
115
292
|
/**
|
|
116
293
|
* Readers share a checkout freely. Null means a management writer currently
|
|
117
|
-
* owns it; a reader in an external checkout is never held back.
|
|
294
|
+
* owns it; a reader in an external checkout is never held back. Throws
|
|
295
|
+
* `workspace-admission-unreadable` when the ledger cannot be read, was
|
|
296
|
+
* written by a newer build, or the grant could not be made durable in both
|
|
297
|
+
* copies: no work may run unrecorded.
|
|
118
298
|
*/
|
|
119
299
|
export function tryAdmitWorkspaceActivity(sessionId, cwd, { pid = process.pid, kind = 'turn' } = {}) {
|
|
120
300
|
if (!['turn', 'command'].includes(kind)) throw new TypeError('Invalid workspace reader kind.');
|
|
@@ -123,12 +303,15 @@ export function tryAdmitWorkspaceActivity(sessionId, cwd, { pid = process.pid, k
|
|
|
123
303
|
const external = isExternalCheckout(cwd);
|
|
124
304
|
const path = canonicalPath(cwd);
|
|
125
305
|
const token = randomUUID();
|
|
306
|
+
const lease = { kind, sessionId, pid, token, path };
|
|
126
307
|
const admitted = mutate(layout.commonDir, (leases) => {
|
|
127
|
-
if (!external && leases.some((
|
|
128
|
-
leases.push(
|
|
308
|
+
if (!external && leases.some((other) => other.kind === 'management' && overlaps(other.path, path))) return false;
|
|
309
|
+
leases.push(lease);
|
|
129
310
|
return true;
|
|
130
311
|
});
|
|
131
|
-
|
|
312
|
+
if (!admitted) return null;
|
|
313
|
+
track(layout.commonDir, lease);
|
|
314
|
+
return release(layout.commonDir, token);
|
|
132
315
|
}
|
|
133
316
|
|
|
134
317
|
export async function admitWorkspaceActivity(sessionId, cwd, signal, options) {
|
|
@@ -166,17 +349,24 @@ export function holdWorkspaceActivity(folders, { sessionId = null } = {}) {
|
|
|
166
349
|
try {
|
|
167
350
|
for (const [commonDir, paths] of [...groups].sort(([a], [b]) => a.localeCompare(b))) {
|
|
168
351
|
const token = randomUUID();
|
|
169
|
-
|
|
352
|
+
const taken = [];
|
|
353
|
+
mutate(commonDir, (leases, { unknown }) => {
|
|
170
354
|
const conflicts = leases.filter(
|
|
171
355
|
(lease) =>
|
|
172
356
|
!(sessionId && lease.sessionId === sessionId && lease.pid === process.pid) &&
|
|
173
357
|
paths.some(({ path }) => overlaps(path, lease.path))
|
|
174
358
|
);
|
|
175
359
|
if (conflicts.length) throw worktreeRefusal('folder-in-use');
|
|
360
|
+
// The ledger may be missing a live foreign lease: refused until reset.
|
|
361
|
+
if (unknown) throw unverifiable();
|
|
176
362
|
for (const { path, external } of paths) {
|
|
177
|
-
if (
|
|
363
|
+
if (external) continue;
|
|
364
|
+
const lease = { kind: 'management', pid: process.pid, token, path };
|
|
365
|
+
leases.push(lease);
|
|
366
|
+
taken.push(lease);
|
|
178
367
|
}
|
|
179
368
|
});
|
|
369
|
+
for (const lease of taken) track(commonDir, lease);
|
|
180
370
|
releases.push(release(commonDir, token));
|
|
181
371
|
}
|
|
182
372
|
} catch (error) {
|