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,7 @@
|
|
|
1
1
|
// Deferred-pool and MCP-instruction manifests injected into BP2.
|
|
2
|
-
import {
|
|
2
|
+
import { accountOfInstructions } from '../../../shared/connector-accounts.mjs';
|
|
3
|
+
import { connectorTrigger } from '../../../shared/connector-triggers.mjs';
|
|
4
|
+
import { parseMcpToolName, titleCaseMcpServer } from '../../../shared/tool-primitives.mjs';
|
|
3
5
|
import { isToolBridgeEnabled, TOOL_BRIDGE_GUIDANCE } from '../runtime-core/tool-exposure.mjs';
|
|
4
6
|
|
|
5
7
|
export function compactPromptManifestText(value, max = 250) {
|
|
@@ -100,11 +102,8 @@ function sanitizeMcpInstructionText(text, max = MCP_INSTRUCTION_MAX_CHARS) {
|
|
|
100
102
|
/**
|
|
101
103
|
* Per-server MCP initialize instructions for deferred-pool tools only.
|
|
102
104
|
* Empty when no instructions or no matching deferred MCP tools → omit block.
|
|
103
|
-
* Emits ONLY the server heading + instruction body:
|
|
104
|
-
*
|
|
105
|
-
* (with its description) in <available-deferred-tools>, and re-listing ~30
|
|
106
|
-
* names per server doubled the MCP share of the BP2 prefix (2026-08-05 audit).
|
|
107
|
-
* Server membership stays evident from the mcp__<server>__ name prefix.
|
|
105
|
+
* Emits ONLY the server heading + instruction body: tool names are never
|
|
106
|
+
* listed — <available-deferred-tools> names each server once (mcp__<server>).
|
|
108
107
|
*/
|
|
109
108
|
function buildMcpInstructionsManifest(mcpServerInstructions, poolNames, catalog = []) {
|
|
110
109
|
const map = mcpServerInstructions && typeof mcpServerInstructions === 'object' ? mcpServerInstructions : {};
|
|
@@ -163,18 +162,82 @@ function rebuildDeferredToolManifestBlock(text, manifest) {
|
|
|
163
162
|
return out;
|
|
164
163
|
}
|
|
165
164
|
|
|
166
|
-
function
|
|
167
|
-
const
|
|
165
|
+
function catalogByName(session) {
|
|
166
|
+
const byName = new Map();
|
|
168
167
|
for (const tool of Array.isArray(session?.deferredToolCatalog) ? session.deferredToolCatalog : []) {
|
|
169
168
|
const name = String(tool?.name || '').trim();
|
|
170
|
-
|
|
171
|
-
if (name && !descByName.has(name)) descByName.set(name, String(tool?.trigger || tool?.description || ''));
|
|
169
|
+
if (name && !byName.has(name)) byName.set(name, tool);
|
|
172
170
|
}
|
|
171
|
+
return byName;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// MCP tools are listed once per server: load_tool loads every tool of
|
|
175
|
+
// `mcp__<server>`. Tool definitions carry their server (shortened names
|
|
176
|
+
// cannot be parsed).
|
|
177
|
+
const mcpServerOf = (byName, name) => parseMcpToolName(byName.get(String(name).trim()) || name)?.server || '';
|
|
178
|
+
const mcpServerLineName = (server) => `mcp__${server}`;
|
|
179
|
+
const firstSentence = (text) => {
|
|
180
|
+
const flat = String(text || '')
|
|
181
|
+
.replace(/\s+/g, ' ')
|
|
182
|
+
.trim();
|
|
183
|
+
return (flat.split(/(?<=[.!?])\s/, 1)[0] || flat).replace(/[.!?]+$/, '');
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// A catalog connector's curated trigger; else the server's MCP instructions;
|
|
187
|
+
// else its tools' leading descriptions (name order); else its name. Derived
|
|
188
|
+
// lines are compacted to at most 100 characters, ellipsis included.
|
|
189
|
+
function mcpServerTrigger(server, tools, instructions) {
|
|
190
|
+
// A multi-account connector's server names its account (connector-accounts.mjs).
|
|
191
|
+
const account = accountOfInstructions(instructions?.[server]);
|
|
192
|
+
const curated = connectorTrigger(server);
|
|
193
|
+
if (curated) return account ? `${curated} Account: ${account}` : curated;
|
|
194
|
+
const instruction = String(instructions?.[server] || '').replace(/[<>]/g, '');
|
|
195
|
+
const described = [...tools]
|
|
196
|
+
.sort((a, b) => String(a?.name || '').localeCompare(String(b?.name || '')))
|
|
197
|
+
.map((tool) => firstSentence(String(tool?.description || '').replace(/[<>]/g, '')))
|
|
198
|
+
.filter(Boolean)
|
|
199
|
+
.join('; ');
|
|
200
|
+
const derived = instruction.trim() ? instruction : described;
|
|
201
|
+
return derived ? compactPromptManifestText(derived, 98) : `${titleCaseMcpServer(server)} MCP server tools.`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function deferredManifestEntries(session, pool) {
|
|
205
|
+
const byName = catalogByName(session);
|
|
173
206
|
const skillRoutedNames = skillRoutedToolNames(session.messages);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
207
|
+
const entries = [];
|
|
208
|
+
const servers = new Map();
|
|
209
|
+
for (const name of pool) {
|
|
210
|
+
const key = String(name).trim();
|
|
211
|
+
const tool = byName.get(key);
|
|
212
|
+
const server = mcpServerOf(byName, name);
|
|
213
|
+
if (server) {
|
|
214
|
+
if (!servers.has(server)) servers.set(server, []);
|
|
215
|
+
servers.get(server).push(tool || { name: key });
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
// Skill-style selection trigger when the tool declares one; else its description.
|
|
219
|
+
entries.push({
|
|
220
|
+
name,
|
|
221
|
+
description: skillRoutedNames.has(key) ? '' : String(tool?.trigger || tool?.description || ''),
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
for (const [server, tools] of servers) {
|
|
225
|
+
entries.push({
|
|
226
|
+
name: mcpServerLineName(server),
|
|
227
|
+
description: mcpServerTrigger(server, tools, session.mcpServerInstructions),
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
return entries;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** The pool names `rendered` lists: by their own line, or their MCP server's. */
|
|
234
|
+
export function listedDeferredToolNames(session, poolNames, rendered) {
|
|
235
|
+
const byName = catalogByName(session);
|
|
236
|
+
const text = String(rendered || '');
|
|
237
|
+
return (Array.isArray(poolNames) ? poolNames : []).filter((name) => {
|
|
238
|
+
const server = mcpServerOf(byName, name);
|
|
239
|
+
return text.includes(server ? `- ${mcpServerLineName(server)}: ` : name);
|
|
240
|
+
});
|
|
178
241
|
}
|
|
179
242
|
|
|
180
243
|
function composeDeferredBp2Manifest(session, pool) {
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
// Agent-scoped role markdown and 4-BP system prompt composition.
|
|
2
2
|
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
3
4
|
import { existsSync, readdirSync } from 'node:fs';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
+
import { basename, dirname, join } from 'node:path';
|
|
5
6
|
import { compareCodePoints } from '../../../shared/code-point-order.mjs';
|
|
7
|
+
import { BOTS_DIR, MIXY_BOT_ID, botStoreActive, isStoreBot, mixyIdentityDir } from '../../../shared/agent-store.mjs';
|
|
6
8
|
import { mixdogRoot } from '../../../shared/plugin-paths.mjs';
|
|
7
9
|
import { readMarkdownDocument } from '../../../shared/markdown-frontmatter.mjs';
|
|
8
10
|
import { listHiddenAgentsByKind, getAgentCatalogShareAgents } from '../internal-agents.mjs';
|
|
9
11
|
import { mixdogGlobalDir, mtimeWithTtl, readSafe } from './skill-catalog.mjs';
|
|
12
|
+
import { mixyDisplayName } from '../session/mixy-name.mjs';
|
|
10
13
|
|
|
11
14
|
// --- Agent-scoped instruction loader ---
|
|
12
15
|
// Emits a BP2 agent/system block scoped to the calling agent:
|
|
@@ -45,6 +48,12 @@ const _scopedRoleInstructionsCache = new Map();
|
|
|
45
48
|
const _scopedRoleInstructionsMtimeCache = new Map();
|
|
46
49
|
const _ROLE_INSTRUCTIONS_MTIME_TTL_MS = 2000;
|
|
47
50
|
|
|
51
|
+
/** A saved AGENT.md applies to the next composition, even inside the mtime TTL. */
|
|
52
|
+
export function invalidateRoleInstructionsCache() {
|
|
53
|
+
_scopedRoleInstructionsCache.clear();
|
|
54
|
+
_scopedRoleInstructionsMtimeCache.clear();
|
|
55
|
+
}
|
|
56
|
+
|
|
48
57
|
function loadHiddenAgentSnippets(pluginRoot) {
|
|
49
58
|
try {
|
|
50
59
|
const agentRulesDir = join(pluginRoot, 'rules', 'agent');
|
|
@@ -81,9 +90,35 @@ function agentSectionDirs(pluginRoot) {
|
|
|
81
90
|
} catch {
|
|
82
91
|
/* unresolvable data dir — built-in roles still load */
|
|
83
92
|
}
|
|
93
|
+
// Bots of the store (agent-store.mjs), last: a bot's own folder wins over
|
|
94
|
+
// the agents/ original the split left behind.
|
|
95
|
+
const root = userDataRoot();
|
|
96
|
+
if (root && botStoreActive(root)) dirs.push(join(root, BOTS_DIR));
|
|
84
97
|
return dirs;
|
|
85
98
|
}
|
|
86
99
|
|
|
100
|
+
// The user data dir (parent of agents/), or '' when it cannot be resolved.
|
|
101
|
+
function userDataRoot() {
|
|
102
|
+
try {
|
|
103
|
+
const userDir = mixdogGlobalDir('agents');
|
|
104
|
+
return userDir ? dirname(userDir) : '';
|
|
105
|
+
} catch {
|
|
106
|
+
return '';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// A store bot's own folder, or ''.
|
|
111
|
+
function storeBotDir(agent) {
|
|
112
|
+
const root = userDataRoot();
|
|
113
|
+
return root && isStoreBot(root, agent) ? join(root, BOTS_DIR, agent) : '';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Mixy's identity folder: bots/mixy with the store, agents/lead before.
|
|
117
|
+
function mixyDir() {
|
|
118
|
+
const root = userDataRoot();
|
|
119
|
+
return root ? mixyIdentityDir(root) : '';
|
|
120
|
+
}
|
|
121
|
+
|
|
87
122
|
function loadAgentSections(pluginRoot) {
|
|
88
123
|
// agents/ accepts both the compatibility flat layout and the current
|
|
89
124
|
// nested agents/<agent>/AGENT.md layout.
|
|
@@ -106,7 +141,10 @@ function loadAgentSections(pluginRoot) {
|
|
|
106
141
|
name = entry.name.replace(/\.md$/, '');
|
|
107
142
|
raw = readSafe(join(agentsDir, entry.name));
|
|
108
143
|
}
|
|
109
|
-
|
|
144
|
+
// Mixy's own AGENT.md (agents/lead/) rides only Mixy's agent-mode
|
|
145
|
+
// prompt (loadMixyRoleSection), never another role catalog.
|
|
146
|
+
if (!name || !raw || name === 'lead') continue;
|
|
147
|
+
if (name === MIXY_BOT_ID && basename(agentsDir) === BOTS_DIR) continue;
|
|
110
148
|
const { body } = readMarkdownDocument(raw);
|
|
111
149
|
if (!body) continue;
|
|
112
150
|
byName.set(name, roleSection(name, body));
|
|
@@ -225,6 +263,174 @@ export function loadScopedRoleInstructions(agent, provider = null) {
|
|
|
225
263
|
}
|
|
226
264
|
}
|
|
227
265
|
|
|
266
|
+
// --- Agent persona (agents/<id>/PERSONA.md) ---
|
|
267
|
+
// Optional tone/addressing/personality guide, rendered right after the
|
|
268
|
+
// agent's role section. Read from the same directory that supplies the
|
|
269
|
+
// agent's AGENT.md (user data dir first, then shipped); the Lead (`lead`,
|
|
270
|
+
// Mixy) has no AGENT.md, so its persona is the first PERSONA.md found.
|
|
271
|
+
// The text depends only on the file, so the BP3 prefix is stable until the
|
|
272
|
+
// file changes; it is re-read per composition, so an edit applies next turn.
|
|
273
|
+
export const PERSONA_MAX_CHARS = 4000;
|
|
274
|
+
export const PERSONA_TRUNCATION_MARKER = '\n\n[persona truncated]';
|
|
275
|
+
const PERSONA_AGENT_ID = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
276
|
+
|
|
277
|
+
export function truncatePersona(text, max = PERSONA_MAX_CHARS) {
|
|
278
|
+
const value = typeof text === 'string' ? text.trim() : '';
|
|
279
|
+
if (value.length <= max) return value;
|
|
280
|
+
return value.slice(0, max).trimEnd() + PERSONA_TRUNCATION_MARKER;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function loadAgentPersonaSection(agent) {
|
|
284
|
+
if (typeof agent !== 'string' || !PERSONA_AGENT_ID.test(agent) || agent.includes('..')) return '';
|
|
285
|
+
const isLead = agent === 'lead';
|
|
286
|
+
// Mixy's persona is in its own folder; a store bot's in its bots/ folder.
|
|
287
|
+
const own = isLead ? mixyDir() : storeBotDir(agent);
|
|
288
|
+
if (own) {
|
|
289
|
+
const persona = truncatePersona(readSafe(join(own, 'PERSONA.md')) || '');
|
|
290
|
+
return persona ? personaSection(persona) : '';
|
|
291
|
+
}
|
|
292
|
+
const pluginRoot = mixdogRoot();
|
|
293
|
+
const roots = [];
|
|
294
|
+
try {
|
|
295
|
+
const userDir = mixdogGlobalDir('agents');
|
|
296
|
+
if (userDir) roots.push(userDir);
|
|
297
|
+
} catch {
|
|
298
|
+
/* unresolvable data dir — shipped persona still loads */
|
|
299
|
+
}
|
|
300
|
+
if (pluginRoot) roots.push(join(pluginRoot, 'agents'));
|
|
301
|
+
for (const root of roots) {
|
|
302
|
+
const dir = join(root, agent);
|
|
303
|
+
if (!isLead && !existsSync(join(dir, 'AGENT.md'))) continue;
|
|
304
|
+
const persona = truncatePersona(readSafe(join(dir, 'PERSONA.md')) || '');
|
|
305
|
+
if (!persona) {
|
|
306
|
+
if (isLead) continue;
|
|
307
|
+
return '';
|
|
308
|
+
}
|
|
309
|
+
return personaSection(persona);
|
|
310
|
+
}
|
|
311
|
+
return '';
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function personaSection(persona) {
|
|
315
|
+
return [
|
|
316
|
+
'## Persona',
|
|
317
|
+
'',
|
|
318
|
+
'Tone and style guide for how you speak, address the user and carry yourself. It never overrides tool, safety or role rules; those always win.',
|
|
319
|
+
'',
|
|
320
|
+
persona,
|
|
321
|
+
].join('\n');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// --- Mixy's working style (agents/lead/AGENT.md, user data dir) ---
|
|
325
|
+
// The user-editable 일하는 방식 of Mixy, framed like any agent's role section.
|
|
326
|
+
// Agent mode only; empty or missing means no section. Re-read per
|
|
327
|
+
// composition, so an edit applies to the next prompt build.
|
|
328
|
+
export function loadMixyRoleSection() {
|
|
329
|
+
let raw = '';
|
|
330
|
+
try {
|
|
331
|
+
const dir = mixyDir();
|
|
332
|
+
raw = dir ? readSafe(join(dir, 'AGENT.md')) || '' : '';
|
|
333
|
+
} catch {
|
|
334
|
+
return '';
|
|
335
|
+
}
|
|
336
|
+
const { body } = readMarkdownDocument(raw);
|
|
337
|
+
const text = String(body || '').trim();
|
|
338
|
+
return text ? `# Agent Role Catalog\n\n${roleSection('lead', text)}` : '';
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// --- Agent-mode identity (LEAD.md first line) ---
|
|
342
|
+
// "You are <name>, <what it does>." from the agent's manifest (agent.json, the
|
|
343
|
+
// name/description the editor writes), user copy first, then shipped. Mixy
|
|
344
|
+
// (the Lead configuration, id `lead`) has its own line, under the display name
|
|
345
|
+
// the user gave it (session/mixy-name.mjs), Mixy by default.
|
|
346
|
+
function agentManifest(agent) {
|
|
347
|
+
const own = storeBotDir(agent);
|
|
348
|
+
if (own) {
|
|
349
|
+
try {
|
|
350
|
+
const value = JSON.parse(readSafe(join(own, 'agent.json')) || '{}');
|
|
351
|
+
return value && typeof value === 'object' ? value : {};
|
|
352
|
+
} catch {
|
|
353
|
+
return {};
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const roots = [];
|
|
357
|
+
try {
|
|
358
|
+
const userDir = mixdogGlobalDir('agents');
|
|
359
|
+
if (userDir) roots.push(userDir);
|
|
360
|
+
} catch {
|
|
361
|
+
/* unresolvable data dir — shipped manifest still loads */
|
|
362
|
+
}
|
|
363
|
+
const pluginRoot = mixdogRoot();
|
|
364
|
+
if (pluginRoot) roots.push(join(pluginRoot, 'agents'));
|
|
365
|
+
for (const root of roots) {
|
|
366
|
+
const dir = join(root, agent);
|
|
367
|
+
if (!existsSync(join(dir, 'AGENT.md'))) continue;
|
|
368
|
+
try {
|
|
369
|
+
const value = JSON.parse(readSafe(join(dir, 'agent.json')) || '{}');
|
|
370
|
+
return value && typeof value === 'object' ? value : {};
|
|
371
|
+
} catch {
|
|
372
|
+
return {};
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return {};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export function agentModeIdentityLine(agent) {
|
|
379
|
+
if (agent === 'lead') return `You are ${mixyDisplayName()}, the user's assistant in Mixdog.`;
|
|
380
|
+
if (typeof agent !== 'string' || !PERSONA_AGENT_ID.test(agent) || agent.includes('..')) return '';
|
|
381
|
+
const oneLine = (value) => (typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '');
|
|
382
|
+
const manifest = agentManifest(agent);
|
|
383
|
+
const name = oneLine(manifest.name) || agent;
|
|
384
|
+
const role = oneLine(manifest.description).replace(/[.。]+$/, '');
|
|
385
|
+
return role ? `You are ${name}. Your role: ${role}.` : `You are ${name}.`;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// --- Agent profile revision ---
|
|
389
|
+
// The files an agent's prompt sections (identity line, role, persona) are
|
|
390
|
+
// read from — its agent.json, AGENT.md and PERSONA.md in the user data dir and
|
|
391
|
+
// the shipped tree — as one hash. A session stamps it when its prompt is
|
|
392
|
+
// composed; another value means the profile was edited since
|
|
393
|
+
// (session-runtime/tool-policy-refresh.mjs refreshAgentProfile). '' for an
|
|
394
|
+
// invalid id. A coding Lead (`lead` outside Mixy's own thread) embeds no
|
|
395
|
+
// profile file at all, so its stamp is a constant: editing Mixy never
|
|
396
|
+
// rebuilds a coding session's prompt.
|
|
397
|
+
export const CODING_LEAD_PROFILE_REVISION = 'coding-lead';
|
|
398
|
+
export function agentProfileRevision(agent, { mixy = true } = {}) {
|
|
399
|
+
if (typeof agent !== 'string' || !PERSONA_AGENT_ID.test(agent) || agent.includes('..')) return '';
|
|
400
|
+
if (agent === 'lead' && !mixy) return CODING_LEAD_PROFILE_REVISION;
|
|
401
|
+
const roots = [mixdogGlobalDir('agents')];
|
|
402
|
+
const pluginRoot = mixdogRoot();
|
|
403
|
+
if (pluginRoot) roots.push(join(pluginRoot, 'agents'));
|
|
404
|
+
const hash = createHash('sha256');
|
|
405
|
+
for (const root of roots) {
|
|
406
|
+
for (const file of ['agent.json', 'AGENT.md', 'PERSONA.md']) {
|
|
407
|
+
hash.update(`${root}\0${file}\0${readSafe(join(root, agent, file)) ?? '\u0001'}\0`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// Mixy's or a store bot's own folder, when it has one.
|
|
411
|
+
const own = agent === 'lead' ? mixyDir() : storeBotDir(agent);
|
|
412
|
+
if (own && own !== join(roots[0] || '', agent)) {
|
|
413
|
+
for (const file of ['agent.json', 'AGENT.md', 'PERSONA.md']) {
|
|
414
|
+
hash.update(`${own}\0${file}\0${readSafe(join(own, file)) ?? '\u0001'}\0`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return hash.digest('hex').slice(0, 24);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// --- Lead roster revision ---
|
|
421
|
+
// The bot roster a Lead's prompt lists (workflow/context.mjs leadPromptParts:
|
|
422
|
+
// the agent catalog and Mixy's contact catalog — never the orchestration
|
|
423
|
+
// mode, a setting that applies to new sessions), as one hash, stamped beside
|
|
424
|
+
// agentProfileRevision. A bot created, renamed, archived or restored changes
|
|
425
|
+
// it for every Lead whose prompt lists that bot.
|
|
426
|
+
export function leadRosterRevision(parts) {
|
|
427
|
+
const hash = createHash('sha256');
|
|
428
|
+
for (const slice of [parts?.agentCatalogContext, parts?.contactCatalogContext]) {
|
|
429
|
+
hash.update(`${slice || ''}\0`);
|
|
430
|
+
}
|
|
431
|
+
return hash.digest('hex').slice(0, 24);
|
|
432
|
+
}
|
|
433
|
+
|
|
228
434
|
// --- Compose system prompt — 4-BP cache layout ---
|
|
229
435
|
// Returns the three stable system blocks and the BP3 core used for refreshes.
|
|
230
436
|
// They map directly to the breakpoint plan:
|
|
@@ -262,32 +468,53 @@ export function composeSystemPrompt(opts) {
|
|
|
262
468
|
]);
|
|
263
469
|
|
|
264
470
|
// ── BP3: workflow/role + session environment layer ─────────────────
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
471
|
+
let roleInstructionContext = '';
|
|
472
|
+
if (!opts.skipRoleCatalog) {
|
|
473
|
+
roleInstructionContext =
|
|
474
|
+
opts.agent === 'lead'
|
|
475
|
+
? loadMixyRoleSection()
|
|
476
|
+
: loadScopedRoleInstructions(opts.agent || null, opts.provider || null);
|
|
477
|
+
}
|
|
478
|
+
// Persona follows the role; Mixy's own thread carries Mixy's persona
|
|
479
|
+
// (bots/mixy/PERSONA.md) — a coding Lead's prompt never does (skipPersona).
|
|
480
|
+
const personaContext = opts.agent && !opts.skipPersona ? loadAgentPersonaSection(opts.agent) : '';
|
|
270
481
|
const coreMemory = skip.memory ? '' : trimmedPromptSlice(opts.coreMemoryContext);
|
|
271
482
|
// User AGENTS.md / CLAUDE.md block (context/instruction-files.mjs), already
|
|
272
|
-
// headed `# Instruction Files`; rides right
|
|
483
|
+
// headed `# Instruction Files`; rides right before core memory.
|
|
273
484
|
const instructionFiles = skip.claudemd ? '' : trimmedPromptSlice(opts.instructionFilesContext);
|
|
485
|
+
// Ordered for the longest shared prefix (session-prompt-composition.mjs):
|
|
486
|
+
// 1. shared by every Lead — the workflow pack and LEAD.md's common lines;
|
|
487
|
+
// 2. the mode block — coding: orchestration mode, LEAD.md's two passages,
|
|
488
|
+
// the full agent catalog; agent mode: the delegation block, their
|
|
489
|
+
// variants, AGENT_MODE.md, the sub-agent catalog (the same for every
|
|
490
|
+
// agent other than Mixy);
|
|
491
|
+
// 3. Mixy only — MIXY.md and its contact-agent catalog;
|
|
492
|
+
// 4. per session or agent, last — routine line, identity, caller prompt,
|
|
493
|
+
// role, persona, instruction files and core memory (changes most).
|
|
274
494
|
const sessionMarkerCore = joinPromptSlices([
|
|
275
495
|
trimmedPromptSlice(opts.workflowContext),
|
|
276
496
|
opts.roleRules || '',
|
|
497
|
+
trimmedPromptSlice(opts.orchestrationContext),
|
|
498
|
+
opts.leadModeRules || '',
|
|
499
|
+
opts.agentModeRules || '',
|
|
500
|
+
trimmedPromptSlice(opts.agentCatalogContext),
|
|
501
|
+
opts.mixyRules || '',
|
|
502
|
+
trimmedPromptSlice(opts.contactCatalogContext),
|
|
503
|
+
opts.routineRules || '',
|
|
504
|
+
opts.identityContext || '',
|
|
277
505
|
opts.userPrompt || '',
|
|
278
506
|
roleInstructionContext,
|
|
279
|
-
|
|
507
|
+
personaContext,
|
|
280
508
|
instructionFiles,
|
|
509
|
+
coreMemory ? `# Core Memory\n${coreMemory}` : '',
|
|
281
510
|
]);
|
|
282
511
|
// Response language is the LAST system text before the conversation. It
|
|
283
512
|
// rides the environment block (not the tier3 core) because the `# Session`
|
|
284
513
|
// / shell / git lines are English and would otherwise trail it, pulling the
|
|
285
514
|
// pre-tool preamble toward English.
|
|
286
|
-
const environmentParts = [
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
opts.languageContext,
|
|
290
|
-
].map(trimmedPromptSlice);
|
|
515
|
+
const environmentParts = [opts.sessionStartContext, opts.environmentContext, opts.languageContext].map(
|
|
516
|
+
trimmedPromptSlice
|
|
517
|
+
);
|
|
291
518
|
// Volatile session/project environment. Kept OUT of sessionMarkerCore so
|
|
292
519
|
// Anthropic providers can leave it as an UNMARKED system block (covered by
|
|
293
520
|
// the messages-tail breakpoint) while prefix-order providers still see it
|
|
@@ -174,8 +174,10 @@ export function collectSkills(cwd) {
|
|
|
174
174
|
} catch {
|
|
175
175
|
continue;
|
|
176
176
|
}
|
|
177
|
-
// Agent Skills requires the manifest name to match its folder.
|
|
178
|
-
|
|
177
|
+
// Agent Skills requires the manifest name to match its folder. Plugins
|
|
178
|
+
// are exempt: they ship nested layouts (skills/<plugin>/<topic>/) and
|
|
179
|
+
// load by their declared name; `seen` still settles collisions.
|
|
180
|
+
if (source !== 'plugin' && basename(dirname(filePath)) !== skill.name) continue;
|
|
179
181
|
if (seen.has(skill.name)) continue;
|
|
180
182
|
seen.add(skill.name);
|
|
181
183
|
skills.push({
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// Connector policy: which globally installed MCP servers (connectors) an agent
|
|
2
|
+
// or a project may use. Every installed connector is on by default for every
|
|
3
|
+
// agent and project, existing and future ones; a policy only switches some off:
|
|
4
|
+
// agents/<id>/agent.json connectors: { off: [serverId…], tools?: { [serverId]: { [tool]: permission } } }
|
|
5
|
+
// <data>/project-connectors.json { projects: { [projectRoot]: { off: [serverId…] } } }
|
|
6
|
+
// `tools` keeps per-tool overrides (auto | needs_approval | disabled); a tool
|
|
7
|
+
// without one is auto, except a catalog connector's high-risk tools, which
|
|
8
|
+
// ask (connectorToolDefault). A legacy agent list (an array: only its enabled
|
|
9
|
+
// entries were mounted) is enforced as written until the host converts it
|
|
10
|
+
// (migrateLegacyConnectorPolicy). A policy that cannot be read, or has neither
|
|
11
|
+
// shape, mounts nothing. Enforced at dispatch only: every session lists every
|
|
12
|
+
// connected tool, so the schema and the BP2 manifest stay shared.
|
|
13
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
import { cwdWithinProjects } from '../../../shared/extension-scopes.mjs';
|
|
16
|
+
import { resolvePluginData } from '../../../shared/plugin-paths.mjs';
|
|
17
|
+
import { userDefinitionDir } from '../../../shared/agent-store.mjs';
|
|
18
|
+
import { isSelfMcpToolName, parseMcpToolName } from '../../../shared/tool-primitives.mjs';
|
|
19
|
+
import { connectorToolDefault } from '../../../shared/connector-catalog.mjs';
|
|
20
|
+
import { serverConnector } from './server-connectors.mjs';
|
|
21
|
+
|
|
22
|
+
export const CONNECTOR_PERMISSIONS = Object.freeze(['auto', 'needs_approval', 'disabled']);
|
|
23
|
+
export const PROJECT_CONNECTORS_FILE = 'project-connectors.json';
|
|
24
|
+
|
|
25
|
+
/** What an unreadable or malformed policy means: no connector is on. */
|
|
26
|
+
export const DENY_ALL_CONNECTORS = Object.freeze({
|
|
27
|
+
allow: Object.freeze([]),
|
|
28
|
+
off: Object.freeze([]),
|
|
29
|
+
tools: Object.freeze({}),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const AGENT_DIR_ID = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
33
|
+
const POLICY_KEYS = new Set(['off', 'tools']);
|
|
34
|
+
|
|
35
|
+
const isPlainObject = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
36
|
+
const clean = (value) => (typeof value === 'string' ? value.trim() : '');
|
|
37
|
+
const uniqueIds = (list) => [...new Set((Array.isArray(list) ? list : []).map(clean).filter(Boolean))].sort();
|
|
38
|
+
|
|
39
|
+
function toolOverrides(value) {
|
|
40
|
+
const out = {};
|
|
41
|
+
for (const [server, tools] of Object.entries(isPlainObject(value) ? value : {})) {
|
|
42
|
+
const own = {};
|
|
43
|
+
for (const [tool, permission] of Object.entries(isPlainObject(tools) ? tools : {})) {
|
|
44
|
+
if (clean(tool) && CONNECTOR_PERMISSIONS.includes(permission)) own[clean(tool)] = permission;
|
|
45
|
+
}
|
|
46
|
+
if (clean(server) && Object.keys(own).length) out[clean(server)] = own;
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** A stored `connectors` field → `{ off, tools }`, or `{ allow, off, tools }`
|
|
52
|
+
* for a legacy list; null when absent. Throws on any other shape. */
|
|
53
|
+
export function parseConnectorPolicy(value) {
|
|
54
|
+
if (value == null) return null;
|
|
55
|
+
if (Array.isArray(value)) {
|
|
56
|
+
const allow = [];
|
|
57
|
+
const tools = {};
|
|
58
|
+
for (const item of value) {
|
|
59
|
+
const id = clean(item?.id);
|
|
60
|
+
if (!id) continue;
|
|
61
|
+
if (item.enabled === true) allow.push(id);
|
|
62
|
+
const own = toolOverrides({ [id]: item.tools })[id];
|
|
63
|
+
if (own) tools[id] = { ...tools[id], ...own };
|
|
64
|
+
}
|
|
65
|
+
return { allow: uniqueIds(allow), off: [], tools };
|
|
66
|
+
}
|
|
67
|
+
if (!isPlainObject(value) || Object.keys(value).some((key) => !POLICY_KEYS.has(key))) {
|
|
68
|
+
throw new Error('connectors must be { off: [connector id…], tools? }');
|
|
69
|
+
}
|
|
70
|
+
if (value.off != null && !Array.isArray(value.off)) throw new Error('connectors.off must be a list');
|
|
71
|
+
if (value.tools != null && !isPlainObject(value.tools)) throw new Error('connectors.tools must be an object');
|
|
72
|
+
return { off: uniqueIds(value.off), tools: toolOverrides(value.tools) };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The form a deny-list policy is saved in; null when it switches nothing off
|
|
76
|
+
* (the same as no policy). A legacy list must be migrated first. */
|
|
77
|
+
export function storedConnectorPolicy(policy) {
|
|
78
|
+
if (!policy) return null;
|
|
79
|
+
if (policy.allow) throw new Error('a legacy connector list must be migrated before it is saved');
|
|
80
|
+
const off = uniqueIds(policy.off);
|
|
81
|
+
const tools = toolOverrides(policy.tools);
|
|
82
|
+
if (!off.length && !Object.keys(tools).length) return null;
|
|
83
|
+
return { off, ...(Object.keys(tools).length ? { tools } : {}) };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** A legacy list (or an unreadable policy) as the equivalent deny list over
|
|
87
|
+
* the installed connectors `installedIds`: each one it did not enable is off.
|
|
88
|
+
* A deny-list policy (or none) passes through. */
|
|
89
|
+
export function migrateLegacyConnectorPolicy(policy, installedIds) {
|
|
90
|
+
if (!policy?.allow) return policy;
|
|
91
|
+
const allow = new Set(policy.allow);
|
|
92
|
+
const missing = uniqueIds(installedIds).filter((id) => !allow.has(id));
|
|
93
|
+
return { off: uniqueIds([...policy.off, ...missing]), tools: toolOverrides(policy.tools) };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Whether connector `serverId` is on under `policy` (null: every one is). */
|
|
97
|
+
export function connectorEnabled(policy, serverId) {
|
|
98
|
+
if (!policy) return true;
|
|
99
|
+
if (policy.allow && !policy.allow.includes(serverId)) return false;
|
|
100
|
+
return !policy.off.includes(serverId);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Policy files are stat-checked on every read (tiny files), so a save from
|
|
104
|
+
// any process applies to the next dispatch without an explicit flush.
|
|
105
|
+
const fileCache = new Map();
|
|
106
|
+
|
|
107
|
+
function cachedRead(path, read) {
|
|
108
|
+
let stat;
|
|
109
|
+
try {
|
|
110
|
+
stat = statSync(path);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
// No file is the "no policy" state; any other fault means it cannot be read.
|
|
113
|
+
return error?.code === 'ENOENT' || error?.code === 'ENOTDIR' ? null : unreadable(path, error);
|
|
114
|
+
}
|
|
115
|
+
const cached = fileCache.get(path);
|
|
116
|
+
if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) return cached.value;
|
|
117
|
+
const value = read(path);
|
|
118
|
+
fileCache.set(path, { mtimeMs: stat.mtimeMs, size: stat.size, value });
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function readJson(path, parse) {
|
|
123
|
+
let text;
|
|
124
|
+
try {
|
|
125
|
+
text = readFileSync(path, 'utf8');
|
|
126
|
+
} catch (error) {
|
|
127
|
+
return error?.code === 'ENOENT' || error?.code === 'ENOTDIR' ? null : unreadable(path, error);
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
return parse(JSON.parse(text));
|
|
131
|
+
} catch (error) {
|
|
132
|
+
return unreadable(path, error);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// A policy that exists but cannot be trusted mounts nothing.
|
|
137
|
+
function unreadable(path, error) {
|
|
138
|
+
process.stderr.write(`[connectors] ${path} is unreadable (${error?.message || error}); denying all connectors\n`);
|
|
139
|
+
return DENY_ALL_CONNECTORS;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Uncached: the policy in the agent.json at `path` (see parseConnectorPolicy);
|
|
143
|
+
* null without a file or field, DENY_ALL_CONNECTORS when it cannot be
|
|
144
|
+
* trusted. The agent editor reads the policy through this too. */
|
|
145
|
+
export function readConnectorsFile(path) {
|
|
146
|
+
return readJson(path, (manifest) => parseConnectorPolicy(manifest?.connectors));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The agent's saved policy under `dataDir`, or null when it has none. */
|
|
150
|
+
export function agentConnectors(agentId, dataDir = resolvePluginData()) {
|
|
151
|
+
const id = clean(agentId);
|
|
152
|
+
if (!id || !AGENT_DIR_ID.test(id)) return null;
|
|
153
|
+
return cachedRead(join(userDefinitionDir(dataDir, id), 'agent.json'), readConnectorsFile);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Uncached: the project policy file → `[{ root, off }]`, null without one,
|
|
157
|
+
* DENY_ALL_CONNECTORS when it cannot be trusted. */
|
|
158
|
+
export function readProjectConnectorsFile(path) {
|
|
159
|
+
return readJson(path, (raw) => {
|
|
160
|
+
if (!isPlainObject(raw) || (raw.projects != null && !isPlainObject(raw.projects))) {
|
|
161
|
+
throw new Error('projects must be an object');
|
|
162
|
+
}
|
|
163
|
+
return Object.entries(raw.projects || {}).flatMap(([root, entry]) => {
|
|
164
|
+
if (!isPlainObject(entry) || (entry.off != null && !Array.isArray(entry.off))) {
|
|
165
|
+
throw new Error(`project "${root}" must be { off: [connector id…] }`);
|
|
166
|
+
}
|
|
167
|
+
const off = uniqueIds(entry.off);
|
|
168
|
+
return clean(root) && off.length ? [{ root: clean(root), off }] : [];
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function projectConnectorPolicies(dataDir = resolvePluginData()) {
|
|
174
|
+
return cachedRead(join(dataDir, PROJECT_CONNECTORS_FILE), readProjectConnectorsFile);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** 'auto' | 'needs_approval' | 'disabled' for an MCP tool (definition or name)
|
|
178
|
+
* under an agent's policy. Non-MCP tools and Mixdog's own self server are
|
|
179
|
+
* not connectors: always 'auto'. */
|
|
180
|
+
export function connectorToolPermission(policy, toolOrName) {
|
|
181
|
+
const mcp = parseMcpToolName(toolOrName);
|
|
182
|
+
if (!mcp || isSelfMcpToolName(toolOrName)) return 'auto';
|
|
183
|
+
if (policy && !connectorEnabled(policy, mcp.server)) return 'disabled';
|
|
184
|
+
// An explicit per-tool entry decides; otherwise a catalog connector's
|
|
185
|
+
// high-risk tools ask (connectorToolDefault) and any other tool is auto.
|
|
186
|
+
return policy?.tools?.[mcp.server]?.[mcp.tool] || connectorToolDefault(serverConnector(mcp.server), mcp.tool);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** The permission `agentId` has for the MCP tool `toolOrName`. */
|
|
190
|
+
export function agentMcpToolPermission(agentId, toolOrName) {
|
|
191
|
+
if (!parseMcpToolName(toolOrName)) return 'auto';
|
|
192
|
+
return connectorToolPermission(agentConnectors(agentId), toolOrName);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Where a session runs: its registered Project (a worktree Lead's too), and
|
|
196
|
+
* its cwd (a delegated worker inside the project). */
|
|
197
|
+
function sessionProjectPaths(session) {
|
|
198
|
+
return [session?.desktopSession?.projectPath, session?.workspace?.projectPath, session?.cwd]
|
|
199
|
+
.map(clean)
|
|
200
|
+
.filter(Boolean);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** True when the project `session` runs in switched the tool's connector off
|
|
204
|
+
* (or its policy file cannot be read). */
|
|
205
|
+
export function projectMcpToolDisabled(session, toolOrName, dataDir = resolvePluginData()) {
|
|
206
|
+
const mcp = parseMcpToolName(toolOrName);
|
|
207
|
+
if (!mcp || isSelfMcpToolName(toolOrName)) return false;
|
|
208
|
+
const paths = sessionProjectPaths(session);
|
|
209
|
+
if (!paths.length) return false;
|
|
210
|
+
const policies = projectConnectorPolicies(dataDir);
|
|
211
|
+
if (!policies) return false;
|
|
212
|
+
if (policies === DENY_ALL_CONNECTORS) return true;
|
|
213
|
+
return policies.some(
|
|
214
|
+
({ root, off }) => off.includes(mcp.server) && paths.some((path) => cwdWithinProjects(path, [root]))
|
|
215
|
+
);
|
|
216
|
+
}
|