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
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
accessSync,
|
|
5
|
+
constants as fsConstants,
|
|
6
|
+
existsSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
readdirSync,
|
|
10
|
+
renameSync,
|
|
11
|
+
rmSync,
|
|
12
|
+
statSync,
|
|
13
|
+
} from 'node:fs';
|
|
14
|
+
import {
|
|
15
|
+
holdsNoData,
|
|
16
|
+
parseJsonText,
|
|
17
|
+
readJsonStateSync,
|
|
18
|
+
writeFileAtomicSync,
|
|
19
|
+
writeJsonAtomicSync,
|
|
20
|
+
} from '../../runtime/shared/atomic-file.mjs';
|
|
4
21
|
import { clean } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
|
|
5
22
|
import { hasOwn } from '../../runtime/shared/object.mjs';
|
|
6
23
|
import { serializeFrontmatterDoc } from '../../runtime/shared/markdown-frontmatter.mjs';
|
|
@@ -12,6 +29,7 @@ import {
|
|
|
12
29
|
clearAgentDefinitionCache,
|
|
13
30
|
} from '../../runtime/agent/orchestrator/runtime-core/workflow.mjs';
|
|
14
31
|
import {
|
|
32
|
+
BOT_AGENT_KIND,
|
|
15
33
|
canonicalizeAgentRouteStorage,
|
|
16
34
|
isAgentDisabled,
|
|
17
35
|
WORKFLOW_AGENT_KIND,
|
|
@@ -34,6 +52,7 @@ import {
|
|
|
34
52
|
parseConnectorPolicy,
|
|
35
53
|
readConnectorsFile,
|
|
36
54
|
storedConnectorPolicy,
|
|
55
|
+
assertConnectorPolicyNotWidened,
|
|
37
56
|
} from '../../runtime/agent/orchestrator/mcp/agent-connectors.mjs';
|
|
38
57
|
|
|
39
58
|
/** A policy given to save → its stored form (null removes the field). Only a
|
|
@@ -43,9 +62,24 @@ function connectorPolicyInput(value) {
|
|
|
43
62
|
return storedConnectorPolicy(parseConnectorPolicy(value));
|
|
44
63
|
}
|
|
45
64
|
import { STANDALONE_ROOT } from '../runtime-paths.mjs';
|
|
65
|
+
import { homeThreadEvidence } from '../../runtime/shared/agent-kind.mjs';
|
|
66
|
+
import { migrateAgentKinds } from '../../runtime/shared/agent-kind-migration.mjs';
|
|
67
|
+
import { BOT_SPLIT_BACKUP_DIRS, migrateBotsToOwnStore } from '../../runtime/shared/bot-split-migration.mjs';
|
|
68
|
+
import { backupUserData } from '../../runtime/shared/user-data-guard.mjs';
|
|
69
|
+
import { normalizeWorkflowRoute } from '../../runtime/agent/orchestrator/runtime-core/workflow.mjs';
|
|
70
|
+
import {
|
|
71
|
+
botDir,
|
|
72
|
+
botStoreActive,
|
|
73
|
+
migratedBotIds,
|
|
74
|
+
mixyIdentityDir,
|
|
75
|
+
readBotRoute,
|
|
76
|
+
readMixyRoute,
|
|
77
|
+
userDefinitionDir,
|
|
78
|
+
} from '../../runtime/shared/agent-store.mjs';
|
|
46
79
|
|
|
47
80
|
const isFixedAgent = (id) => FIXED_AGENT_SLOTS.some((agent) => agent.id === id);
|
|
48
|
-
const
|
|
81
|
+
const userDir = userDefinitionDir;
|
|
82
|
+
const hasUserAgentFile = (dataDir, id) => existsSync(join(userDir(dataDir, id), 'AGENT.md'));
|
|
49
83
|
// An archived (deleted) agent's AGENT.md, moved aside beside the tombstone:
|
|
50
84
|
// no loader reads it, and restoring moves it back.
|
|
51
85
|
const ARCHIVED_AGENT_FILE = 'AGENT.md.archived';
|
|
@@ -57,28 +91,94 @@ function hiddenFrontmatter(definition) {
|
|
|
57
91
|
return rest;
|
|
58
92
|
}
|
|
59
93
|
|
|
60
|
-
// The definition's
|
|
61
|
-
//
|
|
94
|
+
// The definition's kind (isContactAgentId reads it), or null: the explicit
|
|
95
|
+
// marker bot creation (`kind: bot`) or the Workflows page (`kind: agent`)
|
|
96
|
+
// wrote, else the read-only decision for an unmarked one (agent-kind.mjs).
|
|
62
97
|
function kindOf(definition) {
|
|
63
|
-
|
|
64
|
-
return typeof kind === 'string' && kind.trim() ? kind.trim() : null;
|
|
98
|
+
return definition?.kind || null;
|
|
65
99
|
}
|
|
66
100
|
|
|
67
|
-
// A save's `kind`: given only when creating,
|
|
68
|
-
// marker. Unmarked agents are
|
|
101
|
+
// A save's `kind`: given only when creating, as the bot or workflow-agent
|
|
102
|
+
// marker. Unmarked agents are workflow agents; a kind never changes later.
|
|
69
103
|
function creationKind(payload, existed) {
|
|
70
104
|
if (!hasOwn(payload, 'kind')) return null;
|
|
71
105
|
if (existed) throw new Error("an agent's kind is set when it is created and cannot change");
|
|
72
|
-
if (payload.kind !==
|
|
73
|
-
|
|
106
|
+
if (payload.kind !== BOT_AGENT_KIND && payload.kind !== WORKFLOW_AGENT_KIND) {
|
|
107
|
+
throw new Error(`agent kind must be "${BOT_AGENT_KIND}", "${WORKFLOW_AGENT_KIND}" or absent`);
|
|
108
|
+
}
|
|
109
|
+
return payload.kind;
|
|
74
110
|
}
|
|
75
111
|
|
|
76
112
|
function readManifest(dir) {
|
|
113
|
+
return readManifestStrict(join(dir, 'agent.json')) || {};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Definition files are user-authored: every write is atomic (a crash never
|
|
117
|
+
// leaves one torn or NUL-filled) and keeps the replaced copy as `<file>.bak`.
|
|
118
|
+
const DEFINITION_WRITE = Object.freeze({ backup: true, mode: 0o644 });
|
|
119
|
+
const writeDefinitionText = (file, text) => writeFileAtomicSync(file, text, DEFINITION_WRITE);
|
|
120
|
+
const writeManifest = (file, manifest) => writeJsonAtomicSync(file, manifest, DEFINITION_WRITE);
|
|
121
|
+
|
|
122
|
+
// The manifest object at `file`, or null when it is missing, unreadable, holds
|
|
123
|
+
// no data (empty, or NUL-filled by a crash) or is not a JSON object.
|
|
124
|
+
function readManifestStrict(file) {
|
|
77
125
|
try {
|
|
78
|
-
const
|
|
79
|
-
|
|
126
|
+
const text = readFileSync(file, 'utf8');
|
|
127
|
+
if (holdsNoData(text)) return null;
|
|
128
|
+
const value = parseJsonText(text);
|
|
129
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
80
130
|
} catch {
|
|
81
|
-
return
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// A read-only agent.json is a standing refusal, not a transient lock: the
|
|
136
|
+
// atomic write's rename onto it fails with EPERM and is retried for seconds,
|
|
137
|
+
// synchronously on the host's event loop. Refuse it up front (one stat, no wait),
|
|
138
|
+
// in the same synchronous stretch as the write so no-widen checks stay atomic.
|
|
139
|
+
function assertManifestWritable(file) {
|
|
140
|
+
try {
|
|
141
|
+
accessSync(file, fsConstants.W_OK);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
if (error?.code === 'ENOENT') return;
|
|
144
|
+
throw new Error(`${file} is not writable (${error?.code || error}); make it writable, then save again`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function requireManifestObject(value) {
|
|
149
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new TypeError('not a JSON object');
|
|
150
|
+
return value;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// The manifest to modify and write back: {} when agent.json is missing. A
|
|
154
|
+
// damaged one (NUL-filled or torn by a crash, not an object) first gets its
|
|
155
|
+
// last good copy back (agent.json.bak; the damaged bytes are kept aside as
|
|
156
|
+
// agent.json.corrupt-<ts>). With no good copy it stays where it is and the
|
|
157
|
+
// write is refused: the keys it held (connectors, avatar, route…) are unknown,
|
|
158
|
+
// and rewriting from {} would drop a connector deny list — a manifest without
|
|
159
|
+
// one means "every connector on".
|
|
160
|
+
function readManifestForUpdate(dir) {
|
|
161
|
+
const file = join(dir, 'agent.json');
|
|
162
|
+
const manifest = readJsonStateSync(file, {
|
|
163
|
+
validate: requireManifestObject,
|
|
164
|
+
keepWithoutBackup: true,
|
|
165
|
+
mode: DEFINITION_WRITE.mode,
|
|
166
|
+
});
|
|
167
|
+
if (manifest) return manifest;
|
|
168
|
+
if (!existsSync(file)) return {};
|
|
169
|
+
throw new Error(
|
|
170
|
+
`${file} is damaged and has no good copy to restore; it was left untouched. Fix or delete the file, then save again`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// A text file's trimmed content; '' when it is missing, unreadable or holds no
|
|
175
|
+
// data (a crash can leave it NUL-filled, which trim() keeps).
|
|
176
|
+
function readTextOrEmpty(file) {
|
|
177
|
+
try {
|
|
178
|
+
const text = readFileSync(file, 'utf8');
|
|
179
|
+
return holdsNoData(text) ? '' : text.trim();
|
|
180
|
+
} catch {
|
|
181
|
+
return '';
|
|
82
182
|
}
|
|
83
183
|
}
|
|
84
184
|
|
|
@@ -96,11 +196,7 @@ export const AGENT_DEFINITION_CONFLICT = 'AGENT_DEFINITION_CONFLICT';
|
|
|
96
196
|
const LEAD_PERSONA_MAX_BYTES = 256 * 1024;
|
|
97
197
|
|
|
98
198
|
function readPersona(dir) {
|
|
99
|
-
|
|
100
|
-
return readFileSync(join(dir, PERSONA_FILE), 'utf8').trim();
|
|
101
|
-
} catch {
|
|
102
|
-
return '';
|
|
103
|
-
}
|
|
199
|
+
return readTextOrEmpty(join(dir, PERSONA_FILE));
|
|
104
200
|
}
|
|
105
201
|
|
|
106
202
|
const IMAGE_DATA_URL = /^data:image\/(png|gif|webp);base64,/;
|
|
@@ -171,7 +267,7 @@ function avatarUpdate(value, dir) {
|
|
|
171
267
|
function syncAvatarFiles(dir, avatar, writes) {
|
|
172
268
|
for (const { path, bytes } of writes) {
|
|
173
269
|
mkdirSync(join(dir, AGENT_AVATAR_DIR), { recursive: true });
|
|
174
|
-
|
|
270
|
+
writeFileAtomicSync(join(dir, path), bytes, { mode: 0o644 });
|
|
175
271
|
}
|
|
176
272
|
const keep = new Set(
|
|
177
273
|
avatar?.kind === 'custom'
|
|
@@ -227,25 +323,419 @@ const stableJson = (value) =>
|
|
|
227
323
|
function writeAgentFiles(dir, { name, description, body, extra = {}, manifest = {} }) {
|
|
228
324
|
const meta = { name, ...(description ? { description } : {}) };
|
|
229
325
|
mkdirSync(dir, { recursive: true });
|
|
230
|
-
|
|
326
|
+
writeDefinitionText(join(dir, 'AGENT.md'), serializeFrontmatterDoc({ ...meta, ...extra }, body));
|
|
231
327
|
// loadAgentDefinition prefers the manifest for name/description.
|
|
232
|
-
|
|
328
|
+
writeManifest(join(dir, 'agent.json'), { ...meta, ...manifest });
|
|
233
329
|
rmSync(join(dir, AGENT_DELETED_MARKER), { force: true });
|
|
234
330
|
rmSync(join(dir, ARCHIVED_AGENT_FILE), { force: true });
|
|
235
331
|
}
|
|
236
332
|
|
|
333
|
+
const isArchivedAgent = (dataDir, id) => existsSync(join(userDir(dataDir, id), AGENT_DELETED_MARKER));
|
|
334
|
+
|
|
335
|
+
// A save's avatar and connectors applied to `manifest` (in place); returns the
|
|
336
|
+
// avatar update (its image writes), null when the save gives no avatar.
|
|
337
|
+
function applyManifestEdits(manifest, payload, dir, options) {
|
|
338
|
+
// New custom images arrive as checked bytes, stored under content hashes.
|
|
339
|
+
const update = hasOwn(payload, 'avatar') ? avatarUpdate(payload.avatar, dir) : null;
|
|
340
|
+
if (update?.avatar) manifest.avatar = update.avatar;
|
|
341
|
+
else if (update) delete manifest.avatar;
|
|
342
|
+
// Connectors: a policy replaces the saved one, null removes the field
|
|
343
|
+
// (every connector on), absent leaves it as saved.
|
|
344
|
+
if (hasOwn(payload, 'connectors')) {
|
|
345
|
+
const connectors = connectorPolicyInput(payload.connectors);
|
|
346
|
+
// For a remote client: against the manifest just read, in the same synchronous stretch as its write.
|
|
347
|
+
if (options?.noWiden) assertConnectorPolicyNotWidened(manifest.connectors ?? null, connectors);
|
|
348
|
+
if (connectors) manifest.connectors = connectors;
|
|
349
|
+
else delete manifest.connectors;
|
|
350
|
+
}
|
|
351
|
+
return update;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// A saved definition's files in `dir`: AGENT.md and agent.json, PERSONA.md
|
|
355
|
+
// (`persona` '' removes it, null leaves it) and the avatar images.
|
|
356
|
+
function writeSavedDefinition(dir, files, persona, update) {
|
|
357
|
+
writeAgentFiles(dir, files);
|
|
358
|
+
if (persona) writeDefinitionText(join(dir, PERSONA_FILE), `${persona}\n`);
|
|
359
|
+
else if (persona === '') rmSync(join(dir, PERSONA_FILE), { force: true });
|
|
360
|
+
// Only the images the manifest names stay.
|
|
361
|
+
syncAvatarFiles(dir, normalizeAgentAvatar(files.manifest.avatar), update?.writes ?? []);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// A save's id, name and description: a new agent's id comes from its name.
|
|
365
|
+
function resolveSaveTarget(loadAgentDefinition, payload, dataDir) {
|
|
366
|
+
const id = agentEditorId(clean(payload.id));
|
|
367
|
+
if (hasOwn(payload, 'id') && !id) {
|
|
368
|
+
throw new Error('agent id must contain letters/numbers (dashes and dots allowed)');
|
|
369
|
+
}
|
|
370
|
+
if (id && isHiddenAgent(id)) throw new Error(`agent "${id}" is internal and cannot be edited`);
|
|
371
|
+
const name = oneLine(payload.name);
|
|
372
|
+
if (!name && !id) throw new Error('agent name must not be empty');
|
|
373
|
+
return {
|
|
374
|
+
// An archived agent keeps its id: its chat is never handed to a new one.
|
|
375
|
+
id:
|
|
376
|
+
id ||
|
|
377
|
+
availableAgentId(
|
|
378
|
+
name,
|
|
379
|
+
(candidate) =>
|
|
380
|
+
Boolean(loadAgentDefinition(dataDir, candidate)) ||
|
|
381
|
+
isArchivedAgent(dataDir, candidate) ||
|
|
382
|
+
// An id the split copied stays the bot's, even once purged; a
|
|
383
|
+
// folder in either store keeps its id too.
|
|
384
|
+
migratedBotIds(dataDir).has(candidate) ||
|
|
385
|
+
(botStoreActive(dataDir) &&
|
|
386
|
+
(existsSync(join(dataDir, 'agents', candidate)) || existsSync(botDir(dataDir, candidate))))
|
|
387
|
+
),
|
|
388
|
+
name,
|
|
389
|
+
description: oneLine(payload.description),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/** The name and face an archived (deleted) agent left, per id; null for
|
|
394
|
+
* any id that is not archived. Its chat lists under them. */
|
|
395
|
+
function archivedIdentities(dataDir, agentIds) {
|
|
396
|
+
const ids = Array.isArray(agentIds) ? agentIds.filter((id) => typeof id === 'string') : [];
|
|
397
|
+
return Object.fromEntries(
|
|
398
|
+
ids.map((agentId) => {
|
|
399
|
+
const id = agentEditorId(agentId);
|
|
400
|
+
if (!id || id !== agentId || !isArchivedAgent(dataDir, id)) return [agentId, null];
|
|
401
|
+
const manifest = readManifest(userDir(dataDir, id));
|
|
402
|
+
return [agentId, { name: oneLine(manifest.name) || id, avatar: normalizeAgentAvatar(manifest.avatar) ?? null }];
|
|
403
|
+
})
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// A custom avatar's images as data URLs by state, read from `dir`; null
|
|
408
|
+
// without an idle image.
|
|
409
|
+
function customAvatarImages(dir, avatar) {
|
|
410
|
+
const states = {};
|
|
411
|
+
for (const [state, file] of Object.entries(avatar.states)) {
|
|
412
|
+
const image = readAvatarImage(dir, file);
|
|
413
|
+
if (image) states[state] = image;
|
|
414
|
+
}
|
|
415
|
+
return states.idle ? { states } : null;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Mixy (the Lead) has no agent definition to edit: only its PERSONA.md
|
|
419
|
+
// (성격) and AGENT.md (일하는 방식, agent mode only), read by the runtime
|
|
420
|
+
// from Mixy's identity folder: bots/mixy once the bot store is active,
|
|
421
|
+
// agents/lead before (mixyIdentityDir, agent-store.mjs). Saved empty, the
|
|
422
|
+
// file goes. `dataDirOf()` resolves the data folder on each call.
|
|
423
|
+
function createLeadIdentityApi(dataDirOf) {
|
|
424
|
+
const leadDir = () => mixyIdentityDir(dataDirOf());
|
|
425
|
+
function readLeadFile(file) {
|
|
426
|
+
return readTextOrEmpty(join(leadDir(), file));
|
|
427
|
+
}
|
|
428
|
+
function saveLeadFile(file, text) {
|
|
429
|
+
const value = String(text ?? '').trim();
|
|
430
|
+
if (Buffer.byteLength(value) > LEAD_PERSONA_MAX_BYTES) throw new Error(`${file} is larger than 256 KB`);
|
|
431
|
+
const dir = leadDir();
|
|
432
|
+
if (value) {
|
|
433
|
+
mkdirSync(dir, { recursive: true });
|
|
434
|
+
writeDefinitionText(join(dir, file), `${value}\n`);
|
|
435
|
+
} else rmSync(join(dir, file), { force: true });
|
|
436
|
+
// Mixy's threads take it from their next turn (the save answers at once).
|
|
437
|
+
void publishAgentProfileChange();
|
|
438
|
+
return readLeadFile(file);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Whether Mixy was created (the "대표 에이전트 만들기" first run): the
|
|
442
|
+
// explicit `created` flag in Mixy's agent.json (leadDir). null until decided —
|
|
443
|
+
// the desktop host decides once (an existing profile counts as created).
|
|
444
|
+
// Also Mixy's display name (`name`, read by the runtime's identity line) and
|
|
445
|
+
// face (`avatar`, as every agent manifest): '' / null mean the defaults.
|
|
446
|
+
// `homeChatted`: whether Mixy's home thread holds any conversation (the
|
|
447
|
+
// host reads it to tell v1.3.0's never-used automatic Mixy apart).
|
|
448
|
+
function getLeadStatus() {
|
|
449
|
+
const manifest = readManifest(leadDir());
|
|
450
|
+
// A v1.3.0 automatic Mixy the split left in agents/lead still counts as
|
|
451
|
+
// decided (its routines keep running) until bots/mixy says otherwise.
|
|
452
|
+
const legacy = readManifest(join(dataDirOf(), 'agents', LEAD_AGENT_ID)).created;
|
|
453
|
+
let created = null;
|
|
454
|
+
if (typeof manifest.created === 'boolean') created = manifest.created;
|
|
455
|
+
else if (typeof legacy === 'boolean') created = legacy;
|
|
456
|
+
return {
|
|
457
|
+
created,
|
|
458
|
+
name: oneLine(manifest.name),
|
|
459
|
+
avatar: normalizeAgentAvatar(manifest.avatar) ?? null,
|
|
460
|
+
homeChatted: homeThreadEvidence(dataDirOf(), LEAD_AGENT_ID).chatted,
|
|
461
|
+
// Mixy's own model (never the Lead's); null: its thread keeps its own.
|
|
462
|
+
route: readMixyRoute(dataDirOf()),
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
// Mixy's name and face, chosen in the 대표 에이전트 dialog or its profile:
|
|
466
|
+
// a name '' and an avatar null go back to the defaults; absent keys are kept.
|
|
467
|
+
function saveLeadIdentity(payload = {}) {
|
|
468
|
+
const dir = leadDir();
|
|
469
|
+
const manifest = readManifestForUpdate(dir);
|
|
470
|
+
if (hasOwn(payload, 'name')) {
|
|
471
|
+
const name = oneLine(payload.name);
|
|
472
|
+
if (name.length > 64) throw new Error('name must be 64 characters or fewer');
|
|
473
|
+
if (name) manifest.name = name;
|
|
474
|
+
else delete manifest.name;
|
|
475
|
+
}
|
|
476
|
+
const update = hasOwn(payload, 'avatar') ? avatarUpdate(payload.avatar, dir) : null;
|
|
477
|
+
if (update?.avatar) manifest.avatar = update.avatar;
|
|
478
|
+
else if (update) delete manifest.avatar;
|
|
479
|
+
mkdirSync(dir, { recursive: true });
|
|
480
|
+
writeManifest(join(dir, 'agent.json'), manifest);
|
|
481
|
+
syncAvatarFiles(dir, normalizeAgentAvatar(manifest.avatar), update?.writes ?? []);
|
|
482
|
+
// The name is in Mixy's prompt (its identity line); the face is not.
|
|
483
|
+
if (hasOwn(payload, 'name')) void publishAgentProfileChange();
|
|
484
|
+
return getLeadStatus();
|
|
485
|
+
}
|
|
486
|
+
// Sets the flag (and Mixy's 하는 일 when given); every other key is kept.
|
|
487
|
+
// A damaged Lead manifest (NUL-filled, torn, not an object) gets its last
|
|
488
|
+
// good copy back first. Without one it is left alone — nothing is written,
|
|
489
|
+
// the status stays as read — since a rewrite would drop its connector policy.
|
|
490
|
+
function setLeadCreated(created, description) {
|
|
491
|
+
if (typeof created !== 'boolean') throw new TypeError('created must be a boolean');
|
|
492
|
+
let manifest;
|
|
493
|
+
try {
|
|
494
|
+
manifest = readManifestForUpdate(leadDir());
|
|
495
|
+
} catch (error) {
|
|
496
|
+
process.stderr.write(`[agents] ${error.message}; Mixy's created flag was not saved\n`);
|
|
497
|
+
return getLeadStatus();
|
|
498
|
+
}
|
|
499
|
+
manifest.created = created;
|
|
500
|
+
const job = oneLine(description);
|
|
501
|
+
if (job) manifest.description = job;
|
|
502
|
+
mkdirSync(leadDir(), { recursive: true });
|
|
503
|
+
writeManifest(join(leadDir(), 'agent.json'), manifest);
|
|
504
|
+
return getLeadStatus();
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return {
|
|
508
|
+
leadDir,
|
|
509
|
+
getLeadPersona: () => readLeadFile(PERSONA_FILE),
|
|
510
|
+
saveLeadPersona: (text) => saveLeadFile(PERSONA_FILE, text),
|
|
511
|
+
getLeadAgentBody: () => readLeadFile('AGENT.md'),
|
|
512
|
+
saveLeadAgentBody: (text) => saveLeadFile('AGENT.md', text),
|
|
513
|
+
getLeadStatus,
|
|
514
|
+
saveLeadIdentity,
|
|
515
|
+
setLeadCreated,
|
|
516
|
+
// Mixy's connector policy, in its agent.json (`next` null removes it).
|
|
517
|
+
getLeadConnectorPolicy: () => readConnectorsFile(join(leadDir(), 'agent.json')),
|
|
518
|
+
setLeadConnectorPolicy: (next) => {
|
|
519
|
+
const manifest = readManifestForUpdate(leadDir());
|
|
520
|
+
if (next) manifest.connectors = next;
|
|
521
|
+
else delete manifest.connectors;
|
|
522
|
+
mkdirSync(leadDir(), { recursive: true });
|
|
523
|
+
writeManifest(join(leadDir(), 'agent.json'), manifest);
|
|
524
|
+
},
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Whether each agent's definition is on disk, decided per id from the files
|
|
529
|
+
// themselves: 'deleted' only when every source is PROVEN absent (ENOENT) or
|
|
530
|
+
// tombstoned, 'unknown' on any other read fault. A list read swallows
|
|
531
|
+
// directory errors, so its absence never proves a deletion; this does.
|
|
532
|
+
function definitionPresence({ dataDir, rootDir, stat }, agentIds) {
|
|
533
|
+
const probe = (path) => {
|
|
534
|
+
try {
|
|
535
|
+
stat(path);
|
|
536
|
+
return 'present';
|
|
537
|
+
} catch (error) {
|
|
538
|
+
return error?.code === 'ENOENT' || error?.code === 'ENOTDIR' ? 'absent' : 'unknown';
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
const presence = (agentId) => {
|
|
542
|
+
const id = agentEditorId(agentId);
|
|
543
|
+
if (!id || id !== agentId) return 'unknown';
|
|
544
|
+
const user = probe(join(userDir(dataDir, id), 'AGENT.md'));
|
|
545
|
+
if (user !== 'absent') return user;
|
|
546
|
+
const tombstone = probe(join(userDir(dataDir, id), AGENT_DELETED_MARKER));
|
|
547
|
+
if (tombstone === 'present') return 'deleted';
|
|
548
|
+
if (tombstone === 'unknown') return 'unknown';
|
|
549
|
+
const shipped = probe(join(rootDir, 'agents', id, 'AGENT.md'));
|
|
550
|
+
return shipped === 'absent' ? 'deleted' : shipped;
|
|
551
|
+
};
|
|
552
|
+
const ids = Array.isArray(agentIds) ? agentIds.filter((id) => typeof id === 'string') : [];
|
|
553
|
+
return Object.fromEntries(ids.map((id) => [id, presence(id)]));
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// A definition folder's revision: a hash of the three files an editor writes.
|
|
557
|
+
function definitionDirRevision(dir) {
|
|
558
|
+
const hash = createHash('sha256');
|
|
559
|
+
for (const name of ['agent.json', 'AGENT.md', PERSONA_FILE]) {
|
|
560
|
+
hash.update(`${name}\0`);
|
|
561
|
+
try {
|
|
562
|
+
hash.update(readFileSync(join(dir, name)));
|
|
563
|
+
} catch {
|
|
564
|
+
hash.update('\u0001absent');
|
|
565
|
+
}
|
|
566
|
+
hash.update('\0');
|
|
567
|
+
}
|
|
568
|
+
return hash.digest('hex').slice(0, 24);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/** The one-time split of bots into bots/ (bot-split-migration.mjs), from
|
|
572
|
+
* the config as it is now; the catalogs read the store from here on. */
|
|
573
|
+
function migrateBotStore(deps) {
|
|
574
|
+
const dataDir = resolveDataDir(deps);
|
|
575
|
+
const config = deps.getConfig();
|
|
576
|
+
const result = migrateBotsToOwnStore({
|
|
577
|
+
dataDir,
|
|
578
|
+
shippedRoot: deps.rootDir ?? STANDALONE_ROOT,
|
|
579
|
+
routeOf: (id) => deps.agentRouteFromConfig(config, id),
|
|
580
|
+
leadRoute: normalizeWorkflowRoute(deps.resolveRoute(config, {})),
|
|
581
|
+
// The definitions the split copies or a re-sync replaces go with it.
|
|
582
|
+
backup: () => backupUserData(dataDir, 'bot-split', { dirs: BOT_SPLIT_BACKUP_DIRS }),
|
|
583
|
+
log: (line) => process.stderr.write(`${line}\n`),
|
|
584
|
+
});
|
|
585
|
+
clearAgentDefinitionCache();
|
|
586
|
+
deps.listCustomAgentIds?.(dataDir);
|
|
587
|
+
return result;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/** The one-time `kind: agent` stamp for agents made before the marker
|
|
591
|
+
* (agent-kind-migration.mjs); runs before the bot split so a workflow
|
|
592
|
+
* agent is never copied to bots/. */
|
|
593
|
+
function migrateAgentKindMarkers(deps) {
|
|
594
|
+
const dataDir = resolveDataDir(deps);
|
|
595
|
+
const result = migrateAgentKinds({
|
|
596
|
+
dataDir,
|
|
597
|
+
shippedRoot: deps.rootDir ?? STANDALONE_ROOT,
|
|
598
|
+
backup: () => backupUserData(dataDir, 'agent-kind', { dirs: BOT_SPLIT_BACKUP_DIRS }),
|
|
599
|
+
// A restored bot's own route goes back to the config, where a workflow
|
|
600
|
+
// agent's lives (unless the config already holds that model).
|
|
601
|
+
restoreRoute: (id, route) => {
|
|
602
|
+
const config = deps.getConfig();
|
|
603
|
+
const current = deps.agentRouteFromConfig(config, id);
|
|
604
|
+
if (current?.provider === route.provider && current?.model === route.model) return;
|
|
605
|
+
deps.saveConfigAndAdopt(canonicalizeAgentRouteStorage({ ...config, agents: { ...config.agents, [id]: route } }));
|
|
606
|
+
},
|
|
607
|
+
log: (line) => process.stderr.write(`${line}\n`),
|
|
608
|
+
});
|
|
609
|
+
clearAgentDefinitionCache();
|
|
610
|
+
deps.listCustomAgentIds?.(dataDir);
|
|
611
|
+
return result;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Deleting (archiving), restoring and purging agents, and the config cleanup
|
|
615
|
+
// a removed agent needs.
|
|
616
|
+
function createAgentArchiveApi({
|
|
617
|
+
dataDirOf,
|
|
618
|
+
getConfig,
|
|
619
|
+
saveConfigAndAdopt,
|
|
620
|
+
loadAgentDefinition,
|
|
621
|
+
shippedDir,
|
|
622
|
+
announceBotChange,
|
|
623
|
+
getAgentDefinition,
|
|
624
|
+
}) {
|
|
625
|
+
// A removed custom agent must not leave a dangling route/preset or a
|
|
626
|
+
// disabled-roster entry that would silently disable a later agent of the
|
|
627
|
+
// same id.
|
|
628
|
+
function dropAgentRoute(id) {
|
|
629
|
+
const nextConfig = withAgentDisabled(getConfig(), id, false);
|
|
630
|
+
if (nextConfig.agents && id in nextConfig.agents) {
|
|
631
|
+
const agents = { ...nextConfig.agents };
|
|
632
|
+
delete agents[id];
|
|
633
|
+
nextConfig.agents = agents;
|
|
634
|
+
}
|
|
635
|
+
saveConfigAndAdopt(canonicalizeAgentRouteStorage(nextConfig));
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
async function deleteAgentDefinition(agentId) {
|
|
639
|
+
const id = agentEditorId(agentId);
|
|
640
|
+
if (!id) throw new Error(`unknown agent "${agentId}"`);
|
|
641
|
+
if (isHiddenAgent(id)) throw new Error(`agent "${id}" is internal and cannot be deleted`);
|
|
642
|
+
const builtIn = isFixedAgent(id);
|
|
643
|
+
const dataDir = dataDirOf();
|
|
644
|
+
const dir = userDir(dataDir, id);
|
|
645
|
+
if (!hasUserAgentFile(dataDir, id)) {
|
|
646
|
+
if (builtIn) throw new Error(`agent "${id}" has no user override to reset`);
|
|
647
|
+
if (!loadAgentDefinition(dataDir, id)) throw new Error(`agent "${id}" not found`);
|
|
648
|
+
}
|
|
649
|
+
// Agents are global (no workflow rosters): deleting a custom agent removes
|
|
650
|
+
// it from every surface at once — catalog, editor, routes, and spawn (the
|
|
651
|
+
// spawn path rejects unknown agent ids).
|
|
652
|
+
if (builtIn) {
|
|
653
|
+
rmSync(dir, { recursive: true, force: true });
|
|
654
|
+
clearAgentDefinitionCache(id);
|
|
655
|
+
const sourceFallback = loadAgentDefinition(dataDir, id);
|
|
656
|
+
// Its threads, and the Leads listing it, take the shipped definition.
|
|
657
|
+
await announceBotChange(dataDir);
|
|
658
|
+
return { id, deleted: true, revertedToBuiltIn: Boolean(sourceFallback) };
|
|
659
|
+
}
|
|
660
|
+
// A custom agent is archived: its AGENT.md (what makes it an agent
|
|
661
|
+
// anywhere) moves aside, the tombstone keeps the packaged copy of a
|
|
662
|
+
// starter from coming back, and the rest (agent.json: name and face,
|
|
663
|
+
// persona, memory, its model and on/off) stays for its archived chat —
|
|
664
|
+
// until restoreAgentDefinition brings it back or purgeAgentDefinition.
|
|
665
|
+
mkdirSync(dir, { recursive: true });
|
|
666
|
+
if (existsSync(join(dir, 'AGENT.md'))) renameSync(join(dir, 'AGENT.md'), join(dir, ARCHIVED_AGENT_FILE));
|
|
667
|
+
writeFileAtomicSync(join(dir, AGENT_DELETED_MARKER), 'deleted\n');
|
|
668
|
+
clearAgentDefinitionCache(id);
|
|
669
|
+
// The Leads listing it drop it from their roster from their next turn.
|
|
670
|
+
await announceBotChange(dataDir);
|
|
671
|
+
return { id, deleted: true, revertedToBuiltIn: false };
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/** An archived agent back as it was, under its own id: its AGENT.md moved
|
|
675
|
+
* back (a starter without a copy of its own returns to the packaged one)
|
|
676
|
+
* and the tombstone gone. Its routines stay off (the host). */
|
|
677
|
+
async function restoreAgentDefinition(agentId) {
|
|
678
|
+
const id = agentEditorId(agentId);
|
|
679
|
+
if (!id || isHiddenAgent(id) || isFixedAgent(id)) throw new Error(`unknown agent "${agentId}"`);
|
|
680
|
+
const dataDir = dataDirOf();
|
|
681
|
+
if (!isArchivedAgent(dataDir, id)) throw new Error(`agent "${id}" is not deleted`);
|
|
682
|
+
const dir = userDir(dataDir, id);
|
|
683
|
+
const kept = join(dir, ARCHIVED_AGENT_FILE);
|
|
684
|
+
if (!existsSync(kept) && !existsSync(join(shippedDir(id), 'AGENT.md'))) {
|
|
685
|
+
throw new Error(`agent "${id}" was deleted before deleted agents were kept, so it cannot be restored`);
|
|
686
|
+
}
|
|
687
|
+
if (existsSync(kept)) renameSync(kept, join(dir, 'AGENT.md'));
|
|
688
|
+
rmSync(join(dir, AGENT_DELETED_MARKER), { force: true });
|
|
689
|
+
clearAgentDefinitionCache(id);
|
|
690
|
+
// Back in the roster of the Leads that list it, from their next turn.
|
|
691
|
+
await announceBotChange(dataDir);
|
|
692
|
+
return getAgentDefinition(id);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/** Deleting an archived agent again: everything it left goes for good. A
|
|
696
|
+
* starter keeps only its tombstone, so its packaged copy stays gone. */
|
|
697
|
+
async function purgeAgentDefinition(agentId) {
|
|
698
|
+
const id = agentEditorId(agentId);
|
|
699
|
+
if (!id || isHiddenAgent(id) || isFixedAgent(id)) throw new Error(`unknown agent "${agentId}"`);
|
|
700
|
+
const dataDir = dataDirOf();
|
|
701
|
+
// Never a live agent; one deleted before archiving existed left nothing.
|
|
702
|
+
if (!isArchivedAgent(dataDir, id)) return { id, purged: false };
|
|
703
|
+
const dir = userDir(dataDir, id);
|
|
704
|
+
rmSync(dir, { recursive: true, force: true });
|
|
705
|
+
clearAgentDefinitionCache(id);
|
|
706
|
+
dropAgentRoute(id);
|
|
707
|
+
if (loadAgentDefinition(dataDir, id)) {
|
|
708
|
+
mkdirSync(dir, { recursive: true });
|
|
709
|
+
writeFileAtomicSync(join(dir, AGENT_DELETED_MARKER), 'deleted\n');
|
|
710
|
+
clearAgentDefinitionCache(id);
|
|
711
|
+
}
|
|
712
|
+
return { id, purged: true };
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
return { dropAgentRoute, deleteAgentDefinition, restoreAgentDefinition, purgeAgentDefinition };
|
|
716
|
+
}
|
|
717
|
+
|
|
237
718
|
// Agent catalog + editor surface: fixed services stay built-in; starter and
|
|
238
719
|
// user-authored agents are custom definitions under agents/<id>/AGENT.md.
|
|
239
720
|
export function createAgentEditorApi(deps) {
|
|
240
721
|
const { getConfig, loadAgentDefinition, listCustomAgentIds, saveConfigAndAdopt, setAgentRoute } = deps;
|
|
241
|
-
|
|
722
|
+
// A store bot's route lives in its folder; every other agent's in the config.
|
|
723
|
+
const ownRoute = (config, id) => {
|
|
724
|
+
const bot = readBotRoute(resolveDataDir(deps), id);
|
|
725
|
+
return bot !== undefined ? bot : deps.agentRouteFromConfig(config, id);
|
|
726
|
+
};
|
|
727
|
+
const routeFor = (config, id) => effectiveAgentRoute({ ...deps, agentRouteFromConfig: ownRoute }, config, id);
|
|
242
728
|
// `route` falls back to Main; this says whether the agent has a route of
|
|
243
729
|
// its own, so a surface never shows (or saves) the Main fallback as one.
|
|
244
|
-
const routeConfigured = (config, id) => Boolean(
|
|
730
|
+
const routeConfigured = (config, id) => Boolean(ownRoute(config, id));
|
|
245
731
|
const shippedDir = (id) => join(deps.rootDir ?? STANDALONE_ROOT, 'agents', id);
|
|
246
732
|
// The manifest in effect: the user's own copy if any, else the shipped one.
|
|
247
|
-
const manifestDirOf = (dataDir, id) => (hasUserAgentFile(dataDir, id) ?
|
|
733
|
+
const manifestDirOf = (dataDir, id) => (hasUserAgentFile(dataDir, id) ? userDir(dataDir, id) : shippedDir(id));
|
|
248
734
|
const manifestOf = (dataDir, id) => readManifest(manifestDirOf(dataDir, id));
|
|
735
|
+
// The same, to be written back: the user's copy is recovered or refused when
|
|
736
|
+
// damaged (readManifestForUpdate); a shipped one is only read.
|
|
737
|
+
const manifestForUpdate = (dataDir, id) =>
|
|
738
|
+
hasUserAgentFile(dataDir, id) ? readManifestForUpdate(userDir(dataDir, id)) : readManifest(shippedDir(id));
|
|
249
739
|
const avatarOf = (dataDir, id) => normalizeAgentAvatar(manifestOf(dataDir, id).avatar);
|
|
250
740
|
// A saved, archived or restored bot reaches the prompts that embed it: the
|
|
251
741
|
// fresh scan republishes the shared bot list the Lead rosters read (a new id
|
|
@@ -291,20 +781,8 @@ export function createAgentEditorApi(deps) {
|
|
|
291
781
|
// The stored definition's revision: a hash of the three files an editor
|
|
292
782
|
// writes, from the copy in effect. An editor that loaded it can send it back
|
|
293
783
|
// as `expectedRevision`, and a save over a newer copy is refused.
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const hash = createHash('sha256');
|
|
297
|
-
for (const name of ['agent.json', 'AGENT.md', PERSONA_FILE]) {
|
|
298
|
-
hash.update(`${name}\0`);
|
|
299
|
-
try {
|
|
300
|
-
hash.update(readFileSync(join(dir, name)));
|
|
301
|
-
} catch {
|
|
302
|
-
hash.update('\u0001absent');
|
|
303
|
-
}
|
|
304
|
-
hash.update('\0');
|
|
305
|
-
}
|
|
306
|
-
return hash.digest('hex').slice(0, 24);
|
|
307
|
-
}
|
|
784
|
+
const definitionRevision = (dataDir, id) =>
|
|
785
|
+
definitionDirRevision(hasUserAgentFile(dataDir, id) ? userDir(dataDir, id) : shippedDir(id));
|
|
308
786
|
|
|
309
787
|
function getAgentDefinition(agentId) {
|
|
310
788
|
const id = agentEditorId(agentId);
|
|
@@ -326,37 +804,16 @@ export function createAgentEditorApi(deps) {
|
|
|
326
804
|
kind: kindOf(definition),
|
|
327
805
|
disabled: isAgentDisabled(getConfig(), id),
|
|
328
806
|
avatar: avatarOf(dataDir, id),
|
|
329
|
-
persona: readPersona(hasUserAgentFile(dataDir, id) ?
|
|
807
|
+
persona: readPersona(hasUserAgentFile(dataDir, id) ? userDir(dataDir, id) : shippedDir(id)),
|
|
330
808
|
revision: definitionRevision(dataDir, id),
|
|
331
809
|
};
|
|
332
810
|
}
|
|
333
811
|
|
|
334
|
-
function
|
|
335
|
-
const id = agentEditorId(clean(payload.id));
|
|
336
|
-
if (hasOwn(payload, 'id') && !id) {
|
|
337
|
-
throw new Error('agent id must contain letters/numbers (dashes and dots allowed)');
|
|
338
|
-
}
|
|
339
|
-
if (id && isHiddenAgent(id)) throw new Error(`agent "${id}" is internal and cannot be edited`);
|
|
340
|
-
const name = oneLine(payload.name);
|
|
341
|
-
if (!name && !id) throw new Error('agent name must not be empty');
|
|
342
|
-
return {
|
|
343
|
-
// An archived agent keeps its id: its chat is never handed to a new one.
|
|
344
|
-
id:
|
|
345
|
-
id ||
|
|
346
|
-
availableAgentId(
|
|
347
|
-
name,
|
|
348
|
-
(candidate) => Boolean(loadAgentDefinition(dataDir, candidate)) || isArchivedAgent(dataDir, candidate)
|
|
349
|
-
),
|
|
350
|
-
name,
|
|
351
|
-
description: oneLine(payload.description),
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
async function saveAgentDefinition(payload = {}) {
|
|
812
|
+
async function saveAgentDefinition(payload = {}, options) {
|
|
356
813
|
const body = String(payload.body || '').trim();
|
|
357
814
|
if (!body) throw new Error('AGENT.md body must not be empty');
|
|
358
815
|
const dataDir = resolveDataDir(deps);
|
|
359
|
-
const { id, name, description } = resolveSaveTarget(payload, dataDir);
|
|
816
|
+
const { id, name, description } = resolveSaveTarget(loadAgentDefinition, payload, dataDir);
|
|
360
817
|
// Opt-in (the agent profile): without `expectedRevision` the last write
|
|
361
818
|
// wins, as for the Workflows editor and the setup tool.
|
|
362
819
|
if (hasOwn(payload, 'expectedRevision') && String(payload.expectedRevision) !== definitionRevision(dataDir, id)) {
|
|
@@ -370,25 +827,30 @@ export function createAgentEditorApi(deps) {
|
|
|
370
827
|
const reclaimed = !existed && isArchivedAgent(dataDir, id);
|
|
371
828
|
const kind = creationKind(payload, existed);
|
|
372
829
|
const definition = { name: name || id, description, body };
|
|
373
|
-
const manifest = hiddenManifest(
|
|
374
|
-
|
|
375
|
-
//
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
//
|
|
380
|
-
// (
|
|
381
|
-
if (
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
830
|
+
const manifest = hiddenManifest(manifestForUpdate(dataDir, id));
|
|
831
|
+
// With the bot store, a new bot is made in bots/ and a new agent in
|
|
832
|
+
// agents/; either keeps its `kind` marker, so its kind never rests on
|
|
833
|
+
// the folder alone (a copy read without the store is still a bot).
|
|
834
|
+
const storeBot = !existed && kind === BOT_AGENT_KIND && botStoreActive(dataDir);
|
|
835
|
+
// An id is one definition's in one store: a new bot never takes an id
|
|
836
|
+
// agents/ holds (an archived agent's included), a new agent never one of
|
|
837
|
+
// bots/ (or one the split moved there, even if since deleted).
|
|
838
|
+
if (!existed && botStoreActive(dataDir)) {
|
|
839
|
+
const taken = storeBot
|
|
840
|
+
? existsSync(join(dataDir, 'agents', id))
|
|
841
|
+
: existsSync(botDir(dataDir, id)) || migratedBotIds(dataDir).has(id);
|
|
842
|
+
if (taken) throw new Error(`agent id "${id}" is already in use; pick another name`);
|
|
385
843
|
}
|
|
844
|
+
const dir = storeBot ? botDir(dataDir, id) : userDir(dataDir, id);
|
|
845
|
+
const inStore = dir === botDir(dataDir, id);
|
|
846
|
+
const update = applyManifestEdits(manifest, payload, dir, options);
|
|
386
847
|
// PERSONA.md (tone, how it addresses the user): written when given and
|
|
387
848
|
// non-empty, removed when given empty, untouched when absent.
|
|
388
849
|
const persona = hasOwn(payload, 'persona') ? String(payload.persona ?? '').trim() : null;
|
|
389
850
|
// Equal to the shipped agent: keep no copy so it follows the shipped
|
|
390
851
|
// version — unless the manifest (an avatar) or a persona differs.
|
|
391
852
|
const absorbed =
|
|
853
|
+
!inStore &&
|
|
392
854
|
!persona &&
|
|
393
855
|
stableJson(manifest) === stableJson(hiddenManifest(readManifest(shippedDir(id)))) &&
|
|
394
856
|
absorbSaveEqualToShipped({
|
|
@@ -403,11 +865,7 @@ export function createAgentEditorApi(deps) {
|
|
|
403
865
|
if (!absorbed) {
|
|
404
866
|
// Hidden frontmatter (the `kind` marker included) survives every edit.
|
|
405
867
|
const extra = kind ? { ...hiddenFrontmatter(current), kind } : hiddenFrontmatter(current);
|
|
406
|
-
|
|
407
|
-
if (persona) writeFileSync(join(dir, PERSONA_FILE), `${persona}\n`);
|
|
408
|
-
else if (persona === '') rmSync(join(dir, PERSONA_FILE), { force: true });
|
|
409
|
-
// Only the images the manifest names stay.
|
|
410
|
-
syncAvatarFiles(dir, normalizeAgentAvatar(manifest.avatar), update?.writes ?? []);
|
|
868
|
+
writeSavedDefinition(dir, { ...definition, extra, manifest }, persona, update);
|
|
411
869
|
}
|
|
412
870
|
clearAgentDefinitionCache(id);
|
|
413
871
|
if (reclaimed) dropAgentRoute(id);
|
|
@@ -421,143 +879,29 @@ export function createAgentEditorApi(deps) {
|
|
|
421
879
|
return getAgentDefinition(id);
|
|
422
880
|
}
|
|
423
881
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
882
|
+
const { dropAgentRoute, deleteAgentDefinition, restoreAgentDefinition, purgeAgentDefinition } = createAgentArchiveApi(
|
|
883
|
+
{
|
|
884
|
+
dataDirOf: () => resolveDataDir(deps),
|
|
885
|
+
getConfig,
|
|
886
|
+
saveConfigAndAdopt,
|
|
887
|
+
loadAgentDefinition,
|
|
888
|
+
shippedDir,
|
|
889
|
+
announceBotChange,
|
|
890
|
+
getAgentDefinition,
|
|
433
891
|
}
|
|
434
|
-
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
async function deleteAgentDefinition(agentId) {
|
|
438
|
-
const id = agentEditorId(agentId);
|
|
439
|
-
if (!id) throw new Error(`unknown agent "${agentId}"`);
|
|
440
|
-
if (isHiddenAgent(id)) throw new Error(`agent "${id}" is internal and cannot be deleted`);
|
|
441
|
-
const builtIn = isFixedAgent(id);
|
|
442
|
-
const dataDir = resolveDataDir(deps);
|
|
443
|
-
const dir = join(dataDir, 'agents', id);
|
|
444
|
-
if (!hasUserAgentFile(dataDir, id)) {
|
|
445
|
-
if (builtIn) throw new Error(`agent "${id}" has no user override to reset`);
|
|
446
|
-
if (!loadAgentDefinition(dataDir, id)) throw new Error(`agent "${id}" not found`);
|
|
447
|
-
}
|
|
448
|
-
// Agents are global (no workflow rosters): deleting a custom agent removes
|
|
449
|
-
// it from every surface at once — catalog, editor, routes, and spawn (the
|
|
450
|
-
// spawn path rejects unknown agent ids).
|
|
451
|
-
if (builtIn) {
|
|
452
|
-
rmSync(dir, { recursive: true, force: true });
|
|
453
|
-
clearAgentDefinitionCache(id);
|
|
454
|
-
const sourceFallback = loadAgentDefinition(dataDir, id);
|
|
455
|
-
// Its threads, and the Leads listing it, take the shipped definition.
|
|
456
|
-
await announceBotChange(dataDir);
|
|
457
|
-
return { id, deleted: true, revertedToBuiltIn: Boolean(sourceFallback) };
|
|
458
|
-
}
|
|
459
|
-
// A custom agent is archived: its AGENT.md (what makes it an agent
|
|
460
|
-
// anywhere) moves aside, the tombstone keeps the packaged copy of a
|
|
461
|
-
// starter from coming back, and the rest (agent.json: name and face,
|
|
462
|
-
// persona, memory, its model and on/off) stays for its archived chat —
|
|
463
|
-
// until restoreAgentDefinition brings it back or purgeAgentDefinition.
|
|
464
|
-
mkdirSync(dir, { recursive: true });
|
|
465
|
-
if (existsSync(join(dir, 'AGENT.md'))) renameSync(join(dir, 'AGENT.md'), join(dir, ARCHIVED_AGENT_FILE));
|
|
466
|
-
writeFileSync(join(dir, AGENT_DELETED_MARKER), 'deleted\n');
|
|
467
|
-
clearAgentDefinitionCache(id);
|
|
468
|
-
// The Leads listing it drop it from their roster from their next turn.
|
|
469
|
-
await announceBotChange(dataDir);
|
|
470
|
-
return { id, deleted: true, revertedToBuiltIn: false };
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
const isArchivedAgent = (dataDir, id) => existsSync(join(dataDir, 'agents', id, AGENT_DELETED_MARKER));
|
|
892
|
+
);
|
|
474
893
|
|
|
475
|
-
|
|
476
|
-
* back (a starter without a copy of its own returns to the packaged one)
|
|
477
|
-
* and the tombstone gone. Its routines stay off (the host). */
|
|
478
|
-
async function restoreAgentDefinition(agentId) {
|
|
479
|
-
const id = agentEditorId(agentId);
|
|
480
|
-
if (!id || isHiddenAgent(id) || isFixedAgent(id)) throw new Error(`unknown agent "${agentId}"`);
|
|
481
|
-
const dataDir = resolveDataDir(deps);
|
|
482
|
-
if (!isArchivedAgent(dataDir, id)) throw new Error(`agent "${id}" is not deleted`);
|
|
483
|
-
const dir = join(dataDir, 'agents', id);
|
|
484
|
-
const kept = join(dir, ARCHIVED_AGENT_FILE);
|
|
485
|
-
if (!existsSync(kept) && !existsSync(join(shippedDir(id), 'AGENT.md'))) {
|
|
486
|
-
throw new Error(`agent "${id}" was deleted before deleted agents were kept, so it cannot be restored`);
|
|
487
|
-
}
|
|
488
|
-
if (existsSync(kept)) renameSync(kept, join(dir, 'AGENT.md'));
|
|
489
|
-
rmSync(join(dir, AGENT_DELETED_MARKER), { force: true });
|
|
490
|
-
clearAgentDefinitionCache(id);
|
|
491
|
-
// Back in the roster of the Leads that list it, from their next turn.
|
|
492
|
-
await announceBotChange(dataDir);
|
|
493
|
-
return getAgentDefinition(id);
|
|
494
|
-
}
|
|
894
|
+
const archivedAgentIdentities = (agentIds = []) => archivedIdentities(resolveDataDir(deps), agentIds);
|
|
495
895
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
const dir = join(dataDir, 'agents', id);
|
|
505
|
-
rmSync(dir, { recursive: true, force: true });
|
|
506
|
-
clearAgentDefinitionCache(id);
|
|
507
|
-
dropAgentRoute(id);
|
|
508
|
-
if (loadAgentDefinition(dataDir, id)) {
|
|
509
|
-
mkdirSync(dir, { recursive: true });
|
|
510
|
-
writeFileSync(join(dir, AGENT_DELETED_MARKER), 'deleted\n');
|
|
511
|
-
clearAgentDefinitionCache(id);
|
|
512
|
-
}
|
|
513
|
-
return { id, purged: true };
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/** The name and face an archived (deleted) agent left, per id; null for
|
|
517
|
-
* any id that is not archived. Its chat lists under them. */
|
|
518
|
-
function archivedAgentIdentities(agentIds = []) {
|
|
519
|
-
const dataDir = resolveDataDir(deps);
|
|
520
|
-
const ids = Array.isArray(agentIds) ? agentIds.filter((id) => typeof id === 'string') : [];
|
|
521
|
-
return Object.fromEntries(
|
|
522
|
-
ids.map((agentId) => {
|
|
523
|
-
const id = agentEditorId(agentId);
|
|
524
|
-
if (!id || id !== agentId || !isArchivedAgent(dataDir, id)) return [agentId, null];
|
|
525
|
-
const manifest = readManifest(join(dataDir, 'agents', id));
|
|
526
|
-
return [agentId, { name: oneLine(manifest.name) || id, avatar: normalizeAgentAvatar(manifest.avatar) ?? null }];
|
|
527
|
-
})
|
|
896
|
+
const agentDefinitionPresence = (agentIds = []) =>
|
|
897
|
+
definitionPresence(
|
|
898
|
+
{
|
|
899
|
+
dataDir: resolveDataDir(deps),
|
|
900
|
+
rootDir: deps.rootDir ?? STANDALONE_ROOT,
|
|
901
|
+
stat: deps.statSync ?? statSync,
|
|
902
|
+
},
|
|
903
|
+
agentIds
|
|
528
904
|
);
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
// Whether each agent's definition is on disk, decided per id from the files
|
|
532
|
-
// themselves: 'deleted' only when every source is PROVEN absent (ENOENT) or
|
|
533
|
-
// tombstoned, 'unknown' on any other read fault. A list read swallows
|
|
534
|
-
// directory errors, so its absence never proves a deletion; this does.
|
|
535
|
-
function agentDefinitionPresence(agentIds = []) {
|
|
536
|
-
const dataDir = resolveDataDir(deps);
|
|
537
|
-
const rootDir = deps.rootDir ?? STANDALONE_ROOT;
|
|
538
|
-
const stat = deps.statSync ?? statSync;
|
|
539
|
-
const probe = (path) => {
|
|
540
|
-
try {
|
|
541
|
-
stat(path);
|
|
542
|
-
return 'present';
|
|
543
|
-
} catch (error) {
|
|
544
|
-
return error?.code === 'ENOENT' || error?.code === 'ENOTDIR' ? 'absent' : 'unknown';
|
|
545
|
-
}
|
|
546
|
-
};
|
|
547
|
-
const presence = (agentId) => {
|
|
548
|
-
const id = agentEditorId(agentId);
|
|
549
|
-
if (!id || id !== agentId) return 'unknown';
|
|
550
|
-
const user = probe(join(dataDir, 'agents', id, 'AGENT.md'));
|
|
551
|
-
if (user !== 'absent') return user;
|
|
552
|
-
const tombstone = probe(join(dataDir, 'agents', id, AGENT_DELETED_MARKER));
|
|
553
|
-
if (tombstone === 'present') return 'deleted';
|
|
554
|
-
if (tombstone === 'unknown') return 'unknown';
|
|
555
|
-
const shipped = probe(join(rootDir, 'agents', id, 'AGENT.md'));
|
|
556
|
-
return shipped === 'absent' ? 'deleted' : shipped;
|
|
557
|
-
};
|
|
558
|
-
const ids = Array.isArray(agentIds) ? agentIds.filter((id) => typeof id === 'string') : [];
|
|
559
|
-
return Object.fromEntries(ids.map((id) => [id, presence(id)]));
|
|
560
|
-
}
|
|
561
905
|
|
|
562
906
|
// A custom avatar's images as data URLs by state, read on demand: the
|
|
563
907
|
// catalog rows (which also reach the setup tool's model output) carry only
|
|
@@ -566,9 +910,10 @@ export function createAgentEditorApi(deps) {
|
|
|
566
910
|
const id = agentEditorId(agentId);
|
|
567
911
|
if (!id || isHiddenAgent(id)) return null;
|
|
568
912
|
const dataDir = resolveDataDir(deps);
|
|
569
|
-
// Mixy's face lives in
|
|
913
|
+
// Mixy's face lives in its identity folder's agent.json (mixyIdentityDir:
|
|
914
|
+
// bots/mixy with the bot store, agents/lead before), beside no AGENT.md.
|
|
570
915
|
const lead = id === LEAD_AGENT_ID;
|
|
571
|
-
const dir =
|
|
916
|
+
const dir = lead ? leadDir() : userDir(dataDir, id);
|
|
572
917
|
// An archived agent's face stays readable for its archived chat.
|
|
573
918
|
const archived = !lead && isArchivedAgent(dataDir, id);
|
|
574
919
|
const avatar = lead
|
|
@@ -577,87 +922,21 @@ export function createAgentEditorApi(deps) {
|
|
|
577
922
|
? normalizeAgentAvatar(readManifest(dir).avatar)
|
|
578
923
|
: avatarOf(dataDir, id);
|
|
579
924
|
if (avatar?.kind !== 'custom' || (!lead && !archived && !hasUserAgentFile(dataDir, id))) return null;
|
|
580
|
-
|
|
581
|
-
for (const [state, file] of Object.entries(avatar.states)) {
|
|
582
|
-
const image = readAvatarImage(dir, file);
|
|
583
|
-
if (image) states[state] = image;
|
|
584
|
-
}
|
|
585
|
-
return states.idle ? { states } : null;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
// Mixy (the Lead) has no agent definition to edit: only its PERSONA.md
|
|
589
|
-
// (성격) and AGENT.md (일하는 방식, agent mode only), read by the runtime
|
|
590
|
-
// from agents/lead/. Saved empty, the file goes.
|
|
591
|
-
const leadDir = () => join(resolveDataDir(deps), 'agents', LEAD_AGENT_ID);
|
|
592
|
-
function readLeadFile(file) {
|
|
593
|
-
try {
|
|
594
|
-
return readFileSync(join(leadDir(), file), 'utf8').trim();
|
|
595
|
-
} catch {
|
|
596
|
-
return '';
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
function saveLeadFile(file, text) {
|
|
600
|
-
const value = String(text ?? '').trim();
|
|
601
|
-
if (Buffer.byteLength(value) > LEAD_PERSONA_MAX_BYTES) throw new Error(`${file} is larger than 256 KB`);
|
|
602
|
-
const dir = leadDir();
|
|
603
|
-
if (value) {
|
|
604
|
-
mkdirSync(dir, { recursive: true });
|
|
605
|
-
writeFileSync(join(dir, file), `${value}\n`);
|
|
606
|
-
} else rmSync(join(dir, file), { force: true });
|
|
607
|
-
// Mixy's threads take it from their next turn (the save answers at once).
|
|
608
|
-
void publishAgentProfileChange();
|
|
609
|
-
return readLeadFile(file);
|
|
925
|
+
return customAvatarImages(dir, avatar);
|
|
610
926
|
}
|
|
611
|
-
const getLeadPersona = () => readLeadFile(PERSONA_FILE);
|
|
612
|
-
const saveLeadPersona = (text) => saveLeadFile(PERSONA_FILE, text);
|
|
613
|
-
const getLeadAgentBody = () => readLeadFile('AGENT.md');
|
|
614
|
-
const saveLeadAgentBody = (text) => saveLeadFile('AGENT.md', text);
|
|
615
927
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
}
|
|
629
|
-
// Mixy's name and face, chosen in the 대표 에이전트 dialog or its profile:
|
|
630
|
-
// a name '' and an avatar null go back to the defaults; absent keys are kept.
|
|
631
|
-
function saveLeadIdentity(payload = {}) {
|
|
632
|
-
const dir = leadDir();
|
|
633
|
-
const manifest = readManifest(dir);
|
|
634
|
-
if (hasOwn(payload, 'name')) {
|
|
635
|
-
const name = oneLine(payload.name);
|
|
636
|
-
if (name.length > 64) throw new Error('name must be 64 characters or fewer');
|
|
637
|
-
if (name) manifest.name = name;
|
|
638
|
-
else delete manifest.name;
|
|
639
|
-
}
|
|
640
|
-
const update = hasOwn(payload, 'avatar') ? avatarUpdate(payload.avatar, dir) : null;
|
|
641
|
-
if (update?.avatar) manifest.avatar = update.avatar;
|
|
642
|
-
else if (update) delete manifest.avatar;
|
|
643
|
-
mkdirSync(dir, { recursive: true });
|
|
644
|
-
writeFileSync(join(dir, 'agent.json'), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
645
|
-
syncAvatarFiles(dir, normalizeAgentAvatar(manifest.avatar), update?.writes ?? []);
|
|
646
|
-
// The name is in Mixy's prompt (its identity line); the face is not.
|
|
647
|
-
if (hasOwn(payload, 'name')) void publishAgentProfileChange();
|
|
648
|
-
return getLeadStatus();
|
|
649
|
-
}
|
|
650
|
-
// Sets the flag (and Mixy's 하는 일 when given); every other key is kept.
|
|
651
|
-
function setLeadCreated(created, description) {
|
|
652
|
-
if (typeof created !== 'boolean') throw new TypeError('created must be a boolean');
|
|
653
|
-
const manifest = readManifest(leadDir());
|
|
654
|
-
manifest.created = created;
|
|
655
|
-
const job = oneLine(description);
|
|
656
|
-
if (job) manifest.description = job;
|
|
657
|
-
mkdirSync(leadDir(), { recursive: true });
|
|
658
|
-
writeFileSync(join(leadDir(), 'agent.json'), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
659
|
-
return getLeadStatus();
|
|
660
|
-
}
|
|
928
|
+
const {
|
|
929
|
+
leadDir,
|
|
930
|
+
getLeadPersona,
|
|
931
|
+
saveLeadPersona,
|
|
932
|
+
getLeadAgentBody,
|
|
933
|
+
saveLeadAgentBody,
|
|
934
|
+
getLeadStatus,
|
|
935
|
+
saveLeadIdentity,
|
|
936
|
+
setLeadCreated,
|
|
937
|
+
getLeadConnectorPolicy,
|
|
938
|
+
setLeadConnectorPolicy,
|
|
939
|
+
} = createLeadIdentityApi(() => resolveDataDir(deps));
|
|
661
940
|
|
|
662
941
|
// The agent's connector policy (see runtime/.../mcp/agent-connectors.mjs),
|
|
663
942
|
// or null when its definition has none: every connector is on. Read as the
|
|
@@ -668,7 +947,7 @@ export function createAgentEditorApi(deps) {
|
|
|
668
947
|
if (!id) throw new Error(`unknown agent "${agentId}"`);
|
|
669
948
|
if (isHiddenAgent(id)) throw new Error(`agent "${id}" is internal and cannot be edited`);
|
|
670
949
|
const dataDir = resolveDataDir(deps);
|
|
671
|
-
if (id === LEAD_AGENT_ID) return
|
|
950
|
+
if (id === LEAD_AGENT_ID) return getLeadConnectorPolicy();
|
|
672
951
|
if (!loadAgentDefinition(dataDir, id)) throw new Error(`agent "${agentId}" not found`);
|
|
673
952
|
return readConnectorsFile(join(manifestDirOf(dataDir, id), 'agent.json'));
|
|
674
953
|
}
|
|
@@ -678,13 +957,9 @@ export function createAgentEditorApi(deps) {
|
|
|
678
957
|
const id = agentEditorId(agentId);
|
|
679
958
|
const next = connectorPolicyInput(connectors);
|
|
680
959
|
getAgentConnectorPolicy(id);
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
else delete manifest.connectors;
|
|
685
|
-
mkdirSync(leadDir(), { recursive: true });
|
|
686
|
-
writeFileSync(join(leadDir(), 'agent.json'), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
687
|
-
} else {
|
|
960
|
+
assertManifestWritable(join(id === LEAD_AGENT_ID ? leadDir() : userDir(resolveDataDir(deps), id), 'agent.json'));
|
|
961
|
+
if (id === LEAD_AGENT_ID) setLeadConnectorPolicy(next);
|
|
962
|
+
else {
|
|
688
963
|
const { name, description, body } = loadAgentDefinition(resolveDataDir(deps), id);
|
|
689
964
|
await saveAgentDefinition({ id, name, description, body, connectors: next });
|
|
690
965
|
}
|
|
@@ -692,6 +967,8 @@ export function createAgentEditorApi(deps) {
|
|
|
692
967
|
}
|
|
693
968
|
|
|
694
969
|
return {
|
|
970
|
+
migrateAgentKindMarkers: () => migrateAgentKindMarkers(deps),
|
|
971
|
+
migrateBotStore: () => migrateBotStore(deps),
|
|
695
972
|
getAgentConnectorPolicy,
|
|
696
973
|
setAgentConnectorPolicy,
|
|
697
974
|
listAgents,
|