mixdog 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +52 -9
- package/README.md +8 -4
- package/package.json +2 -2
- package/scripts/native-release-status.mjs +238 -0
- package/src/defaults/shipped-history.json +8 -1
- package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
- package/src/defaults/skills/setup/SKILL.md +23 -1
- package/src/defaults/skills/setup/references/actions.md +54 -4
- package/src/defaults/skills/setup/references/surfaces.md +23 -3
- package/src/defaults/skills/setup-guide/SKILL.md +17 -10
- 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/output-styles/common.md +13 -8
- package/src/rules/agent-mode/AGENT_MODE.md +9 -6
- package/src/rules/agent-mode/MIXY.md +11 -6
- package/src/rules/lead/LEAD.md +2 -2
- package/src/rules/shared/10-tool-workflow.md +4 -0
- package/src/rules/shared/20-research.md +2 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
- package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
- package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
- package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
- package/src/runtime/agent/orchestrator/config.mjs +11 -0
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +81 -16
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -4
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
- package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +80 -31
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +67 -38
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +7 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +34 -16
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +19 -1
- package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/lib/tool-call-wire-ids.mjs +65 -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-retry-profile.mjs +8 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +10 -2
- 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-responses-input.mjs +3 -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/provider-error-kind.mjs +14 -2
- package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +17 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
- package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +5 -5
- package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +12 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +17 -6
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +41 -10
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +6 -4
- package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +21 -2
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +149 -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/execution-tail.mjs +14 -11
- package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +68 -18
- package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
- package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +230 -0
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +156 -25
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +21 -9
- package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +189 -105
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +111 -9
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +11 -14
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +38 -5
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +46 -4
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +18 -8
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
- package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +99 -0
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +50 -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-lifecycle.mjs +62 -47
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +6 -8
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +12 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
- package/src/runtime/agent/orchestrator/session/mixy-name.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +42 -10
- package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +35 -4
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +179 -16
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +8 -3
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +36 -1
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
- package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
- package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +49 -14
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +134 -17
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +35 -13
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +6 -14
- package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +34 -21
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
- package/src/runtime/attachments/attachment-reference.mjs +155 -0
- package/src/runtime/attachments/limits.mjs +5 -1
- package/src/runtime/attachments/office-extract.mjs +3 -6
- package/src/runtime/attachments/pdf-extract.mjs +84 -10
- package/src/runtime/attachments/store.mjs +55 -30
- package/src/runtime/browser-bridge/client.mjs +73 -20
- package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
- package/src/runtime/channels/lib/scheduler.mjs +14 -7
- package/src/runtime/channels/lib/state-file.mjs +15 -15
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
- package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
- package/src/runtime/channels/lib/worker-main.mjs +3 -2
- package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
- package/src/runtime/effort-judge/judge-client.mjs +6 -3
- package/src/runtime/effort-judge/model-install.mjs +16 -8
- package/src/runtime/effort-judge/model-manifest.json +7 -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/pg/process.mjs +8 -4
- package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
- package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
- package/src/runtime/office/core/journal.mjs +7 -14
- package/src/runtime/office/design/library/design-library-core.mjs +4 -16
- package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
- package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
- package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
- package/src/runtime/office/pdf/pdf-render.mjs +30 -18
- package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
- package/src/runtime/office/portable/portable-soffice.mjs +4 -0
- package/src/runtime/shared/agent-kind-migration.mjs +200 -0
- package/src/runtime/shared/agent-kind.mjs +152 -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-route-config.d.mts +1 -0
- package/src/runtime/shared/agent-route-config.mjs +22 -8
- package/src/runtime/shared/agent-store.mjs +149 -0
- package/src/runtime/shared/agent-task-starts.mjs +27 -0
- package/src/runtime/shared/atomic-file.d.mts +50 -0
- package/src/runtime/shared/atomic-file.mjs +394 -132
- package/src/runtime/shared/background-tasks.mjs +7 -4
- package/src/runtime/shared/bot-hops.mjs +30 -0
- package/src/runtime/shared/bot-split-migration.mjs +457 -0
- package/src/runtime/shared/bounded-download.mjs +4 -2
- package/src/runtime/shared/config-patch.mjs +20 -1
- package/src/runtime/shared/config.mjs +39 -9
- package/src/runtime/shared/connector-catalog.d.mts +1 -2
- package/src/runtime/shared/connector-catalog.mjs +2 -4
- package/src/runtime/shared/connector-triggers.mjs +2 -0
- 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 +7 -1
- package/src/runtime/shared/error-presentation.mjs +113 -10
- package/src/runtime/shared/file-has-data.mjs +28 -0
- package/src/runtime/shared/handoff-artifacts.d.mts +20 -0
- package/src/runtime/shared/handoff-artifacts.mjs +61 -0
- package/src/runtime/shared/llm/provider-model-identities.mjs +6 -1
- package/src/runtime/shared/llm/usage-accounting.mjs +7 -1
- package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
- package/src/runtime/shared/model-route-fallback.mjs +79 -7
- package/src/runtime/shared/pending-agent-work.mjs +54 -0
- package/src/runtime/shared/plain-preview.mjs +22 -17
- package/src/runtime/shared/process-lifecycle.mjs +8 -12
- package/src/runtime/shared/projects.mjs +50 -41
- package/src/runtime/shared/provider-accounts.mjs +17 -12
- package/src/runtime/shared/routine-trigger.d.mts +6 -1
- package/src/runtime/shared/routine-trigger.mjs +14 -6
- package/src/runtime/shared/run-child.d.mts +3 -0
- package/src/runtime/shared/run-child.mjs +24 -3
- package/src/runtime/shared/sensitive-path-rules.d.mts +15 -0
- package/src/runtime/shared/sensitive-path-rules.mjs +256 -0
- package/src/runtime/shared/session-baseline-store.mjs +8 -2
- package/src/runtime/shared/singleton-owner.mjs +7 -6
- package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
- package/src/runtime/shared/staged-update.mjs +4 -2
- package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
- package/src/runtime/shared/tool-execution-contract.mjs +6 -0
- package/src/runtime/shared/transcript-metadata.mjs +28 -3
- package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
- package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
- package/src/runtime/shared/turn-snapshot.mjs +44 -2
- package/src/runtime/shared/usage-readout-totals.mjs +15 -13
- package/src/runtime/shared/user-cwd.mjs +9 -4
- package/src/runtime/shared/user-data-guard.mjs +46 -44
- package/src/runtime/shared/utf8-cut.mjs +10 -0
- package/src/runtime/shared/worktrees/activity-gate.mjs +269 -61
- package/src/runtime/shared/worktrees/registry.mjs +26 -26
- package/src/runtime/tidy/core-install.mjs +26 -4
- package/src/runtime/tidy/install.mjs +91 -23
- package/src/runtime/web-search/lib/config.mjs +4 -16
- package/src/session-runtime/agent-home-delivery.mjs +859 -136
- package/src/session-runtime/agent-room-delivery.mjs +167 -19
- package/src/session-runtime/agent-task-sessions.mjs +831 -69
- package/src/session-runtime/agent-tool-routing.mjs +1 -0
- package/src/session-runtime/boot/apis.mjs +58 -24
- package/src/session-runtime/boot/config.mjs +1 -0
- package/src/session-runtime/boot/facade.mjs +11 -1
- package/src/session-runtime/boot/infrastructure.mjs +14 -2
- package/src/session-runtime/boot/providers.mjs +16 -6
- package/src/session-runtime/boot/shared.mjs +12 -6
- package/src/session-runtime/boot/tools.mjs +2 -2
- package/src/session-runtime/bot-status.mjs +432 -0
- package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
- package/src/session-runtime/config-lifecycle.mjs +78 -32
- package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
- package/src/session-runtime/handoff-attachments.mjs +593 -0
- package/src/session-runtime/held-requests.mjs +36 -33
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +45 -6
- package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
- package/src/session-runtime/lifecycle/teardown.mjs +11 -3
- package/src/session-runtime/mcp-glue/connect.mjs +100 -186
- package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
- package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
- package/src/session-runtime/media-api.mjs +5 -2
- package/src/session-runtime/model-route/route-persist.mjs +2 -2
- package/src/session-runtime/model-route/route-tuning.mjs +11 -2
- package/src/session-runtime/model-route/set-route.mjs +82 -2
- package/src/session-runtime/native-web-search.mjs +2 -2
- package/src/session-runtime/new-session-config.mjs +6 -1
- package/src/session-runtime/owned-automation-run.mjs +8 -1
- package/src/session-runtime/room-member-gate.mjs +16 -3
- package/src/session-runtime/route-resolution.mjs +21 -19
- package/src/session-runtime/route-state.mjs +1 -59
- package/src/session-runtime/routine-home-turn.mjs +5 -3
- package/src/session-runtime/runtime-bootstrap.mjs +5 -3
- package/src/session-runtime/runtime-paths.mjs +11 -5
- package/src/session-runtime/runtime-tunables.mjs +6 -0
- package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
- package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
- package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
- package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
- package/src/session-runtime/services/agent-tool/helpers.mjs +24 -5
- package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +36 -24
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +12 -6
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +5 -8
- package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
- package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
- package/src/session-runtime/services/agent-tool.mjs +6 -0
- package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
- package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
- package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
- package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
- package/src/session-runtime/services/plugin-admin.mjs +26 -26
- package/src/session-runtime/services/provider-admin.mjs +14 -4
- package/src/session-runtime/services/schedule-access.mjs +33 -4
- package/src/session-runtime/services/seeds.mjs +4 -3
- package/src/session-runtime/services/usage-dashboard.mjs +1 -1
- package/src/session-runtime/services/usage-stats-model.mjs +2 -1
- package/src/session-runtime/session-create/materialize.mjs +2 -3
- package/src/session-runtime/session-create/session-options.mjs +15 -2
- package/src/session-runtime/settings-builtin-tools-api.mjs +63 -1
- package/src/session-runtime/settings-compaction-api.mjs +86 -39
- package/src/session-runtime/setup-tool/executor.mjs +20 -3
- package/src/session-runtime/setup-tool/extended-actions.mjs +19 -7
- package/src/session-runtime/setup-tool/settings-contract.mjs +37 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
- package/src/session-runtime/skills-api.mjs +12 -5
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +265 -0
- package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
- package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
- package/src/session-runtime/tool-policy/policy-stage.mjs +165 -0
- package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
- package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
- package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
- package/src/session-runtime/tool-policy-refresh.mjs +47 -358
- package/src/session-runtime/tool-surface.mjs +5 -3
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +99 -81
- package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
- package/src/session-runtime/turn/turn-run.mjs +2 -1
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +578 -301
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +113 -53
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +11 -4
- package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
- package/src/session-runtime/workspace/workspace-state.mjs +7 -6
- package/src/standalone/daemon-agent-control.mjs +4 -1
- package/src/standalone/daemon-session-control.mjs +60 -11
- package/src/standalone/daemon.mjs +34 -4
- package/src/standalone/local-session-runtime.mjs +22 -14
- package/src/standalone/session-client.mjs +7 -2
- package/src/standalone/session-protocol.mjs +7 -0
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +46 -3
- package/src/standalone/session-service/agent-tree.mjs +4 -3
- package/src/standalone/session-service/session-calls/session-turns.mjs +19 -0
- package/src/standalone/session-service.mjs +11 -0
- package/src/standalone/session-state-projection.mjs +19 -9
- package/src/standalone/session-tool-output.mjs +35 -9
- package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
- package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
- package/src/tui/app/slash-commands.mjs +1 -1
- package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
- package/src/tui/dist/index.mjs +599 -342
- package/src/tui/prompt-history-store.mjs +16 -21
- package/src/tui/session/context-state.mjs +4 -0
- package/src/tui/session/notice-surface.mjs +3 -4
- package/src/tui/session/session-api/intake/abort.mjs +19 -1
- package/src/tui/session/session-api/media.mjs +2 -1
- package/src/tui/session/session-api/routes-agent-definitions.mjs +75 -0
- package/src/tui/session/session-api/routes-write.mjs +203 -0
- package/src/tui/session/session-api/routes.mjs +23 -236
- package/src/tui/session/session-api/settings.mjs +14 -1
- package/src/tui/session/session-flow/drain.mjs +28 -3
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
- package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
- package/src/tui/session/session-flow/steering.mjs +8 -3
- package/src/tui/session/tool-approval.mjs +185 -20
- package/src/tui/session/transcript-intake.mjs +4 -0
- package/src/tui/session/turn-live-call.mjs +11 -3
- package/src/tui/session/turn.mjs +36 -17
- 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
|
@@ -0,0 +1,30 @@
|
|
|
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. A turn started by the user or by a routine starts at 0;
|
|
6
|
+
// a runtime notification keeps the hop of the work it belongs to.
|
|
7
|
+
export const MAX_BOT_HOPS = 3;
|
|
8
|
+
export const BOT_HOP_LIMIT_ERROR = 'session: Bot-to-bot chain limit reached; ask the user before continuing.';
|
|
9
|
+
|
|
10
|
+
const hopOf = (value) => (Number.isInteger(value) && value > 0 ? value : 0);
|
|
11
|
+
|
|
12
|
+
/** The hop a turn starts at: its submission's own, else (a runtime
|
|
13
|
+
* notification, `runtimeSource`) the hop the session was already at, else 0. */
|
|
14
|
+
export function startingTurnHop(transcriptMeta, runtimeSource, previous) {
|
|
15
|
+
return hopOf(transcriptMeta?.hop) || (runtimeSource ? hopOf(previous) : 0);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Stamp the starting hop on `session` for the turn about to run. */
|
|
19
|
+
export function stampTurnHop(session, transcriptMeta, runtimeSource) {
|
|
20
|
+
session.turnHop = startingTurnHop(transcriptMeta, runtimeSource, session.turnHop);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A bot's message steered into the running turn raises the turn's hop. */
|
|
24
|
+
export function raiseTurnHop(session, transcriptMeta) {
|
|
25
|
+
const hop = hopOf(transcriptMeta?.hop);
|
|
26
|
+
if (session && hop > hopOf(session.turnHop)) session.turnHop = hop;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The hop of the turn `session` is in. */
|
|
30
|
+
export const turnHopOf = (session) => hopOf(session?.turnHop);
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
// The one-time 1.3.1 split of bots out of agents/: every definition the
|
|
2
|
+
// read-only rule (agent-kind.mjs) classifies as a bot is COPIED from
|
|
3
|
+
// agents/<id>/ to bots/<id>/ with its own model route, and Mixy's identity
|
|
4
|
+
// from agents/lead/ to bots/mixy/. Nothing under agents/ and no session file
|
|
5
|
+
// is ever moved, rewritten or deleted: the originals stay for rollback and
|
|
6
|
+
// the manifest (bots/migration.json) tells the agent catalog which agents/
|
|
7
|
+
// ids are bots now. Home threads keep their ids — a bot's is still
|
|
8
|
+
// sess_home_<id>, Mixy's still sess_home_lead (MIXY_LEGACY_HOME_SESSION_ID) —
|
|
9
|
+
// so every transcript stays whole and continuable.
|
|
10
|
+
//
|
|
11
|
+
// Every later start re-syncs (migrateBotsToOwnStore): it adds nothing and
|
|
12
|
+
// rewrites nothing unless agents/ gained or changed a bot since (a downgrade
|
|
13
|
+
// to 1.3.0 and back). The re-sync never overwrites a bots/ edit: a copy is
|
|
14
|
+
// replaced only when its fingerprint still equals the one recorded when it
|
|
15
|
+
// was made (bots/ untouched since) AND its agents/ original's fingerprint
|
|
16
|
+
// differs and has newer files; when both sides changed, both are kept (the
|
|
17
|
+
// bots/ copy in use, the 1.3.0 edit left in agents/, logged). Every copy or
|
|
18
|
+
// replacement is preceded by a user-data backup that holds agents/ and bots/.
|
|
19
|
+
//
|
|
20
|
+
// One process migrates at a time (an exclusive lock file beside the profile,
|
|
21
|
+
// reclaimed when its owner died); another starting meanwhile waits for it,
|
|
22
|
+
// or after LOCK_WAIT_MS reads the profile as it is this start. Publishing
|
|
23
|
+
// the manifest is the commit point: a failure before it undoes only this
|
|
24
|
+
// run's own copies and leaves the profile as it was (the next start tries
|
|
25
|
+
// again); nothing after it is undone. A replaced bots/<id> is set aside as
|
|
26
|
+
// `.<id>.<run>.old` until then; the manifest names the committed ones
|
|
27
|
+
// (`retired`), which any later start removes, while one it does not name is
|
|
28
|
+
// an interrupted run's and is put back.
|
|
29
|
+
import { createHash } from 'node:crypto';
|
|
30
|
+
import {
|
|
31
|
+
cpSync,
|
|
32
|
+
existsSync,
|
|
33
|
+
mkdirSync,
|
|
34
|
+
readdirSync,
|
|
35
|
+
readFileSync,
|
|
36
|
+
renameSync,
|
|
37
|
+
rmSync,
|
|
38
|
+
statSync,
|
|
39
|
+
writeFileSync,
|
|
40
|
+
} from 'node:fs';
|
|
41
|
+
import { basename, dirname, join, relative } from 'node:path';
|
|
42
|
+
import { withFileLockSync, writeJsonAtomicSync } from './atomic-file.mjs';
|
|
43
|
+
import { evidenceKind, homeThreadEvidence } from './agent-kind.mjs';
|
|
44
|
+
import { BOT_AGENT_KIND } from './agent-route-config.mjs';
|
|
45
|
+
import { readMarkdownDocument } from './markdown-frontmatter.mjs';
|
|
46
|
+
import {
|
|
47
|
+
BOT_MIGRATION_MANIFEST,
|
|
48
|
+
BOT_MIGRATION_VERSION,
|
|
49
|
+
BOTS_DIR,
|
|
50
|
+
MIXY_BOT_ID,
|
|
51
|
+
migratedBotIds,
|
|
52
|
+
readBotMigration,
|
|
53
|
+
} from './agent-store.mjs';
|
|
54
|
+
|
|
55
|
+
export { BOT_MIGRATION_MANIFEST, BOT_MIGRATION_VERSION, BOTS_DIR, MIXY_BOT_ID, migratedBotIds, readBotMigration };
|
|
56
|
+
/** Mixy's home thread keeps the id it always had: its messages never move. */
|
|
57
|
+
export const MIXY_LEGACY_HOME_SESSION_ID = 'sess_home_lead';
|
|
58
|
+
|
|
59
|
+
/** The cross-process migration lock, beside (never inside) bots/ and agents/. */
|
|
60
|
+
export const BOT_MIGRATION_LOCK = '.bot-split.lock';
|
|
61
|
+
const LOCK_WAIT_MS = 10_000;
|
|
62
|
+
/** What the split's backup carries besides the routine user data: every
|
|
63
|
+
* definition it copies from and every copy a re-sync can replace. */
|
|
64
|
+
export const BOT_SPLIT_BACKUP_DIRS = Object.freeze(['agents', BOTS_DIR]);
|
|
65
|
+
|
|
66
|
+
const DELETED_MARKER = '.deleted';
|
|
67
|
+
const ARCHIVED_FILE = 'AGENT.md.archived';
|
|
68
|
+
// Mixy's own files under agents/lead/ (its identity, persona, 일하는 방식, face).
|
|
69
|
+
const MIXY_IDENTITY_KEYS = ['name', 'description', 'avatar', 'connectors', 'created'];
|
|
70
|
+
|
|
71
|
+
function readJson(file) {
|
|
72
|
+
try {
|
|
73
|
+
const value = JSON.parse(readFileSync(file, 'utf8'));
|
|
74
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function writeJson(file, value) {
|
|
81
|
+
writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// The definition file a bot's kind is read from: AGENT.md, or the archived
|
|
85
|
+
// copy of a deleted bot (its chat is kept, so it moves with the others).
|
|
86
|
+
export function definitionFile(dir) {
|
|
87
|
+
if (existsSync(join(dir, 'AGENT.md'))) return join(dir, 'AGENT.md');
|
|
88
|
+
if (existsSync(join(dir, DELETED_MARKER)) && existsSync(join(dir, ARCHIVED_FILE))) return join(dir, ARCHIVED_FILE);
|
|
89
|
+
return '';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The agents/ ids the rule classifies as bots (Mixy's `lead` never). */
|
|
93
|
+
export function classifyBotIds({ dataDir, shippedRoot }) {
|
|
94
|
+
const root = join(dataDir, 'agents');
|
|
95
|
+
let entries = [];
|
|
96
|
+
try {
|
|
97
|
+
entries = readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory());
|
|
98
|
+
} catch {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
const bots = [];
|
|
102
|
+
for (const { name: id } of entries) {
|
|
103
|
+
if (id === 'lead') continue;
|
|
104
|
+
const dir = join(root, id);
|
|
105
|
+
const file = definitionFile(dir);
|
|
106
|
+
if (!file) continue;
|
|
107
|
+
const doc = readMarkdownDocument(readFileSync(file, 'utf8'));
|
|
108
|
+
// The evidence rule itself, whatever the store says (a re-sync runs it
|
|
109
|
+
// with the store active).
|
|
110
|
+
const kind = evidenceKind({
|
|
111
|
+
frontmatterKind: doc.frontmatter.kind,
|
|
112
|
+
dataDir,
|
|
113
|
+
shippedRoot,
|
|
114
|
+
id,
|
|
115
|
+
manifest: readJson(join(dir, 'agent.json')) || {},
|
|
116
|
+
body: doc.body,
|
|
117
|
+
});
|
|
118
|
+
if (kind === BOT_AGENT_KIND) bots.push(id);
|
|
119
|
+
}
|
|
120
|
+
return bots.sort();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Whether agents/lead holds a Mixy the user made or used: a name, face,
|
|
124
|
+
* connectors, persona or 일하는 방식, or any conversation in its thread —
|
|
125
|
+
* never v1.3.0's untouched automatic `{"created": true}`. */
|
|
126
|
+
export function mixyToMigrate(dataDir) {
|
|
127
|
+
const dir = join(dataDir, 'agents', 'lead');
|
|
128
|
+
const manifest = readJson(join(dir, 'agent.json')) || {};
|
|
129
|
+
if (manifest.created !== true) return false;
|
|
130
|
+
const identity =
|
|
131
|
+
Boolean(String(manifest.name ?? '').trim()) ||
|
|
132
|
+
manifest.avatar != null ||
|
|
133
|
+
manifest.connectors != null ||
|
|
134
|
+
existsSync(join(dir, 'PERSONA.md')) ||
|
|
135
|
+
existsSync(join(dir, 'AGENT.md'));
|
|
136
|
+
return identity || homeThreadEvidence(dataDir, 'lead').chatted;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// The provider/model Mixy's thread runs on, or null.
|
|
140
|
+
function threadRoute(dataDir, sessionId) {
|
|
141
|
+
const record = readJson(join(dataDir, 'sessions', `${sessionId}.json`));
|
|
142
|
+
const provider = String(record?.provider || '').trim();
|
|
143
|
+
const model = String(record?.model || '').trim();
|
|
144
|
+
return provider && model ? { provider, model } : null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const completeRoute = (route) =>
|
|
148
|
+
route && typeof route === 'object' && String(route.provider || '').trim() && String(route.model || '').trim()
|
|
149
|
+
? route
|
|
150
|
+
: null;
|
|
151
|
+
|
|
152
|
+
// --- Content fingerprints (for the re-sync rule) ---
|
|
153
|
+
// A folder's files as one hash; agent.json without the keys only the store
|
|
154
|
+
// writes (a bot's route, Mixy's thread id and created flag), so changing a
|
|
155
|
+
// bot's model on 1.3.1 never counts as editing its definition.
|
|
156
|
+
const STORE_ONLY_KEYS = ['route', 'homeSessionId', 'created'];
|
|
157
|
+
|
|
158
|
+
function folderFiles(dir) {
|
|
159
|
+
try {
|
|
160
|
+
return readdirSync(dir, { recursive: true, withFileTypes: true })
|
|
161
|
+
.filter((entry) => entry.isFile())
|
|
162
|
+
.map((entry) => join(entry.parentPath ?? entry.path, entry.name))
|
|
163
|
+
.filter((file) => !file.endsWith('.lock'))
|
|
164
|
+
.sort();
|
|
165
|
+
} catch {
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// A JSON value with every object's keys sorted, nested ones included (a
|
|
171
|
+
// replacer array would drop every nested key not in the top-level list).
|
|
172
|
+
function canonical(value) {
|
|
173
|
+
if (Array.isArray(value)) return value.map(canonical);
|
|
174
|
+
if (!value || typeof value !== 'object') return value;
|
|
175
|
+
return Object.fromEntries(
|
|
176
|
+
Object.keys(value)
|
|
177
|
+
.sort()
|
|
178
|
+
.map((key) => [key, canonical(value[key])])
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function folderHash(dir) {
|
|
183
|
+
const hash = createHash('sha256');
|
|
184
|
+
for (const file of folderFiles(dir)) {
|
|
185
|
+
let bytes = readFileSync(file);
|
|
186
|
+
const manifest = basename(file) === 'agent.json' && dirname(file) === dir ? readJson(file) : null;
|
|
187
|
+
if (manifest) {
|
|
188
|
+
for (const key of STORE_ONLY_KEYS) delete manifest[key];
|
|
189
|
+
bytes = Buffer.from(JSON.stringify(canonical(manifest)));
|
|
190
|
+
}
|
|
191
|
+
hash
|
|
192
|
+
.update(`${relative(dir, file)}\0`)
|
|
193
|
+
.update(bytes)
|
|
194
|
+
.update('\0');
|
|
195
|
+
}
|
|
196
|
+
return hash.digest('hex').slice(0, 24);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function newestChange(dir) {
|
|
200
|
+
let newest = 0;
|
|
201
|
+
for (const file of folderFiles(dir)) newest = Math.max(newest, statSync(file).mtimeMs);
|
|
202
|
+
return newest;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// A staging or set-aside folder an earlier run left (`.<id>.<pid>-<time>.<kind>`).
|
|
206
|
+
// Swept under the migration lock, so no run that made one is still going.
|
|
207
|
+
const LEFTOVER = /^\.(.+)\.(\d+)-\d+\.(staging|old)$/;
|
|
208
|
+
const removeBestEffort = (path) => {
|
|
209
|
+
try {
|
|
210
|
+
rmSync(path, { recursive: true, force: true });
|
|
211
|
+
} catch {
|
|
212
|
+
// a later start removes it
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
// A staging folder goes; a set-aside `.old` the manifest names as retired is
|
|
216
|
+
// a committed swap's rest and goes; any other `.old` is the bot as it was
|
|
217
|
+
// before a run that never committed: it is put back (a failure here fails
|
|
218
|
+
// this run, which then leaves the profile as it is).
|
|
219
|
+
function sweepLeftovers(botsRoot, retired) {
|
|
220
|
+
for (const name of existsSync(botsRoot) ? readdirSync(botsRoot) : []) {
|
|
221
|
+
const match = LEFTOVER.exec(name);
|
|
222
|
+
if (!match) continue;
|
|
223
|
+
const path = join(botsRoot, name);
|
|
224
|
+
if (match[3] === 'staging' || retired.has(name)) {
|
|
225
|
+
removeBestEffort(path);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const target = join(botsRoot, match[1]);
|
|
229
|
+
if (existsSync(target)) {
|
|
230
|
+
const aside = join(botsRoot, `.${match[1]}.${match[2]}-0.staging`);
|
|
231
|
+
renameSync(target, aside);
|
|
232
|
+
removeBestEffort(aside);
|
|
233
|
+
}
|
|
234
|
+
renameSync(path, target);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// The finished split's record as on disk now (never a cached read: another
|
|
239
|
+
// process may have just written it).
|
|
240
|
+
function readManifestNow(botsRoot) {
|
|
241
|
+
const manifest = readJson(join(botsRoot, BOT_MIGRATION_MANIFEST));
|
|
242
|
+
return manifest?.version === BOT_MIGRATION_VERSION ? manifest : null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Runs the split, and on every later start the re-sync (`backup()` first
|
|
247
|
+
* whenever something is to be copied). `routeOf(id)` is the bot's route in
|
|
248
|
+
* the config (null when none); `leadRoute` the Lead's route at this time
|
|
249
|
+
* (Mixy's route when its thread names none).
|
|
250
|
+
*
|
|
251
|
+
* First run: every bot in agents/ is copied to bots/<id> (a bots/<id>
|
|
252
|
+
* already there — a concurrent run's, a hand-made or restored folder — is
|
|
253
|
+
* kept as it is and adopted), and a used Mixy to bots/mixy.
|
|
254
|
+
* Later runs (the manifest exists), so a downgrade to 1.3.0 and back loses
|
|
255
|
+
* nothing:
|
|
256
|
+
* - a bot 1.3.0 made in agents/ after the split is copied over as above;
|
|
257
|
+
* - a migrated bot whose agents/ original was edited after its copy (newer
|
|
258
|
+
* files, other content) while its bots/ copy was left as copied is
|
|
259
|
+
* copied again, keeping its bots/ route; when both sides changed the
|
|
260
|
+
* bots/ copy wins and the edit stays in agents/ (logged);
|
|
261
|
+
* - a migrated bot whose bots/<id> is gone (deleted for good) never comes
|
|
262
|
+
* back from its agents/ original;
|
|
263
|
+
* - Mixy, once made or used in agents/lead, fills what bots/mixy lacks.
|
|
264
|
+
* Every copy is staged and swapped in by rename; a failure before the
|
|
265
|
+
* manifest is published puts back what this run replaced, removes what it
|
|
266
|
+
* added, writes no manifest and leaves agents/ and every session untouched.
|
|
267
|
+
*
|
|
268
|
+
* Runs under the cross-process migration lock: a second process waits up to
|
|
269
|
+
* `lockWaitMs`, then reads the profile as it is (`{ failed, busy }`).
|
|
270
|
+
* `afterPublish` is an interleaving point for tests (a failure there is one
|
|
271
|
+
* after the commit point).
|
|
272
|
+
*/
|
|
273
|
+
export function migrateBotsToOwnStore(options) {
|
|
274
|
+
const { dataDir, log, lockWaitMs = LOCK_WAIT_MS } = options;
|
|
275
|
+
try {
|
|
276
|
+
return withFileLockSync(join(dataDir, BOT_MIGRATION_LOCK), () => migrateLocked(options), {
|
|
277
|
+
timeoutMs: lockWaitMs,
|
|
278
|
+
});
|
|
279
|
+
} catch (error) {
|
|
280
|
+
// Only the lock fails here (migrateLocked reports its own failures).
|
|
281
|
+
const done = readBotMigration(dataDir);
|
|
282
|
+
log?.(`[bot-split] another start is migrating; this one reads the profile as it is: ${error?.message || error}`);
|
|
283
|
+
return { skipped: false, failed: true, busy: true, bots: done?.bots || [], mixy: done?.mixy === true };
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function migrateLocked({ dataDir, shippedRoot, routeOf = () => null, leadRoute = null, backup, log, afterPublish }) {
|
|
288
|
+
const botsRoot = join(dataDir, BOTS_DIR);
|
|
289
|
+
const done = readManifestNow(botsRoot);
|
|
290
|
+
const runTag = `${process.pid}-${Date.now()}`;
|
|
291
|
+
// What this run changed, undone in reverse on a failure before the commit.
|
|
292
|
+
const changes = [];
|
|
293
|
+
let backedUp = false;
|
|
294
|
+
const beforeCopy = () => {
|
|
295
|
+
if (backedUp) return;
|
|
296
|
+
backedUp = true;
|
|
297
|
+
backup?.();
|
|
298
|
+
};
|
|
299
|
+
// Swaps a staged folder in for bots/<id> (atomic renames); the replaced
|
|
300
|
+
// folder is set aside as `.old` until the commit.
|
|
301
|
+
const swapIn = (id, staging, { replace }) => {
|
|
302
|
+
const target = join(botsRoot, id);
|
|
303
|
+
let old = '';
|
|
304
|
+
if (replace && existsSync(target)) {
|
|
305
|
+
old = join(botsRoot, `.${id}.${runTag}.old`);
|
|
306
|
+
renameSync(target, old);
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
renameSync(staging, target);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
removeBestEffort(staging);
|
|
312
|
+
// Should this fail too, the next start's sweep puts the `.old` back.
|
|
313
|
+
if (old) renameSync(old, target);
|
|
314
|
+
throw error;
|
|
315
|
+
}
|
|
316
|
+
changes.push({ target, old });
|
|
317
|
+
};
|
|
318
|
+
const stage = (id, source) => {
|
|
319
|
+
const staging = join(botsRoot, `.${id}.${runTag}.staging`);
|
|
320
|
+
rmSync(staging, { recursive: true, force: true });
|
|
321
|
+
if (source) cpSync(source, staging, { recursive: true });
|
|
322
|
+
else mkdirSync(staging, { recursive: true });
|
|
323
|
+
return staging;
|
|
324
|
+
};
|
|
325
|
+
let next;
|
|
326
|
+
try {
|
|
327
|
+
mkdirSync(botsRoot, { recursive: true });
|
|
328
|
+
sweepLeftovers(botsRoot, new Set(done?.retired || []));
|
|
329
|
+
// Committed set-asides the sweep could not remove stay named for a later start.
|
|
330
|
+
const retired = (done?.retired || []).filter((name) => existsSync(join(botsRoot, name)));
|
|
331
|
+
const copies = { ...(done?.copies || {}) };
|
|
332
|
+
const bots = new Set(done?.bots || []);
|
|
333
|
+
const record = (id, sourceDir) => {
|
|
334
|
+
copies[id] = { source: folderHash(sourceDir), copy: folderHash(join(botsRoot, id)), at: Date.now() };
|
|
335
|
+
};
|
|
336
|
+
// 1. Bots in agents/ not yet in the store.
|
|
337
|
+
for (const id of classifyBotIds({ dataDir, shippedRoot })) {
|
|
338
|
+
if (bots.has(id)) continue;
|
|
339
|
+
const source = join(dataDir, 'agents', id);
|
|
340
|
+
if (!existsSync(join(botsRoot, id))) {
|
|
341
|
+
beforeCopy();
|
|
342
|
+
const route = completeRoute(routeOf(id));
|
|
343
|
+
const staging = stage(id, source);
|
|
344
|
+
const manifest = readJson(join(staging, 'agent.json')) || {};
|
|
345
|
+
writeJson(join(staging, 'agent.json'), { ...manifest, ...(route ? { route } : {}) });
|
|
346
|
+
swapIn(id, staging, { replace: false });
|
|
347
|
+
}
|
|
348
|
+
bots.add(id);
|
|
349
|
+
record(id, source);
|
|
350
|
+
}
|
|
351
|
+
// 2. Migrated bots edited in agents/ after their copy (a 1.3.0 round trip).
|
|
352
|
+
for (const id of done ? [...bots] : []) {
|
|
353
|
+
const source = join(dataDir, 'agents', id);
|
|
354
|
+
const target = join(botsRoot, id);
|
|
355
|
+
const entry = copies[id];
|
|
356
|
+
if (!entry || !existsSync(target) || !existsSync(source)) continue;
|
|
357
|
+
const sourceChanged = folderHash(source) !== entry.source && newestChange(source) > entry.at;
|
|
358
|
+
if (!sourceChanged) continue;
|
|
359
|
+
if (folderHash(target) !== entry.copy) {
|
|
360
|
+
log?.(`[bot-split] ${id}: edited in both agents/ and bots/; the bots/ copy is kept`);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
beforeCopy();
|
|
364
|
+
const route = readJson(join(target, 'agent.json'))?.route;
|
|
365
|
+
const staging = stage(id, source);
|
|
366
|
+
const manifest = readJson(join(staging, 'agent.json')) || {};
|
|
367
|
+
writeJson(join(staging, 'agent.json'), { ...manifest, ...(completeRoute(route) ? { route } : {}) });
|
|
368
|
+
swapIn(id, staging, { replace: true });
|
|
369
|
+
record(id, source);
|
|
370
|
+
}
|
|
371
|
+
// 3. Mixy: made or used in agents/lead; bots/mixy keeps what it has.
|
|
372
|
+
let mixy = done?.mixy === true;
|
|
373
|
+
const leadDir = join(dataDir, 'agents', 'lead');
|
|
374
|
+
const mixyDir = join(botsRoot, MIXY_BOT_ID);
|
|
375
|
+
const mixyEntry = copies[MIXY_BOT_ID];
|
|
376
|
+
const leadEdited =
|
|
377
|
+
mixyEntry &&
|
|
378
|
+
existsSync(mixyDir) &&
|
|
379
|
+
folderHash(leadDir) !== mixyEntry.source &&
|
|
380
|
+
newestChange(leadDir) > mixyEntry.at;
|
|
381
|
+
if (mixyToMigrate(dataDir) && (!mixyEntry || leadEdited)) {
|
|
382
|
+
const current = existsSync(mixyDir) ? readJson(join(mixyDir, 'agent.json')) || {} : {};
|
|
383
|
+
const untouched = mixyEntry && folderHash(mixyDir) === mixyEntry.copy;
|
|
384
|
+
if (mixyEntry && !untouched) {
|
|
385
|
+
log?.('[bot-split] Mixy: edited in both agents/lead and bots/mixy; bots/mixy is kept');
|
|
386
|
+
} else {
|
|
387
|
+
beforeCopy();
|
|
388
|
+
// A first copy fills what bots/mixy lacks (the host may have written
|
|
389
|
+
// its flag or route there); a re-sync takes agents/lead's identity.
|
|
390
|
+
const staging = stage(MIXY_BOT_ID, existsSync(mixyDir) && !mixyEntry ? mixyDir : null);
|
|
391
|
+
for (const name of folderFiles(leadDir).map((file) => relative(leadDir, file))) {
|
|
392
|
+
if (name === 'agent.json' || (existsSync(join(staging, name)) && !mixyEntry)) continue;
|
|
393
|
+
mkdirSync(dirname(join(staging, name)), { recursive: true });
|
|
394
|
+
cpSync(join(leadDir, name), join(staging, name));
|
|
395
|
+
}
|
|
396
|
+
const lead = readJson(join(leadDir, 'agent.json')) || {};
|
|
397
|
+
const identity = Object.fromEntries(
|
|
398
|
+
MIXY_IDENTITY_KEYS.filter((key) => key in lead).map((key) => [key, lead[key]])
|
|
399
|
+
);
|
|
400
|
+
const route =
|
|
401
|
+
completeRoute(current.route) || threadRoute(dataDir, MIXY_LEGACY_HOME_SESSION_ID) || completeRoute(leadRoute);
|
|
402
|
+
writeJson(join(staging, 'agent.json'), {
|
|
403
|
+
...identity,
|
|
404
|
+
...(mixyEntry ? {} : current),
|
|
405
|
+
...(typeof current.created === 'boolean' ? { created: current.created } : {}),
|
|
406
|
+
...(route ? { route } : {}),
|
|
407
|
+
homeSessionId: MIXY_LEGACY_HOME_SESSION_ID,
|
|
408
|
+
});
|
|
409
|
+
swapIn(MIXY_BOT_ID, staging, { replace: true });
|
|
410
|
+
record(MIXY_BOT_ID, leadDir);
|
|
411
|
+
mixy = true;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
retired.push(...changes.filter(({ old }) => old).map(({ old }) => basename(old)));
|
|
415
|
+
next = {
|
|
416
|
+
version: BOT_MIGRATION_VERSION,
|
|
417
|
+
completedAt: done?.completedAt || new Date().toISOString(),
|
|
418
|
+
bots: [...bots].sort(),
|
|
419
|
+
mixy,
|
|
420
|
+
mixyHomeSessionId: mixy ? MIXY_LEGACY_HOME_SESSION_ID : null,
|
|
421
|
+
copies,
|
|
422
|
+
...(retired.length ? { retired: retired.sort() } : {}),
|
|
423
|
+
};
|
|
424
|
+
// The commit point: once this rename lands, nothing of this run is undone.
|
|
425
|
+
if (!done || changes.length || JSON.stringify(next) !== JSON.stringify(done)) {
|
|
426
|
+
writeJsonAtomicSync(join(botsRoot, BOT_MIGRATION_MANIFEST), next, { mode: 0o644 });
|
|
427
|
+
}
|
|
428
|
+
} catch (error) {
|
|
429
|
+
// Undo this run: what it added goes, what it replaced comes back. A step
|
|
430
|
+
// that fails leaves an unretired `.old` the next start's sweep puts back.
|
|
431
|
+
for (const { target, old } of changes.reverse()) {
|
|
432
|
+
try {
|
|
433
|
+
const aside = join(botsRoot, `.${basename(target)}.${runTag}.staging`);
|
|
434
|
+
renameSync(target, aside);
|
|
435
|
+
if (old) renameSync(old, target);
|
|
436
|
+
removeBestEffort(aside);
|
|
437
|
+
} catch {
|
|
438
|
+
// the leftover sweep of a later start finishes it
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
for (const name of existsSync(botsRoot) ? readdirSync(botsRoot) : []) {
|
|
442
|
+
if (name.endsWith(`.${runTag}.staging`)) removeBestEffort(join(botsRoot, name));
|
|
443
|
+
}
|
|
444
|
+
log?.(`[bot-split] migration failed; the profile is unchanged: ${error?.message || error}`);
|
|
445
|
+
return { skipped: false, failed: true, bots: done?.bots || [], mixy: done?.mixy === true };
|
|
446
|
+
}
|
|
447
|
+
// Committed. Removing the set-aside copies is best-effort: the manifest
|
|
448
|
+
// names them as retired, so a later start removes what is left.
|
|
449
|
+
try {
|
|
450
|
+
afterPublish?.();
|
|
451
|
+
for (const { old } of changes) if (old) rmSync(old, { recursive: true, force: true });
|
|
452
|
+
} catch (error) {
|
|
453
|
+
log?.(`[bot-split] committed; leftover cleanup is retried next start: ${error?.message || error}`);
|
|
454
|
+
}
|
|
455
|
+
if (done && !changes.length) return { skipped: true, bots: next.bots, mixy: next.mixy };
|
|
456
|
+
return { skipped: false, bots: next.bots, mixy: next.mixy, changed: changes.map(({ target }) => basename(target)) };
|
|
457
|
+
}
|
|
@@ -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 {
|
|
@@ -43,6 +43,25 @@ function applyField(current, path, change) {
|
|
|
43
43
|
return next;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
const isSet = (value) => value !== undefined && value !== null && value !== '';
|
|
47
|
+
|
|
48
|
+
function valueAt(source, path) {
|
|
49
|
+
let value = source;
|
|
50
|
+
for (const key of path) {
|
|
51
|
+
if (!isPlainObject(value) || !Object.hasOwn(value, key)) return undefined;
|
|
52
|
+
value = value[key];
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** A change with `unlessSet: [path…]` is skipped when the stored value at that
|
|
58
|
+
* path is already set. It is judged against `current`, the state before the
|
|
59
|
+
* whole patch, so the changes of one patch that share a guard stand or fall
|
|
60
|
+
* together whatever their order. */
|
|
46
61
|
export function applyConfigPatch(current, changes) {
|
|
47
|
-
return changes.reduce(
|
|
62
|
+
return changes.reduce(
|
|
63
|
+
(next, change) =>
|
|
64
|
+
change.unlessSet && isSet(valueAt(current, change.unlessSet)) ? next : applyField(next, change.path, change),
|
|
65
|
+
current
|
|
66
|
+
);
|
|
48
67
|
}
|
|
@@ -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
|
|
|
@@ -19,8 +19,7 @@ export type ConnectorBackend =
|
|
|
19
19
|
placeholder?: string;
|
|
20
20
|
/** Tools that ask before they run unless the agent set them. */
|
|
21
21
|
askTools?: readonly string[];
|
|
22
|
-
}
|
|
23
|
-
| { kind: 'builtin'; feature: string };
|
|
22
|
+
};
|
|
24
23
|
|
|
25
24
|
export interface ConnectorCatalogEntry {
|
|
26
25
|
id: string;
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
// explicitly:
|
|
5
5
|
// oauth — a remote MCP server that signs in with MCP OAuth;
|
|
6
6
|
// apiKey — an MCP server that takes one key (an HTTP header or a stdio
|
|
7
|
-
// environment variable)
|
|
8
|
-
//
|
|
9
|
-
// Git & GitHub built-in, never a GitHub MCP);
|
|
7
|
+
// environment variable).
|
|
8
|
+
// GitHub is not a connector: it is the signed-in GitHub CLI (github tool).
|
|
10
9
|
// A connected catalog service is an ordinary user MCP server whose config
|
|
11
10
|
// carries `connector: <id>`. An existing custom server is never adopted into
|
|
12
11
|
// the catalog row.
|
|
@@ -50,7 +49,6 @@ export const CONNECTOR_CATALOG = Object.freeze(
|
|
|
50
49
|
{ id: 'airtable', name: 'Airtable', backend: oauth('https://mcp.airtable.com/mcp') },
|
|
51
50
|
{ id: 'confluence', name: 'Confluence', backend: oauth('https://mcp.atlassian.com/v1/mcp/authv2') },
|
|
52
51
|
{ id: 'dropbox', name: 'Dropbox', backend: oauth('https://mcp.dropbox.com/mcp') },
|
|
53
|
-
{ id: 'github', name: 'GitHub', backend: Object.freeze({ kind: 'builtin', feature: 'git' }) },
|
|
54
52
|
{
|
|
55
53
|
id: 'gmail',
|
|
56
54
|
name: 'Gmail',
|