mixdog 0.8.1 → 0.9.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/README.md +47 -23
- package/package.json +34 -28
- package/scripts/_test-folder-dialog.mjs +30 -0
- package/scripts/agent-parallel-smoke.mjs +388 -0
- package/scripts/agent-tag-reuse-smoke.mjs +183 -0
- package/scripts/background-task-meta-smoke.mjs +38 -0
- package/scripts/boot-smoke.mjs +52 -9
- package/scripts/build-runtime-linux.sh +348 -0
- package/scripts/build-runtime-macos.sh +217 -0
- package/scripts/build-runtime-windows.ps1 +242 -0
- package/scripts/compact-active-turn-test.mjs +68 -0
- package/scripts/compact-smoke.mjs +859 -129
- package/scripts/compact-trigger-migration-smoke.mjs +187 -0
- package/scripts/fix-brief-fn.mjs +35 -0
- package/scripts/fix-format-tool-surface.mjs +24 -0
- package/scripts/fix-tool-exec-visible.mjs +42 -0
- package/scripts/generate-runtime-manifest.mjs +166 -0
- package/scripts/hook-bus-test.mjs +330 -0
- package/scripts/lead-workflow-smoke.mjs +33 -39
- package/scripts/live-worker-smoke.mjs +43 -37
- package/scripts/llm-trace-summary.mjs +315 -0
- package/scripts/memory-meta-concurrency-test.mjs +20 -0
- package/scripts/output-style-smoke.mjs +56 -15
- package/scripts/parent-abort-link-test.mjs +44 -0
- package/scripts/patch-agent-brief.mjs +48 -0
- package/scripts/patch-app.mjs +21 -0
- package/scripts/patch-app2.mjs +18 -0
- package/scripts/patch-dist-brief.mjs +96 -0
- package/scripts/patch-tool-exec.mjs +70 -0
- package/scripts/pretool-ask-runtime-test.mjs +54 -0
- package/scripts/provider-toolcall-test.mjs +376 -0
- package/scripts/reactive-compact-persist-smoke.mjs +124 -0
- package/scripts/sanitize-tool-pairs-test.mjs +260 -0
- package/scripts/session-context-bench.mjs +205 -33
- package/scripts/session-ingest-smoke.mjs +177 -0
- package/scripts/set-effort-config-test.mjs +41 -0
- package/scripts/smoke-runtime-negative.ps1 +106 -0
- package/scripts/smoke-runtime-negative.sh +97 -0
- package/scripts/smoke.mjs +25 -0
- package/scripts/tool-result-hook-test.mjs +48 -0
- package/scripts/tool-smoke.mjs +1222 -90
- package/scripts/toolcall-args-test.mjs +150 -0
- package/scripts/tui-background-failure-smoke.mjs +73 -0
- package/scripts/usage-metrics-epoch-smoke.mjs +114 -0
- package/src/agents/debugger/AGENT.md +8 -0
- package/src/agents/explore/AGENT.md +4 -0
- package/src/agents/heavy-worker/AGENT.md +9 -3
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +8 -0
- package/src/agents/scheduler-task/AGENT.md +12 -0
- package/src/agents/scheduler-task/agent.json +6 -0
- package/src/agents/webhook-handler/AGENT.md +12 -0
- package/src/agents/webhook-handler/agent.json +6 -0
- package/src/agents/worker/AGENT.md +9 -3
- package/src/app.mjs +77 -3
- package/src/defaults/hidden-roles.json +17 -12
- package/src/headless-role.mjs +117 -0
- package/src/help.mjs +30 -0
- package/src/hooks/lib/permission-evaluator.cjs +11 -475
- package/src/lib/keychain-cjs.cjs +9 -1
- package/src/lib/mixdog-debug.cjs +0 -29
- package/src/lib/plugin-paths.cjs +1 -7
- package/src/lib/rules-builder.cjs +242 -98
- package/src/lib/text-utils.cjs +1 -1
- package/src/mixdog-session-runtime.mjs +2211 -446
- package/src/output-styles/default.md +12 -28
- package/src/output-styles/extreme-simple.md +9 -6
- package/src/output-styles/simple.md +22 -9
- package/src/repl.mjs +108 -59
- package/src/rules/agent/00-common.md +15 -0
- package/src/rules/{bridge → agent}/20-skip-protocol.md +1 -2
- package/src/rules/agent/30-explorer.md +22 -0
- package/src/rules/{bridge → agent}/40-cycle1-agent.md +7 -0
- package/src/rules/{bridge → agent}/41-cycle2-agent.md +7 -0
- package/src/rules/{bridge → agent}/42-cycle3-agent.md +7 -0
- package/src/rules/lead/01-general.md +9 -5
- package/src/rules/lead/04-workflow.md +51 -12
- package/src/rules/lead/lead-tool.md +6 -0
- package/src/rules/shared/01-tool.md +12 -1
- package/src/runtime/agent/orchestrator/activity-bus.mjs +7 -18
- package/src/runtime/agent/orchestrator/agent-owner.mjs +11 -0
- package/src/runtime/agent/orchestrator/{smart-bridge/bridge-llm.mjs → agent-runtime/agent-dispatch.mjs} +138 -111
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +94 -0
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/cache-strategy.mjs +32 -23
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/session-builder.mjs +33 -27
- package/src/runtime/agent/orchestrator/{bridge-trace.mjs → agent-trace.mjs} +132 -81
- package/src/runtime/agent/orchestrator/cache-mtime.mjs +0 -21
- package/src/runtime/agent/orchestrator/config.mjs +174 -55
- package/src/runtime/agent/orchestrator/context/collect.mjs +195 -487
- package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
- package/src/runtime/agent/orchestrator/internal-roles.mjs +77 -29
- package/src/runtime/agent/orchestrator/internal-tools.mjs +5 -6
- package/src/runtime/agent/orchestrator/mcp/client.mjs +15 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +380 -268
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +131 -93
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +236 -4
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +49 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +58 -13
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -149
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +132 -2
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +45 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +61 -116
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +25 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +79 -255
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +203 -71
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +512 -312
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -518
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/registry.mjs +88 -51
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +289 -11
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
- package/src/runtime/agent/orchestrator/session/compact.mjs +1173 -267
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +199 -36
- package/src/runtime/agent/orchestrator/session/loop.mjs +840 -612
- package/src/runtime/agent/orchestrator/session/manager.mjs +1574 -459
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +67 -29
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +31 -16
- package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +3 -219
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +35 -8
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -8
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +10 -53
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +60 -37
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +21 -2
- package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -7
- package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -3
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +36 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +5 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -36
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -40
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +148 -27
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +43 -75
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +90 -20
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -5
- package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +86 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +11 -11
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4106 -4019
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -4
- package/src/runtime/agent/orchestrator/tools/patch.mjs +90 -6
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +6 -4
- package/src/runtime/agent/orchestrator/tools/result-compression.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +8 -1
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -4
- package/src/runtime/channels/index.mjs +149 -51
- package/src/runtime/channels/lib/cli-worker-host.mjs +1 -8
- package/src/runtime/channels/lib/config.mjs +0 -1
- package/src/runtime/channels/lib/drop-trace.mjs +1 -1
- package/src/runtime/channels/lib/executor.mjs +0 -3
- package/src/runtime/channels/lib/memory-client.mjs +0 -38
- package/src/runtime/channels/lib/output-forwarder.mjs +1 -8
- package/src/runtime/channels/lib/runtime-paths.mjs +0 -6
- package/src/runtime/channels/lib/scheduler.mjs +18 -14
- package/src/runtime/channels/lib/session-discovery.mjs +3 -6
- package/src/runtime/channels/lib/tool-format.mjs +0 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +4 -12
- package/src/runtime/channels/lib/webhook.mjs +1 -1
- package/src/runtime/channels/tool-defs.mjs +29 -29
- package/src/runtime/lib/keychain-cjs.cjs +0 -1
- package/src/runtime/memory/data/runtime-manifest.json +6 -7
- package/src/runtime/memory/index.mjs +519 -118
- package/src/runtime/memory/lib/agent-ipc.mjs +29 -12
- package/src/runtime/memory/lib/core-memory-store.mjs +2 -2
- package/src/runtime/memory/lib/embedding-model-config.mjs +55 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +31 -4
- package/src/runtime/memory/lib/embedding-worker.mjs +19 -10
- package/src/runtime/memory/lib/llm-worker-host.mjs +0 -4
- package/src/runtime/memory/lib/memory-cycle1.mjs +28 -7
- package/src/runtime/memory/lib/memory-cycle2.mjs +4 -5
- package/src/runtime/memory/lib/memory-cycle3.mjs +2 -2
- package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -2
- package/src/runtime/memory/lib/memory-session-merge.mjs +38 -0
- package/src/runtime/memory/lib/memory.mjs +88 -9
- package/src/runtime/memory/lib/model-profile.mjs +1 -1
- package/src/runtime/memory/lib/pg/adapter.mjs +1 -1
- package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +80 -21
- package/src/runtime/memory/lib/session-ingest.mjs +196 -0
- package/src/runtime/memory/lib/trace-store.mjs +96 -51
- package/src/runtime/memory/tool-defs.mjs +46 -37
- package/src/runtime/search/index.mjs +104 -473
- package/src/runtime/search/lib/config.mjs +0 -4
- package/src/runtime/search/lib/state.mjs +1 -15
- package/src/runtime/search/lib/web-tools.mjs +44 -25
- package/src/runtime/search/tool-defs.mjs +16 -23
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +4 -3
- package/src/runtime/shared/background-tasks.mjs +122 -11
- package/src/runtime/shared/child-spawn-gate.mjs +139 -0
- package/src/runtime/shared/config.mjs +7 -4
- package/src/runtime/shared/err-text.mjs +131 -4
- package/src/runtime/shared/llm/cost.mjs +2 -2
- package/src/runtime/shared/llm/http-agent.mjs +23 -7
- package/src/runtime/shared/llm/index.mjs +34 -11
- package/src/runtime/shared/llm/usage-log.mjs +4 -4
- package/src/runtime/shared/markdown-frontmatter.mjs +56 -0
- package/src/runtime/shared/singleton-owner.mjs +104 -0
- package/src/runtime/shared/tool-execution-contract.mjs +199 -20
- package/src/runtime/shared/tool-execution-contract.test.mjs +183 -0
- package/src/runtime/shared/tool-surface.mjs +624 -95
- package/src/runtime/shared/user-data-guard.mjs +0 -2
- package/src/standalone/agent-task-status.mjs +203 -0
- package/src/standalone/agent-task-status.test.mjs +76 -0
- package/src/standalone/agent-tool.mjs +1913 -0
- package/src/standalone/channel-worker.mjs +370 -14
- package/src/standalone/explore-tool.mjs +165 -70
- package/src/standalone/folder-dialog.mjs +314 -0
- package/src/standalone/hook-bus.mjs +898 -22
- package/src/standalone/memory-runtime-proxy.mjs +320 -0
- package/src/standalone/projects.mjs +226 -0
- package/src/standalone/provider-admin.mjs +41 -24
- package/src/standalone/seeds.mjs +1 -78
- package/src/standalone/usage-dashboard.mjs +96 -8
- package/src/tui/App.jsx +4800 -2140
- package/src/tui/components/AnsiText.jsx +39 -28
- package/src/tui/components/ContextPanel.jsx +14 -4
- package/src/tui/components/Markdown.jsx +43 -77
- package/src/tui/components/MarkdownTable.jsx +9 -184
- package/src/tui/components/Message.jsx +28 -11
- package/src/tui/components/Picker.jsx +95 -56
- package/src/tui/components/PromptInput.jsx +428 -239
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +27 -21
- package/src/tui/components/Spinner.jsx +67 -38
- package/src/tui/components/StatusLine.jsx +606 -38
- package/src/tui/components/TextEntryPanel.jsx +128 -9
- package/src/tui/components/ToolExecution.jsx +597 -362
- package/src/tui/components/TurnDone.jsx +3 -3
- package/src/tui/components/UsagePanel.jsx +3 -5
- package/src/tui/components/tool-output-format.mjs +499 -0
- package/src/tui/components/tool-output-format.test.mjs +312 -0
- package/src/tui/dist/index.mjs +9192 -2399
- package/src/tui/engine-runtime-notification.test.mjs +115 -0
- package/src/tui/engine-tool-result-text.test.mjs +75 -0
- package/src/tui/engine.mjs +1455 -279
- package/src/tui/figures.mjs +21 -40
- package/src/tui/index.jsx +75 -31
- package/src/tui/input-editing.mjs +25 -0
- package/src/tui/markdown/format-token.mjs +671 -69
- package/src/tui/markdown/format-token.test.mjs +312 -0
- package/src/tui/markdown/render-ansi.mjs +94 -0
- package/src/tui/markdown/render-ansi.test.mjs +108 -0
- package/src/tui/markdown/stream-fence.mjs +34 -0
- package/src/tui/markdown/stream-fence.test.mjs +26 -0
- package/src/tui/markdown/table-layout.mjs +250 -0
- package/src/tui/paste-attachments.mjs +0 -7
- package/src/tui/spinner-verbs.mjs +1 -2
- package/src/tui/statusline-ansi-bridge.mjs +172 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +159 -0
- package/src/tui/theme.mjs +756 -24
- package/src/tui/time-format.mjs +1 -1
- package/src/tui/transcript-tool-failures.mjs +67 -0
- package/src/tui/transcript-tool-failures.test.mjs +111 -0
- package/src/ui/ansi.mjs +1 -2
- package/src/ui/markdown.mjs +85 -26
- package/src/ui/markdown.test.mjs +70 -0
- package/src/ui/model-display.mjs +121 -0
- package/src/ui/session-stats.mjs +44 -0
- package/src/ui/statusline-context-label.test.mjs +15 -0
- package/src/ui/statusline.mjs +386 -178
- package/src/ui/tool-card.mjs +2 -13
- package/src/vendor/statusline/bin/statusline-lib.mjs +1 -620
- package/src/vendor/statusline/bin/statusline-route.mjs +169 -37
- package/src/vendor/statusline/bin/statusline-route.test.mjs +80 -0
- package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +3 -3
- package/src/vendor/statusline/src/gateway/claude-current.mjs +1 -1
- package/src/vendor/statusline/src/gateway/route-meta.mjs +44 -6
- package/src/vendor/statusline/src/gateway/session-routes.mjs +1 -1
- package/src/workflows/default/WORKFLOW.md +12 -5
- package/src/workflows/default/workflow.json +0 -1
- package/src/workflows/solo/WORKFLOW.md +15 -0
- package/src/workflows/solo/workflow.json +7 -0
- package/vendor/ink/build/ink.js +54 -8
- package/vendor/ink/build/output.js +6 -1
- package/src/agents/scheduler-task.md +0 -3
- package/src/agents/web-researcher/AGENT.md +0 -3
- package/src/agents/web-researcher/agent.json +0 -6
- package/src/agents/webhook-handler.md +0 -3
- package/src/hooks/lib/permission-rules.cjs +0 -170
- package/src/hooks/lib/settings-loader.cjs +0 -112
- package/src/lib/hook-pipe-path.cjs +0 -10
- package/src/rules/bridge/00-common.md +0 -5
- package/src/rules/bridge/30-explorer.md +0 -4
- package/src/rules/lead/00-tool-lead.md +0 -5
- package/src/rules/shared/00-language.md +0 -3
- package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
- package/src/runtime/agent/orchestrator/tools/mutation-content-cache.mjs +0 -67
- package/src/runtime/channels/lib/hook-pipe-server.mjs +0 -671
- package/src/runtime/memory/lib/bridge-trace-queries.mjs +0 -120
- package/src/runtime/shared/llm/pid-cleanup.mjs +0 -27
- package/src/standalone/bridge-tool.mjs +0 -1414
- package/src/tui/runtime/shared/process-shutdown.mjs +0 -1
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { readFileSync, existsSync, readdirSync } from 'fs';
|
|
2
2
|
import { homedir } from 'os';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { maxMtimeRecursive } from '../cache-mtime.mjs';
|
|
5
5
|
import { resolvePluginData, mixdogRoot } from '../../../shared/plugin-paths.mjs';
|
|
6
|
+
import {
|
|
7
|
+
parseMarkdownFrontmatter,
|
|
8
|
+
readMarkdownDocument,
|
|
9
|
+
} from '../../../shared/markdown-frontmatter.mjs';
|
|
6
10
|
|
|
7
11
|
// --- mixdog asset roots (standalone CLI owns its own paths; never .claude) ---
|
|
8
12
|
// Project-local: <cwd>/.mixdog/<kind>
|
|
@@ -29,84 +33,12 @@ function mixdogAssetDirs(projectDir, kind) {
|
|
|
29
33
|
dirs.push(mixdogGlobalDir(kind));
|
|
30
34
|
return dirs;
|
|
31
35
|
}
|
|
32
|
-
// Built-in role-identity defaults for the well-known PUBLIC bridge roles.
|
|
33
|
-
// Mirrors setup.html's WF_DEFAULT_ROLE_IDENTITY so the role-identity feature
|
|
34
|
-
// works on a fresh install (no roles/<role>.md on disk) without forcing the
|
|
35
|
-
// user to open the config UI first. A saved custom body in roles/<role>.md
|
|
36
|
-
// always overrides these; unknown and hidden roles are absent here and so
|
|
37
|
-
// inject nothing, leaving hidden roles fully Mixdog-managed.
|
|
38
|
-
export const DEFAULT_ROLE_IDENTITY = {
|
|
39
|
-
'explore': 'You are an exploration agent. Your job is to map the codebase, narrow vague requests into concrete files or symbols, and return concise leads. Do not edit files.',
|
|
40
|
-
'maintainer': 'You are a maintenance agent. Your job is to handle upkeep, cleanup audits, and background health work without occupying the Lead. Return final state, risks, and follow-up needs.',
|
|
41
|
-
'worker': 'You are an implementation agent. Your job is to make the scoped code change quickly and safely, not to fully verify the product. Run only lightweight, targeted self-checks needed to avoid handing off obviously broken code. Broad verification belongs to the Lead/reviewer.',
|
|
42
|
-
'heavy-worker': 'You are a heavy implementation agent for vague, open-ended, or multi-file changes. Your job is to make the scoped change with enough local design judgment to keep files coherent, while keeping validation lightweight. Do not run broad/full verification unless explicitly requested; report what remains for Lead/reviewer.',
|
|
43
|
-
'reviewer': 'You are a correctness reviewer. Your job is to decide whether the change is ship-ready. Report only blocking correctness issues, or return SHIP-READY. Do not perform style review, broad refactors, or extra implementation.',
|
|
44
|
-
'debugger': 'You are a debugging specialist. Your job is to diagnose root cause, minimal repro, and the smallest safe fix direction. Do not implement. Return evidence-backed cause and proposed fix scope, or clearly state what remains unknown.',
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
function shellEnvironmentReminder() {
|
|
48
|
-
const platform = process.platform || 'unknown';
|
|
49
|
-
const arch = process.arch || 'unknown';
|
|
50
|
-
if (platform === 'win32') {
|
|
51
|
-
return [
|
|
52
|
-
'# environment',
|
|
53
|
-
`os: Windows (${platform}/${arch})`,
|
|
54
|
-
'default_shell: PowerShell',
|
|
55
|
-
'shell tool: PowerShell syntax is the default. Use shell:"powershell" for PowerShell commands, or shell:"bash" only when intentionally using Git Bash/POSIX syntax.',
|
|
56
|
-
].join('\n');
|
|
57
|
-
}
|
|
58
|
-
return [
|
|
59
|
-
'# environment',
|
|
60
|
-
`os: ${platform}/${arch}`,
|
|
61
|
-
'default_shell: POSIX /bin/sh',
|
|
62
|
-
'shell tool: POSIX shell syntax is the default. Use shell:"bash" for POSIX commands; use shell:"powershell" only when pwsh syntax is intentional and installed.',
|
|
63
|
-
].join('\n');
|
|
64
|
-
}
|
|
65
|
-
// --- Agent template loading ---
|
|
66
|
-
/**
|
|
67
|
-
* Load an agent MD file (Worker.md, Reviewer.md, etc.) as session instructions.
|
|
68
|
-
* Strips frontmatter, returns the body.
|
|
69
|
-
*/
|
|
70
|
-
// Agent template cache — mtime-invalidated per (name, cwd).
|
|
71
|
-
const _agentTemplateCache = new Map();
|
|
72
|
-
export function loadAgentTemplate(name, cwd) {
|
|
73
|
-
// When cwd is null/missing (bridge maintenance callers like cycle1-agent
|
|
74
|
-
// pass cwd:null on purpose so provider-cache shards don't fork per MCP
|
|
75
|
-
// launch dir), skip the project-scoped template lookup entirely — DO NOT
|
|
76
|
-
// fall back to process.cwd(), which would leak the launcher's working
|
|
77
|
-
// directory into the cache key and fragment the shard per caller workspace.
|
|
78
|
-
const projectDir = (typeof cwd === 'string' && cwd.length > 0) ? cwd : null;
|
|
79
|
-
const key = `${name}|${projectDir ?? '__noproject__'}`;
|
|
80
|
-
// Search paths for agent files (mixdog-owned only; never .claude).
|
|
81
|
-
// Project-local <cwd>/.mixdog/agents first, then user-global. When cwd is
|
|
82
|
-
// missing the project entry is skipped; the global dir still applies so the
|
|
83
|
-
// "no template found → null" contract is preserved via the readSafe loop.
|
|
84
|
-
const agentDirs = mixdogAssetDirs(projectDir, 'agents');
|
|
85
|
-
const searchPaths = agentDirs.map((dir) => join(dir, `${name}.md`));
|
|
86
|
-
// Freshness gate: the same agents/ dirs that hold the files.
|
|
87
|
-
const mtimePaths = [...agentDirs];
|
|
88
|
-
const mtime = maxMtimeRecursive(mtimePaths);
|
|
89
|
-
const cached = _agentTemplateCache.get(key);
|
|
90
|
-
if (cached && mtime <= cached.mtime) return cached.value;
|
|
91
|
-
for (const p of searchPaths) {
|
|
92
|
-
const content = readSafe(p);
|
|
93
|
-
if (content) {
|
|
94
|
-
// Strip YAML frontmatter
|
|
95
|
-
const stripped = content.replace(/^---\n[\s\S]*?\n---\n*/, '');
|
|
96
|
-
const body = stripped.trim();
|
|
97
|
-
_agentTemplateCache.set(key, { mtime, value: body });
|
|
98
|
-
return body;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
_agentTemplateCache.set(key, { mtime, value: null });
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
36
|
/**
|
|
105
37
|
* Collect available skills (frontmatter only — token efficient).
|
|
106
38
|
* Full content loaded on demand via loadSkillContent().
|
|
107
39
|
*/
|
|
108
40
|
export function collectSkills(cwd) {
|
|
109
|
-
// When cwd is null/missing (e.g.
|
|
41
|
+
// When cwd is null/missing (e.g. agent maintenance callers that pass
|
|
110
42
|
// cwd:null on purpose so provider-cache shards don't fork per caller
|
|
111
43
|
// workspace), skip project-scoped skills entirely — DO NOT fall back
|
|
112
44
|
// to process.cwd(), which would leak the MCP launch dir into the
|
|
@@ -130,7 +62,7 @@ export function collectSkills(cwd) {
|
|
|
130
62
|
const content = readSafe(filePath);
|
|
131
63
|
if (!content)
|
|
132
64
|
continue;
|
|
133
|
-
const fm =
|
|
65
|
+
const fm = parseMarkdownFrontmatter(content);
|
|
134
66
|
if (!fm.name)
|
|
135
67
|
continue;
|
|
136
68
|
if (seen.has(fm.name))
|
|
@@ -188,14 +120,47 @@ export function loadSkillContent(name, cwd) {
|
|
|
188
120
|
return null;
|
|
189
121
|
return readSafe(skill.filePath);
|
|
190
122
|
}
|
|
123
|
+
|
|
124
|
+
function compactSkillManifestText(value, max = 180) {
|
|
125
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
126
|
+
return text.length > max ? `${text.slice(0, Math.max(1, max - 3))}...` : text;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Build the compact skill manifest shown to the model.
|
|
131
|
+
* Full SKILL.md content is still loaded only through Skill(name).
|
|
132
|
+
*/
|
|
133
|
+
export function buildSkillManifest(skills, { limit = 80 } = {}) {
|
|
134
|
+
const list = (Array.isArray(skills) ? skills : [])
|
|
135
|
+
.map((skill) => ({
|
|
136
|
+
name: String(skill?.name || '').trim(),
|
|
137
|
+
description: compactSkillManifestText(skill?.description || ''),
|
|
138
|
+
}))
|
|
139
|
+
.filter((skill) => skill.name)
|
|
140
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
141
|
+
if (!list.length) return '';
|
|
142
|
+
const max = Math.max(1, Number(limit) || 80);
|
|
143
|
+
const visible = list.slice(0, max);
|
|
144
|
+
const lines = [
|
|
145
|
+
'# available-skills',
|
|
146
|
+
'Call Skill({"name":"<skill-name>"}) when a skill description matches the task. Load the skill before following its workflow.',
|
|
147
|
+
'<available_skills>',
|
|
148
|
+
...visible.map((skill) => `- ${skill.name}: ${skill.description || 'No description.'}`),
|
|
149
|
+
...(list.length > visible.length ? [`- ... ${list.length - visible.length} more skills omitted`] : []),
|
|
150
|
+
'</available_skills>',
|
|
151
|
+
];
|
|
152
|
+
return lines.join('\n');
|
|
153
|
+
}
|
|
154
|
+
|
|
191
155
|
/**
|
|
192
|
-
* Build
|
|
193
|
-
*
|
|
194
|
-
*
|
|
156
|
+
* Build the fixed skill loader meta-tool.
|
|
157
|
+
* A tiny stable schema keeps provider cache keys steady; concrete skill
|
|
158
|
+
* listings/content are resolved at call time.
|
|
195
159
|
*
|
|
196
|
-
* The structure is constant regardless of how many skills are in scope
|
|
197
|
-
*
|
|
198
|
-
*
|
|
160
|
+
* The structure is constant regardless of how many skills are in scope.
|
|
161
|
+
* Non-agent sessions only expose the loader when a skill exists; agent
|
|
162
|
+
* sessions always expose it so the schema shape stays fixed. Memoise so
|
|
163
|
+
* every createSession doesn't rebuild
|
|
199
164
|
* identical objects (trivial work, but the allocation noise shows up in
|
|
200
165
|
* repeated Pool C fan-out).
|
|
201
166
|
*/
|
|
@@ -203,30 +168,21 @@ let _skillToolDefsCache = null;
|
|
|
203
168
|
/**
|
|
204
169
|
* @param {Array} skills — discovered skill frontmatter list (may be empty)
|
|
205
170
|
* @param {object} [opts]
|
|
206
|
-
* @param {boolean} [opts.
|
|
207
|
-
*
|
|
171
|
+
* @param {boolean} [opts.ownerIsAgentSession=false]
|
|
172
|
+
* Agent sessions ALWAYS include the meta-tool regardless of the current
|
|
208
173
|
* cwd's skill inventory — the concrete skill list is resolved at tool-call
|
|
209
174
|
* time (cwd-scoped) so the tool schema stays bit-identical across roles /
|
|
210
175
|
* cwds and the provider cache shard does not fragment.
|
|
211
|
-
* Non-
|
|
176
|
+
* Non-agent sessions keep the historical "empty when skills.length===0"
|
|
212
177
|
* behaviour.
|
|
213
178
|
*/
|
|
214
|
-
export function buildSkillToolDefs(skills, {
|
|
215
|
-
if (!
|
|
179
|
+
export function buildSkillToolDefs(skills, { ownerIsAgentSession = false } = {}) {
|
|
180
|
+
if (!ownerIsAgentSession && !skills.length) return [];
|
|
216
181
|
if (_skillToolDefsCache) return _skillToolDefsCache;
|
|
217
182
|
_skillToolDefsCache = [
|
|
218
183
|
{
|
|
219
|
-
name: '
|
|
220
|
-
description: '
|
|
221
|
-
inputSchema: {
|
|
222
|
-
type: 'object',
|
|
223
|
-
properties: {},
|
|
224
|
-
required: [],
|
|
225
|
-
},
|
|
226
|
-
},
|
|
227
|
-
{
|
|
228
|
-
name: 'skill_view',
|
|
229
|
-
description: 'Return the full body of a skill by name (without executing).',
|
|
184
|
+
name: 'Skill',
|
|
185
|
+
description: 'Load a named SKILL.md into context.',
|
|
230
186
|
inputSchema: {
|
|
231
187
|
type: 'object',
|
|
232
188
|
properties: {
|
|
@@ -235,170 +191,19 @@ export function buildSkillToolDefs(skills, { ownerIsBridge = false } = {}) {
|
|
|
235
191
|
required: ['name'],
|
|
236
192
|
},
|
|
237
193
|
},
|
|
238
|
-
{
|
|
239
|
-
name: 'skill_execute',
|
|
240
|
-
description: 'Load and execute a skill. Skill body is injected into context.',
|
|
241
|
-
inputSchema: {
|
|
242
|
-
type: 'object',
|
|
243
|
-
properties: {
|
|
244
|
-
name: { type: 'string', description: 'Skill name' },
|
|
245
|
-
args: { type: 'object', description: 'Optional arguments passed to the skill', additionalProperties: true },
|
|
246
|
-
},
|
|
247
|
-
required: ['name'],
|
|
248
|
-
},
|
|
249
|
-
},
|
|
250
194
|
];
|
|
251
195
|
return _skillToolDefsCache;
|
|
252
196
|
}
|
|
253
|
-
// ---
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
* 3. <ancestor>/.mixdog/mixdog.md (from filesystem root down to cwd)
|
|
259
|
-
*
|
|
260
|
-
* This mirrors Claude Code's "memory files are context, not system prompt"
|
|
261
|
-
* model while keeping Mixdog's internal rules in code/rules-builder.
|
|
262
|
-
*/
|
|
263
|
-
const _mixdogMdCache = new Map();
|
|
264
|
-
function uniquePaths(paths) {
|
|
265
|
-
const seen = new Set();
|
|
266
|
-
const out = [];
|
|
267
|
-
for (const p of paths) {
|
|
268
|
-
const key = p ? resolve(p).toLowerCase() : '';
|
|
269
|
-
if (!p || seen.has(key)) continue;
|
|
270
|
-
seen.add(key);
|
|
271
|
-
out.push(p);
|
|
272
|
-
}
|
|
273
|
-
return out;
|
|
274
|
-
}
|
|
275
|
-
function mixdogContextFileCandidates(dir) {
|
|
276
|
-
return [
|
|
277
|
-
join(dir, 'mixdog.md'),
|
|
278
|
-
join(dir, 'Mixdog.md'),
|
|
279
|
-
];
|
|
280
|
-
}
|
|
281
|
-
function mixdogProjectContextFileCandidates(dir) {
|
|
282
|
-
return [
|
|
283
|
-
...mixdogContextFileCandidates(dir),
|
|
284
|
-
...mixdogContextFileCandidates(join(dir, '.mixdog')),
|
|
285
|
-
];
|
|
286
|
-
}
|
|
287
|
-
function mixdogGlobalContextPaths() {
|
|
288
|
-
let dataRoot;
|
|
289
|
-
try {
|
|
290
|
-
dataRoot = resolvePluginData();
|
|
291
|
-
} catch {
|
|
292
|
-
dataRoot = process.env.MIXDOG_DATA_DIR || join(mixdogHome(), 'data');
|
|
293
|
-
}
|
|
294
|
-
return uniquePaths(mixdogContextFileCandidates(dataRoot));
|
|
295
|
-
}
|
|
296
|
-
function mixdogProjectContextPaths(cwd) {
|
|
297
|
-
const projectDir = (typeof cwd === 'string' && cwd.length > 0) ? resolve(cwd) : null;
|
|
298
|
-
if (!projectDir) return [];
|
|
299
|
-
const dirs = [];
|
|
300
|
-
let cur = projectDir;
|
|
301
|
-
while (true) {
|
|
302
|
-
dirs.unshift(cur);
|
|
303
|
-
const parent = dirname(cur);
|
|
304
|
-
if (parent === cur) break;
|
|
305
|
-
cur = parent;
|
|
306
|
-
}
|
|
307
|
-
return uniquePaths(dirs.flatMap(mixdogProjectContextFileCandidates));
|
|
308
|
-
}
|
|
309
|
-
export function collectMixdogMd(cwd) {
|
|
310
|
-
// When cwd is null/missing (bridge maintenance calls deliberately pass
|
|
311
|
-
// cwd:null so provider-cache shards don't fork per caller workspace),
|
|
312
|
-
// include only user-global context and never fall back to process.cwd().
|
|
313
|
-
const key = (typeof cwd === 'string' && cwd.length > 0) ? resolve(cwd) : '__global__';
|
|
314
|
-
const searchPaths = uniquePaths([
|
|
315
|
-
...mixdogGlobalContextPaths(),
|
|
316
|
-
...mixdogProjectContextPaths(cwd),
|
|
317
|
-
]);
|
|
318
|
-
const mtimePaths = uniquePaths([
|
|
319
|
-
...searchPaths,
|
|
320
|
-
...searchPaths.map(p => dirname(p)),
|
|
321
|
-
]);
|
|
322
|
-
const mtime = maxMtime(mtimePaths);
|
|
323
|
-
const cached = _mixdogMdCache.get(key);
|
|
324
|
-
if (cached && mtime <= cached.mtime) return cached.value;
|
|
325
|
-
|
|
326
|
-
const sections = [];
|
|
327
|
-
for (const filePath of searchPaths) {
|
|
328
|
-
const content = readSafe(filePath);
|
|
329
|
-
if (content) {
|
|
330
|
-
sections.push(`<mixdog_instructions path="${filePath}">\n${content}\n</mixdog_instructions>`);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
const value = sections.join('\n\n');
|
|
334
|
-
_mixdogMdCache.set(key, { mtime, value });
|
|
335
|
-
return value;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// --- Role template loading (Phase B §4 — UI-managed) ---
|
|
339
|
-
/**
|
|
340
|
-
* Read <dataDir>/roles/<role>.md, parse frontmatter (name, description,
|
|
341
|
-
* permission) and body. Returns { description, permission, body } or null.
|
|
342
|
-
*
|
|
343
|
-
* The role md is created/edited from the Config UI; runtime parses it on
|
|
344
|
-
* each spawn and injects the result into the Tier 3 system-reminder via
|
|
345
|
-
* composeSystemPrompt's `roleTemplate` slot.
|
|
346
|
-
*/
|
|
347
|
-
// Role template cache — mtime-invalidated so UI edits are visible
|
|
348
|
-
// on the very next createSession call without any TTL delay.
|
|
349
|
-
const _roleTemplateCache = new Map();
|
|
350
|
-
export function loadRoleTemplate(role, dataDir) {
|
|
351
|
-
if (!role || !dataDir) return null;
|
|
352
|
-
// Hidden/internal roles are Mixdog-managed and must never read a
|
|
353
|
-
// DATA_DIR/roles/<hidden>.md file: explorer and the maintenance roles get
|
|
354
|
-
// their identity from hidden rules only, so a stray file on disk must be
|
|
355
|
-
// ignored entirely (no body, no permission/description metadata).
|
|
356
|
-
if (isHiddenRole(role)) return null;
|
|
357
|
-
const key = `${role}|${dataDir}`;
|
|
358
|
-
const path = join(dataDir, 'roles', `${role}.md`);
|
|
359
|
-
const mtime = maxMtime([path]);
|
|
360
|
-
const cached = _roleTemplateCache.get(key);
|
|
361
|
-
if (cached && mtime <= cached.mtime) return cached.value;
|
|
362
|
-
// Absent vs present-but-empty: built-in default identity applies ONLY when
|
|
363
|
-
// the role file is ABSENT (loadRoleTemplate returns null). A file that
|
|
364
|
-
// exists — even frontmatter-only or with an empty body — returns a template
|
|
365
|
-
// so composeSystemPrompt injects its (possibly empty) body verbatim instead
|
|
366
|
-
// of falling back to the default.
|
|
367
|
-
if (!existsSync(path)) {
|
|
368
|
-
_roleTemplateCache.set(key, { mtime, value: null });
|
|
369
|
-
return null;
|
|
370
|
-
}
|
|
371
|
-
const content = readSafe(path) || '';
|
|
372
|
-
const fm = parseFrontmatter(content);
|
|
373
|
-
const body = content.replace(/^---\n[\s\S]*?\n---\n*/, '').trim();
|
|
374
|
-
const description = (fm.description || '').trim();
|
|
375
|
-
const rawPermission = (fm.permission || '').trim().toLowerCase();
|
|
376
|
-
const VALID_ROLE_PERMISSIONS = new Set(['read', 'read-write', 'mcp', 'full']);
|
|
377
|
-
// Fail closed: unknown permission values are rejected rather than silently
|
|
378
|
-
// falling through as full access.
|
|
379
|
-
const permission = VALID_ROLE_PERMISSIONS.has(rawPermission) ? rawPermission : null;
|
|
380
|
-
const template = {
|
|
381
|
-
description: description || null,
|
|
382
|
-
permission,
|
|
383
|
-
body: body || null,
|
|
384
|
-
};
|
|
385
|
-
_roleTemplateCache.set(key, { mtime, value: template });
|
|
386
|
-
return template;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// --- Role-scoped catalog loader ---
|
|
390
|
-
// Emits a BP2 block scoped to the calling role:
|
|
391
|
-
// - Public/custom bridge agents: their own agents/<role>.md when present,
|
|
392
|
-
// plus the public bridge-worker contract.
|
|
393
|
-
// - Hidden roles: their own rules/bridge/<role>.md section only.
|
|
197
|
+
// --- Role-scoped instruction loader ---
|
|
198
|
+
// Emits a BP2 role/system block scoped to the calling role:
|
|
199
|
+
// - Public/custom agents: their own agents/<role>.md when present,
|
|
200
|
+
// plus the public agent-worker contract.
|
|
201
|
+
// - Hidden roles: their own rules/agent/<role>.md section only.
|
|
394
202
|
// - Null role: falls back to the full all-in-one block
|
|
395
203
|
// (explicit-cache unified-shard path).
|
|
396
204
|
//
|
|
397
|
-
//
|
|
398
|
-
//
|
|
399
|
-
// trading a small number of additional shards for ~68% fewer prefix bytes
|
|
400
|
-
// on the public-role hot path. Role identity still rides the sessionMarker
|
|
401
|
-
// user message separately (see composeSystemPrompt).
|
|
205
|
+
// Role-specific markdown intentionally rides BP2, behind the shared BP1 tool
|
|
206
|
+
// and skill manifest prefix, so role changes do not disturb the common layer.
|
|
402
207
|
//
|
|
403
208
|
// Classification is dynamic — hidden retrieval/maintenance sets come from the
|
|
404
209
|
// `kind` field in internal-roles.mjs. Any other non-null role is public/custom.
|
|
@@ -410,7 +215,7 @@ import {
|
|
|
410
215
|
} from '../internal-roles.mjs';
|
|
411
216
|
|
|
412
217
|
function loadRoleClassification() {
|
|
413
|
-
// Not cached — called only on
|
|
218
|
+
// Not cached — called only on instruction rebuild (mtime-busted), and
|
|
414
219
|
// listHiddenRolesByKind now reads from the mtime-aware cache inside
|
|
415
220
|
// internal-roles.mjs so the classification always reflects the current
|
|
416
221
|
// hidden-roles.json on disk.
|
|
@@ -420,20 +225,28 @@ function loadRoleClassification() {
|
|
|
420
225
|
};
|
|
421
226
|
}
|
|
422
227
|
|
|
423
|
-
const
|
|
228
|
+
const _scopedRoleInstructionsCache = new Map();
|
|
229
|
+
// Short-TTL gate for the role-instruction freshness stat. loadScopedRoleInstructions() ran
|
|
230
|
+
// maxMtimeRecursive() over agents/ + rules/agent/ on EVERY call (many per
|
|
231
|
+
// turn across roles), so even a warm cache paid dozens of statSync per turn.
|
|
232
|
+
// Mirror collectSkillsCached(): only re-stat after _ROLE_INSTRUCTIONS_MTIME_TTL_MS, and
|
|
233
|
+
// trust the cached mtime within that window. Edits still propagate within ~1
|
|
234
|
+
// stat interval, which is well under human-perceptible latency.
|
|
235
|
+
const _scopedRoleInstructionsMtimeCache = new Map();
|
|
236
|
+
const _ROLE_INSTRUCTIONS_MTIME_TTL_MS = 2000;
|
|
424
237
|
|
|
425
238
|
function loadHiddenRoleSnippets(pluginRoot) {
|
|
426
239
|
try {
|
|
427
|
-
const
|
|
428
|
-
if (!existsSync(
|
|
429
|
-
const files = readdirSync(
|
|
240
|
+
const agentRulesDir = join(pluginRoot, 'rules', 'agent');
|
|
241
|
+
if (!existsSync(agentRulesDir)) return [];
|
|
242
|
+
const files = readdirSync(agentRulesDir)
|
|
430
243
|
.filter(f => f.endsWith('.md') && f !== '00-common.md')
|
|
431
244
|
.sort();
|
|
432
245
|
const pairs = [];
|
|
433
246
|
for (const f of files) {
|
|
434
|
-
const raw = readSafe(join(
|
|
247
|
+
const raw = readSafe(join(agentRulesDir, f));
|
|
435
248
|
if (!raw) continue;
|
|
436
|
-
const body = raw
|
|
249
|
+
const { body } = readMarkdownDocument(raw);
|
|
437
250
|
if (!body) continue;
|
|
438
251
|
const name = f.replace(/^\d+-/, '').replace(/\.md$/, '');
|
|
439
252
|
pairs.push({ name, body });
|
|
@@ -448,54 +261,80 @@ function loadAgentSections(pluginRoot) {
|
|
|
448
261
|
const agentsDir = join(pluginRoot, 'agents');
|
|
449
262
|
const agentSections = [];
|
|
450
263
|
if (!existsSync(agentsDir)) return agentSections;
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
264
|
+
// agents/ holds two layouts that must BOTH be loaded:
|
|
265
|
+
// - flat: agents/<role>.md (legacy: scheduler-task, webhook-handler)
|
|
266
|
+
// - nested: agents/<role>/AGENT.md (current: worker, heavy-worker, reviewer, …)
|
|
267
|
+
// The previous flat-only readdir silently dropped every nested role, so a
|
|
268
|
+
// public role like heavy-worker produced an EMPTY scoped instruction block
|
|
269
|
+
// (BP2) — the model lost its role contract and the tool smoke's
|
|
270
|
+
// "heavy-worker AGENT.md must be included" assertion failed. Walk both.
|
|
271
|
+
let entries;
|
|
272
|
+
try {
|
|
273
|
+
entries = readdirSync(agentsDir, { withFileTypes: true });
|
|
274
|
+
} catch {
|
|
275
|
+
return agentSections;
|
|
276
|
+
}
|
|
277
|
+
const sections = [];
|
|
278
|
+
for (const entry of entries) {
|
|
279
|
+
let name = '';
|
|
280
|
+
let raw = null;
|
|
281
|
+
if (entry.isDirectory()) {
|
|
282
|
+
name = entry.name;
|
|
283
|
+
raw = readSafe(join(agentsDir, entry.name, 'AGENT.md'));
|
|
284
|
+
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
285
|
+
name = entry.name.replace(/\.md$/, '');
|
|
286
|
+
raw = readSafe(join(agentsDir, entry.name));
|
|
287
|
+
}
|
|
288
|
+
if (!name || !raw) continue;
|
|
289
|
+
const { body } = readMarkdownDocument(raw);
|
|
458
290
|
if (!body) continue;
|
|
459
|
-
|
|
460
|
-
agentSections.push(`## ${name}\n\n${body}`);
|
|
291
|
+
sections.push({ name, text: `## ${name}\n\n${body}` });
|
|
461
292
|
}
|
|
293
|
+
sections.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
294
|
+
for (const s of sections) agentSections.push(s.text);
|
|
462
295
|
return agentSections;
|
|
463
296
|
}
|
|
464
297
|
|
|
465
|
-
// Empty by design
|
|
466
|
-
//
|
|
467
|
-
//
|
|
468
|
-
// cold-load cost at >10 calls/hr. Per-role scoped catalogs cut the
|
|
469
|
-
// average BP2 prefix from 33 KB to <1 KB (~95% reduction), at the
|
|
470
|
-
// price of ~5x more cold loads — a clear net win at observed call
|
|
471
|
-
// volumes (~791 calls/hr, distinct 5 roles/hr). Implicit-prefix-hash
|
|
472
|
-
// providers (deepseek, xai, lmstudio, ollama) prefer the smaller
|
|
473
|
-
// scoped prefix anyway. Leaving the set in place (rather than deleting
|
|
474
|
-
// the branch) so a future provider with different cache economics can
|
|
475
|
-
// be added back without re-introducing the dead code.
|
|
298
|
+
// Empty by design: scoped role markdown already rides BP2 for every provider.
|
|
299
|
+
// Keeping the set in place preserves the old branch point for a future
|
|
300
|
+
// provider-specific experiment without changing today's cache layout.
|
|
476
301
|
const EXPLICIT_CACHE_PROVIDERS = new Set();
|
|
477
302
|
|
|
478
303
|
// Inbound-event maintenance roles that report results back to Lead are
|
|
479
304
|
// declared via `inboundEvent: true` in defaults/hidden-roles.json and read
|
|
480
305
|
// through isInboundEventRole(). Such roles must follow
|
|
481
|
-
// rules/
|
|
306
|
+
// rules/agent/20-skip-protocol.md so genuine no-ops (label-only events,
|
|
482
307
|
// dedup, "nothing to report") prefix their output with `[meta:silent]` and the
|
|
483
308
|
// dispatch layer suppresses the Lead inject. Other roles (cycle1/cycle2 memory
|
|
484
309
|
// maintenance, retrieval roles) never emit toward Lead.
|
|
485
310
|
|
|
486
|
-
export function
|
|
311
|
+
export function loadScopedRoleInstructions(role, provider = null) {
|
|
487
312
|
const useUnified = !!(provider && EXPLICIT_CACHE_PROVIDERS.has(provider));
|
|
488
313
|
const cacheKey = useUnified ? '__unified__' : (role || '__all__');
|
|
489
|
-
const cached =
|
|
314
|
+
const cached = _scopedRoleInstructionsCache.get(cacheKey);
|
|
490
315
|
const pluginRoot = mixdogRoot();
|
|
491
316
|
// Use maxMtimeRecursive so edits to .md files inside agents/ and
|
|
492
|
-
// rules/
|
|
493
|
-
// Linux/macOS when only a nested file's content changes.
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
317
|
+
// rules/agent/ propagate — parent dir mtime is unchanged on
|
|
318
|
+
// Linux/macOS when only a nested file's content changes. Gate the stat
|
|
319
|
+
// behind a short TTL so repeated same-turn calls reuse the last mtime
|
|
320
|
+
// instead of re-walking the trees on every invocation.
|
|
321
|
+
let mtime = 0;
|
|
322
|
+
if (pluginRoot) {
|
|
323
|
+
const mtimeCached = _scopedRoleInstructionsMtimeCache.get(cacheKey);
|
|
324
|
+
if (mtimeCached && Date.now() - mtimeCached.checkedAt < _ROLE_INSTRUCTIONS_MTIME_TTL_MS) {
|
|
325
|
+
mtime = mtimeCached.mtime;
|
|
326
|
+
} else {
|
|
327
|
+
mtime = maxMtimeRecursive([
|
|
328
|
+
join(pluginRoot, 'agents'),
|
|
329
|
+
join(pluginRoot, 'rules', 'agent'),
|
|
330
|
+
join(pluginRoot, 'defaults', 'hidden-roles.json'),
|
|
331
|
+
]);
|
|
332
|
+
_scopedRoleInstructionsMtimeCache.set(cacheKey, { mtime, checkedAt: Date.now() });
|
|
333
|
+
if (_scopedRoleInstructionsMtimeCache.size > 16) {
|
|
334
|
+
_scopedRoleInstructionsMtimeCache.delete(_scopedRoleInstructionsMtimeCache.keys().next().value);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
499
338
|
if (cached && mtime <= cached.mtime) {
|
|
500
339
|
return cached.value;
|
|
501
340
|
}
|
|
@@ -514,58 +353,55 @@ export function loadScopedRoleCatalog(role, provider = null) {
|
|
|
514
353
|
const agentSections = loadAgentSections(pluginRoot);
|
|
515
354
|
const hiddenPairs = loadHiddenRoleSnippets(pluginRoot);
|
|
516
355
|
|
|
517
|
-
// Pick which
|
|
356
|
+
// Pick which agent-rule sections + agents/<role>.md sections to emit
|
|
518
357
|
// based on role classification. Self-only emit keeps BP2 minimal.
|
|
519
|
-
let
|
|
358
|
+
let agentRuleSectionsToEmit = null; // null -> drop the agent-rule block entirely
|
|
520
359
|
let agentSectionsToEmit = agentSections; // default: full (unknown-role fallback)
|
|
521
360
|
if (useUnified) {
|
|
522
361
|
// Explicit-cache providers — every role sees the same all-in-one
|
|
523
|
-
//
|
|
362
|
+
// instruction surface. Cross-role calls hit the same provider-side prefix
|
|
524
363
|
// shard, eliminating the role-shard miss seen on Pool C
|
|
525
|
-
// transitions for
|
|
526
|
-
//
|
|
527
|
-
|
|
364
|
+
// transitions for openai-oauth/openai. This branch is disabled by the
|
|
365
|
+
// empty provider set above; BP2 remains the active role surface.
|
|
366
|
+
agentRuleSectionsToEmit = hiddenPairs.map(p => `## ${p.name}\n\n${p.body}`);
|
|
528
367
|
agentSectionsToEmit = agentSections;
|
|
529
368
|
} else if (role && classification.retrieval.has(role)) {
|
|
530
369
|
// Retrieval roles (explorer) get their own contract section
|
|
531
|
-
// (rules/
|
|
532
|
-
// cache-shard split from `worker`: brief-level descriptive-only
|
|
533
|
-
// constraints proved insufficient (haiku still rendered verdicts on
|
|
534
|
-
// evaluative queries), so the contract must sit at system level.
|
|
370
|
+
// (rules/agent/30-explorer.md) in BP2.
|
|
535
371
|
const self = hiddenPairs.find(p => p.name === role);
|
|
536
|
-
|
|
372
|
+
agentRuleSectionsToEmit = self ? [`## ${self.name}\n\n${self.body}`] : [];
|
|
537
373
|
agentSectionsToEmit = agentSections.filter(s =>
|
|
538
374
|
[...roleSharesCatalog].some(name => s.startsWith(`## ${name}\n`)));
|
|
539
375
|
} else if (role && classification.maintenance.has(role)) {
|
|
540
376
|
const self = hiddenPairs.find(p => p.name === role);
|
|
541
|
-
|
|
377
|
+
agentRuleSectionsToEmit = [];
|
|
542
378
|
if (self) {
|
|
543
|
-
|
|
379
|
+
agentRuleSectionsToEmit.push(`## ${self.name}\n\n${self.body}`);
|
|
544
380
|
} else {
|
|
545
|
-
// Fallback: maintenance role without rules/
|
|
381
|
+
// Fallback: maintenance role without rules/agent/*.md entry —
|
|
546
382
|
// pull self body from agents/<role>.md instead so newly-added
|
|
547
|
-
// hidden roles work without needing a duplicate
|
|
383
|
+
// hidden roles work without needing a duplicate agent rule file.
|
|
548
384
|
const fromAgent = agentSections.find(s => s.startsWith(`## ${role}\n`));
|
|
549
|
-
if (fromAgent)
|
|
385
|
+
if (fromAgent) agentRuleSectionsToEmit.push(fromAgent);
|
|
550
386
|
}
|
|
551
387
|
// Inbound-event roles also need the skip-protocol rule so they
|
|
552
388
|
// can opt their no-op outputs out of the Lead inject (BP1 would
|
|
553
|
-
// waste the bytes on unrelated
|
|
389
|
+
// waste the bytes on unrelated agents).
|
|
554
390
|
if (roleIsInboundEvent) {
|
|
555
391
|
const skip = hiddenPairs.find(p => p.name === 'skip-protocol');
|
|
556
|
-
if (skip)
|
|
392
|
+
if (skip) agentRuleSectionsToEmit.push(`## ${skip.name}\n\n${skip.body}`);
|
|
557
393
|
}
|
|
558
394
|
agentSectionsToEmit = [];
|
|
559
395
|
} else if (role) {
|
|
560
396
|
// Public/custom role — self-only agents/<role>.md when present,
|
|
561
|
-
// not the full hidden/maintenance bundle. The universal
|
|
562
|
-
// contract rides
|
|
563
|
-
|
|
397
|
+
// not the full hidden/maintenance bundle. The universal agent
|
|
398
|
+
// contract rides BP2 (rules/agent/00-common.md).
|
|
399
|
+
agentRuleSectionsToEmit = [];
|
|
564
400
|
agentSectionsToEmit = agentSections.filter(s => s.startsWith(`## ${role}\n`));
|
|
565
401
|
} else {
|
|
566
|
-
// Null role — full
|
|
402
|
+
// Null role — full instruction surface emitted (explicit-cache providers that
|
|
567
403
|
// shard by __all__ key).
|
|
568
|
-
|
|
404
|
+
agentRuleSectionsToEmit = hiddenPairs.map(p => `## ${p.name}\n\n${p.body}`);
|
|
569
405
|
// agentSectionsToEmit already set to full agentSections above.
|
|
570
406
|
}
|
|
571
407
|
|
|
@@ -573,11 +409,11 @@ export function loadScopedRoleCatalog(role, provider = null) {
|
|
|
573
409
|
if (agentSectionsToEmit.length) {
|
|
574
410
|
blocks.push(`# Agent Role Catalog\n\n${agentSectionsToEmit.join('\n\n---\n\n')}`);
|
|
575
411
|
}
|
|
576
|
-
if (
|
|
577
|
-
blocks.push(`#
|
|
412
|
+
if (agentRuleSectionsToEmit && agentRuleSectionsToEmit.length) {
|
|
413
|
+
blocks.push(`# Agent Role Rules\n\n${agentRuleSectionsToEmit.join('\n\n---\n\n')}`);
|
|
578
414
|
}
|
|
579
415
|
const value = blocks.join('\n\n---\n\n');
|
|
580
|
-
|
|
416
|
+
_scopedRoleInstructionsCache.set(cacheKey, { mtime, value });
|
|
581
417
|
return value;
|
|
582
418
|
} catch {
|
|
583
419
|
return '';
|
|
@@ -585,38 +421,15 @@ export function loadScopedRoleCatalog(role, provider = null) {
|
|
|
585
421
|
}
|
|
586
422
|
|
|
587
423
|
// --- Compose system prompt — 4-BP cache layout ---
|
|
588
|
-
// Returns { baseRules,
|
|
424
|
+
// Returns { baseRules, stableSystemContext, sessionMarker, volatileTail } mapping
|
|
589
425
|
// directly to the breakpoint plan:
|
|
590
|
-
// BP1 (1h, system block #1) = baseRules
|
|
591
|
-
// BP2 (1h, system block #2) =
|
|
592
|
-
// BP3 (1h,
|
|
593
|
-
// BP4 (5m, messages tail)
|
|
594
|
-
//
|
|
595
|
-
// Design note — why role/permission sit in BP4, not BP3:
|
|
596
|
-
// BP3 is reserved for user-authored mixdog.md context and role-specific task
|
|
597
|
-
// bodies that are stable within the bridge session (webhook/schedule/hidden
|
|
598
|
-
// retrieval details). A cross-role burst within the same project should
|
|
599
|
-
// still share BP1+BP2, while BP4 picks up the per-call role / permission /
|
|
600
|
-
// task variance. Tool-routing hints are static cross-role, so they live in
|
|
601
|
-
// shared bridge rules rather than being regenerated per call.
|
|
602
|
-
//
|
|
603
|
-
// BP1 inputs:
|
|
604
|
-
// - opts.bridgeRules : rules-builder buildBridgeInjectionContent output
|
|
605
|
-
// (Pool B roles share bit-identical prefix)
|
|
606
|
-
// - opts.userPrompt : explicit systemPrompt override from callsite
|
|
426
|
+
// BP1 (1h, system block #1) = baseRules — shared tool policy + compact skill manifest
|
|
427
|
+
// BP2 (1h, system block #2) = stableSystemContext — Lead/agent/hidden role system
|
|
428
|
+
// BP3 (1h, system block #3) = sessionMarker — stable memory/meta context
|
|
429
|
+
// BP4 (5m/1h, messages tail) = live user/task/tool message tail
|
|
607
430
|
//
|
|
608
|
-
//
|
|
609
|
-
//
|
|
610
|
-
//
|
|
611
|
-
// BP3/BP4 inputs:
|
|
612
|
-
// - opts.projectContext : mixdog.md user/project context, when present
|
|
613
|
-
// - opts.workflowContext : active workflow + agent catalog, captured at session start
|
|
614
|
-
// - opts.workspaceContext : cwd + project list snapshot, captured at session start
|
|
615
|
-
// - opts.role : role name from user-workflow.json or hidden-role registry
|
|
616
|
-
// - opts.agentTemplate : agents/<role>.md body when authored
|
|
617
|
-
// - opts.taskBrief : Lead-issued task description (Sub only)
|
|
618
|
-
// - opts.hasSkills : true → skills_list hint
|
|
619
|
-
// - opts.coreMemoryContext : compact core memory context
|
|
431
|
+
// Dynamic schedule/webhook/task payloads stay in normal user messages so
|
|
432
|
+
// changing one event does not rewrite the stable memory layer.
|
|
620
433
|
//
|
|
621
434
|
// `profile.skip` still filters specific buckets (claudemd, skills, memory)
|
|
622
435
|
// for backward compatibility with existing profiles.
|
|
@@ -624,132 +437,58 @@ export function composeSystemPrompt(opts) {
|
|
|
624
437
|
const profile = opts.profile || null;
|
|
625
438
|
const _skip = profile?.skip || {};
|
|
626
439
|
|
|
627
|
-
// ── BP1:
|
|
628
|
-
// Bridge common rules + explicit systemPrompt override. Contains
|
|
629
|
-
// bridgeRules (MCP instructions, Pool B shared rules, _shared/tool
|
|
630
|
-
// efficiency). Identical across ALL roles — BP1 shared pool-wide.
|
|
440
|
+
// ── BP1: shared tool/skill layer ────────────────────────────────────
|
|
631
441
|
const baseParts = [];
|
|
632
|
-
if (opts.
|
|
633
|
-
if (opts.
|
|
442
|
+
if (opts.agentRules) baseParts.push(opts.agentRules);
|
|
443
|
+
if (!_skip.skills && opts.skillManifest && typeof opts.skillManifest === 'string' && opts.skillManifest.trim()) {
|
|
444
|
+
baseParts.push(opts.skillManifest.trim());
|
|
445
|
+
}
|
|
634
446
|
const baseRules = baseParts.join('\n\n---\n\n');
|
|
635
447
|
|
|
636
|
-
// ── BP2:
|
|
637
|
-
|
|
638
|
-
// mixdog.md context rides in BP3 as a user context message, matching
|
|
639
|
-
// Claude Code's "memory files are context, not system prompt" behavior.
|
|
640
|
-
// Role / permission markers are emitted in BP4 instead so a cross-role
|
|
641
|
-
// burst within the same project shares the stable prefix.
|
|
642
|
-
const roleCatalogScoped = opts.skipRoleCatalog
|
|
448
|
+
// ── BP2: role/system layer ─────────────────────────────────────────
|
|
449
|
+
const roleInstructionContext = opts.skipRoleCatalog
|
|
643
450
|
? ''
|
|
644
|
-
:
|
|
645
|
-
const
|
|
646
|
-
if (
|
|
647
|
-
|
|
451
|
+
: loadScopedRoleInstructions(opts.role || null, opts.provider || null);
|
|
452
|
+
const stableSystemParts = [];
|
|
453
|
+
if (opts.roleRules) stableSystemParts.push(opts.roleRules);
|
|
454
|
+
if (opts.userPrompt) stableSystemParts.push(opts.userPrompt);
|
|
455
|
+
if (roleInstructionContext) stableSystemParts.push(roleInstructionContext);
|
|
456
|
+
const stableSystemContext = stableSystemParts.join('\n\n---\n\n');
|
|
648
457
|
|
|
649
|
-
// ── BP3:
|
|
650
|
-
//
|
|
651
|
-
//
|
|
652
|
-
//
|
|
653
|
-
//
|
|
654
|
-
// on to claim a 1h BP3 slot.
|
|
458
|
+
// ── BP3: stable memory/meta layer ──────────────────────────────────
|
|
459
|
+
// sessionMarker is injected by session/manager as its own `system` role
|
|
460
|
+
// block (the 3rd system block). It carries the tier3 1h cache_control on
|
|
461
|
+
// the Anthropic providers and pins language/name (Profile Preferences)
|
|
462
|
+
// instructions as a real system directive rather than a user reminder.
|
|
655
463
|
const sessionMarkerParts = [];
|
|
656
|
-
if (opts.
|
|
657
|
-
sessionMarkerParts.push(
|
|
464
|
+
if (opts.metaContext && typeof opts.metaContext === 'string' && opts.metaContext.trim()) {
|
|
465
|
+
sessionMarkerParts.push(opts.metaContext.trim());
|
|
658
466
|
}
|
|
659
467
|
if (opts.workflowContext && typeof opts.workflowContext === 'string' && opts.workflowContext.trim()) {
|
|
660
468
|
sessionMarkerParts.push(opts.workflowContext.trim());
|
|
661
469
|
}
|
|
662
|
-
if (opts.
|
|
663
|
-
sessionMarkerParts.push(opts.
|
|
470
|
+
if (!_skip.memory && opts.coreMemoryContext && typeof opts.coreMemoryContext === 'string' && opts.coreMemoryContext.trim()) {
|
|
471
|
+
sessionMarkerParts.push('# Core Memory\n' + opts.coreMemoryContext.trim());
|
|
664
472
|
}
|
|
665
473
|
const sessionMarker = sessionMarkerParts.length
|
|
666
|
-
?
|
|
474
|
+
? sessionMarkerParts.join('\n\n')
|
|
667
475
|
: '';
|
|
668
476
|
|
|
669
|
-
// ── BP4
|
|
670
|
-
//
|
|
671
|
-
//
|
|
672
|
-
//
|
|
673
|
-
//
|
|
477
|
+
// ── BP4: live message tail ─────────────────────────────────────────
|
|
478
|
+
// Raw role, permission, and task labels are intentionally omitted: role
|
|
479
|
+
// selection already shapes the session/rules/tools, permissions are
|
|
480
|
+
// enforced structurally, and the task body is sent as the actual user turn
|
|
481
|
+
// by askSession().
|
|
674
482
|
const volatileParts = [];
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
const permissionName = typeof permission === 'string'
|
|
680
|
-
? permission.trim().toLowerCase()
|
|
681
|
-
: '';
|
|
682
|
-
if (permission && permissionName !== 'full') {
|
|
683
|
-
let permissionLabel = String(permission);
|
|
684
|
-
let allow =
|
|
685
|
-
permission === 'read'
|
|
686
|
-
? 'read-only; apply_patch/shell rejected'
|
|
687
|
-
: permission === 'read-write'
|
|
688
|
-
? 'read + apply_patch/shell'
|
|
689
|
-
: permission === 'mcp'
|
|
690
|
-
? 'MCP/internal retrieval tools only; file/shell mutation tools rejected'
|
|
691
|
-
: permission === 'full'
|
|
692
|
-
? 'full — all tools'
|
|
693
|
-
: 'unknown — treat as read-only';
|
|
694
|
-
if (permission && typeof permission === 'object') {
|
|
695
|
-
const allowList = Array.isArray(permission.allow)
|
|
696
|
-
? permission.allow.map(v => String(v || '').trim()).filter(Boolean)
|
|
697
|
-
: [];
|
|
698
|
-
const denyList = Array.isArray(permission.deny)
|
|
699
|
-
? permission.deny.map(v => String(v || '').trim()).filter(Boolean)
|
|
700
|
-
: [];
|
|
701
|
-
const parts = [];
|
|
702
|
-
if (allowList.length) parts.push(`allow: ${allowList.join(', ')}`);
|
|
703
|
-
if (denyList.length) parts.push(`deny: ${denyList.join(', ')}`);
|
|
704
|
-
permissionLabel = parts.length ? parts.join('; ') : 'custom tool permission';
|
|
705
|
-
allow = allowList.length
|
|
706
|
-
? 'only listed tools are available'
|
|
707
|
-
: denyList.length
|
|
708
|
-
? 'listed tools are rejected'
|
|
709
|
-
: 'custom allow/deny policy';
|
|
710
|
-
}
|
|
711
|
-
volatileParts.push(`permission: ${permissionLabel} — ${allow}.`);
|
|
712
|
-
}
|
|
713
|
-
// Role identity — the role template body (DATA_DIR/roles/<role>.md, edited
|
|
714
|
-
// in the config UI's Custom Workflow panel) is injected immediately ABOVE
|
|
715
|
-
// the task brief and labelled so the model reads "who am I" before "what to
|
|
716
|
-
// do". Empty/absent bodies (legacy role files, hidden roles without a
|
|
717
|
-
// roles/<name>.md) contribute nothing, preserving backward compatibility.
|
|
718
|
-
//
|
|
719
|
-
// Default install has no roles/<role>.md on disk, so loadRoleTemplate
|
|
720
|
-
// returns null and a public role would otherwise get NO identity. Fall back
|
|
721
|
-
// to the built-in default identity for the well-known public roles (kept in
|
|
722
|
-
// sync with setup.html's WF_DEFAULT_ROLE_IDENTITY) so the feature works out
|
|
723
|
-
// of the box; a saved custom body always wins, and unknown/hidden roles get
|
|
724
|
-
// nothing (hidden roles stay Mixdog-managed via their systemFile rules).
|
|
725
|
-
//
|
|
726
|
-
// Absent vs present-but-empty: when opts.roleTemplate is defined the role
|
|
727
|
-
// file EXISTS on disk — its body (possibly empty for a frontmatter-only or
|
|
728
|
-
// intentionally blanked file) is authoritative and the built-in default is
|
|
729
|
-
// NOT applied. The default applies only when roleTemplate is absent
|
|
730
|
-
// (undefined) — i.e. no role file at all.
|
|
731
|
-
const roleIdentity = opts.roleTemplate
|
|
732
|
-
? String(opts.roleTemplate.body || '').trim()
|
|
733
|
-
: (DEFAULT_ROLE_IDENTITY[String(opts.role || '').trim().toLowerCase()] || '');
|
|
734
|
-
if (roleIdentity) {
|
|
735
|
-
volatileParts.push('# role-identity\n' + roleIdentity);
|
|
736
|
-
}
|
|
737
|
-
if (opts.taskBrief) volatileParts.push('# task-brief\n' + opts.taskBrief);
|
|
738
|
-
if (opts.workspaceContext && typeof opts.workspaceContext === 'string' && opts.workspaceContext.trim()) {
|
|
739
|
-
volatileParts.push(opts.workspaceContext.trim());
|
|
740
|
-
}
|
|
741
|
-
if (opts.coreMemoryContext && typeof opts.coreMemoryContext === 'string' && opts.coreMemoryContext.trim()) {
|
|
742
|
-
volatileParts.push('# Core Memory\n' + opts.coreMemoryContext.trim());
|
|
743
|
-
}
|
|
744
|
-
if (opts.cwd && typeof opts.cwd === 'string' && opts.cwd.trim()) {
|
|
745
|
-
volatileParts.push(`cwd: ${opts.cwd.trim()}`);
|
|
746
|
-
}
|
|
747
|
-
volatileParts.push(shellEnvironmentReminder());
|
|
483
|
+
// workspaceContext (current cwd + discovered project list) is intentionally
|
|
484
|
+
// NOT injected: it inlines the cwd/project layout into the prompt, which the
|
|
485
|
+
// model does not need (tools read the live cwd at call time) and which would
|
|
486
|
+
// otherwise re-fragment the cache / go stale after an in-place cwd switch.
|
|
748
487
|
const volatileTail = volatileParts.length > 0
|
|
749
488
|
? volatileParts.join('\n\n')
|
|
750
489
|
: '';
|
|
751
490
|
|
|
752
|
-
return { baseRules,
|
|
491
|
+
return { baseRules, stableSystemContext, sessionMarker, volatileTail };
|
|
753
492
|
}
|
|
754
493
|
// --- Helpers ---
|
|
755
494
|
function readSafe(path) {
|
|
@@ -763,34 +502,3 @@ function readSafe(path) {
|
|
|
763
502
|
return null;
|
|
764
503
|
}
|
|
765
504
|
}
|
|
766
|
-
function parseFrontmatter(content) {
|
|
767
|
-
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
768
|
-
if (!match)
|
|
769
|
-
return {};
|
|
770
|
-
const fm = match[1];
|
|
771
|
-
const name = fm.match(/^name:\s*["']?(.+?)["']?\s*$/m)?.[1]?.trim();
|
|
772
|
-
const description = fm.match(/^description:\s*["']?(.+?)["']?\s*$/m)?.[1]?.trim();
|
|
773
|
-
const permission = fm.match(/^permission:\s*["']?(.+?)["']?\s*$/m)?.[1]?.trim();
|
|
774
|
-
return { name, description, permission };
|
|
775
|
-
}
|
|
776
|
-
// depth cap: marketplaces/<plugin>/skills/ is depth 2 from pluginBase
|
|
777
|
-
function walkForSkills(dir, result, depth = 0) {
|
|
778
|
-
if (depth > 3) return;
|
|
779
|
-
try {
|
|
780
|
-
const entries = readdirSync(dir, { withFileTypes: true });
|
|
781
|
-
for (const entry of entries) {
|
|
782
|
-
if (entry.name === 'node_modules')
|
|
783
|
-
continue;
|
|
784
|
-
const full = join(dir, entry.name);
|
|
785
|
-
if (entry.isDirectory()) {
|
|
786
|
-
if (entry.name === 'skills') {
|
|
787
|
-
result.push(full);
|
|
788
|
-
}
|
|
789
|
-
else {
|
|
790
|
-
walkForSkills(full, result, depth + 1);
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
catch { /* ignore */ }
|
|
796
|
-
}
|