mixdog 0.9.166 → 0.9.167

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.
Files changed (221) hide show
  1. package/package.json +1 -1
  2. package/scripts/audit-usage-ledger.mjs +86 -0
  3. package/scripts/generate-voice-runtime-manifest.mjs +22 -17
  4. package/scripts/memory-chunk-quality.mjs +128 -0
  5. package/scripts/memory-layer2-check.mjs +133 -0
  6. package/scripts/model-catalog-audit.mjs +6 -0
  7. package/scripts/native-tool-download.mjs +14 -0
  8. package/scripts/prepare-native-assets.mjs +2 -7
  9. package/scripts/tool-failures.mjs +7 -2
  10. package/scripts/verify-release-assets.mjs +48 -90
  11. package/src/defaults/skills/browser-use/SKILL.md +33 -18
  12. package/src/defaults/skills/computer-use/SKILL.md +16 -8
  13. package/src/lib/keychain-cjs.cjs +0 -1
  14. package/src/lib/rules-builder.cjs +4 -1
  15. package/src/rules/agent/40-cycle1-agent.md +21 -13
  16. package/src/rules/lead/01-general.md +8 -3
  17. package/src/rules/shared/10-tool-workflow.md +20 -11
  18. package/src/rules/shared/30-exploration.md +11 -4
  19. package/src/rules/shared/40-editing.md +2 -3
  20. package/src/rules/shared/50-execution.md +6 -3
  21. package/src/rules/shared/60-verification.md +3 -2
  22. package/src/rules/shared/70-delivery.md +0 -4
  23. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +51 -45
  24. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +3 -4
  25. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +44 -7
  26. package/src/runtime/agent/orchestrator/agent-trace.mjs +9 -0
  27. package/src/runtime/agent/orchestrator/config.mjs +9 -4
  28. package/src/runtime/agent/orchestrator/context/collect.mjs +25 -1055
  29. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +272 -0
  30. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +296 -0
  31. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +523 -0
  32. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +7 -2
  33. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +13 -14
  34. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +214 -0
  35. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +220 -116
  36. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +74 -44
  37. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +191 -29
  38. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +13 -12
  39. package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +1 -9
  40. package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +5 -2
  41. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +48 -25
  42. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +28 -2
  43. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +3 -1
  44. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +1 -8
  45. package/src/runtime/agent/orchestrator/providers/lib/action-input-contract.mjs +32 -0
  46. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +21 -6
  47. package/src/runtime/agent/orchestrator/providers/lib/grok-tool-schema.mjs +61 -13
  48. package/src/runtime/agent/orchestrator/providers/model-catalog-projection.mjs +6 -1
  49. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +123 -118
  50. package/src/runtime/agent/orchestrator/providers/model-pricing-rates.mjs +54 -0
  51. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +5 -2
  52. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +34 -3
  53. package/src/runtime/agent/orchestrator/providers/openai-codex-endpoints.mjs +21 -0
  54. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +154 -0
  55. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +2 -1
  56. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +322 -0
  57. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +243 -702
  58. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +2 -2
  59. package/src/runtime/agent/orchestrator/providers/openai-startup-prewarm.mjs +221 -0
  60. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +3 -2
  61. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +1 -1
  62. package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +19 -0
  63. package/src/runtime/agent/orchestrator/providers/provider-model-identities.mjs +40 -0
  64. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +120 -710
  65. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +36 -15
  66. package/src/runtime/agent/orchestrator/session/cache/util.mjs +9 -0
  67. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +17 -4
  68. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +36 -31
  69. package/src/runtime/agent/orchestrator/session/compact.mjs +4 -5
  70. package/src/runtime/agent/orchestrator/session/context-compaction-policy.mjs +6 -5
  71. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +8 -6
  72. package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +121 -0
  73. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +2 -3
  74. package/src/runtime/agent/orchestrator/session/loop/diagnostics.mjs +74 -0
  75. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +44 -17
  76. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +266 -0
  77. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +111 -0
  78. package/src/runtime/agent/orchestrator/session/loop/request-projection.mjs +99 -0
  79. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +91 -0
  80. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +36 -0
  81. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +3 -3
  82. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
  83. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +20 -11
  84. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +3 -5
  85. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +9 -3
  86. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +39 -0
  87. package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +3 -2
  88. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +21 -3
  89. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +1 -1
  90. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +2 -3
  91. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +25 -25
  92. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +62 -10
  93. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +17 -0
  94. package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +61 -4
  95. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +13 -9
  96. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +12 -2
  97. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +3 -2
  98. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +8 -3
  99. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +69 -6
  100. package/src/runtime/agent/orchestrator/tools/builtin.mjs +14 -17
  101. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
  102. package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +1 -1
  103. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  104. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +1 -1
  105. package/src/runtime/bridge-discovery.mjs +8 -0
  106. package/src/runtime/browser-bridge/action-schema.mjs +37 -43
  107. package/src/runtime/browser-bridge/client.mjs +111 -99
  108. package/src/runtime/browser-bridge/input-fields.mjs +187 -0
  109. package/src/runtime/browser-bridge/tool-defs.mjs +12 -227
  110. package/src/runtime/computer-bridge/action-schema.mjs +2 -46
  111. package/src/runtime/computer-bridge/client.mjs +69 -77
  112. package/src/runtime/computer-bridge/core-actions.mjs +7 -5
  113. package/src/runtime/computer-bridge/error-recovery.mjs +41 -37
  114. package/src/runtime/computer-bridge/tool-defs.mjs +1 -1
  115. package/src/runtime/github/commands.mjs +4 -1
  116. package/src/runtime/github/contract.mjs +1 -1
  117. package/src/runtime/github/tool.mjs +2 -2
  118. package/src/runtime/media/adapters/antigravity-image.mjs +72 -0
  119. package/src/runtime/media/adapters/gemini-image.mjs +20 -11
  120. package/src/runtime/media/auth.mjs +12 -0
  121. package/src/runtime/media/catalog.mjs +30 -10
  122. package/src/runtime/media/jobs.mjs +4 -0
  123. package/src/runtime/media/lanes.mjs +40 -31
  124. package/src/runtime/memory/index.mjs +2 -9
  125. package/src/runtime/memory/lib/compact-handoff.mjs +26 -18
  126. package/src/runtime/memory/lib/memory-chunk-audit.mjs +41 -0
  127. package/src/runtime/memory/lib/memory-chunk-quality.mjs +388 -0
  128. package/src/runtime/memory/lib/memory-cycle1.mjs +95 -394
  129. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +29 -4
  130. package/src/runtime/memory/lib/memory-recall-store.mjs +8 -12
  131. package/src/runtime/memory/lib/memory-retrievers.mjs +8 -11
  132. package/src/runtime/memory/lib/memory.mjs +2 -0
  133. package/src/runtime/memory/lib/query-handlers.mjs +98 -144
  134. package/src/runtime/memory/lib/query-ranking.mjs +23 -9
  135. package/src/runtime/memory/lib/recall-format.mjs +37 -27
  136. package/src/runtime/memory/lib/session-ingest-runtime.mjs +14 -8
  137. package/src/runtime/memory/lib/session-ingest.mjs +45 -84
  138. package/src/runtime/memory/lib/transcript-ingest.mjs +19 -21
  139. package/src/runtime/memory/tool-defs.mjs +2 -2
  140. package/src/runtime/office/authoring/pptx-author-session.mjs +3 -7
  141. package/src/runtime/office/core/office-actions-batch.mjs +2 -8
  142. package/src/runtime/office/core/office-actions-open.mjs +124 -0
  143. package/src/runtime/office/core/office-actions-read.mjs +127 -0
  144. package/src/runtime/office/core/office-core.mjs +80 -1
  145. package/src/runtime/office/core/office-sessions.mjs +116 -138
  146. package/src/runtime/office/index.mjs +18 -271
  147. package/src/runtime/office/pdf/pdf-analysis.mjs +2 -2
  148. package/src/runtime/office/pdf/pdf-limits.mjs +2 -0
  149. package/src/runtime/office/tool-defs.mjs +7 -6
  150. package/src/runtime/shared/agent-route-config.mjs +8 -8
  151. package/src/runtime/shared/child-spawn-gate.mjs +10 -13
  152. package/src/runtime/shared/config.mjs +2 -7
  153. package/src/runtime/shared/env.mjs +3 -3
  154. package/src/runtime/shared/err-text.mjs +1 -1
  155. package/src/runtime/shared/extension-scopes.mjs +3 -2
  156. package/src/runtime/shared/heap-cap.mjs +10 -6
  157. package/src/runtime/shared/llm/cost.mjs +35 -10
  158. package/src/runtime/shared/llm/usage-accounting.mjs +11 -1
  159. package/src/runtime/shared/llm/usage-context.mjs +6 -0
  160. package/src/runtime/shared/llm/usage-ledger-import.mjs +5 -0
  161. package/src/runtime/shared/llm/usage-ledger-repair.mjs +109 -0
  162. package/src/runtime/shared/llm/usage-ledger.mjs +59 -20
  163. package/src/runtime/shared/llm/usage-pricing-refresh.mjs +26 -0
  164. package/src/runtime/shared/llm/usage-rollup.mjs +17 -15
  165. package/src/runtime/shared/native-asset.mjs +3 -4
  166. package/src/runtime/shared/object.mjs +4 -0
  167. package/src/runtime/shared/pg-schema-db.mjs +21 -0
  168. package/src/runtime/shared/plugin-metadata.mjs +3 -1
  169. package/src/runtime/shared/resource-admission.mjs +1 -6
  170. package/src/runtime/shared/schedules-db.mjs +8 -24
  171. package/src/runtime/shared/schema-value-error.d.mts +2 -0
  172. package/src/runtime/shared/schema-value-error.mjs +68 -0
  173. package/src/runtime/shared/singleton-owner.mjs +11 -24
  174. package/src/runtime/shared/staged-update.mjs +1 -10
  175. package/src/runtime/shared/tool-card-model.mjs +8 -5
  176. package/src/runtime/shared/tool-execution-contract.mjs +1 -1
  177. package/src/runtime/shared/tool-surface.mjs +1 -5
  178. package/src/runtime/shared/user-data-guard.mjs +1 -4
  179. package/src/runtime/shared/webhooks-db.mjs +8 -24
  180. package/src/runtime/web-search/index.mjs +32 -8
  181. package/src/runtime/web-search/tool-defs.mjs +4 -4
  182. package/src/session-runtime/config-lifecycle.mjs +34 -0
  183. package/src/session-runtime/context-status.mjs +19 -0
  184. package/src/session-runtime/cwd-plugins.mjs +12 -29
  185. package/src/session-runtime/goal-tasks.mjs +2 -2
  186. package/src/session-runtime/goal-tool-defs.mjs +3 -2
  187. package/src/session-runtime/internal-tool-executor.mjs +7 -0
  188. package/src/session-runtime/lifecycle-api.mjs +5 -11
  189. package/src/session-runtime/notification-bus.mjs +41 -0
  190. package/src/session-runtime/prewarm.mjs +22 -0
  191. package/src/session-runtime/runtime-bootstrap.mjs +90 -0
  192. package/src/session-runtime/runtime-core.mjs +88 -202
  193. package/src/session-runtime/runtime-feature-gates.mjs +37 -0
  194. package/src/session-runtime/session-lifecycle.mjs +5 -5
  195. package/src/session-runtime/session-turn-api.mjs +1 -12
  196. package/src/session-runtime/setup-tool/executor.mjs +13 -5
  197. package/src/session-runtime/setup-tool/tool-defs.mjs +52 -46
  198. package/src/session-runtime/tool-defs.mjs +73 -4
  199. package/src/session-runtime/usage-stats-api.mjs +4 -1
  200. package/src/session-runtime/warmup-schedulers.mjs +8 -2
  201. package/src/session-runtime/workflow.mjs +14 -0
  202. package/src/standalone/channel-session-router.mjs +3 -1
  203. package/src/standalone/channel-worker.mjs +18 -9
  204. package/src/standalone/daemon-crash-capture.mjs +534 -0
  205. package/src/standalone/daemon-telemetry.mjs +175 -0
  206. package/src/standalone/daemon.mjs +31 -21
  207. package/src/standalone/session-client.mjs +31 -23
  208. package/src/standalone/usage-dashboard-model.mjs +2 -0
  209. package/src/standalone/usage-stats-hours.mjs +18 -11
  210. package/src/standalone/usage-stats-model.mjs +39 -28
  211. package/src/standalone/usage-stats-period.mjs +43 -45
  212. package/src/tui/app/use-transcript-window.mjs +1 -1
  213. package/src/tui/components/StatusLine.jsx +13 -42
  214. package/src/tui/dist/index.mjs +256 -239
  215. package/src/tui/session/session-api-ext.mjs +44 -41
  216. package/src/tui/session/session-api.mjs +4 -0
  217. package/src/tui/session/tool-card-results.mjs +25 -78
  218. package/src/tui/session/tool-result-status.mjs +94 -1
  219. package/src/tui/session/turn-aggregate-cards.mjs +17 -54
  220. package/src/ui/statusline-format.mjs +6 -26
  221. package/src/workflows/default/WORKFLOW.md +5 -0
@@ -0,0 +1,272 @@
1
+ // Deferred-pool and MCP-instruction manifests injected into BP2.
2
+ // Extracted from collect.mjs so skill discovery and prompt composition
3
+ // no longer share a god-file with session-surface mutation.
4
+
5
+ export function compactPromptManifestText(value, max = 250) {
6
+ const text = String(value || '').replace(/\s+/g, ' ').trim();
7
+ const limit = Math.max(1, Math.floor(max));
8
+ if (text.length <= limit) return text;
9
+ const head = text.slice(0, Math.max(1, limit - 1));
10
+ const space = head.lastIndexOf(' ');
11
+ const cut = space >= Math.floor(limit * 0.6) ? head.slice(0, space) : head;
12
+ return `${cut.replace(/[\s,.;:!?/\-]+$/, '')}...`;
13
+ }
14
+
15
+ const DEFERRED_TOOLS_BLOCK_RE = /(\n\n---\n*)?<available-deferred-tools>[\s\S]*?<\/available-deferred-tools>\s*/gi;
16
+ const MCP_INSTRUCTIONS_BLOCK_RE = /(\n\n---\n*)?<mcp-instructions>[\s\S]*?<\/mcp-instructions>\s*/gi;
17
+ const DEFERRED_TOOL_NAME_SAFE_RE = /^[A-Za-z0-9_.:-]+$/;
18
+ const MCP_SERVER_NAME_SAFE_RE = /^[A-Za-z0-9_.:-]+$/;
19
+ const MCP_INSTRUCTION_MAX_CHARS = 600;
20
+
21
+ function sanitizeDeferredToolManifestName(name) {
22
+ const text = String(name || '').trim();
23
+ if (!text || text.includes('<') || text.includes('>')) return '';
24
+ if (!DEFERRED_TOOL_NAME_SAFE_RE.test(text)) return '';
25
+ return text;
26
+ }
27
+
28
+ function skillRoutedToolNames(messages) {
29
+ const names = new Set();
30
+ for (const message of Array.isArray(messages) ? messages : []) {
31
+ if (message?.role !== 'system' || typeof message.content !== 'string') continue;
32
+ for (const block of message.content.matchAll(/<available_skills>([\s\S]*?)<\/available_skills>/g)) {
33
+ // Use only routes actually visible to this session, not the global
34
+ // skill catalog (which may include disabled or omitted skills).
35
+ for (const route of block[1].matchAll(/^- [^:\r\n]+:\s+\S[^\r\n]* \[tools: ([^\]\r\n]+)\]$/gm)) {
36
+ for (const name of route[1].split(',').map((value) => value.trim())) {
37
+ // MCP dependencies name servers, not individual tools.
38
+ if (/^[A-Za-z0-9_]+$/.test(name) && !name.startsWith('mcp__')) names.add(name);
39
+ }
40
+ }
41
+ }
42
+ }
43
+ return names;
44
+ }
45
+
46
+ function hasDeferredToolManifestBlock(text) {
47
+ const raw = String(text || '');
48
+ return /<available-deferred-tools>[\s\S]*?<\/available-deferred-tools>/i.test(raw)
49
+ || /<mcp-instructions>[\s\S]*?<\/mcp-instructions>/i.test(raw);
50
+ }
51
+
52
+ /**
53
+ * Skill-style manifest for tools in the deferred pool (catalog minus active
54
+ * wire tools). Each entry is either a bare name string or `{ name, description }`;
55
+ * output lines are `- name: description` (description omitted when absent),
56
+ * mirroring the available-skills manifest so the model calls deferred tools
57
+ * directly. Descriptions are compacted and stripped of `<`/`>`.
58
+ * Empty pool → '' (caller omits the block).
59
+ */
60
+ export function buildDeferredToolManifest(entries) {
61
+ const list = [];
62
+ const seen = new Set();
63
+ for (const entry of Array.isArray(entries) ? entries : []) {
64
+ const rawName = typeof entry === 'string' ? entry : entry?.name;
65
+ const name = sanitizeDeferredToolManifestName(rawName);
66
+ if (!name || seen.has(name)) continue;
67
+ seen.add(name);
68
+ const description = typeof entry === 'string'
69
+ ? ''
70
+ : compactPromptManifestText(String(entry?.description || '').replace(/[<>]/g, ''), 100);
71
+ list.push({ name, description });
72
+ }
73
+ if (!list.length) return '';
74
+ list.sort((a, b) => a.name.localeCompare(b.name));
75
+ return [
76
+ '<available-deferred-tools>',
77
+ 'Deferred tool names and purposes; schemas load on demand.',
78
+ ...list.map((entry) => (entry.description ? `- ${entry.name}: ${entry.description}` : `- ${entry.name}`)),
79
+ '</available-deferred-tools>',
80
+ ].join('\n');
81
+ }
82
+
83
+ function sanitizeMcpManifestServerName(name) {
84
+ const text = String(name || '').trim();
85
+ if (!text || text.includes('<') || text.includes('>')) return '';
86
+ if (!MCP_SERVER_NAME_SAFE_RE.test(text)) return '';
87
+ return text;
88
+ }
89
+
90
+ function sanitizeMcpInstructionText(text, max = MCP_INSTRUCTION_MAX_CHARS) {
91
+ const stripped = String(text || '').replace(/[<>]/g, '').trim();
92
+ if (!stripped) return '';
93
+ const cap = Math.max(1, Number(max) || MCP_INSTRUCTION_MAX_CHARS);
94
+ return stripped.length > cap ? `${stripped.slice(0, Math.max(1, cap - 3))}...` : stripped;
95
+ }
96
+
97
+ /**
98
+ * Per-server MCP initialize instructions for deferred-pool tools only.
99
+ * Empty when no instructions or no matching deferred MCP tools → omit block.
100
+ * Emits ONLY the server heading + instruction body: the per-server tool names
101
+ * are deliberately NOT repeated here — every pool tool is already listed once
102
+ * (with its description) in <available-deferred-tools>, and re-listing ~30
103
+ * names per server doubled the MCP share of the BP2 prefix (2026-08-05 audit).
104
+ * Server membership stays evident from the mcp__<server>__ name prefix.
105
+ */
106
+ function buildMcpInstructionsManifest(mcpServerInstructions, poolNames) {
107
+ const map = mcpServerInstructions && typeof mcpServerInstructions === 'object'
108
+ ? mcpServerInstructions
109
+ : {};
110
+ const pool = [...new Set((Array.isArray(poolNames) ? poolNames : [])
111
+ .map((name) => sanitizeDeferredToolManifestName(name))
112
+ .filter(Boolean))];
113
+ const toolsByServer = new Map();
114
+ for (const name of pool) {
115
+ const match = name.match(/^mcp__(.+?)__(.+)$/);
116
+ if (!match) continue;
117
+ const server = match[1];
118
+ if (!toolsByServer.has(server)) toolsByServer.set(server, []);
119
+ toolsByServer.get(server).push(name);
120
+ }
121
+ const servers = [...toolsByServer.keys()]
122
+ .filter((server) => sanitizeMcpManifestServerName(server)
123
+ && sanitizeMcpInstructionText(map[server])
124
+ && toolsByServer.get(server).length)
125
+ .sort((a, b) => a.localeCompare(b));
126
+ if (!servers.length) return '';
127
+ const lines = ['<mcp-instructions>'];
128
+ for (const server of servers) {
129
+ const safeServer = sanitizeMcpManifestServerName(server);
130
+ const body = sanitizeMcpInstructionText(map[server]);
131
+ lines.push(`## ${safeServer}`, body);
132
+ }
133
+ lines.push('</mcp-instructions>');
134
+ return lines.join('\n');
135
+ }
136
+
137
+ export function stripDeferredToolManifestBlock(text) {
138
+ return String(text || '')
139
+ .replace(DEFERRED_TOOLS_BLOCK_RE, '')
140
+ .replace(MCP_INSTRUCTIONS_BLOCK_RE, '')
141
+ .replace(/\n\n---\n*$/,'')
142
+ .trimEnd();
143
+ }
144
+
145
+ // Rebuild path: replace the FIRST previously-injected <available-deferred-tools>
146
+ // block (with its leading `---` separator) with the fresh manifest IN PLACE, so
147
+ // the block keeps its original position and no sibling BP2 block (skills
148
+ // manifest, agent rules, …) is reordered or dropped. The fresh manifest already
149
+ // carries the mcp-instructions companion, so any pre-existing standalone one is
150
+ // removed first to avoid duplication.
151
+ function rebuildDeferredToolManifestBlock(text, manifest) {
152
+ let out = String(text || '').replace(MCP_INSTRUCTIONS_BLOCK_RE, '');
153
+ let replaced = false;
154
+ out = out.replace(DEFERRED_TOOLS_BLOCK_RE, (match, sep) => {
155
+ if (replaced) return '';
156
+ replaced = true;
157
+ return `${sep || ''}${manifest}`;
158
+ });
159
+ if (!replaced) {
160
+ const base = out.trimEnd();
161
+ out = base ? `${base}\n\n---\n\n${manifest}` : manifest;
162
+ }
163
+ return out;
164
+ }
165
+
166
+ function deferredManifestEntries(session, pool) {
167
+ const descByName = new Map();
168
+ for (const tool of Array.isArray(session?.deferredToolCatalog) ? session.deferredToolCatalog : []) {
169
+ const name = String(tool?.name || '').trim();
170
+ if (name && !descByName.has(name)) descByName.set(name, String(tool?.description || ''));
171
+ }
172
+ const skillRoutedNames = skillRoutedToolNames(session.messages);
173
+ return pool.map((name) => ({
174
+ name,
175
+ description: skillRoutedNames.has(String(name).trim())
176
+ ? ''
177
+ : descByName.get(String(name).trim()) || '',
178
+ }));
179
+ }
180
+
181
+ function composeDeferredBp2Manifest(session, pool) {
182
+ const parts = [];
183
+ const deferredManifest = buildDeferredToolManifest(deferredManifestEntries(session, pool));
184
+ if (deferredManifest) parts.push(deferredManifest);
185
+ const mcpManifest = buildMcpInstructionsManifest(session.mcpServerInstructions, pool);
186
+ if (mcpManifest) parts.push(mcpManifest);
187
+ return parts.join('\n\n');
188
+ }
189
+
190
+ function markDeferredBp2Applied(session) {
191
+ session.deferredToolBp2Applied = true;
192
+ delete session.deferredToolBp1Applied;
193
+ }
194
+
195
+ function clearDeferredManifestBlocks(session) {
196
+ for (const message of session.messages) {
197
+ if (message?.role === 'system' && typeof message.content === 'string') {
198
+ message.content = stripDeferredToolManifestBlock(message.content);
199
+ }
200
+ }
201
+ session.messages = session.messages.filter((message) => (
202
+ message?.role !== 'system' || String(message.content || '').trim()
203
+ ));
204
+ }
205
+
206
+ function resolveDeferredBp2Index(session) {
207
+ const systemIndexes = session.messages
208
+ .map((message, index) => (message?.role === 'system' ? index : -1))
209
+ .filter((index) => index >= 0);
210
+ if (!systemIndexes.length) return -1;
211
+ const tier3Index = systemIndexes.find((index) => session.messages[index]?.cacheTier === 'tier3');
212
+ let idx = systemIndexes.length >= 2 && systemIndexes[1] !== tier3Index
213
+ ? systemIndexes[1]
214
+ : -1;
215
+ if (idx >= 0) return idx;
216
+ idx = tier3Index >= 0 ? tier3Index : systemIndexes[0] + 1;
217
+ session.messages.splice(idx, 0, { role: 'system', content: '' });
218
+ return idx;
219
+ }
220
+
221
+ function writeDeferredManifestAt(session, idx, manifest, rebuild) {
222
+ for (let i = 0; i < session.messages.length; i++) {
223
+ if (i === idx || session.messages[i]?.role !== 'system' || typeof session.messages[i].content !== 'string') continue;
224
+ session.messages[i].content = stripDeferredToolManifestBlock(session.messages[i].content);
225
+ }
226
+ const raw = typeof session.messages[idx].content === 'string' ? session.messages[idx].content : '';
227
+ if (rebuild && hasDeferredToolManifestBlock(raw)) {
228
+ session.messages[idx].content = rebuildDeferredToolManifestBlock(raw, manifest);
229
+ return;
230
+ }
231
+ const base = stripDeferredToolManifestBlock(raw);
232
+ session.messages[idx].content = base
233
+ ? `${base}\n\n---\n\n${manifest}`
234
+ : manifest;
235
+ }
236
+
237
+ /**
238
+ * Inject the skill-style deferred pool (name + description) into BP2 at session
239
+ * start. Normally once; with `{ rebuild: true }` it strips any existing
240
+ * <available-deferred-tools>/<mcp-instructions> block and re-injects the fresh
241
+ * pool in place (used by the first-turn MCP refresh, before the prompt renders,
242
+ * so late-connected MCP tools land in the INITIAL manifest — never duplicated).
243
+ */
244
+ export function applyInitialDeferredToolManifestToBp2(session, poolNames, options = {}) {
245
+ const rebuild = options?.rebuild === true;
246
+ if (!session || !Array.isArray(session.messages)) return false;
247
+ if (session.deferredToolBp2Applied && !rebuild) return false;
248
+ const pool = Array.isArray(poolNames) ? poolNames : [];
249
+ const manifest = composeDeferredBp2Manifest(session, pool);
250
+ if (!manifest) {
251
+ clearDeferredManifestBlocks(session);
252
+ markDeferredBp2Applied(session);
253
+ session.updatedAt = Date.now();
254
+ return true;
255
+ }
256
+
257
+ const existingMessage = session.messages.find((message) => (
258
+ message?.role === 'system'
259
+ && typeof message.content === 'string'
260
+ && hasDeferredToolManifestBlock(message.content)
261
+ ));
262
+ const idx = resolveDeferredBp2Index(session);
263
+ if (idx < 0) return false;
264
+ if (existingMessage === session.messages[idx] && !rebuild) {
265
+ markDeferredBp2Applied(session);
266
+ return true;
267
+ }
268
+ writeDeferredManifestAt(session, idx, manifest, rebuild);
269
+ markDeferredBp2Applied(session);
270
+ session.updatedAt = Date.now();
271
+ return true;
272
+ }
@@ -0,0 +1,296 @@
1
+ // Agent-scoped role markdown and 4-BP system prompt composition.
2
+ // Extracted from collect.mjs so skill catalog loading is not mixed into
3
+ // role-specific prompt assembly.
4
+
5
+ import { existsSync, readdirSync } from 'fs';
6
+ import { join } from 'path';
7
+ import { mixdogRoot } from '../../../shared/plugin-paths.mjs';
8
+ import { readMarkdownDocument } from '../../../shared/markdown-frontmatter.mjs';
9
+ import { listHiddenAgentsByKind, getAgentCatalogShareAgents } from '../internal-agents.mjs';
10
+ import { mixdogGlobalDir, mtimeWithTtl, readSafe } from './skill-catalog.mjs';
11
+
12
+ // --- Agent-scoped instruction loader ---
13
+ // Emits a BP2 agent/system block scoped to the calling agent:
14
+ // - Public/custom agents: their own agents/<agent>.md when present,
15
+ // plus the public agent-worker contract.
16
+ // - Hidden agents: their own rules/agent/<agent>.md section only.
17
+ // - Null agent: falls back to the full all-in-one block
18
+ // (explicit-cache unified-shard path).
19
+ //
20
+ // Agent-specific markdown intentionally rides BP2, behind the shared BP1 tool
21
+ // and skill manifest prefix, so agent changes do not disturb the common layer.
22
+ //
23
+ // Classification is dynamic — hidden retrieval/maintenance sets come from the
24
+ // `kind` field in internal-agents.mjs. Any other non-null agent is public/custom.
25
+
26
+ function loadAgentClassification() {
27
+ // Not cached — called only on instruction rebuild (mtime-busted), and
28
+ // listHiddenAgentsByKind now reads from the mtime-aware cache inside
29
+ // internal-agents.mjs so the classification always reflects the current
30
+ // agents.json on disk.
31
+ return {
32
+ retrieval: new Set(listHiddenAgentsByKind('retrieval')),
33
+ maintenance: new Set(listHiddenAgentsByKind('maintenance')),
34
+ };
35
+ }
36
+
37
+ const _scopedRoleInstructionsCache = new Map();
38
+ // Short-TTL gate for the role-instruction freshness stat. loadScopedRoleInstructions() ran
39
+ // maxMtimeRecursive() over agents/ + rules/agent/ on EVERY call (many per
40
+ // turn across roles), so even a warm cache paid dozens of statSync per turn.
41
+ // Mirror collectSkillsCached(): only re-stat after _ROLE_INSTRUCTIONS_MTIME_TTL_MS, and
42
+ // trust the cached mtime within that window. Edits still propagate within ~1
43
+ // stat interval, which is well under human-perceptible latency.
44
+ const _scopedRoleInstructionsMtimeCache = new Map();
45
+ const _ROLE_INSTRUCTIONS_MTIME_TTL_MS = 2000;
46
+
47
+ function loadHiddenAgentSnippets(pluginRoot) {
48
+ try {
49
+ const agentRulesDir = join(pluginRoot, 'rules', 'agent');
50
+ if (!existsSync(agentRulesDir)) return [];
51
+ const files = readdirSync(agentRulesDir)
52
+ .filter(f => f.endsWith('.md') && f !== '00-common.md' && f !== '00-core.md')
53
+ .sort();
54
+ const pairs = [];
55
+ for (const f of files) {
56
+ const raw = readSafe(join(agentRulesDir, f));
57
+ if (!raw) continue;
58
+ const { body } = readMarkdownDocument(raw);
59
+ if (!body) continue;
60
+ const name = f.replace(/^\d+-/, '').replace(/\.md$/, '');
61
+ pairs.push({ name, body });
62
+ }
63
+ return pairs;
64
+ } catch {
65
+ return [];
66
+ }
67
+ }
68
+
69
+ // Role-markdown roots, in precedence order (later wins on the same name):
70
+ // the shipped agents/ tree, then the user data dir. User-authored roles and
71
+ // built-in overrides written by the Workflows editor live in the data dir —
72
+ // reading only the install root left a custom agent with an EMPTY role
73
+ // catalog while Lead already saw the edited AGENT.md.
74
+ function agentSectionDirs(pluginRoot) {
75
+ const dirs = [];
76
+ if (pluginRoot) dirs.push(join(pluginRoot, 'agents'));
77
+ try {
78
+ const userDir = mixdogGlobalDir('agents');
79
+ if (userDir && !dirs.includes(userDir)) dirs.push(userDir);
80
+ } catch { /* unresolvable data dir — built-in roles still load */ }
81
+ return dirs;
82
+ }
83
+
84
+ function loadAgentSections(pluginRoot) {
85
+ // agents/ accepts both the compatibility flat layout and the current
86
+ // nested agents/<agent>/AGENT.md layout.
87
+ // The previous flat-only readdir silently dropped every nested agent, so a
88
+ // public agent like heavy-worker produced an EMPTY scoped instruction block
89
+ // (BP2) — the model lost its agent contract and the tool smoke's
90
+ // "heavy-worker AGENT.md must be included" assertion failed. Walk both.
91
+ const byName = new Map();
92
+ for (const agentsDir of agentSectionDirs(pluginRoot)) {
93
+ if (!existsSync(agentsDir)) continue;
94
+ let entries;
95
+ try {
96
+ entries = readdirSync(agentsDir, { withFileTypes: true });
97
+ } catch {
98
+ continue;
99
+ }
100
+ for (const entry of entries) {
101
+ let name = '';
102
+ let raw = null;
103
+ if (entry.isDirectory()) {
104
+ name = entry.name;
105
+ raw = readSafe(join(agentsDir, entry.name, 'AGENT.md'));
106
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
107
+ name = entry.name.replace(/\.md$/, '');
108
+ raw = readSafe(join(agentsDir, entry.name));
109
+ }
110
+ if (!name || !raw) continue;
111
+ const { body } = readMarkdownDocument(raw);
112
+ if (!body) continue;
113
+ byName.set(name, `## ${name}\n\n${body}`);
114
+ }
115
+ }
116
+ return [...byName.entries()]
117
+ .sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0))
118
+ .map(([, text]) => text);
119
+ }
120
+
121
+ // Empty by design: scoped agent markdown already rides BP2 for every provider.
122
+ // Keeping the set in place preserves the old branch point for a future
123
+ // provider-specific experiment without changing today's cache layout.
124
+ const EXPLICIT_CACHE_PROVIDERS = new Set();
125
+
126
+ function hiddenRuleSections(hiddenPairs) {
127
+ return hiddenPairs.map((p) => `## ${p.name}\n\n${p.body}`);
128
+ }
129
+
130
+ function selfHiddenRuleSection(hiddenPairs, agent) {
131
+ const self = hiddenPairs.find((p) => p.name === agent);
132
+ return self ? [`## ${self.name}\n\n${self.body}`] : [];
133
+ }
134
+
135
+ function selectRoleInstructionSections({
136
+ useUnified,
137
+ agent,
138
+ classification,
139
+ agentSharesCatalog,
140
+ agentSections,
141
+ hiddenPairs,
142
+ }) {
143
+ if (useUnified || !agent) {
144
+ return {
145
+ agentRuleSectionsToEmit: hiddenRuleSections(hiddenPairs),
146
+ agentSectionsToEmit: agentSections,
147
+ };
148
+ }
149
+ if (classification.retrieval.has(agent)) {
150
+ return {
151
+ agentRuleSectionsToEmit: selfHiddenRuleSection(hiddenPairs, agent),
152
+ agentSectionsToEmit: agentSections.filter((s) =>
153
+ [...agentSharesCatalog].some((name) => s.startsWith(`## ${name}\n`))),
154
+ };
155
+ }
156
+ if (classification.maintenance.has(agent)) {
157
+ const selfRules = selfHiddenRuleSection(hiddenPairs, agent);
158
+ const fromAgent = agentSections.find((s) => s.startsWith(`## ${agent}\n`));
159
+ return {
160
+ agentRuleSectionsToEmit: selfRules.length ? selfRules : (fromAgent ? [fromAgent] : []),
161
+ agentSectionsToEmit: [],
162
+ };
163
+ }
164
+ return {
165
+ agentRuleSectionsToEmit: [],
166
+ agentSectionsToEmit: agentSections.filter((s) => s.startsWith(`## ${agent}\n`)),
167
+ };
168
+ }
169
+
170
+ export function loadScopedRoleInstructions(agent, provider = null) {
171
+ const useUnified = !!(provider && EXPLICIT_CACHE_PROVIDERS.has(provider));
172
+ const cacheKey = useUnified ? '__unified__' : (agent || '__all__');
173
+ const cached = _scopedRoleInstructionsCache.get(cacheKey);
174
+ const pluginRoot = mixdogRoot();
175
+ // Use maxMtimeRecursive so edits to .md files inside agents/ and
176
+ // rules/agent/ propagate — parent dir mtime is unchanged on
177
+ // Linux/macOS when only a nested file's content changes. Gate the stat
178
+ // behind a short TTL so repeated same-turn calls reuse the last mtime
179
+ // instead of re-walking the trees on every invocation.
180
+ const mtime = pluginRoot
181
+ ? mtimeWithTtl(_scopedRoleInstructionsMtimeCache, cacheKey, [
182
+ ...agentSectionDirs(pluginRoot),
183
+ join(pluginRoot, 'rules', 'agent'),
184
+ join(pluginRoot, 'defaults', 'agents.json'),
185
+ ], _ROLE_INSTRUCTIONS_MTIME_TTL_MS)
186
+ : 0;
187
+ if (cached && mtime <= cached.mtime) {
188
+ return cached.value;
189
+ }
190
+ // Compute classification before file loading — internal-agents metadata
191
+ // failures (malformed/missing agents.json) must propagate, not be
192
+ // silently swallowed by the file-IO catch below.
193
+ const classification = loadAgentClassification();
194
+ const agentSharesCatalog = agent && classification.retrieval.has(agent)
195
+ ? new Set(getAgentCatalogShareAgents(agent))
196
+ : new Set();
197
+ try {
198
+ const agentSections = loadAgentSections(pluginRoot);
199
+ const hiddenPairs = loadHiddenAgentSnippets(pluginRoot);
200
+
201
+ const { agentRuleSectionsToEmit, agentSectionsToEmit } = selectRoleInstructionSections({
202
+ useUnified,
203
+ agent,
204
+ classification,
205
+ agentSharesCatalog,
206
+ agentSections,
207
+ hiddenPairs,
208
+ });
209
+
210
+ const blocks = [];
211
+ if (agentSectionsToEmit.length) {
212
+ blocks.push(`# Agent Role Catalog\n\n${agentSectionsToEmit.join('\n\n---\n\n')}`);
213
+ }
214
+ if (agentRuleSectionsToEmit.length) {
215
+ blocks.push(`# Agent Role Rules\n\n${agentRuleSectionsToEmit.join('\n\n---\n\n')}`);
216
+ }
217
+ const value = blocks.join('\n\n---\n\n');
218
+ _scopedRoleInstructionsCache.set(cacheKey, { mtime, value });
219
+ return value;
220
+ } catch {
221
+ return '';
222
+ }
223
+ }
224
+
225
+ // --- Compose system prompt — 4-BP cache layout ---
226
+ // Returns the three stable system blocks and the BP3 core used for refreshes.
227
+ // directly to the breakpoint plan:
228
+ // BP1 (1h, system block #1) = baseRules — shared tool policy
229
+ // BP2 (1h, system block #2) = stableSystemContext — profile, skills, deferred/MCP
230
+ // BP3 (1h, system block #3) = sessionMarker — workflow/role, memory, session/project environment
231
+ // BP4 (5m/1h, messages tail) = live user/task/tool message tail
232
+ //
233
+ // Dynamic schedule/webhook/task payloads stay in normal user messages so
234
+ // changing one event does not rewrite the stable memory layer.
235
+ //
236
+ // `profile.skip` still filters specific buckets (claudemd, skills, memory)
237
+ // for backward compatibility with existing profiles.
238
+ function trimmedPromptSlice(value) {
239
+ return typeof value === 'string' && value.trim() ? value.trim() : '';
240
+ }
241
+
242
+ function joinPromptSlices(parts) {
243
+ return parts.filter(Boolean).join('\n\n---\n\n');
244
+ }
245
+
246
+ export function composeSystemPrompt(opts) {
247
+ const skip = opts.profile?.skip || {};
248
+
249
+ // ── BP1: globally shared tool policy ────────────────────────────────
250
+ const baseParts = [];
251
+ if (opts.agentRules) baseParts.push(opts.agentRules);
252
+ const baseRules = baseParts.join('\n\n---\n\n');
253
+
254
+ // ── BP2: persistent profile/tool catalog layer ──────────────────────
255
+ // deferredToolManifest: optional BP2 slice; production path is
256
+ // applyInitialDeferredToolManifestToBp2 once after applyDeferredToolSurface.
257
+ const stableSystemContext = joinPromptSlices([
258
+ trimmedPromptSlice(opts.metaContext),
259
+ skip.skills ? '' : trimmedPromptSlice(opts.skillManifest),
260
+ trimmedPromptSlice(opts.deferredToolManifest),
261
+ ]);
262
+
263
+ // ── BP3: workflow/role + session environment layer ─────────────────
264
+ const roleInstructionContext = opts.skipRoleCatalog
265
+ ? ''
266
+ : loadScopedRoleInstructions(opts.agent || null, opts.provider || null);
267
+ // Keep the active workflow first within BP3 so it leads the role and
268
+ // environment material that varies with the session.
269
+ const coreMemory = skip.memory ? '' : trimmedPromptSlice(opts.coreMemoryContext);
270
+ const sessionMarkerCore = joinPromptSlices([
271
+ trimmedPromptSlice(opts.workflowContext),
272
+ opts.roleRules || '',
273
+ opts.userPrompt || '',
274
+ roleInstructionContext,
275
+ coreMemory ? `# Core Memory\n${coreMemory}` : '',
276
+ ]);
277
+ // Response language is the LAST system text before the conversation. It
278
+ // rides the environment block (not the tier3 core) because the `# Session`
279
+ // / shell / git lines are English and would otherwise trail it, pulling the
280
+ // pre-tool preamble toward English.
281
+ const environmentParts = [
282
+ opts.sessionStartContext,
283
+ opts.projectInstructionsContext,
284
+ opts.environmentContext,
285
+ opts.languageContext,
286
+ ].map(trimmedPromptSlice);
287
+ // Volatile session/project environment. Kept OUT of sessionMarkerCore so
288
+ // Anthropic providers can leave it as an UNMARKED system block (covered by
289
+ // the messages-tail breakpoint) while prefix-order providers still see it
290
+ // after the stable context. sessionMarker keeps the combined legacy shape
291
+ // for callers that consume a single BP3 string.
292
+ const sessionEnvironment = joinPromptSlices(environmentParts);
293
+ const sessionMarker = joinPromptSlices([sessionMarkerCore, sessionEnvironment]);
294
+
295
+ return { baseRules, stableSystemContext, sessionMarkerCore, sessionEnvironment, sessionMarker };
296
+ }