mixdog 1.2.6 → 1.3.1
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/LICENSES/simple-icons-CC0-1.0.txt +30 -0
- package/NOTICE.md +71 -9
- package/package.json +4 -1
- package/scripts/check-model-recommendations.mjs +89 -0
- package/scripts/gate-local.mjs +7 -0
- package/scripts/lib/isolated-test-env.mjs +16 -0
- package/src/agents/reviewer/AGENT.md +4 -2
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +10 -0
- package/src/defaults/skills/browser-use/SKILL.md +5 -0
- package/src/defaults/skills/memory-management/SKILL.md +4 -1
- package/src/defaults/skills/setup/references/actions.md +14 -4
- package/src/defaults/skills/setup-guide/SKILL.md +178 -0
- package/src/defaults/skills/setup-guide/model-recommendations.json +333 -0
- package/src/lib/rules-builder.cjs +55 -6
- package/src/output-styles/common.md +13 -8
- package/src/rules/agent/AGENT.md +4 -0
- package/src/rules/agent-mode/AGENT_MODE.md +24 -0
- package/src/rules/agent-mode/MIXY.md +28 -0
- package/src/rules/agent-mode/ROUTINE.md +6 -0
- package/src/rules/lead/LEAD.md +7 -3
- package/src/rules/shared/70-delivery.md +3 -0
- package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +86 -0
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +77 -14
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +241 -14
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +4 -2
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +216 -0
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -4
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +10 -1
- package/src/runtime/agent/orchestrator/mcp/client.mjs +115 -11
- package/src/runtime/agent/orchestrator/mcp/oauth-account.mjs +97 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +49 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +59 -9
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +32 -7
- package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +45 -6
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +195 -6
- package/src/runtime/agent/orchestrator/mcp/server-connectors.mjs +17 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +7 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +10 -3
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +7 -8
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/lib/tool-call-wire-ids.mjs +65 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +8 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +17 -12
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -0
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +14 -2
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +17 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +96 -1
- package/src/runtime/agent/orchestrator/runtime-core/context-status-shape.mjs +4 -0
- package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +6 -4
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +14 -0
- package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +12 -6
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +32 -5
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +21 -5
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +40 -7
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +52 -20
- package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/agent-home.d.mts +4 -0
- package/src/runtime/agent/orchestrator/session/agent-home.mjs +33 -0
- package/src/runtime/agent/orchestrator/session/agent-mode.mjs +62 -0
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +31 -0
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +241 -0
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +166 -0
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +650 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +192 -0
- package/src/runtime/agent/orchestrator/session/human-gate/held-message.mjs +95 -0
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +221 -0
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +195 -0
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +167 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +108 -6
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +74 -15
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +9 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +5 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +13 -3
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +49 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +18 -8
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-lifecycle-epoch.mjs +0 -8
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +44 -16
- package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +50 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +22 -5
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +39 -4
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +33 -24
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +58 -8
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/mixy-name.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +32 -5
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +86 -0
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +329 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/store-record-strict-scan.mjs +28 -9
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +16 -2
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -12
- package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +46 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +0 -4
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +58 -10
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +92 -15
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +12 -0
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +113 -5
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +4 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +25 -1
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +6 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +3 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +75 -57
- package/src/runtime/attachments/store.mjs +6 -0
- package/src/runtime/browser-bridge/client.mjs +24 -0
- package/src/runtime/channels/lib/scheduler.mjs +40 -12
- package/src/runtime/channels/lib/schedules-db.mjs +71 -12
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +36 -16
- package/src/runtime/channels/lib/webhook.mjs +0 -1
- package/src/runtime/channels/lib/webhooks-db.mjs +100 -10
- package/src/runtime/channels/lib/worker-main.mjs +17 -20
- package/src/runtime/computer-bridge/client.mjs +28 -1
- package/src/runtime/connectors/gmail-app-password.mjs +690 -0
- package/src/runtime/connectors/google-mcp.mjs +446 -0
- package/src/runtime/effort-judge/judge-client.mjs +1 -1
- package/src/runtime/effort-judge/model-manifest.json +7 -7
- package/src/runtime/memory/lib/core-memory-access.mjs +69 -0
- package/src/runtime/memory/lib/core-memory-file.mjs +16 -8
- package/src/runtime/memory/lib/core-memory-index.mjs +15 -9
- package/src/runtime/memory/lib/core-memory-management.mjs +10 -4
- package/src/runtime/memory/lib/core-memory-store.mjs +53 -30
- package/src/runtime/memory/lib/core-memory-uniqueness.mjs +10 -6
- package/src/runtime/memory/lib/http-router/session-routes.mjs +3 -2
- package/src/runtime/memory/lib/memory-action-handlers/core-actions.mjs +44 -14
- package/src/runtime/memory/lib/memory-schema/core-and-meta.mjs +11 -0
- package/src/runtime/memory/lib/memory.mjs +7 -1
- package/src/runtime/memory/lib/query-core-recall.mjs +9 -1
- package/src/runtime/memory/lib/query-hybrid-search.mjs +2 -0
- package/src/runtime/memory/lib/tool-call-handler.mjs +2 -0
- package/src/runtime/memory/tool-defs.mjs +5 -0
- package/src/runtime/shared/agent-avatar-sprites.mjs +445 -0
- package/src/runtime/shared/agent-avatar.d.mts +64 -0
- package/src/runtime/shared/agent-avatar.mjs +320 -0
- package/src/runtime/shared/agent-kind.mjs +152 -0
- package/src/runtime/shared/agent-route-config.d.mts +4 -0
- package/src/runtime/shared/agent-route-config.mjs +30 -0
- package/src/runtime/shared/agent-store.mjs +132 -0
- package/src/runtime/shared/atomic-file.mjs +14 -1
- package/src/runtime/shared/bot-split-migration.mjs +457 -0
- package/src/runtime/shared/config-patch.mjs +20 -1
- package/src/runtime/shared/config.mjs +8 -1
- package/src/runtime/shared/connector-accounts.mjs +15 -0
- package/src/runtime/shared/connector-catalog.d.mts +51 -0
- package/src/runtime/shared/connector-catalog.mjs +228 -0
- package/src/runtime/shared/connector-triggers.d.mts +3 -0
- package/src/runtime/shared/connector-triggers.mjs +45 -0
- package/src/runtime/shared/err-text.mjs +3 -1
- package/src/runtime/shared/error-presentation.mjs +91 -6
- package/src/runtime/shared/google-oauth-client.mjs +24 -0
- package/src/runtime/shared/handoff-artifacts.d.mts +20 -0
- package/src/runtime/shared/handoff-artifacts.mjs +61 -0
- package/src/runtime/shared/held-request-host.d.mts +12 -0
- package/src/runtime/shared/held-request-host.mjs +71 -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/model-route-fallback.mjs +79 -7
- package/src/runtime/shared/plain-preview.d.mts +1 -0
- package/src/runtime/shared/plain-preview.mjs +33 -0
- package/src/runtime/shared/routine-limits.d.mts +11 -0
- package/src/runtime/shared/routine-limits.mjs +21 -0
- package/src/runtime/shared/routine-trigger.d.mts +10 -0
- package/src/runtime/shared/routine-trigger.mjs +44 -0
- package/src/runtime/shared/schedule-model-ref.mjs +9 -10
- 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/slow-span.mjs +4 -12
- package/src/runtime/shared/statusline/statusline-segments.mjs +13 -1
- package/src/runtime/shared/transcript-metadata.mjs +10 -0
- package/src/runtime/shared/turn-changes.mjs +0 -8
- package/src/runtime/shared/usage-readout-totals.mjs +58 -0
- package/src/runtime/shared/user-data-guard.mjs +119 -38
- package/src/session-runtime/agent-home-delivery.mjs +826 -0
- package/src/session-runtime/agent-profile-changes.mjs +21 -0
- package/src/session-runtime/agent-room-delivery.mjs +133 -0
- package/src/session-runtime/agent-task-sessions.mjs +460 -0
- package/src/session-runtime/boot/apis.mjs +63 -18
- package/src/session-runtime/boot/facade.mjs +5 -8
- package/src/session-runtime/boot/infrastructure.mjs +28 -17
- package/src/session-runtime/boot/providers.mjs +14 -4
- package/src/session-runtime/boot/tools.mjs +3 -0
- package/src/session-runtime/channel-config-api.mjs +53 -5
- package/src/session-runtime/config-lifecycle.mjs +43 -2
- package/src/session-runtime/cwd-plugins/core-memory-context.mjs +36 -20
- package/src/session-runtime/cwd-plugins/plugin-status.mjs +6 -1
- package/src/session-runtime/handoff-attachments.mjs +593 -0
- package/src/session-runtime/held-requests.mjs +228 -0
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +121 -0
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +5 -1
- package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +31 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +56 -3
- package/src/session-runtime/internal-tool-executor.mjs +2 -0
- package/src/session-runtime/lifecycle/session-catalog.mjs +9 -0
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +4 -0
- package/src/session-runtime/lifecycle/shared.mjs +6 -0
- package/src/session-runtime/mcp-glue/server-catalog.mjs +8 -0
- package/src/session-runtime/mcp-glue/server-input.mjs +3 -0
- package/src/session-runtime/mcp-oauth-login.mjs +112 -15
- package/src/session-runtime/model-route/route-persist.mjs +2 -2
- package/src/session-runtime/model-route/route-tuning.mjs +7 -1
- package/src/session-runtime/model-route/set-route.mjs +35 -4
- 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 +229 -0
- package/src/session-runtime/resource-api.mjs +5 -0
- package/src/session-runtime/resource-mcp-api.mjs +107 -13
- package/src/session-runtime/resource-skills-api.mjs +12 -0
- package/src/session-runtime/room-member-gate.mjs +48 -0
- package/src/session-runtime/route-resolution.mjs +5 -11
- package/src/session-runtime/routine-home-turn.mjs +156 -0
- package/src/session-runtime/routine-owner.mjs +48 -0
- package/src/session-runtime/schedule-session-run.mjs +30 -64
- package/src/session-runtime/services/agent-tool/helpers.mjs +30 -1
- package/src/session-runtime/services/agent-tool/send-flow.mjs +11 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +19 -4
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +2 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
- 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/channel-admin.mjs +93 -24
- package/src/session-runtime/services/memory-access.mjs +37 -0
- package/src/session-runtime/services/provider-admin.mjs +74 -39
- package/src/session-runtime/services/schedule-access.mjs +91 -0
- package/src/session-runtime/session-create/session-options.mjs +34 -7
- package/src/session-runtime/session-hooks.mjs +16 -8
- package/src/session-runtime/setup-tool/executor.mjs +79 -6
- package/src/session-runtime/setup-tool/extended-actions.mjs +90 -20
- package/src/session-runtime/setup-tool/settings-contract.mjs +14 -6
- package/src/session-runtime/setup-tool/tool-defs.mjs +8 -0
- package/src/session-runtime/skills-api.mjs +16 -1
- package/src/session-runtime/tool-policy-refresh.mjs +249 -29
- package/src/session-runtime/turn/session-ops.mjs +8 -1
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +10 -2
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +14 -0
- package/src/session-runtime/webhook-session-run.mjs +49 -67
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +239 -0
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +650 -25
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +67 -7
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +11 -4
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +11 -1
- package/src/session-runtime/workflow-agents-api.mjs +4 -1
- package/src/standalone/daemon-agent-control.mjs +1 -8
- package/src/standalone/daemon-session-control.mjs +21 -4
- package/src/standalone/daemon.mjs +31 -5
- package/src/standalone/session-protocol.mjs +27 -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 +28 -0
- package/src/standalone/session-service.mjs +19 -0
- package/src/tui/app/slash-commands.mjs +20 -0
- package/src/tui/dist/index.mjs +234 -95
- package/src/tui/session/context-state.mjs +6 -1
- package/src/tui/session/notice-surface.mjs +34 -4
- package/src/tui/session/queue-helpers.mjs +2 -1
- package/src/tui/session/session-api/extensions.mjs +4 -0
- package/src/tui/session/session-api/intake/submission.mjs +5 -0
- package/src/tui/session/session-api/integrations.mjs +8 -0
- package/src/tui/session/session-api/lifecycle/session-reset.mjs +13 -3
- package/src/tui/session/session-api/lifecycle.mjs +17 -1
- package/src/tui/session/session-api/routes.mjs +61 -5
- package/src/tui/session/session-flow/drain.mjs +12 -3
- package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -1
- package/src/tui/session/session-flow/queue/take-entries.mjs +3 -2
- package/src/tui/session/session-flow/steering.mjs +58 -24
- package/src/tui/session/tool-approval.mjs +26 -5
- package/src/tui/session/transcript-intake.mjs +8 -2
- package/src/tui/session/transcript-spill.mjs +5 -1
- package/src/tui/session/tui-steering-persist.mjs +65 -42
- package/src/tui/session/turn-live-call.mjs +64 -0
- package/src/tui/session/turn.mjs +34 -8
- package/src/session-runtime/automation-agents.mjs +0 -94
- package/src/session-runtime/automation-workflow.mjs +0 -59
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import {
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
3
4
|
import { clean } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
|
|
4
5
|
import { hasOwn } from '../../runtime/shared/object.mjs';
|
|
5
6
|
import { serializeFrontmatterDoc } from '../../runtime/shared/markdown-frontmatter.mjs';
|
|
@@ -11,16 +12,58 @@ import {
|
|
|
11
12
|
clearAgentDefinitionCache,
|
|
12
13
|
} from '../../runtime/agent/orchestrator/runtime-core/workflow.mjs';
|
|
13
14
|
import {
|
|
15
|
+
BOT_AGENT_KIND,
|
|
14
16
|
canonicalizeAgentRouteStorage,
|
|
15
17
|
isAgentDisabled,
|
|
18
|
+
WORKFLOW_AGENT_KIND,
|
|
16
19
|
withAgentDisabled,
|
|
17
20
|
} from '../../runtime/shared/agent-route-config.mjs';
|
|
18
21
|
import { agentEditorId, effectiveAgentRoute, oneLine, resolveDataDir } from './shared.mjs';
|
|
22
|
+
import { publishAgentProfileChange } from '../agent-profile-changes.mjs';
|
|
19
23
|
import { absorbSaveEqualToShipped } from '../../runtime/shared/shipped-definitions.mjs';
|
|
24
|
+
import {
|
|
25
|
+
AGENT_AVATAR_DIR,
|
|
26
|
+
AGENT_AVATAR_IMAGE_FILE,
|
|
27
|
+
AGENT_AVATAR_STATES,
|
|
28
|
+
agentAvatarImageProblem,
|
|
29
|
+
agentAvatarStatePath,
|
|
30
|
+
avatarImageInfo,
|
|
31
|
+
avatarPosterFile,
|
|
32
|
+
normalizeAgentAvatar,
|
|
33
|
+
} from '../../runtime/shared/agent-avatar.mjs';
|
|
34
|
+
import {
|
|
35
|
+
parseConnectorPolicy,
|
|
36
|
+
readConnectorsFile,
|
|
37
|
+
storedConnectorPolicy,
|
|
38
|
+
} from '../../runtime/agent/orchestrator/mcp/agent-connectors.mjs';
|
|
39
|
+
|
|
40
|
+
/** A policy given to save → its stored form (null removes the field). Only a
|
|
41
|
+
* deny-list policy `{ off, tools? }` is accepted; anything else throws. */
|
|
42
|
+
function connectorPolicyInput(value) {
|
|
43
|
+
if (Array.isArray(value)) throw new Error('connectors must be { off: [connector id…], tools? } or null');
|
|
44
|
+
return storedConnectorPolicy(parseConnectorPolicy(value));
|
|
45
|
+
}
|
|
20
46
|
import { STANDALONE_ROOT } from '../runtime-paths.mjs';
|
|
47
|
+
import { homeThreadEvidence } from '../../runtime/shared/agent-kind.mjs';
|
|
48
|
+
import { BOT_SPLIT_BACKUP_DIRS, migrateBotsToOwnStore } from '../../runtime/shared/bot-split-migration.mjs';
|
|
49
|
+
import { backupUserData } from '../../runtime/shared/user-data-guard.mjs';
|
|
50
|
+
import { normalizeWorkflowRoute } from '../../runtime/agent/orchestrator/runtime-core/workflow.mjs';
|
|
51
|
+
import {
|
|
52
|
+
botDir,
|
|
53
|
+
botStoreActive,
|
|
54
|
+
migratedBotIds,
|
|
55
|
+
mixyIdentityDir,
|
|
56
|
+
readBotRoute,
|
|
57
|
+
readMixyRoute,
|
|
58
|
+
userDefinitionDir,
|
|
59
|
+
} from '../../runtime/shared/agent-store.mjs';
|
|
21
60
|
|
|
22
61
|
const isFixedAgent = (id) => FIXED_AGENT_SLOTS.some((agent) => agent.id === id);
|
|
23
|
-
const
|
|
62
|
+
const userDir = userDefinitionDir;
|
|
63
|
+
const hasUserAgentFile = (dataDir, id) => existsSync(join(userDir(dataDir, id), 'AGENT.md'));
|
|
64
|
+
// An archived (deleted) agent's AGENT.md, moved aside beside the tombstone:
|
|
65
|
+
// no loader reads it, and restoring moves it back.
|
|
66
|
+
const ARCHIVED_AGENT_FILE = 'AGENT.md.archived';
|
|
24
67
|
|
|
25
68
|
// Frontmatter the editor does not show (e.g. `permission`), taken from the
|
|
26
69
|
// definition in effect: the user's own copy if any, else the shipped one.
|
|
@@ -29,20 +72,216 @@ function hiddenFrontmatter(definition) {
|
|
|
29
72
|
return rest;
|
|
30
73
|
}
|
|
31
74
|
|
|
32
|
-
|
|
75
|
+
// The definition's kind (isContactAgentId reads it), or null: the explicit
|
|
76
|
+
// marker bot creation (`kind: bot`) or the Workflows page (`kind: agent`)
|
|
77
|
+
// wrote, else the read-only decision for an unmarked one (agent-kind.mjs).
|
|
78
|
+
function kindOf(definition) {
|
|
79
|
+
return definition?.kind || null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// A save's `kind`: given only when creating, as the bot or workflow-agent
|
|
83
|
+
// marker. Unmarked agents are workflow agents; a kind never changes later.
|
|
84
|
+
function creationKind(payload, existed) {
|
|
85
|
+
if (!hasOwn(payload, 'kind')) return null;
|
|
86
|
+
if (existed) throw new Error("an agent's kind is set when it is created and cannot change");
|
|
87
|
+
if (payload.kind !== BOT_AGENT_KIND && payload.kind !== WORKFLOW_AGENT_KIND) {
|
|
88
|
+
throw new Error(`agent kind must be "${BOT_AGENT_KIND}", "${WORKFLOW_AGENT_KIND}" or absent`);
|
|
89
|
+
}
|
|
90
|
+
return payload.kind;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function readManifest(dir) {
|
|
94
|
+
return readManifestStrict(join(dir, 'agent.json')) || {};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// The manifest object at `file`, or null when it is missing, unreadable or
|
|
98
|
+
// not a JSON object.
|
|
99
|
+
function readManifestStrict(file) {
|
|
100
|
+
try {
|
|
101
|
+
const value = JSON.parse(readFileSync(file, 'utf8'));
|
|
102
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Manifest keys the editor does not show (e.g. `avatar`). `entry` and `id`
|
|
109
|
+
// go: the editor always writes AGENT.md, and the directory names the agent.
|
|
110
|
+
function hiddenManifest(manifest) {
|
|
111
|
+
const { id: _id, name: _name, description: _description, entry: _entry, ...rest } = manifest;
|
|
112
|
+
return rest;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const PERSONA_FILE = 'PERSONA.md';
|
|
116
|
+
const LEAD_AGENT_ID = 'lead';
|
|
117
|
+
/** The code (and message prefix, which survives IPC) of a stale save. */
|
|
118
|
+
export const AGENT_DEFINITION_CONFLICT = 'AGENT_DEFINITION_CONFLICT';
|
|
119
|
+
const LEAD_PERSONA_MAX_BYTES = 256 * 1024;
|
|
120
|
+
|
|
121
|
+
function readPersona(dir) {
|
|
122
|
+
try {
|
|
123
|
+
return readFileSync(join(dir, PERSONA_FILE), 'utf8').trim();
|
|
124
|
+
} catch {
|
|
125
|
+
return '';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const IMAGE_DATA_URL = /^data:image\/(png|gif|webp);base64,/;
|
|
130
|
+
|
|
131
|
+
// One uploaded image (a data URL) as checked bytes. Only a real PNG, GIF or
|
|
132
|
+
// WebP whose bytes match its declared type passes: no markup (SVG) is ever
|
|
133
|
+
// stored, and the image is never inlined anywhere but an <img>.
|
|
134
|
+
function avatarImageUpload(dataUrl, label) {
|
|
135
|
+
const match = typeof dataUrl === 'string' ? IMAGE_DATA_URL.exec(dataUrl) : null;
|
|
136
|
+
if (!match) throw new Error(`${label} must be a PNG, GIF or WebP image`);
|
|
137
|
+
const bytes = Buffer.from(dataUrl.slice(match[0].length), 'base64');
|
|
138
|
+
const problem = agentAvatarImageProblem(bytes);
|
|
139
|
+
if (problem) throw new Error(`${label} ${problem}`);
|
|
140
|
+
const info = avatarImageInfo(bytes);
|
|
141
|
+
if (info.format !== match[1]) throw new Error(`${label} must be a PNG, GIF or WebP image`);
|
|
142
|
+
return { bytes, info };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// The `avatar` of a save → the record to store and the image files to write
|
|
146
|
+
// (paths under the agent's folder). A custom avatar's state is either a kept
|
|
147
|
+
// file name or a new upload `{ data, poster? }`: an animated upload brings its
|
|
148
|
+
// still first frame (a PNG) for reduced motion and paused views. The older
|
|
149
|
+
// `{ kind: 'image', data }` is an upload of idle alone. null clears the avatar.
|
|
150
|
+
function avatarUpdate(value, dir) {
|
|
151
|
+
if (value == null) return { avatar: null, writes: [] };
|
|
152
|
+
const custom =
|
|
153
|
+
value.kind === 'image' && typeof value.data === 'string'
|
|
154
|
+
? { kind: 'custom', states: { idle: { data: value.data } } }
|
|
155
|
+
: value;
|
|
156
|
+
const writes = [];
|
|
157
|
+
let avatar;
|
|
158
|
+
if (custom.kind === 'custom' && custom.states && typeof custom.states === 'object') {
|
|
159
|
+
const states = {};
|
|
160
|
+
for (const state of AGENT_AVATAR_STATES) {
|
|
161
|
+
const entry = custom.states[state];
|
|
162
|
+
if (entry == null || typeof entry === 'string') {
|
|
163
|
+
if (entry) states[state] = entry;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const label = `agent avatar ${state} image`;
|
|
167
|
+
const { bytes, info } = avatarImageUpload(entry?.data, label);
|
|
168
|
+
const file = `${state}-${createHash('sha256').update(bytes).digest('hex').slice(0, 12)}.${info.format}`;
|
|
169
|
+
writes.push({ path: agentAvatarStatePath(file), bytes });
|
|
170
|
+
if (info.animated) {
|
|
171
|
+
const poster = avatarImageUpload(entry.poster, `${label}'s first frame`);
|
|
172
|
+
if (poster.info.format !== 'png' || poster.info.animated)
|
|
173
|
+
throw new Error(`${label}'s first frame must be a still PNG`);
|
|
174
|
+
writes.push({ path: agentAvatarStatePath(avatarPosterFile(file)), bytes: poster.bytes });
|
|
175
|
+
}
|
|
176
|
+
states[state] = file;
|
|
177
|
+
}
|
|
178
|
+
avatar = normalizeAgentAvatar({ kind: 'custom', states });
|
|
179
|
+
if (!avatar) throw new Error('agent avatar needs an idle image');
|
|
180
|
+
} else {
|
|
181
|
+
avatar = normalizeAgentAvatar(value);
|
|
182
|
+
if (!avatar) throw new Error('agent avatar must be a known mascot or an uploaded image');
|
|
183
|
+
}
|
|
184
|
+
const written = new Set(writes.map((write) => write.path));
|
|
185
|
+
for (const file of avatar.kind === 'custom' ? Object.values(avatar.states) : []) {
|
|
186
|
+
const path = agentAvatarStatePath(file);
|
|
187
|
+
if (!written.has(path) && !existsSync(join(dir, path))) throw new Error('agent avatar image was not found');
|
|
188
|
+
}
|
|
189
|
+
return { avatar, writes };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Writes an update's images, then drops every avatar image the stored avatar
|
|
193
|
+
// no longer names (the older single image beside AGENT.md included).
|
|
194
|
+
function syncAvatarFiles(dir, avatar, writes) {
|
|
195
|
+
for (const { path, bytes } of writes) {
|
|
196
|
+
mkdirSync(join(dir, AGENT_AVATAR_DIR), { recursive: true });
|
|
197
|
+
writeFileSync(join(dir, path), bytes);
|
|
198
|
+
}
|
|
199
|
+
const keep = new Set(
|
|
200
|
+
avatar?.kind === 'custom'
|
|
201
|
+
? Object.values(avatar.states).flatMap((file) => [
|
|
202
|
+
agentAvatarStatePath(file),
|
|
203
|
+
agentAvatarStatePath(avatarPosterFile(file)),
|
|
204
|
+
])
|
|
205
|
+
: []
|
|
206
|
+
);
|
|
207
|
+
for (const name of readdirSync(dir)) {
|
|
208
|
+
if (AGENT_AVATAR_IMAGE_FILE.test(name) && !keep.has(name)) rmSync(join(dir, name), { force: true });
|
|
209
|
+
}
|
|
210
|
+
if (!existsSync(join(dir, AGENT_AVATAR_DIR))) return;
|
|
211
|
+
for (const name of readdirSync(join(dir, AGENT_AVATAR_DIR))) {
|
|
212
|
+
if (!keep.has(`${AGENT_AVATAR_DIR}/${name}`)) rmSync(join(dir, AGENT_AVATAR_DIR, name), { force: true });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// One state image as a data URL, with its still first frame ('' when it has
|
|
217
|
+
// none): checked again on read, so a hand-placed file never reaches an <img>
|
|
218
|
+
// unless it is an image the editor would have accepted.
|
|
219
|
+
function readAvatarImage(dir, file) {
|
|
220
|
+
const read = (path) => {
|
|
221
|
+
try {
|
|
222
|
+
const bytes = readFileSync(join(dir, path));
|
|
223
|
+
if (agentAvatarImageProblem(bytes)) return null;
|
|
224
|
+
const info = avatarImageInfo(bytes);
|
|
225
|
+
return { info, url: `data:image/${info.format};base64,${bytes.toString('base64')}` };
|
|
226
|
+
} catch {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
const image = read(agentAvatarStatePath(file));
|
|
231
|
+
if (!image) return null;
|
|
232
|
+
const poster =
|
|
233
|
+
image.info.animated && !AGENT_AVATAR_IMAGE_FILE.test(file)
|
|
234
|
+
? read(agentAvatarStatePath(avatarPosterFile(file)))
|
|
235
|
+
: null;
|
|
236
|
+
return {
|
|
237
|
+
file,
|
|
238
|
+
url: image.url,
|
|
239
|
+
poster: poster && poster.info.format === 'png' && !poster.info.animated ? poster.url : '',
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const stableJson = (value) =>
|
|
244
|
+
JSON.stringify(
|
|
245
|
+
Object.keys(value)
|
|
246
|
+
.sort()
|
|
247
|
+
.map((key) => [key, value[key]])
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
function writeAgentFiles(dir, { name, description, body, extra = {}, manifest = {} }) {
|
|
33
251
|
const meta = { name, ...(description ? { description } : {}) };
|
|
34
252
|
mkdirSync(dir, { recursive: true });
|
|
35
253
|
writeFileSync(join(dir, 'AGENT.md'), serializeFrontmatterDoc({ ...meta, ...extra }, body));
|
|
36
254
|
// loadAgentDefinition prefers the manifest for name/description.
|
|
37
|
-
writeFileSync(join(dir, 'agent.json'), `${JSON.stringify(meta, null, 2)}\n`);
|
|
255
|
+
writeFileSync(join(dir, 'agent.json'), `${JSON.stringify({ ...meta, ...manifest }, null, 2)}\n`);
|
|
38
256
|
rmSync(join(dir, AGENT_DELETED_MARKER), { force: true });
|
|
257
|
+
rmSync(join(dir, ARCHIVED_AGENT_FILE), { force: true });
|
|
39
258
|
}
|
|
40
259
|
|
|
41
260
|
// Agent catalog + editor surface: fixed services stay built-in; starter and
|
|
42
261
|
// user-authored agents are custom definitions under agents/<id>/AGENT.md.
|
|
43
262
|
export function createAgentEditorApi(deps) {
|
|
44
263
|
const { getConfig, loadAgentDefinition, listCustomAgentIds, saveConfigAndAdopt, setAgentRoute } = deps;
|
|
45
|
-
|
|
264
|
+
// A store bot's route lives in its folder; every other agent's in the config.
|
|
265
|
+
const ownRoute = (config, id) => {
|
|
266
|
+
const bot = readBotRoute(resolveDataDir(deps), id);
|
|
267
|
+
return bot !== undefined ? bot : deps.agentRouteFromConfig(config, id);
|
|
268
|
+
};
|
|
269
|
+
const routeFor = (config, id) => effectiveAgentRoute({ ...deps, agentRouteFromConfig: ownRoute }, config, id);
|
|
270
|
+
// `route` falls back to Main; this says whether the agent has a route of
|
|
271
|
+
// its own, so a surface never shows (or saves) the Main fallback as one.
|
|
272
|
+
const routeConfigured = (config, id) => Boolean(ownRoute(config, id));
|
|
273
|
+
const shippedDir = (id) => join(deps.rootDir ?? STANDALONE_ROOT, 'agents', id);
|
|
274
|
+
// The manifest in effect: the user's own copy if any, else the shipped one.
|
|
275
|
+
const manifestDirOf = (dataDir, id) => (hasUserAgentFile(dataDir, id) ? userDir(dataDir, id) : shippedDir(id));
|
|
276
|
+
const manifestOf = (dataDir, id) => readManifest(manifestDirOf(dataDir, id));
|
|
277
|
+
const avatarOf = (dataDir, id) => normalizeAgentAvatar(manifestOf(dataDir, id).avatar);
|
|
278
|
+
// A saved, archived or restored bot reaches the prompts that embed it: the
|
|
279
|
+
// fresh scan republishes the shared bot list the Lead rosters read (a new id
|
|
280
|
+
// is in it at once), then every runtime is told.
|
|
281
|
+
async function announceBotChange(dataDir) {
|
|
282
|
+
listCustomAgentIds?.(dataDir);
|
|
283
|
+
await publishAgentProfileChange();
|
|
284
|
+
}
|
|
46
285
|
|
|
47
286
|
function listAgents() {
|
|
48
287
|
const dataDir = resolveDataDir(deps);
|
|
@@ -52,6 +291,7 @@ export function createAgentEditorApi(deps) {
|
|
|
52
291
|
locked: true,
|
|
53
292
|
userOverride: hasUserAgentFile(dataDir, agent.id),
|
|
54
293
|
route: routeFor(config, agent.id),
|
|
294
|
+
routeConfigured: routeConfigured(config, agent.id),
|
|
55
295
|
disabled: isAgentDisabled(config, agent.id),
|
|
56
296
|
definition: loadAgentDefinition(dataDir, agent.id),
|
|
57
297
|
}));
|
|
@@ -66,13 +306,34 @@ export function createAgentEditorApi(deps) {
|
|
|
66
306
|
custom: true,
|
|
67
307
|
userOverride: hasUserAgentFile(dataDir, id),
|
|
68
308
|
route: routeFor(config, id),
|
|
309
|
+
routeConfigured: routeConfigured(config, id),
|
|
310
|
+
kind: kindOf(definition),
|
|
69
311
|
disabled: isAgentDisabled(config, id),
|
|
312
|
+
avatar: avatarOf(dataDir, id),
|
|
70
313
|
definition,
|
|
71
314
|
};
|
|
72
315
|
});
|
|
73
316
|
return [...fixed, ...custom];
|
|
74
317
|
}
|
|
75
318
|
|
|
319
|
+
// The stored definition's revision: a hash of the three files an editor
|
|
320
|
+
// writes, from the copy in effect. An editor that loaded it can send it back
|
|
321
|
+
// as `expectedRevision`, and a save over a newer copy is refused.
|
|
322
|
+
function definitionRevision(dataDir, id) {
|
|
323
|
+
const dir = hasUserAgentFile(dataDir, id) ? userDir(dataDir, id) : shippedDir(id);
|
|
324
|
+
const hash = createHash('sha256');
|
|
325
|
+
for (const name of ['agent.json', 'AGENT.md', PERSONA_FILE]) {
|
|
326
|
+
hash.update(`${name}\0`);
|
|
327
|
+
try {
|
|
328
|
+
hash.update(readFileSync(join(dir, name)));
|
|
329
|
+
} catch {
|
|
330
|
+
hash.update('\u0001absent');
|
|
331
|
+
}
|
|
332
|
+
hash.update('\0');
|
|
333
|
+
}
|
|
334
|
+
return hash.digest('hex').slice(0, 24);
|
|
335
|
+
}
|
|
336
|
+
|
|
76
337
|
function getAgentDefinition(agentId) {
|
|
77
338
|
const id = agentEditorId(agentId);
|
|
78
339
|
if (!id) throw new Error(`unknown agent "${agentId}"`);
|
|
@@ -89,7 +350,12 @@ export function createAgentEditorApi(deps) {
|
|
|
89
350
|
custom: !isFixedAgent(id),
|
|
90
351
|
userOverride: hasUserAgentFile(dataDir, id),
|
|
91
352
|
route: routeFor(getConfig(), id),
|
|
353
|
+
routeConfigured: routeConfigured(getConfig(), id),
|
|
354
|
+
kind: kindOf(definition),
|
|
92
355
|
disabled: isAgentDisabled(getConfig(), id),
|
|
356
|
+
avatar: avatarOf(dataDir, id),
|
|
357
|
+
persona: readPersona(hasUserAgentFile(dataDir, id) ? userDir(dataDir, id) : shippedDir(id)),
|
|
358
|
+
revision: definitionRevision(dataDir, id),
|
|
93
359
|
};
|
|
94
360
|
}
|
|
95
361
|
|
|
@@ -102,7 +368,20 @@ export function createAgentEditorApi(deps) {
|
|
|
102
368
|
const name = oneLine(payload.name);
|
|
103
369
|
if (!name && !id) throw new Error('agent name must not be empty');
|
|
104
370
|
return {
|
|
105
|
-
id:
|
|
371
|
+
// An archived agent keeps its id: its chat is never handed to a new one.
|
|
372
|
+
id:
|
|
373
|
+
id ||
|
|
374
|
+
availableAgentId(
|
|
375
|
+
name,
|
|
376
|
+
(candidate) =>
|
|
377
|
+
Boolean(loadAgentDefinition(dataDir, candidate)) ||
|
|
378
|
+
isArchivedAgent(dataDir, candidate) ||
|
|
379
|
+
// An id the split copied stays the bot's, even once purged; a
|
|
380
|
+
// folder in either store keeps its id too.
|
|
381
|
+
migratedBotIds(dataDir).has(candidate) ||
|
|
382
|
+
(botStoreActive(dataDir) &&
|
|
383
|
+
(existsSync(join(dataDir, 'agents', candidate)) || existsSync(botDir(dataDir, candidate))))
|
|
384
|
+
),
|
|
106
385
|
name,
|
|
107
386
|
description: oneLine(payload.description),
|
|
108
387
|
};
|
|
@@ -113,25 +392,82 @@ export function createAgentEditorApi(deps) {
|
|
|
113
392
|
if (!body) throw new Error('AGENT.md body must not be empty');
|
|
114
393
|
const dataDir = resolveDataDir(deps);
|
|
115
394
|
const { id, name, description } = resolveSaveTarget(payload, dataDir);
|
|
395
|
+
// Opt-in (the agent profile): without `expectedRevision` the last write
|
|
396
|
+
// wins, as for the Workflows editor and the setup tool.
|
|
397
|
+
if (hasOwn(payload, 'expectedRevision') && String(payload.expectedRevision) !== definitionRevision(dataDir, id)) {
|
|
398
|
+
const conflict = new Error(`${AGENT_DEFINITION_CONFLICT}: agent "${id}" changed since it was loaded`);
|
|
399
|
+
conflict.code = AGENT_DEFINITION_CONFLICT;
|
|
400
|
+
throw conflict;
|
|
401
|
+
}
|
|
116
402
|
const current = loadAgentDefinition(dataDir, id);
|
|
117
403
|
const existed = Boolean(current);
|
|
404
|
+
// Made anew under an archived agent's id (never restored): its kept route goes.
|
|
405
|
+
const reclaimed = !existed && isArchivedAgent(dataDir, id);
|
|
406
|
+
const kind = creationKind(payload, existed);
|
|
118
407
|
const definition = { name: name || id, description, body };
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
408
|
+
const manifest = hiddenManifest(manifestOf(dataDir, id));
|
|
409
|
+
// With the bot store, a new bot is made in bots/ and a new agent in
|
|
410
|
+
// agents/; either keeps its `kind` marker, so its kind never rests on
|
|
411
|
+
// the folder alone (a copy read without the store is still a bot).
|
|
412
|
+
const storeBot = !existed && kind === BOT_AGENT_KIND && botStoreActive(dataDir);
|
|
413
|
+
// An id is one definition's in one store: a new bot never takes an id
|
|
414
|
+
// agents/ holds (an archived agent's included), a new agent never one of
|
|
415
|
+
// bots/ (or one the split moved there, even if since deleted).
|
|
416
|
+
if (!existed && botStoreActive(dataDir)) {
|
|
417
|
+
const taken = storeBot
|
|
418
|
+
? existsSync(join(dataDir, 'agents', id))
|
|
419
|
+
: existsSync(botDir(dataDir, id)) || migratedBotIds(dataDir).has(id);
|
|
420
|
+
if (taken) throw new Error(`agent id "${id}" is already in use; pick another name`);
|
|
421
|
+
}
|
|
422
|
+
const dir = storeBot ? botDir(dataDir, id) : userDir(dataDir, id);
|
|
423
|
+
const inStore = dir === botDir(dataDir, id);
|
|
424
|
+
// New custom images arrive as checked bytes, stored under content hashes.
|
|
425
|
+
const update = hasOwn(payload, 'avatar') ? avatarUpdate(payload.avatar, dir) : null;
|
|
426
|
+
if (update?.avatar) manifest.avatar = update.avatar;
|
|
427
|
+
else if (update) delete manifest.avatar;
|
|
428
|
+
// Connectors: a policy replaces the saved one, null removes the field
|
|
429
|
+
// (every connector on), absent leaves it as saved.
|
|
430
|
+
if (hasOwn(payload, 'connectors')) {
|
|
431
|
+
const connectors = connectorPolicyInput(payload.connectors);
|
|
432
|
+
if (connectors) manifest.connectors = connectors;
|
|
433
|
+
else delete manifest.connectors;
|
|
434
|
+
}
|
|
435
|
+
// PERSONA.md (tone, how it addresses the user): written when given and
|
|
436
|
+
// non-empty, removed when given empty, untouched when absent.
|
|
437
|
+
const persona = hasOwn(payload, 'persona') ? String(payload.persona ?? '').trim() : null;
|
|
438
|
+
// Equal to the shipped agent: keep no copy so it follows the shipped
|
|
439
|
+
// version — unless the manifest (an avatar) or a persona differs.
|
|
440
|
+
const absorbed =
|
|
441
|
+
!inStore &&
|
|
442
|
+
!persona &&
|
|
443
|
+
stableJson(manifest) === stableJson(hiddenManifest(readManifest(shippedDir(id)))) &&
|
|
444
|
+
absorbSaveEqualToShipped({
|
|
445
|
+
rootDir: deps.rootDir ?? STANDALONE_ROOT,
|
|
446
|
+
dataDir,
|
|
447
|
+
dir: 'agents',
|
|
448
|
+
id,
|
|
449
|
+
entry: 'AGENT.md',
|
|
450
|
+
files: ['AGENT.md', 'agent.json'],
|
|
451
|
+
definition,
|
|
452
|
+
});
|
|
453
|
+
if (!absorbed) {
|
|
454
|
+
// Hidden frontmatter (the `kind` marker included) survives every edit.
|
|
455
|
+
const extra = kind ? { ...hiddenFrontmatter(current), kind } : hiddenFrontmatter(current);
|
|
456
|
+
writeAgentFiles(dir, { ...definition, extra, manifest });
|
|
457
|
+
if (persona) writeFileSync(join(dir, PERSONA_FILE), `${persona}\n`);
|
|
458
|
+
else if (persona === '') rmSync(join(dir, PERSONA_FILE), { force: true });
|
|
459
|
+
// Only the images the manifest names stay.
|
|
460
|
+
syncAvatarFiles(dir, normalizeAgentAvatar(manifest.avatar), update?.writes ?? []);
|
|
461
|
+
}
|
|
130
462
|
clearAgentDefinitionCache(id);
|
|
463
|
+
if (reclaimed) dropAgentRoute(id);
|
|
131
464
|
// A newly created agent starts enabled; a stale roster entry left by an
|
|
132
465
|
// earlier agent of the same id must not disable it.
|
|
133
466
|
if (!existed && isAgentDisabled(getConfig(), id)) saveConfigAndAdopt(withAgentDisabled(getConfig(), id, false));
|
|
134
467
|
if (payload.route) await setAgentRoute(id, payload.route);
|
|
468
|
+
// Its threads take the saved persona, AGENT.md and name, and the Leads
|
|
469
|
+
// listing it (a new bot, a new name) their roster, from their next turn.
|
|
470
|
+
await announceBotChange(dataDir);
|
|
135
471
|
return getAgentDefinition(id);
|
|
136
472
|
}
|
|
137
473
|
|
|
@@ -154,7 +490,7 @@ export function createAgentEditorApi(deps) {
|
|
|
154
490
|
if (isHiddenAgent(id)) throw new Error(`agent "${id}" is internal and cannot be deleted`);
|
|
155
491
|
const builtIn = isFixedAgent(id);
|
|
156
492
|
const dataDir = resolveDataDir(deps);
|
|
157
|
-
const dir =
|
|
493
|
+
const dir = userDir(dataDir, id);
|
|
158
494
|
if (!hasUserAgentFile(dataDir, id)) {
|
|
159
495
|
if (builtIn) throw new Error(`agent "${id}" has no user override to reset`);
|
|
160
496
|
if (!loadAgentDefinition(dataDir, id)) throw new Error(`agent "${id}" not found`);
|
|
@@ -162,19 +498,308 @@ export function createAgentEditorApi(deps) {
|
|
|
162
498
|
// Agents are global (no workflow rosters): deleting a custom agent removes
|
|
163
499
|
// it from every surface at once — catalog, editor, routes, and spawn (the
|
|
164
500
|
// spawn path rejects unknown agent ids).
|
|
501
|
+
if (builtIn) {
|
|
502
|
+
rmSync(dir, { recursive: true, force: true });
|
|
503
|
+
clearAgentDefinitionCache(id);
|
|
504
|
+
const sourceFallback = loadAgentDefinition(dataDir, id);
|
|
505
|
+
// Its threads, and the Leads listing it, take the shipped definition.
|
|
506
|
+
await announceBotChange(dataDir);
|
|
507
|
+
return { id, deleted: true, revertedToBuiltIn: Boolean(sourceFallback) };
|
|
508
|
+
}
|
|
509
|
+
// A custom agent is archived: its AGENT.md (what makes it an agent
|
|
510
|
+
// anywhere) moves aside, the tombstone keeps the packaged copy of a
|
|
511
|
+
// starter from coming back, and the rest (agent.json: name and face,
|
|
512
|
+
// persona, memory, its model and on/off) stays for its archived chat —
|
|
513
|
+
// until restoreAgentDefinition brings it back or purgeAgentDefinition.
|
|
514
|
+
mkdirSync(dir, { recursive: true });
|
|
515
|
+
if (existsSync(join(dir, 'AGENT.md'))) renameSync(join(dir, 'AGENT.md'), join(dir, ARCHIVED_AGENT_FILE));
|
|
516
|
+
writeFileSync(join(dir, AGENT_DELETED_MARKER), 'deleted\n');
|
|
517
|
+
clearAgentDefinitionCache(id);
|
|
518
|
+
// The Leads listing it drop it from their roster from their next turn.
|
|
519
|
+
await announceBotChange(dataDir);
|
|
520
|
+
return { id, deleted: true, revertedToBuiltIn: false };
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const isArchivedAgent = (dataDir, id) => existsSync(join(userDir(dataDir, id), AGENT_DELETED_MARKER));
|
|
524
|
+
|
|
525
|
+
/** An archived agent back as it was, under its own id: its AGENT.md moved
|
|
526
|
+
* back (a starter without a copy of its own returns to the packaged one)
|
|
527
|
+
* and the tombstone gone. Its routines stay off (the host). */
|
|
528
|
+
async function restoreAgentDefinition(agentId) {
|
|
529
|
+
const id = agentEditorId(agentId);
|
|
530
|
+
if (!id || isHiddenAgent(id) || isFixedAgent(id)) throw new Error(`unknown agent "${agentId}"`);
|
|
531
|
+
const dataDir = resolveDataDir(deps);
|
|
532
|
+
if (!isArchivedAgent(dataDir, id)) throw new Error(`agent "${id}" is not deleted`);
|
|
533
|
+
const dir = userDir(dataDir, id);
|
|
534
|
+
const kept = join(dir, ARCHIVED_AGENT_FILE);
|
|
535
|
+
if (!existsSync(kept) && !existsSync(join(shippedDir(id), 'AGENT.md'))) {
|
|
536
|
+
throw new Error(`agent "${id}" was deleted before deleted agents were kept, so it cannot be restored`);
|
|
537
|
+
}
|
|
538
|
+
if (existsSync(kept)) renameSync(kept, join(dir, 'AGENT.md'));
|
|
539
|
+
rmSync(join(dir, AGENT_DELETED_MARKER), { force: true });
|
|
540
|
+
clearAgentDefinitionCache(id);
|
|
541
|
+
// Back in the roster of the Leads that list it, from their next turn.
|
|
542
|
+
await announceBotChange(dataDir);
|
|
543
|
+
return getAgentDefinition(id);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/** Deleting an archived agent again: everything it left goes for good. A
|
|
547
|
+
* starter keeps only its tombstone, so its packaged copy stays gone. */
|
|
548
|
+
async function purgeAgentDefinition(agentId) {
|
|
549
|
+
const id = agentEditorId(agentId);
|
|
550
|
+
if (!id || isHiddenAgent(id) || isFixedAgent(id)) throw new Error(`unknown agent "${agentId}"`);
|
|
551
|
+
const dataDir = resolveDataDir(deps);
|
|
552
|
+
// Never a live agent; one deleted before archiving existed left nothing.
|
|
553
|
+
if (!isArchivedAgent(dataDir, id)) return { id, purged: false };
|
|
554
|
+
const dir = userDir(dataDir, id);
|
|
165
555
|
rmSync(dir, { recursive: true, force: true });
|
|
166
556
|
clearAgentDefinitionCache(id);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
// tombstone so deleting one does not fall back to the packaged copy.
|
|
170
|
-
if (!builtIn && sourceFallback) {
|
|
557
|
+
dropAgentRoute(id);
|
|
558
|
+
if (loadAgentDefinition(dataDir, id)) {
|
|
171
559
|
mkdirSync(dir, { recursive: true });
|
|
172
560
|
writeFileSync(join(dir, AGENT_DELETED_MARKER), 'deleted\n');
|
|
173
561
|
clearAgentDefinitionCache(id);
|
|
174
562
|
}
|
|
175
|
-
|
|
176
|
-
|
|
563
|
+
return { id, purged: true };
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/** The name and face an archived (deleted) agent left, per id; null for
|
|
567
|
+
* any id that is not archived. Its chat lists under them. */
|
|
568
|
+
function archivedAgentIdentities(agentIds = []) {
|
|
569
|
+
const dataDir = resolveDataDir(deps);
|
|
570
|
+
const ids = Array.isArray(agentIds) ? agentIds.filter((id) => typeof id === 'string') : [];
|
|
571
|
+
return Object.fromEntries(
|
|
572
|
+
ids.map((agentId) => {
|
|
573
|
+
const id = agentEditorId(agentId);
|
|
574
|
+
if (!id || id !== agentId || !isArchivedAgent(dataDir, id)) return [agentId, null];
|
|
575
|
+
const manifest = readManifest(userDir(dataDir, id));
|
|
576
|
+
return [agentId, { name: oneLine(manifest.name) || id, avatar: normalizeAgentAvatar(manifest.avatar) ?? null }];
|
|
577
|
+
})
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Whether each agent's definition is on disk, decided per id from the files
|
|
582
|
+
// themselves: 'deleted' only when every source is PROVEN absent (ENOENT) or
|
|
583
|
+
// tombstoned, 'unknown' on any other read fault. A list read swallows
|
|
584
|
+
// directory errors, so its absence never proves a deletion; this does.
|
|
585
|
+
function agentDefinitionPresence(agentIds = []) {
|
|
586
|
+
const dataDir = resolveDataDir(deps);
|
|
587
|
+
const rootDir = deps.rootDir ?? STANDALONE_ROOT;
|
|
588
|
+
const stat = deps.statSync ?? statSync;
|
|
589
|
+
const probe = (path) => {
|
|
590
|
+
try {
|
|
591
|
+
stat(path);
|
|
592
|
+
return 'present';
|
|
593
|
+
} catch (error) {
|
|
594
|
+
return error?.code === 'ENOENT' || error?.code === 'ENOTDIR' ? 'absent' : 'unknown';
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
const presence = (agentId) => {
|
|
598
|
+
const id = agentEditorId(agentId);
|
|
599
|
+
if (!id || id !== agentId) return 'unknown';
|
|
600
|
+
const user = probe(join(userDir(dataDir, id), 'AGENT.md'));
|
|
601
|
+
if (user !== 'absent') return user;
|
|
602
|
+
const tombstone = probe(join(userDir(dataDir, id), AGENT_DELETED_MARKER));
|
|
603
|
+
if (tombstone === 'present') return 'deleted';
|
|
604
|
+
if (tombstone === 'unknown') return 'unknown';
|
|
605
|
+
const shipped = probe(join(rootDir, 'agents', id, 'AGENT.md'));
|
|
606
|
+
return shipped === 'absent' ? 'deleted' : shipped;
|
|
607
|
+
};
|
|
608
|
+
const ids = Array.isArray(agentIds) ? agentIds.filter((id) => typeof id === 'string') : [];
|
|
609
|
+
return Object.fromEntries(ids.map((id) => [id, presence(id)]));
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// A custom avatar's images as data URLs by state, read on demand: the
|
|
613
|
+
// catalog rows (which also reach the setup tool's model output) carry only
|
|
614
|
+
// file names, and the images themselves never go to a model.
|
|
615
|
+
function getAgentAvatarImage(agentId) {
|
|
616
|
+
const id = agentEditorId(agentId);
|
|
617
|
+
if (!id || isHiddenAgent(id)) return null;
|
|
618
|
+
const dataDir = resolveDataDir(deps);
|
|
619
|
+
// Mixy's face lives in its identity folder's agent.json (mixyIdentityDir:
|
|
620
|
+
// bots/mixy with the bot store, agents/lead before), beside no AGENT.md.
|
|
621
|
+
const lead = id === LEAD_AGENT_ID;
|
|
622
|
+
const dir = lead ? leadDir() : userDir(dataDir, id);
|
|
623
|
+
// An archived agent's face stays readable for its archived chat.
|
|
624
|
+
const archived = !lead && isArchivedAgent(dataDir, id);
|
|
625
|
+
const avatar = lead
|
|
626
|
+
? normalizeAgentAvatar(readManifest(leadDir()).avatar)
|
|
627
|
+
: archived
|
|
628
|
+
? normalizeAgentAvatar(readManifest(dir).avatar)
|
|
629
|
+
: avatarOf(dataDir, id);
|
|
630
|
+
if (avatar?.kind !== 'custom' || (!lead && !archived && !hasUserAgentFile(dataDir, id))) return null;
|
|
631
|
+
const states = {};
|
|
632
|
+
for (const [state, file] of Object.entries(avatar.states)) {
|
|
633
|
+
const image = readAvatarImage(dir, file);
|
|
634
|
+
if (image) states[state] = image;
|
|
635
|
+
}
|
|
636
|
+
return states.idle ? { states } : null;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Mixy (the Lead) has no agent definition to edit: only its PERSONA.md
|
|
640
|
+
// (성격) and AGENT.md (일하는 방식, agent mode only), read by the runtime
|
|
641
|
+
// from Mixy's identity folder: bots/mixy once the bot store is active,
|
|
642
|
+
// agents/lead before (mixyIdentityDir, agent-store.mjs). Saved empty, the
|
|
643
|
+
// file goes.
|
|
644
|
+
const leadDir = () => mixyIdentityDir(resolveDataDir(deps));
|
|
645
|
+
function readLeadFile(file) {
|
|
646
|
+
try {
|
|
647
|
+
return readFileSync(join(leadDir(), file), 'utf8').trim();
|
|
648
|
+
} catch {
|
|
649
|
+
return '';
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
function saveLeadFile(file, text) {
|
|
653
|
+
const value = String(text ?? '').trim();
|
|
654
|
+
if (Buffer.byteLength(value) > LEAD_PERSONA_MAX_BYTES) throw new Error(`${file} is larger than 256 KB`);
|
|
655
|
+
const dir = leadDir();
|
|
656
|
+
if (value) {
|
|
657
|
+
mkdirSync(dir, { recursive: true });
|
|
658
|
+
writeFileSync(join(dir, file), `${value}\n`);
|
|
659
|
+
} else rmSync(join(dir, file), { force: true });
|
|
660
|
+
// Mixy's threads take it from their next turn (the save answers at once).
|
|
661
|
+
void publishAgentProfileChange();
|
|
662
|
+
return readLeadFile(file);
|
|
663
|
+
}
|
|
664
|
+
const getLeadPersona = () => readLeadFile(PERSONA_FILE);
|
|
665
|
+
const saveLeadPersona = (text) => saveLeadFile(PERSONA_FILE, text);
|
|
666
|
+
const getLeadAgentBody = () => readLeadFile('AGENT.md');
|
|
667
|
+
const saveLeadAgentBody = (text) => saveLeadFile('AGENT.md', text);
|
|
668
|
+
|
|
669
|
+
// Whether Mixy was created (the "대표 에이전트 만들기" first run): the
|
|
670
|
+
// explicit `created` flag in Mixy's agent.json (leadDir). null until decided —
|
|
671
|
+
// the desktop host decides once (an existing profile counts as created).
|
|
672
|
+
// Also Mixy's display name (`name`, read by the runtime's identity line) and
|
|
673
|
+
// face (`avatar`, as every agent manifest): '' / null mean the defaults.
|
|
674
|
+
// `homeChatted`: whether Mixy's home thread holds any conversation (the
|
|
675
|
+
// host reads it to tell v1.3.0's never-used automatic Mixy apart).
|
|
676
|
+
function getLeadStatus() {
|
|
677
|
+
const manifest = readManifest(leadDir());
|
|
678
|
+
// A v1.3.0 automatic Mixy the split left in agents/lead still counts as
|
|
679
|
+
// decided (its routines keep running) until bots/mixy says otherwise.
|
|
680
|
+
const legacy = readManifest(join(resolveDataDir(deps), 'agents', LEAD_AGENT_ID)).created;
|
|
681
|
+
let created = null;
|
|
682
|
+
if (typeof manifest.created === 'boolean') created = manifest.created;
|
|
683
|
+
else if (typeof legacy === 'boolean') created = legacy;
|
|
684
|
+
return {
|
|
685
|
+
created,
|
|
686
|
+
name: oneLine(manifest.name),
|
|
687
|
+
avatar: normalizeAgentAvatar(manifest.avatar) ?? null,
|
|
688
|
+
homeChatted: homeThreadEvidence(resolveDataDir(deps), LEAD_AGENT_ID).chatted,
|
|
689
|
+
// Mixy's own model (never the Lead's); null: its thread keeps its own.
|
|
690
|
+
route: readMixyRoute(resolveDataDir(deps)),
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
// Mixy's name and face, chosen in the 대표 에이전트 dialog or its profile:
|
|
694
|
+
// a name '' and an avatar null go back to the defaults; absent keys are kept.
|
|
695
|
+
function saveLeadIdentity(payload = {}) {
|
|
696
|
+
const dir = leadDir();
|
|
697
|
+
const manifest = readManifest(dir);
|
|
698
|
+
if (hasOwn(payload, 'name')) {
|
|
699
|
+
const name = oneLine(payload.name);
|
|
700
|
+
if (name.length > 64) throw new Error('name must be 64 characters or fewer');
|
|
701
|
+
if (name) manifest.name = name;
|
|
702
|
+
else delete manifest.name;
|
|
703
|
+
}
|
|
704
|
+
const update = hasOwn(payload, 'avatar') ? avatarUpdate(payload.avatar, dir) : null;
|
|
705
|
+
if (update?.avatar) manifest.avatar = update.avatar;
|
|
706
|
+
else if (update) delete manifest.avatar;
|
|
707
|
+
mkdirSync(dir, { recursive: true });
|
|
708
|
+
writeFileSync(join(dir, 'agent.json'), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
709
|
+
syncAvatarFiles(dir, normalizeAgentAvatar(manifest.avatar), update?.writes ?? []);
|
|
710
|
+
// The name is in Mixy's prompt (its identity line); the face is not.
|
|
711
|
+
if (hasOwn(payload, 'name')) void publishAgentProfileChange();
|
|
712
|
+
return getLeadStatus();
|
|
713
|
+
}
|
|
714
|
+
// Sets the flag (and Mixy's 하는 일 when given); every other key is kept.
|
|
715
|
+
// A Lead manifest that exists but cannot be read as an object is the
|
|
716
|
+
// user's and is left alone: nothing is written, the status stays as read.
|
|
717
|
+
function setLeadCreated(created, description) {
|
|
718
|
+
if (typeof created !== 'boolean') throw new TypeError('created must be a boolean');
|
|
719
|
+
const file = join(leadDir(), 'agent.json');
|
|
720
|
+
if (existsSync(file) && !readManifestStrict(file)) {
|
|
721
|
+
process.stderr.write(`[agents] ${file} is not a readable JSON object; Mixy's created flag was not saved\n`);
|
|
722
|
+
return getLeadStatus();
|
|
723
|
+
}
|
|
724
|
+
const manifest = readManifest(leadDir());
|
|
725
|
+
manifest.created = created;
|
|
726
|
+
const job = oneLine(description);
|
|
727
|
+
if (job) manifest.description = job;
|
|
728
|
+
mkdirSync(leadDir(), { recursive: true });
|
|
729
|
+
writeFileSync(join(leadDir(), 'agent.json'), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
730
|
+
return getLeadStatus();
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// The agent's connector policy (see runtime/.../mcp/agent-connectors.mjs),
|
|
734
|
+
// or null when its definition has none: every connector is on. Read as the
|
|
735
|
+
// runtime reads it: a broken file or field is DENY_ALL_CONNECTORS, never
|
|
736
|
+
// "every connector".
|
|
737
|
+
function getAgentConnectorPolicy(agentId) {
|
|
738
|
+
const id = agentEditorId(agentId);
|
|
739
|
+
if (!id) throw new Error(`unknown agent "${agentId}"`);
|
|
740
|
+
if (isHiddenAgent(id)) throw new Error(`agent "${id}" is internal and cannot be edited`);
|
|
741
|
+
const dataDir = resolveDataDir(deps);
|
|
742
|
+
if (id === LEAD_AGENT_ID) return readConnectorsFile(join(leadDir(), 'agent.json'));
|
|
743
|
+
if (!loadAgentDefinition(dataDir, id)) throw new Error(`agent "${agentId}" not found`);
|
|
744
|
+
return readConnectorsFile(join(manifestDirOf(dataDir, id), 'agent.json'));
|
|
745
|
+
}
|
|
746
|
+
// Replaces the policy (null removes the field). Every other key — avatar,
|
|
747
|
+
// persona, frontmatter — is kept; for Mixy only its agent.json is touched.
|
|
748
|
+
async function setAgentConnectorPolicy(agentId, connectors) {
|
|
749
|
+
const id = agentEditorId(agentId);
|
|
750
|
+
const next = connectorPolicyInput(connectors);
|
|
751
|
+
getAgentConnectorPolicy(id);
|
|
752
|
+
if (id === LEAD_AGENT_ID) {
|
|
753
|
+
const manifest = readManifest(leadDir());
|
|
754
|
+
if (next) manifest.connectors = next;
|
|
755
|
+
else delete manifest.connectors;
|
|
756
|
+
mkdirSync(leadDir(), { recursive: true });
|
|
757
|
+
writeFileSync(join(leadDir(), 'agent.json'), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
758
|
+
} else {
|
|
759
|
+
const { name, description, body } = loadAgentDefinition(resolveDataDir(deps), id);
|
|
760
|
+
await saveAgentDefinition({ id, name, description, body, connectors: next });
|
|
761
|
+
}
|
|
762
|
+
return getAgentConnectorPolicy(id);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/** The one-time split of bots into bots/ (bot-split-migration.mjs), from
|
|
766
|
+
* the config as it is now; the catalogs read the store from here on. */
|
|
767
|
+
function migrateBotStore() {
|
|
768
|
+
const dataDir = resolveDataDir(deps);
|
|
769
|
+
const config = getConfig();
|
|
770
|
+
const result = migrateBotsToOwnStore({
|
|
771
|
+
dataDir,
|
|
772
|
+
shippedRoot: deps.rootDir ?? STANDALONE_ROOT,
|
|
773
|
+
routeOf: (id) => deps.agentRouteFromConfig(config, id),
|
|
774
|
+
leadRoute: normalizeWorkflowRoute(deps.resolveRoute(config, {})),
|
|
775
|
+
// The definitions the split copies or a re-sync replaces go with it.
|
|
776
|
+
backup: () => backupUserData(dataDir, 'bot-split', { dirs: BOT_SPLIT_BACKUP_DIRS }),
|
|
777
|
+
log: (line) => process.stderr.write(`${line}\n`),
|
|
778
|
+
});
|
|
779
|
+
clearAgentDefinitionCache();
|
|
780
|
+
listCustomAgentIds?.(dataDir);
|
|
781
|
+
return result;
|
|
177
782
|
}
|
|
178
783
|
|
|
179
|
-
return {
|
|
784
|
+
return {
|
|
785
|
+
migrateBotStore,
|
|
786
|
+
getAgentConnectorPolicy,
|
|
787
|
+
setAgentConnectorPolicy,
|
|
788
|
+
listAgents,
|
|
789
|
+
getAgentDefinition,
|
|
790
|
+
getAgentAvatarImage,
|
|
791
|
+
getLeadPersona,
|
|
792
|
+
saveLeadPersona,
|
|
793
|
+
getLeadAgentBody,
|
|
794
|
+
saveLeadAgentBody,
|
|
795
|
+
getLeadStatus,
|
|
796
|
+
setLeadCreated,
|
|
797
|
+
saveLeadIdentity,
|
|
798
|
+
saveAgentDefinition,
|
|
799
|
+
deleteAgentDefinition,
|
|
800
|
+
restoreAgentDefinition,
|
|
801
|
+
purgeAgentDefinition,
|
|
802
|
+
archivedAgentIdentities,
|
|
803
|
+
agentDefinitionPresence,
|
|
804
|
+
};
|
|
180
805
|
}
|