open-multi-agent-kit 0.99.0 → 1.0.0

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 (271) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +3 -3
  3. package/dist/bun/cli.d.ts.map +1 -1
  4. package/dist/bun/cli.js +1 -0
  5. package/dist/bun/cli.js.map +1 -1
  6. package/dist/bun/register-bundled-coding-agent.d.ts +10 -0
  7. package/dist/bun/register-bundled-coding-agent.d.ts.map +1 -0
  8. package/dist/bun/register-bundled-coding-agent.js +12 -0
  9. package/dist/bun/register-bundled-coding-agent.js.map +1 -0
  10. package/dist/cli/args.d.ts +1 -1
  11. package/dist/cli/args.d.ts.map +1 -1
  12. package/dist/cli/args.js +1 -1
  13. package/dist/cli/args.js.map +1 -1
  14. package/dist/cli/help.d.ts.map +1 -1
  15. package/dist/cli/help.js +1 -1
  16. package/dist/cli/help.js.map +1 -1
  17. package/dist/cli.d.ts.map +1 -1
  18. package/dist/cli.js +14 -2
  19. package/dist/cli.js.map +1 -1
  20. package/dist/commands/neo-cli.d.ts +9 -0
  21. package/dist/commands/neo-cli.d.ts.map +1 -0
  22. package/dist/commands/neo-cli.js +61 -0
  23. package/dist/commands/neo-cli.js.map +1 -0
  24. package/dist/core/agent-session.d.ts +29 -2
  25. package/dist/core/agent-session.d.ts.map +1 -1
  26. package/dist/core/agent-session.js +107 -30
  27. package/dist/core/agent-session.js.map +1 -1
  28. package/dist/core/bundled-skills.d.ts +4 -0
  29. package/dist/core/bundled-skills.d.ts.map +1 -0
  30. package/dist/core/bundled-skills.js +32 -0
  31. package/dist/core/bundled-skills.js.map +1 -0
  32. package/dist/core/cli-diagnostics.d.ts +6 -0
  33. package/dist/core/cli-diagnostics.d.ts.map +1 -0
  34. package/dist/core/cli-diagnostics.js +20 -0
  35. package/dist/core/cli-diagnostics.js.map +1 -0
  36. package/dist/core/context-budget-headroom.d.ts +12 -0
  37. package/dist/core/context-budget-headroom.d.ts.map +1 -1
  38. package/dist/core/context-budget-headroom.js +35 -0
  39. package/dist/core/context-budget-headroom.js.map +1 -1
  40. package/dist/core/context-budget-v2-input-validation.d.ts +4 -0
  41. package/dist/core/context-budget-v2-input-validation.d.ts.map +1 -0
  42. package/dist/core/context-budget-v2-input-validation.js +79 -0
  43. package/dist/core/context-budget-v2-input-validation.js.map +1 -0
  44. package/dist/core/context-budget-v2-observability.d.ts +15 -0
  45. package/dist/core/context-budget-v2-observability.d.ts.map +1 -0
  46. package/dist/core/context-budget-v2-observability.js +35 -0
  47. package/dist/core/context-budget-v2-observability.js.map +1 -0
  48. package/dist/core/context-budget-v2-planned-items.d.ts +5 -0
  49. package/dist/core/context-budget-v2-planned-items.d.ts.map +1 -0
  50. package/dist/core/context-budget-v2-planned-items.js +40 -0
  51. package/dist/core/context-budget-v2-planned-items.js.map +1 -0
  52. package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
  53. package/dist/core/context-budget-v2-planner.js +57 -38
  54. package/dist/core/context-budget-v2-planner.js.map +1 -1
  55. package/dist/core/context-budget-v2-selection.d.ts +19 -3
  56. package/dist/core/context-budget-v2-selection.d.ts.map +1 -1
  57. package/dist/core/context-budget-v2-selection.js +35 -57
  58. package/dist/core/context-budget-v2-selection.js.map +1 -1
  59. package/dist/core/context-budget-v2-tiers.d.ts.map +1 -1
  60. package/dist/core/context-budget-v2-tiers.js +9 -42
  61. package/dist/core/context-budget-v2-tiers.js.map +1 -1
  62. package/dist/core/context-budget-v2-types.d.ts +8 -1
  63. package/dist/core/context-budget-v2-types.d.ts.map +1 -1
  64. package/dist/core/context-budget-v2-types.js.map +1 -1
  65. package/dist/core/extensions/bundled-virtual-modules.d.ts +15 -0
  66. package/dist/core/extensions/bundled-virtual-modules.d.ts.map +1 -0
  67. package/dist/core/extensions/bundled-virtual-modules.js +63 -0
  68. package/dist/core/extensions/bundled-virtual-modules.js.map +1 -0
  69. package/dist/core/extensions/loader.d.ts.map +1 -1
  70. package/dist/core/extensions/loader.js +2 -42
  71. package/dist/core/extensions/loader.js.map +1 -1
  72. package/dist/core/extensions/runner.d.ts +1 -0
  73. package/dist/core/extensions/runner.d.ts.map +1 -1
  74. package/dist/core/extensions/runner.js +6 -0
  75. package/dist/core/extensions/runner.js.map +1 -1
  76. package/dist/core/extensions/types.d.ts +10 -0
  77. package/dist/core/extensions/types.d.ts.map +1 -1
  78. package/dist/core/extensions/types.js.map +1 -1
  79. package/dist/core/index.d.ts +2 -1
  80. package/dist/core/index.d.ts.map +1 -1
  81. package/dist/core/index.js +2 -1
  82. package/dist/core/index.js.map +1 -1
  83. package/dist/core/loadout-runtime.d.ts +2 -8
  84. package/dist/core/loadout-runtime.d.ts.map +1 -1
  85. package/dist/core/loadout-runtime.js.map +1 -1
  86. package/dist/core/mcp/client.d.ts +27 -6
  87. package/dist/core/mcp/client.d.ts.map +1 -1
  88. package/dist/core/mcp/client.js +80 -20
  89. package/dist/core/mcp/client.js.map +1 -1
  90. package/dist/core/mcp/manager.d.ts +10 -1
  91. package/dist/core/mcp/manager.d.ts.map +1 -1
  92. package/dist/core/mcp/manager.js +68 -9
  93. package/dist/core/mcp/manager.js.map +1 -1
  94. package/dist/core/mcp/protocol.d.ts +9 -3
  95. package/dist/core/mcp/protocol.d.ts.map +1 -1
  96. package/dist/core/mcp/protocol.js +61 -15
  97. package/dist/core/mcp/protocol.js.map +1 -1
  98. package/dist/core/mcp/stdio-transport.d.ts +12 -1
  99. package/dist/core/mcp/stdio-transport.d.ts.map +1 -1
  100. package/dist/core/mcp/stdio-transport.js +30 -2
  101. package/dist/core/mcp/stdio-transport.js.map +1 -1
  102. package/dist/core/mcp-descriptor-injection.d.ts +26 -0
  103. package/dist/core/mcp-descriptor-injection.d.ts.map +1 -0
  104. package/dist/core/mcp-descriptor-injection.js +25 -0
  105. package/dist/core/mcp-descriptor-injection.js.map +1 -0
  106. package/dist/core/mcp-public-presets.d.ts +1 -3
  107. package/dist/core/mcp-public-presets.d.ts.map +1 -1
  108. package/dist/core/mcp-public-presets.js +3 -15
  109. package/dist/core/mcp-public-presets.js.map +1 -1
  110. package/dist/core/model-registry.d.ts.map +1 -1
  111. package/dist/core/model-registry.js +24 -3
  112. package/dist/core/model-registry.js.map +1 -1
  113. package/dist/core/neo/catalog.d.ts +20 -0
  114. package/dist/core/neo/catalog.d.ts.map +1 -0
  115. package/dist/core/neo/catalog.js +50 -0
  116. package/dist/core/neo/catalog.js.map +1 -0
  117. package/dist/core/neo/setup.d.ts +3 -0
  118. package/dist/core/neo/setup.d.ts.map +1 -0
  119. package/dist/core/neo/setup.js +47 -0
  120. package/dist/core/neo/setup.js.map +1 -0
  121. package/dist/core/provider-default-models.d.ts +1 -0
  122. package/dist/core/provider-default-models.d.ts.map +1 -1
  123. package/dist/core/provider-default-models.js +1 -0
  124. package/dist/core/provider-default-models.js.map +1 -1
  125. package/dist/core/provider-display-names.d.ts.map +1 -1
  126. package/dist/core/provider-display-names.js +1 -0
  127. package/dist/core/provider-display-names.js.map +1 -1
  128. package/dist/core/provider-error-classification.d.ts +57 -0
  129. package/dist/core/provider-error-classification.d.ts.map +1 -0
  130. package/dist/core/provider-error-classification.js +103 -0
  131. package/dist/core/provider-error-classification.js.map +1 -0
  132. package/dist/core/provider-resilience.d.ts +10 -0
  133. package/dist/core/provider-resilience.d.ts.map +1 -1
  134. package/dist/core/provider-resilience.js +36 -3
  135. package/dist/core/provider-resilience.js.map +1 -1
  136. package/dist/core/provider-usage-commandcode.d.ts +9 -0
  137. package/dist/core/provider-usage-commandcode.d.ts.map +1 -0
  138. package/dist/core/provider-usage-commandcode.js +198 -0
  139. package/dist/core/provider-usage-commandcode.js.map +1 -0
  140. package/dist/core/provider-usage-devin.d.ts +5 -3
  141. package/dist/core/provider-usage-devin.d.ts.map +1 -1
  142. package/dist/core/provider-usage-devin.js +15 -5
  143. package/dist/core/provider-usage-devin.js.map +1 -1
  144. package/dist/core/provider-usage-types.d.ts +1 -1
  145. package/dist/core/provider-usage-types.d.ts.map +1 -1
  146. package/dist/core/provider-usage-types.js.map +1 -1
  147. package/dist/core/provider-usage.d.ts.map +1 -1
  148. package/dist/core/provider-usage.js +6 -0
  149. package/dist/core/provider-usage.js.map +1 -1
  150. package/dist/core/resource-admission.d.ts +36 -0
  151. package/dist/core/resource-admission.d.ts.map +1 -1
  152. package/dist/core/resource-admission.js +59 -0
  153. package/dist/core/resource-admission.js.map +1 -1
  154. package/dist/core/resource-loader.d.ts.map +1 -1
  155. package/dist/core/resource-loader.js +2 -2
  156. package/dist/core/resource-loader.js.map +1 -1
  157. package/dist/core/run-usage-ledger.d.ts +47 -0
  158. package/dist/core/run-usage-ledger.d.ts.map +1 -0
  159. package/dist/core/run-usage-ledger.js +162 -0
  160. package/dist/core/run-usage-ledger.js.map +1 -0
  161. package/dist/core/run-usage-operation.d.ts +8 -0
  162. package/dist/core/run-usage-operation.d.ts.map +1 -0
  163. package/dist/core/run-usage-operation.js +12 -0
  164. package/dist/core/run-usage-operation.js.map +1 -0
  165. package/dist/core/sdk.d.ts.map +1 -1
  166. package/dist/core/sdk.js +5 -6
  167. package/dist/core/sdk.js.map +1 -1
  168. package/dist/core/session-failure-cause.d.ts.map +1 -1
  169. package/dist/core/session-failure-cause.js +14 -5
  170. package/dist/core/session-failure-cause.js.map +1 -1
  171. package/dist/core/session-prompt-lifecycle.d.ts +6 -0
  172. package/dist/core/session-prompt-lifecycle.d.ts.map +1 -1
  173. package/dist/core/session-prompt-lifecycle.js +47 -2
  174. package/dist/core/session-prompt-lifecycle.js.map +1 -1
  175. package/dist/core/session-termination.d.ts.map +1 -1
  176. package/dist/core/session-termination.js +4 -2
  177. package/dist/core/session-termination.js.map +1 -1
  178. package/dist/core/subagent-lane-authority.d.ts +28 -0
  179. package/dist/core/subagent-lane-authority.d.ts.map +1 -0
  180. package/dist/core/subagent-lane-authority.js +119 -0
  181. package/dist/core/subagent-lane-authority.js.map +1 -0
  182. package/dist/core/subagent-lane-contract.d.ts +114 -0
  183. package/dist/core/subagent-lane-contract.d.ts.map +1 -0
  184. package/dist/core/subagent-lane-contract.js +18 -0
  185. package/dist/core/subagent-lane-contract.js.map +1 -0
  186. package/dist/core/subagent-lane-launcher.d.ts +3 -6
  187. package/dist/core/subagent-lane-launcher.d.ts.map +1 -1
  188. package/dist/core/subagent-lane-launcher.js +53 -12
  189. package/dist/core/subagent-lane-launcher.js.map +1 -1
  190. package/dist/core/subagent-orchestration.d.ts +4 -17
  191. package/dist/core/subagent-orchestration.d.ts.map +1 -1
  192. package/dist/core/subagent-orchestration.js.map +1 -1
  193. package/dist/core/verified-run/broker.d.ts.map +1 -1
  194. package/dist/core/verified-run/broker.js +4 -1
  195. package/dist/core/verified-run/broker.js.map +1 -1
  196. package/dist/core/workload-permit-pool.d.ts +1 -1
  197. package/dist/core/workload-permit-pool.d.ts.map +1 -1
  198. package/dist/core/workload-permit-pool.js +3 -0
  199. package/dist/core/workload-permit-pool.js.map +1 -1
  200. package/dist/guardrails/strict-evidence-approval-adapter.d.ts +34 -0
  201. package/dist/guardrails/strict-evidence-approval-adapter.d.ts.map +1 -0
  202. package/dist/guardrails/strict-evidence-approval-adapter.js +70 -0
  203. package/dist/guardrails/strict-evidence-approval-adapter.js.map +1 -0
  204. package/dist/index.d.ts +3 -0
  205. package/dist/index.d.ts.map +1 -1
  206. package/dist/index.js +1 -0
  207. package/dist/index.js.map +1 -1
  208. package/dist/main.d.ts.map +1 -1
  209. package/dist/main.js +11 -18
  210. package/dist/main.js.map +1 -1
  211. package/dist/modes/acp/acp-agent.d.ts +24 -0
  212. package/dist/modes/acp/acp-agent.d.ts.map +1 -0
  213. package/dist/modes/acp/acp-agent.js +133 -0
  214. package/dist/modes/acp/acp-agent.js.map +1 -0
  215. package/dist/modes/acp/acp-mode.d.ts +4 -0
  216. package/dist/modes/acp/acp-mode.d.ts.map +1 -0
  217. package/dist/modes/acp/acp-mode.js +34 -0
  218. package/dist/modes/acp/acp-mode.js.map +1 -0
  219. package/dist/modes/acp/acp-session.d.ts +4 -0
  220. package/dist/modes/acp/acp-session.d.ts.map +1 -0
  221. package/dist/modes/acp/acp-session.js +76 -0
  222. package/dist/modes/acp/acp-session.js.map +1 -0
  223. package/dist/modes/acp/acp-transport.d.ts +5 -0
  224. package/dist/modes/acp/acp-transport.d.ts.map +1 -0
  225. package/dist/modes/acp/acp-transport.js +90 -0
  226. package/dist/modes/acp/acp-transport.js.map +1 -0
  227. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  228. package/dist/modes/interactive/interactive-mode.js +1 -0
  229. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  230. package/docs/audit-revalidation-2026-09-17.md +77 -0
  231. package/docs/devin-harness.md +33 -4
  232. package/docs/ecraf-normalization.md +83 -0
  233. package/docs/model-catalog-refresh.md +51 -1
  234. package/docs/models.md +1 -1
  235. package/docs/neo.md +134 -0
  236. package/docs/providers.md +72 -6
  237. package/docs/run-usage-ledger.md +56 -0
  238. package/docs/runtime-algorithms.md +44 -0
  239. package/docs/skills.md +1 -1
  240. package/docs/usage.md +2 -1
  241. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  242. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  243. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  244. package/examples/extensions/gondolin/package-lock.json +2 -2
  245. package/examples/extensions/gondolin/package.json +1 -1
  246. package/examples/extensions/sandbox/package-lock.json +2 -2
  247. package/examples/extensions/sandbox/package.json +1 -1
  248. package/examples/extensions/subagent/adaptive-agent-runtime.ts +14 -1
  249. package/examples/extensions/subagent/graph-result.ts +48 -0
  250. package/examples/extensions/subagent/index.ts +246 -111
  251. package/examples/extensions/subagent/managed-process-tree.ts +42 -0
  252. package/examples/extensions/subagent/managed-process.test.ts +24 -0
  253. package/examples/extensions/subagent/managed-process.ts +93 -112
  254. package/examples/extensions/subagent/subagent-runtime-types.ts +17 -1
  255. package/examples/extensions/subagent/subagent-stream.ts +161 -0
  256. package/examples/extensions/terminal-browser/README.md +54 -0
  257. package/examples/extensions/terminal-browser/bridge-protocol.ts +29 -0
  258. package/examples/extensions/terminal-browser/bridge.ts +219 -0
  259. package/examples/extensions/terminal-browser/browser-surface.ts +288 -0
  260. package/examples/extensions/terminal-browser/index.ts +215 -0
  261. package/examples/extensions/terminal-browser/placeholders.ts +49 -0
  262. package/examples/extensions/with-deps/package-lock.json +2 -2
  263. package/examples/extensions/with-deps/package.json +1 -1
  264. package/npm-shrinkwrap.json +18 -18
  265. package/package.json +8 -7
  266. package/resources/neo/skills/omk-browser/SKILL.md +32 -0
  267. package/resources/neo/skills/omk-code-review/SKILL.md +24 -0
  268. package/resources/neo/skills/omk-computeruse/SKILL.md +34 -0
  269. package/resources/neo/skills/omk-mcp-setup/SKILL.md +48 -0
  270. package/resources/neo/skills/omk-research/SKILL.md +24 -0
  271. package/resources/neo/skills/omk-site/SKILL.md +28 -0
@@ -136,4 +136,63 @@ function effectiveCap(configured, admission) {
136
136
  }
137
137
  return Math.max(MIN_CAP, Math.min(Math.floor(configured), admitted));
138
138
  }
139
+ /**
140
+ * Re-derive an admission decision's cap row for a stabilized pressure while
141
+ * keeping the decision's identity (id, digest, reasons, timestamp) — the
142
+ * applied policy is the smoothed one, but what it was decided against does
143
+ * not change. Pure apart from reading the cap table the caller supplies.
144
+ */
145
+ export function rederiveResourceAdmissionForPressure(decision, caps, configuredCaps, pressure) {
146
+ const tier = caps[pressure];
147
+ return {
148
+ ...decision,
149
+ pressure,
150
+ action: ACTION_BY_PRESSURE[pressure],
151
+ maxToolConcurrency: effectiveCap(configuredCaps?.maxToolConcurrency, tier.maxToolConcurrency),
152
+ maxParallelLanes: effectiveCap(configuredCaps?.maxParallelLanes, tier.maxParallelLanes),
153
+ maxHeavyProcesses: effectiveCap(configuredCaps?.maxHeavyProcesses, tier.maxHeavyProcesses),
154
+ };
155
+ }
156
+ /**
157
+ * Per-session pressure hysteresis (audit §16.1). The raw evaluator is
158
+ * stateless by contract — callers that apply its output on every probe would
159
+ * flap the effective cap around a threshold. This stabilizer is the explicit
160
+ * state: critical wins immediately, recovery is earned over consecutive
161
+ * healthier evaluations, and a missing/partial probe keeps the current level
162
+ * rather than silently relaxing it.
163
+ */
164
+ export function createResourcePressureStabilizer(options = {}) {
165
+ const recoverProbes = Math.max(1, Math.floor(options.recoverProbes ?? 2));
166
+ let stabilized = "normal";
167
+ let streak = 0;
168
+ let streakRank = PRESSURE_RANK.normal;
169
+ return {
170
+ get pressure() {
171
+ return stabilized;
172
+ },
173
+ observe(evaluation) {
174
+ const rank = PRESSURE_RANK[evaluation.pressure];
175
+ const stabilizedRank = PRESSURE_RANK[stabilized];
176
+ if (rank >= stabilizedRank) {
177
+ // Escalation (or same level) is immediate and resets the recovery
178
+ // streak — raw critical is never smoothed.
179
+ stabilized = RANK_TO_PRESSURE[rank] ?? "critical";
180
+ streak = 0;
181
+ streakRank = stabilizedRank;
182
+ return stabilized;
183
+ }
184
+ // De-escalation candidate: count consecutive evaluations strictly
185
+ // better than the stabilized level. Each rank recovers independently;
186
+ // a mid-level reading earns the streak but only drops one step.
187
+ streak = rank < streakRank ? 1 : streak + 1;
188
+ streakRank = rank;
189
+ if (streak >= recoverProbes) {
190
+ stabilized = RANK_TO_PRESSURE[stabilizedRank - 1] ?? "normal";
191
+ streak = 0;
192
+ streakRank = stabilizedRank - 1;
193
+ }
194
+ return stabilized;
195
+ },
196
+ };
197
+ }
139
198
  //# sourceMappingURL=resource-admission.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"resource-admission.js","sourceRoot":"","sources":["../../src/core/resource-admission.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport {\n\tcomputeHostResourceSnapshotDigest,\n\teffectiveAvailableMemoryBytes,\n\ttype HostResourceSnapshot,\n} from \"./host-resource-snapshot.ts\";\nimport {\n\tDEFAULT_RESOURCE_ADMISSION_CONFIG,\n\tDEFAULT_RESOURCE_ADMISSION_THRESHOLDS,\n\ttype ResourceAdmissionConfig,\n\ttype ResourceAdmissionThresholds,\n} from \"./resource-admission-config.ts\";\n\nexport type {\n\tResolvedResourceAdmissionConfig,\n\tResourceAdmissionCapTable,\n\tResourceAdmissionConfig,\n\tResourceAdmissionConfigOverrides,\n\tResourceAdmissionThresholds,\n\tResourceGovernorMode,\n\tResourcePressureCaps,\n} from \"./resource-admission-config.ts\";\nexport {\n\tDEFAULT_RESOURCE_ADMISSION_CAP_TABLE,\n\tDEFAULT_RESOURCE_ADMISSION_CONFIG,\n\tDEFAULT_RESOURCE_ADMISSION_THRESHOLDS,\n\tresolveResourceAdmissionConfig,\n\tvalidateResourceAdmissionConfig,\n} from \"./resource-admission-config.ts\";\n\n/**\n * Pure resource admission policy (OMK v0.97.x roadmap §6.6-§7, M1/PR1).\n *\n * This module maps an immutable {@link HostResourceSnapshot} to an immutable\n * {@link ResourceAdmissionDecision}. It is deterministic (clock and id are\n * injectable), side-effect free, and performs no scheduler wiring — the\n * decision only becomes scheduler authority in later milestones (M2+).\n *\n * Policy invariants (verified by property tests, roadmap §23.2):\n * - Monotonic degradation (§4.4): worse memory/disk/CPU never improves\n * pressure and never raises a cap.\n * - Conservative unknown handling (§4.3): a known critical fact wins; a\n * missing key probe (memory, disk, heap) degrades to `constrained`; only a\n * fully healthy snapshot is `normal`.\n * - CPU alone never produces `critical` (§6.7): CPU pressure throttles,\n * while memory/disk/heap may block heavy admission.\n * - User cap precedence (§7.3): effective caps never exceed configured caps,\n * and `0 = unlimited` settings are limited to the admission cap.\n */\n\nexport const RESOURCE_ADMISSION_VERSION = 1 as const;\n\nexport type ResourcePressure = \"normal\" | \"constrained\" | \"critical\";\n\nexport type ResourceAdmissionAction = \"allow\" | \"throttle\" | \"defer-heavy\";\n\nexport const RESOURCE_REASON_CODES = [\n\t\"resource.memory.low\",\n\t\"resource.memory.critical\",\n\t\"resource.disk.low\",\n\t\"resource.disk.critical\",\n\t\"resource.cpu.busy\",\n\t\"resource.heap.high\",\n\t\"resource.heap.critical\",\n\t\"resource.probe.partial\",\n\t\"resource.probe.timeout\",\n] as const;\n\nexport type ResourceReasonCode = (typeof RESOURCE_REASON_CODES)[number];\n\nexport interface ResourceAdmissionDecision {\n\treadonly schemaVersion: typeof RESOURCE_ADMISSION_VERSION;\n\treadonly decisionId: string;\n\treadonly snapshotDigest: string;\n\treadonly pressure: ResourcePressure;\n\treadonly action: ResourceAdmissionAction;\n\n\treadonly maxToolConcurrency: number;\n\treadonly maxParallelLanes: number;\n\treadonly maxHeavyProcesses: number;\n\n\treadonly reasons: readonly ResourceReasonCode[];\n\treadonly decidedAt: string;\n}\n\n/** Bounded model-facing hint (§6.2). Never expose the raw snapshot to the model. */\nexport interface ModelResourceBudgetHint {\n\treadonly pressure: ResourcePressure;\n\treadonly maxToolConcurrency: number;\n\treadonly maxParallelLanes: number;\n\treadonly maxHeavyProcesses: number;\n\treadonly reasons: readonly ResourceReasonCode[];\n}\n\nconst MIB = 1024 * 1024;\nconst MIN_CAP = 1;\nconst PRESSURE_RANK: Record<ResourcePressure, number> = { normal: 0, constrained: 1, critical: 2 };\nconst RANK_TO_PRESSURE: readonly ResourcePressure[] = [\"normal\", \"constrained\", \"critical\"];\n/** §7 action mapping: allow at normal, throttle at constrained, defer heavy work at critical. */\nconst ACTION_BY_PRESSURE: Record<ResourcePressure, ResourceAdmissionAction> = {\n\tnormal: \"allow\",\n\tconstrained: \"throttle\",\n\tcritical: \"defer-heavy\",\n};\n\n/** Total order for pressure levels: `normal` (0) < `constrained` (1) < `critical` (2). */\nexport function resourcePressureRank(pressure: ResourcePressure): number {\n\treturn PRESSURE_RANK[pressure];\n}\n\nexport interface ResourcePressureEvaluation {\n\treadonly pressure: ResourcePressure;\n\treadonly reasons: readonly ResourceReasonCode[];\n}\n\n/**\n * Derive pressure and reason codes from a snapshot (§4.3, §6.6, §6.7).\n * Pure and monotonic: strictly worse inputs never yield a better pressure.\n */\nexport function evaluateResourcePressure(\n\tsnapshot: HostResourceSnapshot,\n\tthresholds: ResourceAdmissionThresholds = DEFAULT_RESOURCE_ADMISSION_THRESHOLDS,\n): ResourcePressureEvaluation {\n\tconst reasons: ResourceReasonCode[] = [];\n\tlet rank = PRESSURE_RANK.normal;\n\tconst escalate = (target: ResourcePressure, reason: ResourceReasonCode): void => {\n\t\treasons.push(reason);\n\t\trank = Math.max(rank, PRESSURE_RANK[target]);\n\t};\n\n\tconst memory = effectiveAvailableMemoryBytes(snapshot);\n\tif (memory !== null) {\n\t\tif (memory < thresholds.criticalAvailableMemoryMiB * MIB) {\n\t\t\tescalate(\"critical\", \"resource.memory.critical\");\n\t\t} else if (memory < thresholds.constrainedAvailableMemoryMiB * MIB) {\n\t\t\tescalate(\"constrained\", \"resource.memory.low\");\n\t\t}\n\t}\n\n\tconst disk = snapshot.workspaceAvailableBytes;\n\tif (disk !== null) {\n\t\tif (disk < thresholds.criticalDiskFreeMiB * MIB) {\n\t\t\tescalate(\"critical\", \"resource.disk.critical\");\n\t\t} else if (disk < thresholds.constrainedDiskFreeMiB * MIB) {\n\t\t\tescalate(\"constrained\", \"resource.disk.low\");\n\t\t}\n\t}\n\n\tconst heapRatio = snapshot.heapLimitBytes > 0 ? snapshot.heapUsedBytes / snapshot.heapLimitBytes : null;\n\tif (heapRatio !== null) {\n\t\tif (heapRatio >= thresholds.criticalHeapRatio) {\n\t\t\tescalate(\"critical\", \"resource.heap.critical\");\n\t\t} else if (heapRatio >= thresholds.constrainedHeapRatio) {\n\t\t\tescalate(\"constrained\", \"resource.heap.high\");\n\t\t}\n\t}\n\n\t// §6.7: CPU pressure only throttles; it can never be critical on its own.\n\tif (snapshot.systemCpuPercent !== null && snapshot.systemCpuPercent >= thresholds.busyCpuPercent) {\n\t\tescalate(\"constrained\", \"resource.cpu.busy\");\n\t}\n\n\t// §4.3 conservative unknown handling. Key probes are the heavy-admission\n\t// authorities (memory, disk, heap); a missing one degrades to constrained.\n\tif (memory === null || disk === null || heapRatio === null) {\n\t\tescalate(\"constrained\", \"resource.probe.partial\");\n\t}\n\t// §21: any probe timeout keeps the prompt going but never reports normal.\n\tif (snapshot.diagnostics.some((code) => code.endsWith(\".timeout\"))) {\n\t\tescalate(\"constrained\", \"resource.probe.timeout\");\n\t}\n\n\treturn { pressure: RANK_TO_PRESSURE[rank] ?? \"critical\", reasons };\n}\n\n/**\n * Existing user settings that admission must never exceed (§7.3).\n * `0` or `undefined` means \"unlimited\" and is limited to the admission cap.\n */\nexport interface ConfiguredResourceCaps {\n\treadonly maxToolConcurrency?: number;\n\treadonly maxParallelLanes?: number;\n\treadonly maxHeavyProcesses?: number;\n}\n\nexport interface ResourceAdmissionInput {\n\treadonly snapshot: HostResourceSnapshot;\n\treadonly config?: ResourceAdmissionConfig;\n\treadonly configuredCaps?: ConfiguredResourceCaps;\n\t/** Injectable for deterministic tests; defaults to a UUID-based id. */\n\treadonly decisionId?: string;\n\t/** Injectable ISO timestamp; defaults to the current time. */\n\treadonly decidedAt?: string;\n}\n\n/** Map a snapshot to an immutable admission decision (§7.1-§7.3). */\nexport function decideResourceAdmission(input: ResourceAdmissionInput): ResourceAdmissionDecision {\n\tconst config = input.config ?? DEFAULT_RESOURCE_ADMISSION_CONFIG;\n\tconst { pressure, reasons } = evaluateResourcePressure(input.snapshot, config.thresholds);\n\tconst tier = config.caps[pressure];\n\treturn {\n\t\tschemaVersion: RESOURCE_ADMISSION_VERSION,\n\t\tdecisionId: input.decisionId ?? `res-adm-${randomUUID()}`,\n\t\tsnapshotDigest: computeHostResourceSnapshotDigest(input.snapshot),\n\t\tpressure,\n\t\taction: ACTION_BY_PRESSURE[pressure],\n\t\tmaxToolConcurrency: effectiveCap(input.configuredCaps?.maxToolConcurrency, tier.maxToolConcurrency),\n\t\tmaxParallelLanes: effectiveCap(input.configuredCaps?.maxParallelLanes, tier.maxParallelLanes),\n\t\tmaxHeavyProcesses: effectiveCap(input.configuredCaps?.maxHeavyProcesses, tier.maxHeavyProcesses),\n\t\treasons,\n\t\tdecidedAt: input.decidedAt ?? new Date().toISOString(),\n\t};\n}\n\n/** Bounded hint for the model (§6.2). Carries no raw host values. */\nexport function toModelResourceBudgetHint(decision: ResourceAdmissionDecision): ModelResourceBudgetHint {\n\treturn {\n\t\tpressure: decision.pressure,\n\t\tmaxToolConcurrency: decision.maxToolConcurrency,\n\t\tmaxParallelLanes: decision.maxParallelLanes,\n\t\tmaxHeavyProcesses: decision.maxHeavyProcesses,\n\t\treasons: decision.reasons,\n\t};\n}\n\n/** §7.3: `min(configured, admission)`, where configured `0`/absent means unlimited. */\nfunction effectiveCap(configured: number | undefined, admission: number): number {\n\tconst admitted = Math.max(MIN_CAP, Math.floor(admission));\n\tif (typeof configured !== \"number\" || !Number.isFinite(configured) || configured <= 0) {\n\t\treturn admitted;\n\t}\n\treturn Math.max(MIN_CAP, Math.min(Math.floor(configured), admitted));\n}\n"]}
1
+ {"version":3,"file":"resource-admission.js","sourceRoot":"","sources":["../../src/core/resource-admission.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport {\n\tcomputeHostResourceSnapshotDigest,\n\teffectiveAvailableMemoryBytes,\n\ttype HostResourceSnapshot,\n} from \"./host-resource-snapshot.ts\";\nimport {\n\tDEFAULT_RESOURCE_ADMISSION_CONFIG,\n\tDEFAULT_RESOURCE_ADMISSION_THRESHOLDS,\n\ttype ResourceAdmissionConfig,\n\ttype ResourceAdmissionThresholds,\n} from \"./resource-admission-config.ts\";\n\nexport type {\n\tResolvedResourceAdmissionConfig,\n\tResourceAdmissionCapTable,\n\tResourceAdmissionConfig,\n\tResourceAdmissionConfigOverrides,\n\tResourceAdmissionThresholds,\n\tResourceGovernorMode,\n\tResourcePressureCaps,\n} from \"./resource-admission-config.ts\";\nexport {\n\tDEFAULT_RESOURCE_ADMISSION_CAP_TABLE,\n\tDEFAULT_RESOURCE_ADMISSION_CONFIG,\n\tDEFAULT_RESOURCE_ADMISSION_THRESHOLDS,\n\tresolveResourceAdmissionConfig,\n\tvalidateResourceAdmissionConfig,\n} from \"./resource-admission-config.ts\";\n\n/**\n * Pure resource admission policy (OMK v0.97.x roadmap §6.6-§7, M1/PR1).\n *\n * This module maps an immutable {@link HostResourceSnapshot} to an immutable\n * {@link ResourceAdmissionDecision}. It is deterministic (clock and id are\n * injectable), side-effect free, and performs no scheduler wiring — the\n * decision only becomes scheduler authority in later milestones (M2+).\n *\n * Policy invariants (verified by property tests, roadmap §23.2):\n * - Monotonic degradation (§4.4): worse memory/disk/CPU never improves\n * pressure and never raises a cap.\n * - Conservative unknown handling (§4.3): a known critical fact wins; a\n * missing key probe (memory, disk, heap) degrades to `constrained`; only a\n * fully healthy snapshot is `normal`.\n * - CPU alone never produces `critical` (§6.7): CPU pressure throttles,\n * while memory/disk/heap may block heavy admission.\n * - User cap precedence (§7.3): effective caps never exceed configured caps,\n * and `0 = unlimited` settings are limited to the admission cap.\n */\n\nexport const RESOURCE_ADMISSION_VERSION = 1 as const;\n\nexport type ResourcePressure = \"normal\" | \"constrained\" | \"critical\";\n\nexport type ResourceAdmissionAction = \"allow\" | \"throttle\" | \"defer-heavy\";\n\nexport const RESOURCE_REASON_CODES = [\n\t\"resource.memory.low\",\n\t\"resource.memory.critical\",\n\t\"resource.disk.low\",\n\t\"resource.disk.critical\",\n\t\"resource.cpu.busy\",\n\t\"resource.heap.high\",\n\t\"resource.heap.critical\",\n\t\"resource.probe.partial\",\n\t\"resource.probe.timeout\",\n] as const;\n\nexport type ResourceReasonCode = (typeof RESOURCE_REASON_CODES)[number];\n\nexport interface ResourceAdmissionDecision {\n\treadonly schemaVersion: typeof RESOURCE_ADMISSION_VERSION;\n\treadonly decisionId: string;\n\treadonly snapshotDigest: string;\n\treadonly pressure: ResourcePressure;\n\treadonly action: ResourceAdmissionAction;\n\n\treadonly maxToolConcurrency: number;\n\treadonly maxParallelLanes: number;\n\treadonly maxHeavyProcesses: number;\n\n\treadonly reasons: readonly ResourceReasonCode[];\n\treadonly decidedAt: string;\n}\n\n/** Bounded model-facing hint (§6.2). Never expose the raw snapshot to the model. */\nexport interface ModelResourceBudgetHint {\n\treadonly pressure: ResourcePressure;\n\treadonly maxToolConcurrency: number;\n\treadonly maxParallelLanes: number;\n\treadonly maxHeavyProcesses: number;\n\treadonly reasons: readonly ResourceReasonCode[];\n}\n\nconst MIB = 1024 * 1024;\nconst MIN_CAP = 1;\nconst PRESSURE_RANK: Record<ResourcePressure, number> = { normal: 0, constrained: 1, critical: 2 };\nconst RANK_TO_PRESSURE: readonly ResourcePressure[] = [\"normal\", \"constrained\", \"critical\"];\n/** §7 action mapping: allow at normal, throttle at constrained, defer heavy work at critical. */\nconst ACTION_BY_PRESSURE: Record<ResourcePressure, ResourceAdmissionAction> = {\n\tnormal: \"allow\",\n\tconstrained: \"throttle\",\n\tcritical: \"defer-heavy\",\n};\n\n/** Total order for pressure levels: `normal` (0) < `constrained` (1) < `critical` (2). */\nexport function resourcePressureRank(pressure: ResourcePressure): number {\n\treturn PRESSURE_RANK[pressure];\n}\n\nexport interface ResourcePressureEvaluation {\n\treadonly pressure: ResourcePressure;\n\treadonly reasons: readonly ResourceReasonCode[];\n}\n\n/**\n * Derive pressure and reason codes from a snapshot (§4.3, §6.6, §6.7).\n * Pure and monotonic: strictly worse inputs never yield a better pressure.\n */\nexport function evaluateResourcePressure(\n\tsnapshot: HostResourceSnapshot,\n\tthresholds: ResourceAdmissionThresholds = DEFAULT_RESOURCE_ADMISSION_THRESHOLDS,\n): ResourcePressureEvaluation {\n\tconst reasons: ResourceReasonCode[] = [];\n\tlet rank = PRESSURE_RANK.normal;\n\tconst escalate = (target: ResourcePressure, reason: ResourceReasonCode): void => {\n\t\treasons.push(reason);\n\t\trank = Math.max(rank, PRESSURE_RANK[target]);\n\t};\n\n\tconst memory = effectiveAvailableMemoryBytes(snapshot);\n\tif (memory !== null) {\n\t\tif (memory < thresholds.criticalAvailableMemoryMiB * MIB) {\n\t\t\tescalate(\"critical\", \"resource.memory.critical\");\n\t\t} else if (memory < thresholds.constrainedAvailableMemoryMiB * MIB) {\n\t\t\tescalate(\"constrained\", \"resource.memory.low\");\n\t\t}\n\t}\n\n\tconst disk = snapshot.workspaceAvailableBytes;\n\tif (disk !== null) {\n\t\tif (disk < thresholds.criticalDiskFreeMiB * MIB) {\n\t\t\tescalate(\"critical\", \"resource.disk.critical\");\n\t\t} else if (disk < thresholds.constrainedDiskFreeMiB * MIB) {\n\t\t\tescalate(\"constrained\", \"resource.disk.low\");\n\t\t}\n\t}\n\n\tconst heapRatio = snapshot.heapLimitBytes > 0 ? snapshot.heapUsedBytes / snapshot.heapLimitBytes : null;\n\tif (heapRatio !== null) {\n\t\tif (heapRatio >= thresholds.criticalHeapRatio) {\n\t\t\tescalate(\"critical\", \"resource.heap.critical\");\n\t\t} else if (heapRatio >= thresholds.constrainedHeapRatio) {\n\t\t\tescalate(\"constrained\", \"resource.heap.high\");\n\t\t}\n\t}\n\n\t// §6.7: CPU pressure only throttles; it can never be critical on its own.\n\tif (snapshot.systemCpuPercent !== null && snapshot.systemCpuPercent >= thresholds.busyCpuPercent) {\n\t\tescalate(\"constrained\", \"resource.cpu.busy\");\n\t}\n\n\t// §4.3 conservative unknown handling. Key probes are the heavy-admission\n\t// authorities (memory, disk, heap); a missing one degrades to constrained.\n\tif (memory === null || disk === null || heapRatio === null) {\n\t\tescalate(\"constrained\", \"resource.probe.partial\");\n\t}\n\t// §21: any probe timeout keeps the prompt going but never reports normal.\n\tif (snapshot.diagnostics.some((code) => code.endsWith(\".timeout\"))) {\n\t\tescalate(\"constrained\", \"resource.probe.timeout\");\n\t}\n\n\treturn { pressure: RANK_TO_PRESSURE[rank] ?? \"critical\", reasons };\n}\n\n/**\n * Existing user settings that admission must never exceed (§7.3).\n * `0` or `undefined` means \"unlimited\" and is limited to the admission cap.\n */\nexport interface ConfiguredResourceCaps {\n\treadonly maxToolConcurrency?: number;\n\treadonly maxParallelLanes?: number;\n\treadonly maxHeavyProcesses?: number;\n}\n\nexport interface ResourceAdmissionInput {\n\treadonly snapshot: HostResourceSnapshot;\n\treadonly config?: ResourceAdmissionConfig;\n\treadonly configuredCaps?: ConfiguredResourceCaps;\n\t/** Injectable for deterministic tests; defaults to a UUID-based id. */\n\treadonly decisionId?: string;\n\t/** Injectable ISO timestamp; defaults to the current time. */\n\treadonly decidedAt?: string;\n}\n\n/** Map a snapshot to an immutable admission decision (§7.1-§7.3). */\nexport function decideResourceAdmission(input: ResourceAdmissionInput): ResourceAdmissionDecision {\n\tconst config = input.config ?? DEFAULT_RESOURCE_ADMISSION_CONFIG;\n\tconst { pressure, reasons } = evaluateResourcePressure(input.snapshot, config.thresholds);\n\tconst tier = config.caps[pressure];\n\treturn {\n\t\tschemaVersion: RESOURCE_ADMISSION_VERSION,\n\t\tdecisionId: input.decisionId ?? `res-adm-${randomUUID()}`,\n\t\tsnapshotDigest: computeHostResourceSnapshotDigest(input.snapshot),\n\t\tpressure,\n\t\taction: ACTION_BY_PRESSURE[pressure],\n\t\tmaxToolConcurrency: effectiveCap(input.configuredCaps?.maxToolConcurrency, tier.maxToolConcurrency),\n\t\tmaxParallelLanes: effectiveCap(input.configuredCaps?.maxParallelLanes, tier.maxParallelLanes),\n\t\tmaxHeavyProcesses: effectiveCap(input.configuredCaps?.maxHeavyProcesses, tier.maxHeavyProcesses),\n\t\treasons,\n\t\tdecidedAt: input.decidedAt ?? new Date().toISOString(),\n\t};\n}\n\n/** Bounded hint for the model (§6.2). Carries no raw host values. */\nexport function toModelResourceBudgetHint(decision: ResourceAdmissionDecision): ModelResourceBudgetHint {\n\treturn {\n\t\tpressure: decision.pressure,\n\t\tmaxToolConcurrency: decision.maxToolConcurrency,\n\t\tmaxParallelLanes: decision.maxParallelLanes,\n\t\tmaxHeavyProcesses: decision.maxHeavyProcesses,\n\t\treasons: decision.reasons,\n\t};\n}\n\n/** §7.3: `min(configured, admission)`, where configured `0`/absent means unlimited. */\nfunction effectiveCap(configured: number | undefined, admission: number): number {\n\tconst admitted = Math.max(MIN_CAP, Math.floor(admission));\n\tif (typeof configured !== \"number\" || !Number.isFinite(configured) || configured <= 0) {\n\t\treturn admitted;\n\t}\n\treturn Math.max(MIN_CAP, Math.min(Math.floor(configured), admitted));\n}\n\n/**\n * Re-derive an admission decision's cap row for a stabilized pressure while\n * keeping the decision's identity (id, digest, reasons, timestamp) — the\n * applied policy is the smoothed one, but what it was decided against does\n * not change. Pure apart from reading the cap table the caller supplies.\n */\nexport function rederiveResourceAdmissionForPressure(\n\tdecision: ResourceAdmissionDecision,\n\tcaps: ResourceAdmissionConfig[\"caps\"],\n\tconfiguredCaps: ConfiguredResourceCaps | undefined,\n\tpressure: ResourcePressure,\n): ResourceAdmissionDecision {\n\tconst tier = caps[pressure];\n\treturn {\n\t\t...decision,\n\t\tpressure,\n\t\taction: ACTION_BY_PRESSURE[pressure],\n\t\tmaxToolConcurrency: effectiveCap(configuredCaps?.maxToolConcurrency, tier.maxToolConcurrency),\n\t\tmaxParallelLanes: effectiveCap(configuredCaps?.maxParallelLanes, tier.maxParallelLanes),\n\t\tmaxHeavyProcesses: effectiveCap(configuredCaps?.maxHeavyProcesses, tier.maxHeavyProcesses),\n\t};\n}\n\nexport interface ResourcePressureStabilizerOptions {\n\t/**\n\t * Consecutive de-escalating evaluations required before the stabilized\n\t * pressure may improve one step. Default 2: a single healthy reading at a\n\t * threshold boundary cannot flap the cap back up.\n\t */\n\treadonly recoverProbes?: number;\n}\n\nexport interface ResourcePressureStabilizer {\n\t/** Current stabilized pressure. */\n\treadonly pressure: ResourcePressure;\n\t/**\n\t * Feed one raw evaluation and get the stabilized pressure. Escalation is\n\t * immediate (a critical memory/disk signal never waits for a streak);\n\t * de-escalation requires `recoverProbes` consecutive evaluations each\n\t * strictly better than the stabilized level, then improves one rank at a\n\t * time.\n\t */\n\tobserve(evaluation: ResourcePressureEvaluation): ResourcePressure;\n}\n\n/**\n * Per-session pressure hysteresis (audit §16.1). The raw evaluator is\n * stateless by contract — callers that apply its output on every probe would\n * flap the effective cap around a threshold. This stabilizer is the explicit\n * state: critical wins immediately, recovery is earned over consecutive\n * healthier evaluations, and a missing/partial probe keeps the current level\n * rather than silently relaxing it.\n */\nexport function createResourcePressureStabilizer(\n\toptions: ResourcePressureStabilizerOptions = {},\n): ResourcePressureStabilizer {\n\tconst recoverProbes = Math.max(1, Math.floor(options.recoverProbes ?? 2));\n\tlet stabilized: ResourcePressure = \"normal\";\n\tlet streak = 0;\n\tlet streakRank = PRESSURE_RANK.normal;\n\n\treturn {\n\t\tget pressure() {\n\t\t\treturn stabilized;\n\t\t},\n\t\tobserve(evaluation) {\n\t\t\tconst rank = PRESSURE_RANK[evaluation.pressure];\n\t\t\tconst stabilizedRank = PRESSURE_RANK[stabilized];\n\t\t\tif (rank >= stabilizedRank) {\n\t\t\t\t// Escalation (or same level) is immediate and resets the recovery\n\t\t\t\t// streak — raw critical is never smoothed.\n\t\t\t\tstabilized = RANK_TO_PRESSURE[rank] ?? \"critical\";\n\t\t\t\tstreak = 0;\n\t\t\t\tstreakRank = stabilizedRank;\n\t\t\t\treturn stabilized;\n\t\t\t}\n\t\t\t// De-escalation candidate: count consecutive evaluations strictly\n\t\t\t// better than the stabilized level. Each rank recovers independently;\n\t\t\t// a mid-level reading earns the streak but only drops one step.\n\t\t\tstreak = rank < streakRank ? 1 : streak + 1;\n\t\t\tstreakRank = rank;\n\t\t\tif (streak >= recoverProbes) {\n\t\t\t\tstabilized = RANK_TO_PRESSURE[stabilizedRank - 1] ?? \"normal\";\n\t\t\t\tstreak = 0;\n\t\t\t\tstreakRank = stabilizedRank - 1;\n\t\t\t}\n\t\t\treturn stabilized;\n\t\t},\n\t};\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"resource-loader.d.ts","sourceRoot":"","sources":["../../src/core/resource-loader.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { join, resolve, sep } from \"node:path\";\nimport chalk from \"chalk\";\nimport { CONFIG_DIR_NAME } from \"../config.ts\";\nimport { loadThemeFromPath, type Theme } from \"../modes/interactive/theme/theme.ts\";\nimport type { ContextFile } from \"./context-file.ts\";\nimport type { ResourceDiagnostic } from \"./diagnostics.ts\";\n\nexport type { ResourceCollision, ResourceDiagnostic } from \"./diagnostics.ts\";\n\nimport { canonicalizePath, isLocalPath, resolvePath } from \"../utils/paths.ts\";\nimport { createEventBus, type EventBus } from \"./event-bus.ts\";\nimport commandSafetyGate from \"./extensions/builtin/command-safety-gate.ts\";\nimport goalController from \"./extensions/builtin/goal-controller.ts\";\nimport identicalLoop from \"./extensions/builtin/identical-loop.ts\";\nimport promptPreset from \"./extensions/builtin/prompt-preset.ts\";\nimport todoChecklist from \"./extensions/builtin/todo-checklist.ts\";\nimport toolPairRepair from \"./extensions/builtin/tool-pair-repair.ts\";\nimport { createExtensionRuntime, loadExtensionFromFactory, loadExtensions } from \"./extensions/loader.ts\";\nimport type { Extension, ExtensionFactory, ExtensionRuntime, LoadExtensionsResult } from \"./extensions/types.ts\";\nimport { DefaultPackageManager, type PathMetadata } from \"./package-manager.ts\";\nimport type { PromptTemplate } from \"./prompt-templates.ts\";\nimport { loadPromptTemplates } from \"./prompt-templates.ts\";\nimport { SettingsManager } from \"./settings-manager.ts\";\nimport type { Skill } from \"./skills.ts\";\nimport { loadSkills } from \"./skills.ts\";\nimport { createSourceInfo, type SourceInfo } from \"./source-info.ts\";\n\n/** Values that turn a built-in extension off via its environment variable. */\nconst DISABLED_ENV_VALUES = new Set([\"0\", \"false\", \"off\", \"disable\", \"disabled\"]);\n\n/** `true` when an env var is explicitly set to a disabling value. Unset means enabled. */\nfunction isDisabledByEnv(value: string | undefined): boolean {\n\treturn value !== undefined && DISABLED_ENV_VALUES.has(value.trim().toLowerCase());\n}\n\nexport interface ResourceExtensionPaths {\n\tskillPaths?: Array<{ path: string; metadata: PathMetadata }>;\n\tpromptPaths?: Array<{ path: string; metadata: PathMetadata }>;\n\tthemePaths?: Array<{ path: string; metadata: PathMetadata }>;\n}\n\nexport interface ResourceLoader {\n\tgetExtensions(): LoadExtensionsResult;\n\tgetSkills(): { skills: Skill[]; diagnostics: ResourceDiagnostic[] };\n\tgetPrompts(): { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] };\n\tgetThemes(): { themes: Theme[]; diagnostics: ResourceDiagnostic[] };\n\tgetAgentsFiles(): { agentsFiles: ContextFile[] };\n\tgetSystemPrompt(): string | undefined;\n\tgetAppendSystemPrompt(): string[];\n\textendResources(paths: ResourceExtensionPaths): void;\n\treload(): Promise<void>;\n}\n\nfunction hasLegacyPathSegment(path: string): boolean {\n\treturn path.split(/[\\\\/]+/).some((segment) => segment.includes(\".legacy.\"));\n}\n\nfunction isLegacyAutoSkillResource(resource: { path: string; metadata: PathMetadata }): boolean {\n\treturn (\n\t\tresource.metadata.source === \"auto\" &&\n\t\tresource.metadata.origin === \"top-level\" &&\n\t\thasLegacyPathSegment(resource.path)\n\t);\n}\n\nfunction resolvePromptInput(input: string | undefined, description: string): string | undefined {\n\tif (!input) {\n\t\treturn undefined;\n\t}\n\n\tif (existsSync(input)) {\n\t\ttry {\n\t\t\treturn readFileSync(input, \"utf-8\");\n\t\t} catch (error) {\n\t\t\tconsole.error(chalk.yellow(`Warning: Could not read ${description} file ${input}: ${error}`));\n\t\t\treturn input;\n\t\t}\n\t}\n\n\treturn input;\n}\n\nfunction loadContextFileFromDir(dir: string): ContextFile | null {\n\tconst candidates = [\"AGENTS.md\", \"AGENTS.MD\", \"CLAUDE.md\", \"CLAUDE.MD\"];\n\tfor (const filename of candidates) {\n\t\tconst filePath = join(dir, filename);\n\t\tif (existsSync(filePath)) {\n\t\t\ttry {\n\t\t\t\treturn {\n\t\t\t\t\tpath: filePath,\n\t\t\t\t\tcontent: readFileSync(filePath, \"utf-8\"),\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(chalk.yellow(`Warning: Could not read ${filePath}: ${error}`));\n\t\t\t}\n\t\t}\n\t}\n\treturn null;\n}\n\nfunction loadOverrideFilesFromDir(dir: string): ContextFile[] {\n\tconst candidates = [\"AGENTS.override.md\", \"AGENTS.GODMODE.md\", \"GODMODE.md\", \"ENI.override.md\"];\n\tconst results: ContextFile[] = [];\n\tfor (const filename of candidates) {\n\t\tconst filePath = join(dir, filename);\n\t\tif (existsSync(filePath)) {\n\t\t\ttry {\n\t\t\t\tresults.push({\n\t\t\t\t\tpath: filePath,\n\t\t\t\t\tcontent: readFileSync(filePath, \"utf-8\"),\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(chalk.yellow(`Warning: Could not read ${filePath}: ${error}`));\n\t\t\t}\n\t\t}\n\t}\n\treturn results;\n}\n\nfunction isGlobalContextFile(file: ContextFile, agentDir: string): boolean {\n\tconst resolvedAgentDir = resolvePath(agentDir);\n\tconst normalizedPath = resolve(file.path);\n\tconst parentDir = resolve(normalizedPath, \"..\");\n\treturn parentDir === resolvedAgentDir;\n}\n\nexport function loadProjectContextFiles(options: { cwd: string; agentDir: string }): ContextFile[] {\n\tconst resolvedCwd = resolvePath(options.cwd);\n\tconst resolvedAgentDir = resolvePath(options.agentDir);\n\n\tconst contextFiles: ContextFile[] = [];\n\tconst seenPaths = new Set<string>();\n\n\tconst globalContext = loadContextFileFromDir(resolvedAgentDir);\n\tif (globalContext) {\n\t\tcontextFiles.push({ ...globalContext, isGlobal: true });\n\t\tseenPaths.add(globalContext.path);\n\t}\n\n\tconst globalOverrides = loadOverrideFilesFromDir(resolvedAgentDir);\n\tfor (const override of globalOverrides) {\n\t\tif (!seenPaths.has(override.path)) {\n\t\t\tcontextFiles.push({ ...override, isGlobal: true });\n\t\t\tseenPaths.add(override.path);\n\t\t}\n\t}\n\n\tconst ancestorContextFiles: ContextFile[] = [];\n\n\tlet currentDir = resolvedCwd;\n\tconst root = resolve(\"/\");\n\n\twhile (true) {\n\t\tconst contextFile = loadContextFileFromDir(currentDir);\n\t\tif (contextFile && !seenPaths.has(contextFile.path)) {\n\t\t\tancestorContextFiles.unshift({\n\t\t\t\t...contextFile,\n\t\t\t\tisGlobal: isGlobalContextFile(contextFile, resolvedAgentDir),\n\t\t\t});\n\t\t\tseenPaths.add(contextFile.path);\n\t\t}\n\n\t\tconst dirOverrides = loadOverrideFilesFromDir(currentDir);\n\t\tfor (const override of dirOverrides) {\n\t\t\tif (!seenPaths.has(override.path)) {\n\t\t\t\tancestorContextFiles.unshift({\n\t\t\t\t\t...override,\n\t\t\t\t\tisGlobal: isGlobalContextFile(override, resolvedAgentDir),\n\t\t\t\t});\n\t\t\t\tseenPaths.add(override.path);\n\t\t\t}\n\t\t}\n\n\t\tif (currentDir === root) break;\n\n\t\tconst parentDir = resolve(currentDir, \"..\");\n\t\tif (parentDir === currentDir) break;\n\t\tcurrentDir = parentDir;\n\t}\n\n\tcontextFiles.push(...ancestorContextFiles);\n\n\treturn contextFiles;\n}\n\nexport interface DefaultResourceLoaderOptions {\n\tcwd: string;\n\tagentDir: string;\n\tsettingsManager?: SettingsManager;\n\teventBus?: EventBus;\n\tadditionalExtensionPaths?: string[];\n\tadditionalSkillPaths?: string[];\n\tadditionalPromptTemplatePaths?: string[];\n\tadditionalThemePaths?: string[];\n\textensionFactories?: ExtensionFactory[];\n\tnoExtensions?: boolean;\n\tnoSkills?: boolean;\n\tnoPromptTemplates?: boolean;\n\tnoThemes?: boolean;\n\tnoContextFiles?: boolean;\n\tsystemPrompt?: string;\n\tappendSystemPrompt?: string[];\n\textensionsOverride?: (base: LoadExtensionsResult) => LoadExtensionsResult;\n\tskillsOverride?: (base: { skills: Skill[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tskills: Skill[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tpromptsOverride?: (base: { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tprompts: PromptTemplate[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tthemesOverride?: (base: { themes: Theme[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tthemes: Theme[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tagentsFilesOverride?: (base: { agentsFiles: ContextFile[] }) => {\n\t\tagentsFiles: ContextFile[];\n\t};\n\tsystemPromptOverride?: (base: string | undefined) => string | undefined;\n\tappendSystemPromptOverride?: (base: string[]) => string[];\n}\n\nexport class DefaultResourceLoader implements ResourceLoader {\n\tprivate cwd: string;\n\tprivate agentDir: string;\n\tprivate settingsManager: SettingsManager;\n\tprivate eventBus: EventBus;\n\tprivate packageManager: DefaultPackageManager;\n\tprivate additionalExtensionPaths: string[];\n\tprivate additionalSkillPaths: string[];\n\tprivate additionalPromptTemplatePaths: string[];\n\tprivate additionalThemePaths: string[];\n\tprivate extensionFactories: ExtensionFactory[];\n\tprivate noExtensions: boolean;\n\tprivate noSkills: boolean;\n\tprivate noPromptTemplates: boolean;\n\tprivate noThemes: boolean;\n\tprivate noContextFiles: boolean;\n\tprivate systemPromptSource?: string;\n\tprivate appendSystemPromptSource?: string[];\n\tprivate extensionsOverride?: (base: LoadExtensionsResult) => LoadExtensionsResult;\n\tprivate skillsOverride?: (base: { skills: Skill[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tskills: Skill[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tprivate promptsOverride?: (base: { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tprompts: PromptTemplate[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tprivate themesOverride?: (base: { themes: Theme[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tthemes: Theme[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tprivate agentsFilesOverride?: (base: { agentsFiles: ContextFile[] }) => {\n\t\tagentsFiles: ContextFile[];\n\t};\n\tprivate systemPromptOverride?: (base: string | undefined) => string | undefined;\n\tprivate appendSystemPromptOverride?: (base: string[]) => string[];\n\n\tprivate extensionsResult: LoadExtensionsResult;\n\t/** Extensions from the most recent load (pre-override), tracked so their event-bus subscriptions can be disposed on reload. */\n\tprivate loadedExtensions: Extension[];\n\tprivate skills: Skill[];\n\tprivate skillDiagnostics: ResourceDiagnostic[];\n\tprivate prompts: PromptTemplate[];\n\tprivate promptDiagnostics: ResourceDiagnostic[];\n\tprivate themes: Theme[];\n\tprivate themeDiagnostics: ResourceDiagnostic[];\n\tprivate agentsFiles: ContextFile[];\n\tprivate systemPrompt?: string;\n\tprivate appendSystemPrompt: string[];\n\tprivate lastSkillPaths: string[];\n\tprivate resourceMetadataByPath: Map<string, PathMetadata>;\n\tprivate extensionSkillSourceInfos: Map<string, SourceInfo>;\n\tprivate extensionPromptSourceInfos: Map<string, SourceInfo>;\n\tprivate extensionThemeSourceInfos: Map<string, SourceInfo>;\n\tprivate lastPromptPaths: string[];\n\tprivate lastThemePaths: string[];\n\n\tconstructor(options: DefaultResourceLoaderOptions) {\n\t\tthis.cwd = resolvePath(options.cwd);\n\t\tthis.agentDir = resolvePath(options.agentDir);\n\t\tthis.settingsManager = options.settingsManager ?? SettingsManager.create(this.cwd, this.agentDir);\n\t\tthis.eventBus = options.eventBus ?? createEventBus();\n\t\tthis.packageManager = new DefaultPackageManager({\n\t\t\tcwd: this.cwd,\n\t\t\tagentDir: this.agentDir,\n\t\t\tsettingsManager: this.settingsManager,\n\t\t});\n\t\tthis.additionalExtensionPaths = options.additionalExtensionPaths ?? [];\n\t\tthis.additionalSkillPaths = options.additionalSkillPaths ?? [];\n\t\tthis.additionalPromptTemplatePaths = options.additionalPromptTemplatePaths ?? [];\n\t\tthis.additionalThemePaths = options.additionalThemePaths ?? [];\n\t\tthis.extensionFactories = options.extensionFactories ?? [];\n\t\tthis.noExtensions = options.noExtensions ?? false;\n\t\tthis.noSkills = options.noSkills ?? false;\n\t\tthis.noPromptTemplates = options.noPromptTemplates ?? false;\n\t\tthis.noThemes = options.noThemes ?? false;\n\t\tthis.noContextFiles = options.noContextFiles ?? false;\n\t\tthis.systemPromptSource = options.systemPrompt;\n\t\tthis.appendSystemPromptSource = options.appendSystemPrompt;\n\t\tthis.extensionsOverride = options.extensionsOverride;\n\t\tthis.skillsOverride = options.skillsOverride;\n\t\tthis.promptsOverride = options.promptsOverride;\n\t\tthis.themesOverride = options.themesOverride;\n\t\tthis.agentsFilesOverride = options.agentsFilesOverride;\n\t\tthis.systemPromptOverride = options.systemPromptOverride;\n\t\tthis.appendSystemPromptOverride = options.appendSystemPromptOverride;\n\n\t\tthis.extensionsResult = { extensions: [], errors: [], runtime: createExtensionRuntime() };\n\t\tthis.loadedExtensions = [];\n\t\tthis.skills = [];\n\t\tthis.skillDiagnostics = [];\n\t\tthis.prompts = [];\n\t\tthis.promptDiagnostics = [];\n\t\tthis.themes = [];\n\t\tthis.themeDiagnostics = [];\n\t\tthis.agentsFiles = [];\n\t\tthis.appendSystemPrompt = [];\n\t\tthis.lastSkillPaths = [];\n\t\tthis.resourceMetadataByPath = new Map();\n\t\tthis.extensionSkillSourceInfos = new Map();\n\t\tthis.extensionPromptSourceInfos = new Map();\n\t\tthis.extensionThemeSourceInfos = new Map();\n\t\tthis.lastPromptPaths = [];\n\t\tthis.lastThemePaths = [];\n\t}\n\n\tgetExtensions(): LoadExtensionsResult {\n\t\treturn this.extensionsResult;\n\t}\n\n\tgetSkills(): { skills: Skill[]; diagnostics: ResourceDiagnostic[] } {\n\t\treturn { skills: this.skills, diagnostics: this.skillDiagnostics };\n\t}\n\n\tgetPrompts(): { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] } {\n\t\treturn { prompts: this.prompts, diagnostics: this.promptDiagnostics };\n\t}\n\n\tgetThemes(): { themes: Theme[]; diagnostics: ResourceDiagnostic[] } {\n\t\treturn { themes: this.themes, diagnostics: this.themeDiagnostics };\n\t}\n\n\tgetAgentsFiles(): { agentsFiles: ContextFile[] } {\n\t\treturn { agentsFiles: this.agentsFiles };\n\t}\n\n\tgetSystemPrompt(): string | undefined {\n\t\treturn this.systemPrompt;\n\t}\n\n\tgetAppendSystemPrompt(): string[] {\n\t\treturn this.appendSystemPrompt;\n\t}\n\n\textendResources(paths: ResourceExtensionPaths): void {\n\t\tconst skillPaths = this.normalizeExtensionPaths(paths.skillPaths ?? []);\n\t\tconst promptPaths = this.normalizeExtensionPaths(paths.promptPaths ?? []);\n\t\tconst themePaths = this.normalizeExtensionPaths(paths.themePaths ?? []);\n\n\t\tfor (const entry of skillPaths) {\n\t\t\tthis.resourceMetadataByPath.set(entry.path, entry.metadata);\n\t\t\tthis.extensionSkillSourceInfos.set(entry.path, createSourceInfo(entry.path, entry.metadata));\n\t\t}\n\t\tfor (const entry of promptPaths) {\n\t\t\tthis.resourceMetadataByPath.set(entry.path, entry.metadata);\n\t\t\tthis.extensionPromptSourceInfos.set(entry.path, createSourceInfo(entry.path, entry.metadata));\n\t\t}\n\t\tfor (const entry of themePaths) {\n\t\t\tthis.resourceMetadataByPath.set(entry.path, entry.metadata);\n\t\t\tthis.extensionThemeSourceInfos.set(entry.path, createSourceInfo(entry.path, entry.metadata));\n\t\t}\n\n\t\tif (skillPaths.length > 0) {\n\t\t\tthis.lastSkillPaths = this.mergePaths(\n\t\t\t\tthis.lastSkillPaths,\n\t\t\t\tskillPaths.map((entry) => entry.path),\n\t\t\t);\n\t\t\tthis.updateSkillsFromPaths(this.lastSkillPaths, this.resourceMetadataByPath);\n\t\t}\n\n\t\tif (promptPaths.length > 0) {\n\t\t\tthis.lastPromptPaths = this.mergePaths(\n\t\t\t\tthis.lastPromptPaths,\n\t\t\t\tpromptPaths.map((entry) => entry.path),\n\t\t\t);\n\t\t\tthis.updatePromptsFromPaths(this.lastPromptPaths, this.resourceMetadataByPath);\n\t\t}\n\n\t\tif (themePaths.length > 0) {\n\t\t\tthis.lastThemePaths = this.mergePaths(\n\t\t\t\tthis.lastThemePaths,\n\t\t\t\tthemePaths.map((entry) => entry.path),\n\t\t\t);\n\t\t\tthis.updateThemesFromPaths(this.lastThemePaths, this.resourceMetadataByPath);\n\t\t}\n\t}\n\n\tasync reload(): Promise<void> {\n\t\tawait this.settingsManager.reload();\n\t\tconst resolvedPaths = await this.packageManager.resolve();\n\t\tconst cliExtensionPaths = await this.packageManager.resolveExtensionSources(this.additionalExtensionPaths, {\n\t\t\ttemporary: true,\n\t\t});\n\t\tconst metadataByPath = new Map<string, PathMetadata>();\n\t\tthis.resourceMetadataByPath = metadataByPath;\n\n\t\tthis.extensionSkillSourceInfos = new Map();\n\t\tthis.extensionPromptSourceInfos = new Map();\n\t\tthis.extensionThemeSourceInfos = new Map();\n\n\t\t// Helper to extract enabled paths and store metadata\n\t\tconst getEnabledResources = (\n\t\t\tresources: Array<{ path: string; enabled: boolean; metadata: PathMetadata }>,\n\t\t): Array<{ path: string; enabled: boolean; metadata: PathMetadata }> => {\n\t\t\tfor (const r of resources) {\n\t\t\t\tif (!metadataByPath.has(r.path)) {\n\t\t\t\t\tmetadataByPath.set(r.path, r.metadata);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn resources.filter((r) => r.enabled);\n\t\t};\n\n\t\tconst getEnabledPaths = (\n\t\t\tresources: Array<{ path: string; enabled: boolean; metadata: PathMetadata }>,\n\t\t): string[] => getEnabledResources(resources).map((r) => r.path);\n\t\tconst enabledExtensions = getEnabledPaths(resolvedPaths.extensions);\n\t\tconst enabledSkillResources = getEnabledResources(resolvedPaths.skills).filter(\n\t\t\t(resource) => !isLegacyAutoSkillResource(resource),\n\t\t);\n\t\tconst enabledPrompts = getEnabledPaths(resolvedPaths.prompts);\n\t\tconst enabledThemes = getEnabledPaths(resolvedPaths.themes);\n\n\t\tconst mapSkillPath = (resource: { path: string; metadata: PathMetadata }): string => {\n\t\t\tif (resource.metadata.source !== \"auto\" && resource.metadata.origin !== \"package\") {\n\t\t\t\treturn resource.path;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst stats = statSync(resource.path);\n\t\t\t\tif (!stats.isDirectory()) {\n\t\t\t\t\treturn resource.path;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\treturn resource.path;\n\t\t\t}\n\t\t\tconst skillFile = join(resource.path, \"SKILL.md\");\n\t\t\tif (existsSync(skillFile)) {\n\t\t\t\tif (!metadataByPath.has(skillFile)) {\n\t\t\t\t\tmetadataByPath.set(skillFile, resource.metadata);\n\t\t\t\t}\n\t\t\t\treturn skillFile;\n\t\t\t}\n\t\t\treturn resource.path;\n\t\t};\n\n\t\tconst enabledSkills = enabledSkillResources.map(mapSkillPath);\n\n\t\t// Add CLI paths metadata\n\t\tfor (const r of cliExtensionPaths.extensions) {\n\t\t\tif (!metadataByPath.has(r.path)) {\n\t\t\t\tmetadataByPath.set(r.path, { source: \"cli\", scope: \"temporary\", origin: \"top-level\" });\n\t\t\t}\n\t\t}\n\t\tfor (const r of cliExtensionPaths.skills) {\n\t\t\tif (!metadataByPath.has(r.path)) {\n\t\t\t\tmetadataByPath.set(r.path, { source: \"cli\", scope: \"temporary\", origin: \"top-level\" });\n\t\t\t}\n\t\t}\n\n\t\tconst cliEnabledExtensions = getEnabledPaths(cliExtensionPaths.extensions);\n\t\tconst cliEnabledSkills = getEnabledPaths(cliExtensionPaths.skills);\n\t\tconst cliEnabledPrompts = getEnabledPaths(cliExtensionPaths.prompts);\n\t\tconst cliEnabledThemes = getEnabledPaths(cliExtensionPaths.themes);\n\n\t\tconst extensionPaths = this.noExtensions\n\t\t\t? cliEnabledExtensions\n\t\t\t: this.mergePaths(cliEnabledExtensions, enabledExtensions);\n\n\t\t// Unsubscribe event-bus listeners registered by the previous load before\n\t\t// re-running extension factories, so /reload does not stack duplicate\n\t\t// handlers (or keep stale extensions alive) on the shared event bus.\n\t\tthis.disposeExtensionEventSubscriptions();\n\n\t\tconst extensionsResult = await loadExtensions(extensionPaths, this.cwd, this.eventBus);\n\n\t\t// Keep exactly one visible task list. A user-installed `todo` extension owns\n\t\t// the checklist UI; otherwise fall back to OMK's built-in `update_todo`.\n\t\t// Set OMK_TODO_CHECKLIST=0 to disable the built-in explicitly.\n\t\tconst hasTodoExtension = extensionsResult.extensions.some((extension) => extension.tools.has(\"todo\"));\n\t\tif (!isDisabledByEnv(process.env.OMK_TODO_CHECKLIST) && !hasTodoExtension) {\n\t\t\tconst todoExtension = await loadExtensionFromFactory(\n\t\t\t\ttodoChecklist,\n\t\t\t\tthis.cwd,\n\t\t\t\tthis.eventBus,\n\t\t\t\textensionsResult.runtime,\n\t\t\t\t\"<builtin:todo-checklist>\",\n\t\t\t);\n\t\t\textensionsResult.extensions.unshift(todoExtension);\n\t\t}\n\n\t\t// Built-in command-safety gate runs FIRST (unless YOLO / OMK_COMMAND_SAFETY=0).\n\t\t// tool_call block short-circuits on first block; user_bash on first truthy result.\n\t\tconst yoloOff =\n\t\t\tprocess.env.OMK_YOLO === \"1\" ||\n\t\t\tprocess.env.OMK_YOLO === \"true\" ||\n\t\t\tprocess.env.OMK_DISABLE_COMMAND_SAFETY === \"1\" ||\n\t\t\tprocess.env.OMK_DISABLE_COMMAND_SAFETY === \"true\" ||\n\t\t\tisDisabledByEnv(process.env.OMK_COMMAND_SAFETY);\n\t\tif (!yoloOff) {\n\t\t\tconst commandSafetyExtension = await loadExtensionFromFactory(\n\t\t\t\tcommandSafetyGate,\n\t\t\t\tthis.cwd,\n\t\t\t\tthis.eventBus,\n\t\t\t\textensionsResult.runtime,\n\t\t\t\t\"<builtin:command-safety>\",\n\t\t\t);\n\t\t\textensionsResult.extensions.unshift(commandSafetyExtension);\n\t\t}\n\n\t\tconst harnessFactories: Array<{ factory: ExtensionFactory; path: string; disabled: boolean }> = [\n\t\t\t{\n\t\t\t\tfactory: identicalLoop,\n\t\t\t\tpath: \"<builtin:identical-loop>\",\n\t\t\t\tdisabled: isDisabledByEnv(process.env.OMK_IDENTICAL_LOOP),\n\t\t\t},\n\t\t\t{\n\t\t\t\tfactory: toolPairRepair,\n\t\t\t\tpath: \"<builtin:tool-pair-repair>\",\n\t\t\t\tdisabled: isDisabledByEnv(process.env.OMK_TOOL_PAIR_REPAIR),\n\t\t\t},\n\t\t\t{\n\t\t\t\tfactory: promptPreset,\n\t\t\t\tpath: \"<builtin:prompt-preset>\",\n\t\t\t\tdisabled: isDisabledByEnv(process.env.OMK_PROMPT_PRESET),\n\t\t\t},\n\t\t\t{\n\t\t\t\tfactory: goalController,\n\t\t\t\tpath: \"<builtin:goal-controller>\",\n\t\t\t\tdisabled: isDisabledByEnv(process.env.OMK_GOAL_CONTROLLER),\n\t\t\t},\n\t\t];\n\t\tfor (const { factory, path, disabled } of harnessFactories) {\n\t\t\tif (disabled) continue;\n\t\t\tconst extension = await loadExtensionFromFactory(\n\t\t\t\tfactory,\n\t\t\t\tthis.cwd,\n\t\t\t\tthis.eventBus,\n\t\t\t\textensionsResult.runtime,\n\t\t\t\tpath,\n\t\t\t);\n\t\t\textensionsResult.extensions.push(extension);\n\t\t}\n\n\t\tconst inlineExtensions = await this.loadExtensionFactories(extensionsResult.runtime);\n\t\textensionsResult.extensions.push(...inlineExtensions.extensions);\n\t\textensionsResult.errors.push(...inlineExtensions.errors);\n\t\tthis.loadedExtensions = [...extensionsResult.extensions];\n\n\t\t// Detect extension conflicts (tools, commands, flags with same names from different extensions)\n\t\t// Keep all extensions loaded. Conflicts are reported as diagnostics, and precedence is handled by load order.\n\t\tconst conflicts = this.detectExtensionConflicts(extensionsResult.extensions);\n\t\tfor (const conflict of conflicts) {\n\t\t\textensionsResult.errors.push({ path: conflict.path, error: conflict.message });\n\t\t}\n\n\t\tfor (const p of this.additionalExtensionPaths) {\n\t\t\tif (isLocalPath(p)) {\n\t\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\t\tif (!existsSync(resolved)) {\n\t\t\t\t\textensionsResult.errors.push({ path: resolved, error: `Extension path does not exist: ${resolved}` });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tthis.extensionsResult = this.extensionsOverride ? this.extensionsOverride(extensionsResult) : extensionsResult;\n\t\tthis.applyExtensionSourceInfo(this.extensionsResult.extensions, metadataByPath);\n\n\t\tconst skillPaths = this.noSkills\n\t\t\t? this.mergePaths(cliEnabledSkills, this.additionalSkillPaths)\n\t\t\t: this.mergePaths([...cliEnabledSkills, ...enabledSkills], this.additionalSkillPaths);\n\n\t\tthis.lastSkillPaths = skillPaths;\n\t\tthis.updateSkillsFromPaths(skillPaths, metadataByPath);\n\t\tfor (const p of this.additionalSkillPaths) {\n\t\t\tif (isLocalPath(p)) {\n\t\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\t\tif (!existsSync(resolved) && !this.skillDiagnostics.some((d) => d.path === resolved)) {\n\t\t\t\t\tthis.skillDiagnostics.push({ type: \"error\", message: \"Skill path does not exist\", path: resolved });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst promptPaths = this.noPromptTemplates\n\t\t\t? this.mergePaths(cliEnabledPrompts, this.additionalPromptTemplatePaths)\n\t\t\t: this.mergePaths([...cliEnabledPrompts, ...enabledPrompts], this.additionalPromptTemplatePaths);\n\n\t\tthis.lastPromptPaths = promptPaths;\n\t\tthis.updatePromptsFromPaths(promptPaths, metadataByPath);\n\t\tfor (const p of this.additionalPromptTemplatePaths) {\n\t\t\tif (isLocalPath(p)) {\n\t\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\t\tif (!existsSync(resolved) && !this.promptDiagnostics.some((d) => d.path === resolved)) {\n\t\t\t\t\tthis.promptDiagnostics.push({\n\t\t\t\t\t\ttype: \"error\",\n\t\t\t\t\t\tmessage: \"Prompt template path does not exist\",\n\t\t\t\t\t\tpath: resolved,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst themePaths = this.noThemes\n\t\t\t? this.mergePaths(cliEnabledThemes, this.additionalThemePaths)\n\t\t\t: this.mergePaths([...cliEnabledThemes, ...enabledThemes], this.additionalThemePaths);\n\n\t\tthis.lastThemePaths = themePaths;\n\t\tthis.updateThemesFromPaths(themePaths, metadataByPath);\n\t\tfor (const p of this.additionalThemePaths) {\n\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\tif (!existsSync(resolved) && !this.themeDiagnostics.some((d) => d.path === resolved)) {\n\t\t\t\tthis.themeDiagnostics.push({ type: \"error\", message: \"Theme path does not exist\", path: resolved });\n\t\t\t}\n\t\t}\n\n\t\tconst agentsFiles = {\n\t\t\tagentsFiles: this.noContextFiles ? [] : loadProjectContextFiles({ cwd: this.cwd, agentDir: this.agentDir }),\n\t\t};\n\t\tconst resolvedAgentsFiles = this.agentsFilesOverride ? this.agentsFilesOverride(agentsFiles) : agentsFiles;\n\t\tthis.agentsFiles = resolvedAgentsFiles.agentsFiles;\n\n\t\tconst baseSystemPrompt = resolvePromptInput(\n\t\t\tthis.systemPromptSource ?? this.discoverSystemPromptFile(),\n\t\t\t\"system prompt\",\n\t\t);\n\t\tthis.systemPrompt = this.systemPromptOverride ? this.systemPromptOverride(baseSystemPrompt) : baseSystemPrompt;\n\n\t\tconst appendSources =\n\t\t\tthis.appendSystemPromptSource ??\n\t\t\t(this.discoverAppendSystemPromptFile() ? [this.discoverAppendSystemPromptFile()!] : []);\n\t\tconst baseAppend = appendSources\n\t\t\t.map((s) => resolvePromptInput(s, \"append system prompt\"))\n\t\t\t.filter((s): s is string => s !== undefined);\n\t\tthis.appendSystemPrompt = this.appendSystemPromptOverride\n\t\t\t? this.appendSystemPromptOverride(baseAppend)\n\t\t\t: baseAppend;\n\t}\n\n\tprivate normalizeExtensionPaths(\n\t\tentries: Array<{ path: string; metadata: PathMetadata }>,\n\t): Array<{ path: string; metadata: PathMetadata }> {\n\t\treturn entries.map((entry) => {\n\t\t\tconst metadata = entry.metadata.baseDir\n\t\t\t\t? { ...entry.metadata, baseDir: this.resolveResourcePath(entry.metadata.baseDir) }\n\t\t\t\t: entry.metadata;\n\t\t\treturn {\n\t\t\t\tpath: this.resolveResourcePath(entry.path),\n\t\t\t\tmetadata,\n\t\t\t};\n\t\t});\n\t}\n\n\tprivate updateSkillsFromPaths(skillPaths: string[], metadataByPath?: Map<string, PathMetadata>): void {\n\t\tlet skillsResult: { skills: Skill[]; diagnostics: ResourceDiagnostic[] };\n\t\tif (this.noSkills && skillPaths.length === 0) {\n\t\t\tskillsResult = { skills: [], diagnostics: [] };\n\t\t} else {\n\t\t\tskillsResult = loadSkills({\n\t\t\t\tcwd: this.cwd,\n\t\t\t\tagentDir: this.agentDir,\n\t\t\t\tskillPaths,\n\t\t\t\tincludeDefaults: false,\n\t\t\t\tresolveSourceInfo: (filePath) =>\n\t\t\t\t\tthis.findSourceInfoForPath(filePath, this.extensionSkillSourceInfos, metadataByPath),\n\t\t\t});\n\t\t}\n\t\tconst resolvedSkills = this.skillsOverride ? this.skillsOverride(skillsResult) : skillsResult;\n\t\tthis.skills = resolvedSkills.skills.map((skill) => ({\n\t\t\t...skill,\n\t\t\tsourceInfo:\n\t\t\t\tthis.findSourceInfoForPath(skill.filePath, this.extensionSkillSourceInfos, metadataByPath) ??\n\t\t\t\tskill.sourceInfo ??\n\t\t\t\tthis.getDefaultSourceInfoForPath(skill.filePath),\n\t\t}));\n\t\tthis.skillDiagnostics = this.enrichCollisionDiagnostics(\n\t\t\tresolvedSkills.diagnostics,\n\t\t\tthis.extensionSkillSourceInfos,\n\t\t\tmetadataByPath,\n\t\t);\n\t}\n\n\tprivate enrichCollisionDiagnostics(\n\t\tdiagnostics: ResourceDiagnostic[],\n\t\textraSourceInfos?: Map<string, SourceInfo>,\n\t\tmetadataByPath?: Map<string, PathMetadata>,\n\t): ResourceDiagnostic[] {\n\t\treturn diagnostics.map((diagnostic) => {\n\t\t\tconst collision = diagnostic.collision;\n\t\t\tif (!collision) {\n\t\t\t\treturn diagnostic;\n\t\t\t}\n\n\t\t\tconst winnerSourceInfo = this.findSourceInfoForPath(collision.winnerPath, extraSourceInfos, metadataByPath);\n\t\t\tconst loserSourceInfo = this.findSourceInfoForPath(collision.loserPath, extraSourceInfos, metadataByPath);\n\t\t\tif (!winnerSourceInfo && !loserSourceInfo) {\n\t\t\t\treturn diagnostic;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\t...diagnostic,\n\t\t\t\tcollision: {\n\t\t\t\t\t...collision,\n\t\t\t\t\twinnerSource: winnerSourceInfo?.source ?? collision.winnerSource,\n\t\t\t\t\tloserSource: loserSourceInfo?.source ?? collision.loserSource,\n\t\t\t\t\twinnerScope: winnerSourceInfo?.scope ?? collision.winnerScope,\n\t\t\t\t\tloserScope: loserSourceInfo?.scope ?? collision.loserScope,\n\t\t\t\t\twinnerOrigin: winnerSourceInfo?.origin ?? collision.winnerOrigin,\n\t\t\t\t\tloserOrigin: loserSourceInfo?.origin ?? collision.loserOrigin,\n\t\t\t\t},\n\t\t\t};\n\t\t});\n\t}\n\n\tprivate updatePromptsFromPaths(promptPaths: string[], metadataByPath?: Map<string, PathMetadata>): void {\n\t\tlet promptsResult: { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] };\n\t\tif (this.noPromptTemplates && promptPaths.length === 0) {\n\t\t\tpromptsResult = { prompts: [], diagnostics: [] };\n\t\t} else {\n\t\t\tconst allPrompts = loadPromptTemplates({\n\t\t\t\tcwd: this.cwd,\n\t\t\t\tagentDir: this.agentDir,\n\t\t\t\tpromptPaths,\n\t\t\t\tincludeDefaults: false,\n\t\t\t});\n\t\t\tpromptsResult = this.dedupePrompts(allPrompts);\n\t\t}\n\t\tconst resolvedPrompts = this.promptsOverride ? this.promptsOverride(promptsResult) : promptsResult;\n\t\tthis.prompts = resolvedPrompts.prompts.map((prompt) => ({\n\t\t\t...prompt,\n\t\t\tsourceInfo:\n\t\t\t\tthis.findSourceInfoForPath(prompt.filePath, this.extensionPromptSourceInfos, metadataByPath) ??\n\t\t\t\tprompt.sourceInfo ??\n\t\t\t\tthis.getDefaultSourceInfoForPath(prompt.filePath),\n\t\t}));\n\t\tthis.promptDiagnostics = resolvedPrompts.diagnostics;\n\t}\n\n\tprivate updateThemesFromPaths(themePaths: string[], metadataByPath?: Map<string, PathMetadata>): void {\n\t\tlet themesResult: { themes: Theme[]; diagnostics: ResourceDiagnostic[] };\n\t\tif (this.noThemes && themePaths.length === 0) {\n\t\t\tthemesResult = { themes: [], diagnostics: [] };\n\t\t} else {\n\t\t\tconst loaded = this.loadThemes(themePaths, false);\n\t\t\tconst deduped = this.dedupeThemes(loaded.themes);\n\t\t\tthemesResult = { themes: deduped.themes, diagnostics: [...loaded.diagnostics, ...deduped.diagnostics] };\n\t\t}\n\t\tconst resolvedThemes = this.themesOverride ? this.themesOverride(themesResult) : themesResult;\n\t\tthis.themes = resolvedThemes.themes.map((theme) => {\n\t\t\tconst sourcePath = theme.sourcePath;\n\t\t\ttheme.sourceInfo = sourcePath\n\t\t\t\t? (this.findSourceInfoForPath(sourcePath, this.extensionThemeSourceInfos, metadataByPath) ??\n\t\t\t\t\ttheme.sourceInfo ??\n\t\t\t\t\tthis.getDefaultSourceInfoForPath(sourcePath))\n\t\t\t\t: theme.sourceInfo;\n\t\t\treturn theme;\n\t\t});\n\t\tthis.themeDiagnostics = resolvedThemes.diagnostics;\n\t}\n\n\tprivate applyExtensionSourceInfo(extensions: Extension[], metadataByPath: Map<string, PathMetadata>): void {\n\t\tfor (const extension of extensions) {\n\t\t\textension.sourceInfo =\n\t\t\t\tthis.findSourceInfoForPath(extension.path, undefined, metadataByPath) ??\n\t\t\t\tthis.getDefaultSourceInfoForPath(extension.path);\n\t\t\tfor (const command of extension.commands.values()) {\n\t\t\t\tcommand.sourceInfo = extension.sourceInfo;\n\t\t\t}\n\t\t\tfor (const tool of extension.tools.values()) {\n\t\t\t\ttool.sourceInfo = extension.sourceInfo;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate findSourceInfoForPath(\n\t\tresourcePath: string,\n\t\textraSourceInfos?: Map<string, SourceInfo>,\n\t\tmetadataByPath?: Map<string, PathMetadata>,\n\t): SourceInfo | undefined {\n\t\tif (!resourcePath) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (resourcePath.startsWith(\"<\")) {\n\t\t\treturn this.getDefaultSourceInfoForPath(resourcePath);\n\t\t}\n\n\t\tconst normalizedResourcePath = resolve(resourcePath);\n\t\tif (extraSourceInfos) {\n\t\t\tfor (const [sourcePath, sourceInfo] of extraSourceInfos.entries()) {\n\t\t\t\tconst normalizedSourcePath = resolve(sourcePath);\n\t\t\t\tif (\n\t\t\t\t\tnormalizedResourcePath === normalizedSourcePath ||\n\t\t\t\t\tnormalizedResourcePath.startsWith(`${normalizedSourcePath}${sep}`)\n\t\t\t\t) {\n\t\t\t\t\treturn { ...sourceInfo, path: resourcePath };\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (metadataByPath) {\n\t\t\tconst exact = metadataByPath.get(normalizedResourcePath) ?? metadataByPath.get(resourcePath);\n\t\t\tif (exact) {\n\t\t\t\treturn createSourceInfo(resourcePath, exact);\n\t\t\t}\n\n\t\t\tfor (const [sourcePath, metadata] of metadataByPath.entries()) {\n\t\t\t\tconst normalizedSourcePath = resolve(sourcePath);\n\t\t\t\tif (\n\t\t\t\t\tnormalizedResourcePath === normalizedSourcePath ||\n\t\t\t\t\tnormalizedResourcePath.startsWith(`${normalizedSourcePath}${sep}`)\n\t\t\t\t) {\n\t\t\t\t\treturn createSourceInfo(resourcePath, metadata);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate getDefaultSourceInfoForPath(filePath: string): SourceInfo {\n\t\tif (filePath.startsWith(\"<\") && filePath.endsWith(\">\")) {\n\t\t\treturn {\n\t\t\t\tpath: filePath,\n\t\t\t\tsource: filePath.slice(1, -1).split(\":\")[0] || \"temporary\",\n\t\t\t\tscope: \"temporary\",\n\t\t\t\torigin: \"top-level\",\n\t\t\t};\n\t\t}\n\n\t\tconst normalizedPath = resolve(filePath);\n\t\tconst agentRoots = [\n\t\t\tjoin(this.agentDir, \"skills\"),\n\t\t\tjoin(this.agentDir, \"prompts\"),\n\t\t\tjoin(this.agentDir, \"themes\"),\n\t\t\tjoin(this.agentDir, \"extensions\"),\n\t\t];\n\t\tconst projectRoots = [\n\t\t\tjoin(this.cwd, CONFIG_DIR_NAME, \"skills\"),\n\t\t\tjoin(this.cwd, CONFIG_DIR_NAME, \"prompts\"),\n\t\t\tjoin(this.cwd, CONFIG_DIR_NAME, \"themes\"),\n\t\t\tjoin(this.cwd, CONFIG_DIR_NAME, \"extensions\"),\n\t\t];\n\n\t\tfor (const root of agentRoots) {\n\t\t\tif (this.isUnderPath(normalizedPath, root)) {\n\t\t\t\treturn { path: filePath, source: \"local\", scope: \"user\", origin: \"top-level\", baseDir: root };\n\t\t\t}\n\t\t}\n\n\t\tfor (const root of projectRoots) {\n\t\t\tif (this.isUnderPath(normalizedPath, root)) {\n\t\t\t\treturn { path: filePath, source: \"local\", scope: \"project\", origin: \"top-level\", baseDir: root };\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tpath: filePath,\n\t\t\tsource: \"local\",\n\t\t\tscope: \"temporary\",\n\t\t\torigin: \"top-level\",\n\t\t\tbaseDir: statSync(normalizedPath).isDirectory() ? normalizedPath : resolve(normalizedPath, \"..\"),\n\t\t};\n\t}\n\n\tprivate mergePaths(primary: string[], additional: string[]): string[] {\n\t\tconst merged: string[] = [];\n\t\tconst seen = new Set<string>();\n\n\t\tfor (const p of [...primary, ...additional]) {\n\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\tconst canonicalPath = canonicalizePath(resolved);\n\t\t\tif (seen.has(canonicalPath)) continue;\n\t\t\tseen.add(canonicalPath);\n\t\t\tmerged.push(resolved);\n\t\t}\n\n\t\treturn merged;\n\t}\n\n\tprivate resolveResourcePath(p: string): string {\n\t\treturn resolvePath(p, this.cwd, { trim: true });\n\t}\n\n\tprivate loadThemes(\n\t\tpaths: string[],\n\t\tincludeDefaults: boolean = true,\n\t): {\n\t\tthemes: Theme[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t} {\n\t\tconst themes: Theme[] = [];\n\t\tconst diagnostics: ResourceDiagnostic[] = [];\n\t\tif (includeDefaults) {\n\t\t\tconst defaultDirs = [join(this.agentDir, \"themes\"), join(this.cwd, CONFIG_DIR_NAME, \"themes\")];\n\n\t\t\tfor (const dir of defaultDirs) {\n\t\t\t\tthis.loadThemesFromDir(dir, themes, diagnostics);\n\t\t\t}\n\t\t}\n\n\t\tfor (const p of paths) {\n\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\tif (!existsSync(resolved)) {\n\t\t\t\tdiagnostics.push({ type: \"warning\", message: \"theme path does not exist\", path: resolved });\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst stats = statSync(resolved);\n\t\t\t\tif (stats.isDirectory()) {\n\t\t\t\t\tthis.loadThemesFromDir(resolved, themes, diagnostics);\n\t\t\t\t} else if (stats.isFile() && resolved.endsWith(\".json\")) {\n\t\t\t\t\tthis.loadThemeFromFile(resolved, themes, diagnostics);\n\t\t\t\t} else {\n\t\t\t\t\tdiagnostics.push({ type: \"warning\", message: \"theme path is not a json file\", path: resolved });\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : \"failed to read theme path\";\n\t\t\t\tdiagnostics.push({ type: \"warning\", message, path: resolved });\n\t\t\t}\n\t\t}\n\n\t\treturn { themes, diagnostics };\n\t}\n\n\tprivate loadThemesFromDir(dir: string, themes: Theme[], diagnostics: ResourceDiagnostic[]): void {\n\t\tif (!existsSync(dir)) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\t\t\tfor (const entry of entries) {\n\t\t\t\tlet isFile = entry.isFile();\n\t\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tisFile = statSync(join(dir, entry.name)).isFile();\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!isFile) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (!entry.name.endsWith(\".json\")) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tthis.loadThemeFromFile(join(dir, entry.name), themes, diagnostics);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to read theme directory\";\n\t\t\tdiagnostics.push({ type: \"warning\", message, path: dir });\n\t\t}\n\t}\n\n\tprivate loadThemeFromFile(filePath: string, themes: Theme[], diagnostics: ResourceDiagnostic[]): void {\n\t\ttry {\n\t\t\tthemes.push(loadThemeFromPath(filePath));\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to load theme\";\n\t\t\tdiagnostics.push({ type: \"warning\", message, path: filePath });\n\t\t}\n\t}\n\n\t/**\n\t * Unsubscribe event listeners registered by the previously loaded\n\t * extensions. Only subscriptions made through the extension API are\n\t * removed, so listeners added directly on an injected event bus survive.\n\t */\n\tprivate disposeExtensionEventSubscriptions(): void {\n\t\tfor (const extension of this.loadedExtensions) {\n\t\t\tconst subscriptions = extension.eventBusSubscriptions;\n\t\t\tif (!subscriptions) continue;\n\t\t\tfor (const unsubscribe of subscriptions) {\n\t\t\t\tunsubscribe();\n\t\t\t}\n\t\t\tsubscriptions.length = 0;\n\t\t}\n\t\tthis.loadedExtensions = [];\n\t}\n\n\tprivate async loadExtensionFactories(runtime: ExtensionRuntime): Promise<{\n\t\textensions: Extension[];\n\t\terrors: Array<{ path: string; error: string }>;\n\t}> {\n\t\tconst extensions: Extension[] = [];\n\t\tconst errors: Array<{ path: string; error: string }> = [];\n\n\t\tfor (const [index, factory] of this.extensionFactories.entries()) {\n\t\t\tconst extensionPath = `<inline:${index + 1}>`;\n\t\t\ttry {\n\t\t\t\tconst extension = await loadExtensionFromFactory(factory, this.cwd, this.eventBus, runtime, extensionPath);\n\t\t\t\textensions.push(extension);\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : \"failed to load extension\";\n\t\t\t\terrors.push({ path: extensionPath, error: message });\n\t\t\t}\n\t\t}\n\n\t\treturn { extensions, errors };\n\t}\n\n\tprivate dedupePrompts(prompts: PromptTemplate[]): { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] } {\n\t\tconst seen = new Map<string, PromptTemplate>();\n\t\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\t\tfor (const prompt of prompts) {\n\t\t\tconst existing = seen.get(prompt.name);\n\t\t\tif (existing) {\n\t\t\t\tdiagnostics.push({\n\t\t\t\t\ttype: \"collision\",\n\t\t\t\t\tmessage: `name \"/${prompt.name}\" collision`,\n\t\t\t\t\tpath: prompt.filePath,\n\t\t\t\t\tcollision: {\n\t\t\t\t\t\tresourceType: \"prompt\",\n\t\t\t\t\t\tname: prompt.name,\n\t\t\t\t\t\twinnerPath: existing.filePath,\n\t\t\t\t\t\tloserPath: prompt.filePath,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tseen.set(prompt.name, prompt);\n\t\t\t}\n\t\t}\n\n\t\treturn { prompts: Array.from(seen.values()), diagnostics };\n\t}\n\n\tprivate dedupeThemes(themes: Theme[]): { themes: Theme[]; diagnostics: ResourceDiagnostic[] } {\n\t\tconst seen = new Map<string, Theme>();\n\t\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\t\tfor (const t of themes) {\n\t\t\tconst name = t.name ?? \"unnamed\";\n\t\t\tconst existing = seen.get(name);\n\t\t\tif (existing) {\n\t\t\t\tdiagnostics.push({\n\t\t\t\t\ttype: \"collision\",\n\t\t\t\t\tmessage: `name \"${name}\" collision`,\n\t\t\t\t\tpath: t.sourcePath,\n\t\t\t\t\tcollision: {\n\t\t\t\t\t\tresourceType: \"theme\",\n\t\t\t\t\t\tname,\n\t\t\t\t\t\twinnerPath: existing.sourcePath ?? \"<builtin>\",\n\t\t\t\t\t\tloserPath: t.sourcePath ?? \"<builtin>\",\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tseen.set(name, t);\n\t\t\t}\n\t\t}\n\n\t\treturn { themes: Array.from(seen.values()), diagnostics };\n\t}\n\n\tprivate discoverSystemPromptFile(): string | undefined {\n\t\tconst projectPath = join(this.cwd, CONFIG_DIR_NAME, \"SYSTEM.md\");\n\t\tif (existsSync(projectPath)) {\n\t\t\treturn projectPath;\n\t\t}\n\n\t\tconst globalPath = join(this.agentDir, \"SYSTEM.md\");\n\t\tif (existsSync(globalPath)) {\n\t\t\treturn globalPath;\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate discoverAppendSystemPromptFile(): string | undefined {\n\t\tconst projectPath = join(this.cwd, CONFIG_DIR_NAME, \"APPEND_SYSTEM.md\");\n\t\tif (existsSync(projectPath)) {\n\t\t\treturn projectPath;\n\t\t}\n\n\t\tconst globalPath = join(this.agentDir, \"APPEND_SYSTEM.md\");\n\t\tif (existsSync(globalPath)) {\n\t\t\treturn globalPath;\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate isUnderPath(target: string, root: string): boolean {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) {\n\t\t\treturn true;\n\t\t}\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t}\n\n\tprivate detectExtensionConflicts(extensions: Extension[]): Array<{ path: string; message: string }> {\n\t\tconst conflicts: Array<{ path: string; message: string }> = [];\n\n\t\t// Track which extension registered each tool and flag\n\t\tconst toolOwners = new Map<string, string>();\n\t\tconst flagOwners = new Map<string, string>();\n\n\t\tfor (const ext of extensions) {\n\t\t\t// Check tools\n\t\t\tfor (const toolName of ext.tools.keys()) {\n\t\t\t\tconst existingOwner = toolOwners.get(toolName);\n\t\t\t\tif (existingOwner && existingOwner !== ext.path) {\n\t\t\t\t\tconflicts.push({\n\t\t\t\t\t\tpath: ext.path,\n\t\t\t\t\t\tmessage: `Tool \"${toolName}\" conflicts with ${existingOwner}`,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\ttoolOwners.set(toolName, ext.path);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Check flags\n\t\t\tfor (const flagName of ext.flags.keys()) {\n\t\t\t\tconst existingOwner = flagOwners.get(flagName);\n\t\t\t\tif (existingOwner && existingOwner !== ext.path) {\n\t\t\t\t\tconflicts.push({\n\t\t\t\t\t\tpath: ext.path,\n\t\t\t\t\t\tmessage: `Flag \"--${flagName}\" conflicts with ${existingOwner}`,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tflagOwners.set(flagName, ext.path);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn conflicts;\n\t}\n}\n"]}
1
+ {"version":3,"file":"resource-loader.d.ts","sourceRoot":"","sources":["../../src/core/resource-loader.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { join, resolve, sep } from \"node:path\";\nimport chalk from \"chalk\";\nimport { CONFIG_DIR_NAME } from \"../config.ts\";\nimport { loadThemeFromPath, type Theme } from \"../modes/interactive/theme/theme.ts\";\nimport type { ContextFile } from \"./context-file.ts\";\nimport type { ResourceDiagnostic } from \"./diagnostics.ts\";\n\nexport type { ResourceCollision, ResourceDiagnostic } from \"./diagnostics.ts\";\n\nimport { canonicalizePath, isLocalPath, resolvePath } from \"../utils/paths.ts\";\nimport { loadSkillsWithBundled } from \"./bundled-skills.ts\";\nimport { createEventBus, type EventBus } from \"./event-bus.ts\";\nimport commandSafetyGate from \"./extensions/builtin/command-safety-gate.ts\";\nimport goalController from \"./extensions/builtin/goal-controller.ts\";\nimport identicalLoop from \"./extensions/builtin/identical-loop.ts\";\nimport promptPreset from \"./extensions/builtin/prompt-preset.ts\";\nimport todoChecklist from \"./extensions/builtin/todo-checklist.ts\";\nimport toolPairRepair from \"./extensions/builtin/tool-pair-repair.ts\";\nimport { createExtensionRuntime, loadExtensionFromFactory, loadExtensions } from \"./extensions/loader.ts\";\nimport type { Extension, ExtensionFactory, ExtensionRuntime, LoadExtensionsResult } from \"./extensions/types.ts\";\nimport { DefaultPackageManager, type PathMetadata } from \"./package-manager.ts\";\nimport type { PromptTemplate } from \"./prompt-templates.ts\";\nimport { loadPromptTemplates } from \"./prompt-templates.ts\";\nimport { SettingsManager } from \"./settings-manager.ts\";\nimport type { Skill } from \"./skills.ts\";\nimport { createSourceInfo, type SourceInfo } from \"./source-info.ts\";\n\n/** Values that turn a built-in extension off via its environment variable. */\nconst DISABLED_ENV_VALUES = new Set([\"0\", \"false\", \"off\", \"disable\", \"disabled\"]);\n\n/** `true` when an env var is explicitly set to a disabling value. Unset means enabled. */\nfunction isDisabledByEnv(value: string | undefined): boolean {\n\treturn value !== undefined && DISABLED_ENV_VALUES.has(value.trim().toLowerCase());\n}\n\nexport interface ResourceExtensionPaths {\n\tskillPaths?: Array<{ path: string; metadata: PathMetadata }>;\n\tpromptPaths?: Array<{ path: string; metadata: PathMetadata }>;\n\tthemePaths?: Array<{ path: string; metadata: PathMetadata }>;\n}\n\nexport interface ResourceLoader {\n\tgetExtensions(): LoadExtensionsResult;\n\tgetSkills(): { skills: Skill[]; diagnostics: ResourceDiagnostic[] };\n\tgetPrompts(): { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] };\n\tgetThemes(): { themes: Theme[]; diagnostics: ResourceDiagnostic[] };\n\tgetAgentsFiles(): { agentsFiles: ContextFile[] };\n\tgetSystemPrompt(): string | undefined;\n\tgetAppendSystemPrompt(): string[];\n\textendResources(paths: ResourceExtensionPaths): void;\n\treload(): Promise<void>;\n}\n\nfunction hasLegacyPathSegment(path: string): boolean {\n\treturn path.split(/[\\\\/]+/).some((segment) => segment.includes(\".legacy.\"));\n}\n\nfunction isLegacyAutoSkillResource(resource: { path: string; metadata: PathMetadata }): boolean {\n\treturn (\n\t\tresource.metadata.source === \"auto\" &&\n\t\tresource.metadata.origin === \"top-level\" &&\n\t\thasLegacyPathSegment(resource.path)\n\t);\n}\n\nfunction resolvePromptInput(input: string | undefined, description: string): string | undefined {\n\tif (!input) {\n\t\treturn undefined;\n\t}\n\n\tif (existsSync(input)) {\n\t\ttry {\n\t\t\treturn readFileSync(input, \"utf-8\");\n\t\t} catch (error) {\n\t\t\tconsole.error(chalk.yellow(`Warning: Could not read ${description} file ${input}: ${error}`));\n\t\t\treturn input;\n\t\t}\n\t}\n\n\treturn input;\n}\n\nfunction loadContextFileFromDir(dir: string): ContextFile | null {\n\tconst candidates = [\"AGENTS.md\", \"AGENTS.MD\", \"CLAUDE.md\", \"CLAUDE.MD\"];\n\tfor (const filename of candidates) {\n\t\tconst filePath = join(dir, filename);\n\t\tif (existsSync(filePath)) {\n\t\t\ttry {\n\t\t\t\treturn {\n\t\t\t\t\tpath: filePath,\n\t\t\t\t\tcontent: readFileSync(filePath, \"utf-8\"),\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(chalk.yellow(`Warning: Could not read ${filePath}: ${error}`));\n\t\t\t}\n\t\t}\n\t}\n\treturn null;\n}\n\nfunction loadOverrideFilesFromDir(dir: string): ContextFile[] {\n\tconst candidates = [\"AGENTS.override.md\", \"AGENTS.GODMODE.md\", \"GODMODE.md\", \"ENI.override.md\"];\n\tconst results: ContextFile[] = [];\n\tfor (const filename of candidates) {\n\t\tconst filePath = join(dir, filename);\n\t\tif (existsSync(filePath)) {\n\t\t\ttry {\n\t\t\t\tresults.push({\n\t\t\t\t\tpath: filePath,\n\t\t\t\t\tcontent: readFileSync(filePath, \"utf-8\"),\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(chalk.yellow(`Warning: Could not read ${filePath}: ${error}`));\n\t\t\t}\n\t\t}\n\t}\n\treturn results;\n}\n\nfunction isGlobalContextFile(file: ContextFile, agentDir: string): boolean {\n\tconst resolvedAgentDir = resolvePath(agentDir);\n\tconst normalizedPath = resolve(file.path);\n\tconst parentDir = resolve(normalizedPath, \"..\");\n\treturn parentDir === resolvedAgentDir;\n}\n\nexport function loadProjectContextFiles(options: { cwd: string; agentDir: string }): ContextFile[] {\n\tconst resolvedCwd = resolvePath(options.cwd);\n\tconst resolvedAgentDir = resolvePath(options.agentDir);\n\n\tconst contextFiles: ContextFile[] = [];\n\tconst seenPaths = new Set<string>();\n\n\tconst globalContext = loadContextFileFromDir(resolvedAgentDir);\n\tif (globalContext) {\n\t\tcontextFiles.push({ ...globalContext, isGlobal: true });\n\t\tseenPaths.add(globalContext.path);\n\t}\n\n\tconst globalOverrides = loadOverrideFilesFromDir(resolvedAgentDir);\n\tfor (const override of globalOverrides) {\n\t\tif (!seenPaths.has(override.path)) {\n\t\t\tcontextFiles.push({ ...override, isGlobal: true });\n\t\t\tseenPaths.add(override.path);\n\t\t}\n\t}\n\n\tconst ancestorContextFiles: ContextFile[] = [];\n\n\tlet currentDir = resolvedCwd;\n\tconst root = resolve(\"/\");\n\n\twhile (true) {\n\t\tconst contextFile = loadContextFileFromDir(currentDir);\n\t\tif (contextFile && !seenPaths.has(contextFile.path)) {\n\t\t\tancestorContextFiles.unshift({\n\t\t\t\t...contextFile,\n\t\t\t\tisGlobal: isGlobalContextFile(contextFile, resolvedAgentDir),\n\t\t\t});\n\t\t\tseenPaths.add(contextFile.path);\n\t\t}\n\n\t\tconst dirOverrides = loadOverrideFilesFromDir(currentDir);\n\t\tfor (const override of dirOverrides) {\n\t\t\tif (!seenPaths.has(override.path)) {\n\t\t\t\tancestorContextFiles.unshift({\n\t\t\t\t\t...override,\n\t\t\t\t\tisGlobal: isGlobalContextFile(override, resolvedAgentDir),\n\t\t\t\t});\n\t\t\t\tseenPaths.add(override.path);\n\t\t\t}\n\t\t}\n\n\t\tif (currentDir === root) break;\n\n\t\tconst parentDir = resolve(currentDir, \"..\");\n\t\tif (parentDir === currentDir) break;\n\t\tcurrentDir = parentDir;\n\t}\n\n\tcontextFiles.push(...ancestorContextFiles);\n\n\treturn contextFiles;\n}\n\nexport interface DefaultResourceLoaderOptions {\n\tcwd: string;\n\tagentDir: string;\n\tsettingsManager?: SettingsManager;\n\teventBus?: EventBus;\n\tadditionalExtensionPaths?: string[];\n\tadditionalSkillPaths?: string[];\n\tadditionalPromptTemplatePaths?: string[];\n\tadditionalThemePaths?: string[];\n\textensionFactories?: ExtensionFactory[];\n\tnoExtensions?: boolean;\n\tnoSkills?: boolean;\n\tnoPromptTemplates?: boolean;\n\tnoThemes?: boolean;\n\tnoContextFiles?: boolean;\n\tsystemPrompt?: string;\n\tappendSystemPrompt?: string[];\n\textensionsOverride?: (base: LoadExtensionsResult) => LoadExtensionsResult;\n\tskillsOverride?: (base: { skills: Skill[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tskills: Skill[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tpromptsOverride?: (base: { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tprompts: PromptTemplate[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tthemesOverride?: (base: { themes: Theme[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tthemes: Theme[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tagentsFilesOverride?: (base: { agentsFiles: ContextFile[] }) => {\n\t\tagentsFiles: ContextFile[];\n\t};\n\tsystemPromptOverride?: (base: string | undefined) => string | undefined;\n\tappendSystemPromptOverride?: (base: string[]) => string[];\n}\n\nexport class DefaultResourceLoader implements ResourceLoader {\n\tprivate cwd: string;\n\tprivate agentDir: string;\n\tprivate settingsManager: SettingsManager;\n\tprivate eventBus: EventBus;\n\tprivate packageManager: DefaultPackageManager;\n\tprivate additionalExtensionPaths: string[];\n\tprivate additionalSkillPaths: string[];\n\tprivate additionalPromptTemplatePaths: string[];\n\tprivate additionalThemePaths: string[];\n\tprivate extensionFactories: ExtensionFactory[];\n\tprivate noExtensions: boolean;\n\tprivate noSkills: boolean;\n\tprivate noPromptTemplates: boolean;\n\tprivate noThemes: boolean;\n\tprivate noContextFiles: boolean;\n\tprivate systemPromptSource?: string;\n\tprivate appendSystemPromptSource?: string[];\n\tprivate extensionsOverride?: (base: LoadExtensionsResult) => LoadExtensionsResult;\n\tprivate skillsOverride?: (base: { skills: Skill[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tskills: Skill[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tprivate promptsOverride?: (base: { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tprompts: PromptTemplate[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tprivate themesOverride?: (base: { themes: Theme[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tthemes: Theme[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tprivate agentsFilesOverride?: (base: { agentsFiles: ContextFile[] }) => {\n\t\tagentsFiles: ContextFile[];\n\t};\n\tprivate systemPromptOverride?: (base: string | undefined) => string | undefined;\n\tprivate appendSystemPromptOverride?: (base: string[]) => string[];\n\n\tprivate extensionsResult: LoadExtensionsResult;\n\t/** Extensions from the most recent load (pre-override), tracked so their event-bus subscriptions can be disposed on reload. */\n\tprivate loadedExtensions: Extension[];\n\tprivate skills: Skill[];\n\tprivate skillDiagnostics: ResourceDiagnostic[];\n\tprivate prompts: PromptTemplate[];\n\tprivate promptDiagnostics: ResourceDiagnostic[];\n\tprivate themes: Theme[];\n\tprivate themeDiagnostics: ResourceDiagnostic[];\n\tprivate agentsFiles: ContextFile[];\n\tprivate systemPrompt?: string;\n\tprivate appendSystemPrompt: string[];\n\tprivate lastSkillPaths: string[];\n\tprivate resourceMetadataByPath: Map<string, PathMetadata>;\n\tprivate extensionSkillSourceInfos: Map<string, SourceInfo>;\n\tprivate extensionPromptSourceInfos: Map<string, SourceInfo>;\n\tprivate extensionThemeSourceInfos: Map<string, SourceInfo>;\n\tprivate lastPromptPaths: string[];\n\tprivate lastThemePaths: string[];\n\n\tconstructor(options: DefaultResourceLoaderOptions) {\n\t\tthis.cwd = resolvePath(options.cwd);\n\t\tthis.agentDir = resolvePath(options.agentDir);\n\t\tthis.settingsManager = options.settingsManager ?? SettingsManager.create(this.cwd, this.agentDir);\n\t\tthis.eventBus = options.eventBus ?? createEventBus();\n\t\tthis.packageManager = new DefaultPackageManager({\n\t\t\tcwd: this.cwd,\n\t\t\tagentDir: this.agentDir,\n\t\t\tsettingsManager: this.settingsManager,\n\t\t});\n\t\tthis.additionalExtensionPaths = options.additionalExtensionPaths ?? [];\n\t\tthis.additionalSkillPaths = options.additionalSkillPaths ?? [];\n\t\tthis.additionalPromptTemplatePaths = options.additionalPromptTemplatePaths ?? [];\n\t\tthis.additionalThemePaths = options.additionalThemePaths ?? [];\n\t\tthis.extensionFactories = options.extensionFactories ?? [];\n\t\tthis.noExtensions = options.noExtensions ?? false;\n\t\tthis.noSkills = options.noSkills ?? false;\n\t\tthis.noPromptTemplates = options.noPromptTemplates ?? false;\n\t\tthis.noThemes = options.noThemes ?? false;\n\t\tthis.noContextFiles = options.noContextFiles ?? false;\n\t\tthis.systemPromptSource = options.systemPrompt;\n\t\tthis.appendSystemPromptSource = options.appendSystemPrompt;\n\t\tthis.extensionsOverride = options.extensionsOverride;\n\t\tthis.skillsOverride = options.skillsOverride;\n\t\tthis.promptsOverride = options.promptsOverride;\n\t\tthis.themesOverride = options.themesOverride;\n\t\tthis.agentsFilesOverride = options.agentsFilesOverride;\n\t\tthis.systemPromptOverride = options.systemPromptOverride;\n\t\tthis.appendSystemPromptOverride = options.appendSystemPromptOverride;\n\n\t\tthis.extensionsResult = { extensions: [], errors: [], runtime: createExtensionRuntime() };\n\t\tthis.loadedExtensions = [];\n\t\tthis.skills = [];\n\t\tthis.skillDiagnostics = [];\n\t\tthis.prompts = [];\n\t\tthis.promptDiagnostics = [];\n\t\tthis.themes = [];\n\t\tthis.themeDiagnostics = [];\n\t\tthis.agentsFiles = [];\n\t\tthis.appendSystemPrompt = [];\n\t\tthis.lastSkillPaths = [];\n\t\tthis.resourceMetadataByPath = new Map();\n\t\tthis.extensionSkillSourceInfos = new Map();\n\t\tthis.extensionPromptSourceInfos = new Map();\n\t\tthis.extensionThemeSourceInfos = new Map();\n\t\tthis.lastPromptPaths = [];\n\t\tthis.lastThemePaths = [];\n\t}\n\n\tgetExtensions(): LoadExtensionsResult {\n\t\treturn this.extensionsResult;\n\t}\n\n\tgetSkills(): { skills: Skill[]; diagnostics: ResourceDiagnostic[] } {\n\t\treturn { skills: this.skills, diagnostics: this.skillDiagnostics };\n\t}\n\n\tgetPrompts(): { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] } {\n\t\treturn { prompts: this.prompts, diagnostics: this.promptDiagnostics };\n\t}\n\n\tgetThemes(): { themes: Theme[]; diagnostics: ResourceDiagnostic[] } {\n\t\treturn { themes: this.themes, diagnostics: this.themeDiagnostics };\n\t}\n\n\tgetAgentsFiles(): { agentsFiles: ContextFile[] } {\n\t\treturn { agentsFiles: this.agentsFiles };\n\t}\n\n\tgetSystemPrompt(): string | undefined {\n\t\treturn this.systemPrompt;\n\t}\n\n\tgetAppendSystemPrompt(): string[] {\n\t\treturn this.appendSystemPrompt;\n\t}\n\n\textendResources(paths: ResourceExtensionPaths): void {\n\t\tconst skillPaths = this.normalizeExtensionPaths(paths.skillPaths ?? []);\n\t\tconst promptPaths = this.normalizeExtensionPaths(paths.promptPaths ?? []);\n\t\tconst themePaths = this.normalizeExtensionPaths(paths.themePaths ?? []);\n\n\t\tfor (const entry of skillPaths) {\n\t\t\tthis.resourceMetadataByPath.set(entry.path, entry.metadata);\n\t\t\tthis.extensionSkillSourceInfos.set(entry.path, createSourceInfo(entry.path, entry.metadata));\n\t\t}\n\t\tfor (const entry of promptPaths) {\n\t\t\tthis.resourceMetadataByPath.set(entry.path, entry.metadata);\n\t\t\tthis.extensionPromptSourceInfos.set(entry.path, createSourceInfo(entry.path, entry.metadata));\n\t\t}\n\t\tfor (const entry of themePaths) {\n\t\t\tthis.resourceMetadataByPath.set(entry.path, entry.metadata);\n\t\t\tthis.extensionThemeSourceInfos.set(entry.path, createSourceInfo(entry.path, entry.metadata));\n\t\t}\n\n\t\tif (skillPaths.length > 0) {\n\t\t\tthis.lastSkillPaths = this.mergePaths(\n\t\t\t\tthis.lastSkillPaths,\n\t\t\t\tskillPaths.map((entry) => entry.path),\n\t\t\t);\n\t\t\tthis.updateSkillsFromPaths(this.lastSkillPaths, this.resourceMetadataByPath);\n\t\t}\n\n\t\tif (promptPaths.length > 0) {\n\t\t\tthis.lastPromptPaths = this.mergePaths(\n\t\t\t\tthis.lastPromptPaths,\n\t\t\t\tpromptPaths.map((entry) => entry.path),\n\t\t\t);\n\t\t\tthis.updatePromptsFromPaths(this.lastPromptPaths, this.resourceMetadataByPath);\n\t\t}\n\n\t\tif (themePaths.length > 0) {\n\t\t\tthis.lastThemePaths = this.mergePaths(\n\t\t\t\tthis.lastThemePaths,\n\t\t\t\tthemePaths.map((entry) => entry.path),\n\t\t\t);\n\t\t\tthis.updateThemesFromPaths(this.lastThemePaths, this.resourceMetadataByPath);\n\t\t}\n\t}\n\n\tasync reload(): Promise<void> {\n\t\tawait this.settingsManager.reload();\n\t\tconst resolvedPaths = await this.packageManager.resolve();\n\t\tconst cliExtensionPaths = await this.packageManager.resolveExtensionSources(this.additionalExtensionPaths, {\n\t\t\ttemporary: true,\n\t\t});\n\t\tconst metadataByPath = new Map<string, PathMetadata>();\n\t\tthis.resourceMetadataByPath = metadataByPath;\n\n\t\tthis.extensionSkillSourceInfos = new Map();\n\t\tthis.extensionPromptSourceInfos = new Map();\n\t\tthis.extensionThemeSourceInfos = new Map();\n\n\t\t// Helper to extract enabled paths and store metadata\n\t\tconst getEnabledResources = (\n\t\t\tresources: Array<{ path: string; enabled: boolean; metadata: PathMetadata }>,\n\t\t): Array<{ path: string; enabled: boolean; metadata: PathMetadata }> => {\n\t\t\tfor (const r of resources) {\n\t\t\t\tif (!metadataByPath.has(r.path)) {\n\t\t\t\t\tmetadataByPath.set(r.path, r.metadata);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn resources.filter((r) => r.enabled);\n\t\t};\n\n\t\tconst getEnabledPaths = (\n\t\t\tresources: Array<{ path: string; enabled: boolean; metadata: PathMetadata }>,\n\t\t): string[] => getEnabledResources(resources).map((r) => r.path);\n\t\tconst enabledExtensions = getEnabledPaths(resolvedPaths.extensions);\n\t\tconst enabledSkillResources = getEnabledResources(resolvedPaths.skills).filter(\n\t\t\t(resource) => !isLegacyAutoSkillResource(resource),\n\t\t);\n\t\tconst enabledPrompts = getEnabledPaths(resolvedPaths.prompts);\n\t\tconst enabledThemes = getEnabledPaths(resolvedPaths.themes);\n\n\t\tconst mapSkillPath = (resource: { path: string; metadata: PathMetadata }): string => {\n\t\t\tif (resource.metadata.source !== \"auto\" && resource.metadata.origin !== \"package\") {\n\t\t\t\treturn resource.path;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst stats = statSync(resource.path);\n\t\t\t\tif (!stats.isDirectory()) {\n\t\t\t\t\treturn resource.path;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\treturn resource.path;\n\t\t\t}\n\t\t\tconst skillFile = join(resource.path, \"SKILL.md\");\n\t\t\tif (existsSync(skillFile)) {\n\t\t\t\tif (!metadataByPath.has(skillFile)) {\n\t\t\t\t\tmetadataByPath.set(skillFile, resource.metadata);\n\t\t\t\t}\n\t\t\t\treturn skillFile;\n\t\t\t}\n\t\t\treturn resource.path;\n\t\t};\n\n\t\tconst enabledSkills = enabledSkillResources.map(mapSkillPath);\n\n\t\t// Add CLI paths metadata\n\t\tfor (const r of cliExtensionPaths.extensions) {\n\t\t\tif (!metadataByPath.has(r.path)) {\n\t\t\t\tmetadataByPath.set(r.path, { source: \"cli\", scope: \"temporary\", origin: \"top-level\" });\n\t\t\t}\n\t\t}\n\t\tfor (const r of cliExtensionPaths.skills) {\n\t\t\tif (!metadataByPath.has(r.path)) {\n\t\t\t\tmetadataByPath.set(r.path, { source: \"cli\", scope: \"temporary\", origin: \"top-level\" });\n\t\t\t}\n\t\t}\n\n\t\tconst cliEnabledExtensions = getEnabledPaths(cliExtensionPaths.extensions);\n\t\tconst cliEnabledSkills = getEnabledPaths(cliExtensionPaths.skills);\n\t\tconst cliEnabledPrompts = getEnabledPaths(cliExtensionPaths.prompts);\n\t\tconst cliEnabledThemes = getEnabledPaths(cliExtensionPaths.themes);\n\n\t\tconst extensionPaths = this.noExtensions\n\t\t\t? cliEnabledExtensions\n\t\t\t: this.mergePaths(cliEnabledExtensions, enabledExtensions);\n\n\t\t// Unsubscribe event-bus listeners registered by the previous load before\n\t\t// re-running extension factories, so /reload does not stack duplicate\n\t\t// handlers (or keep stale extensions alive) on the shared event bus.\n\t\tthis.disposeExtensionEventSubscriptions();\n\n\t\tconst extensionsResult = await loadExtensions(extensionPaths, this.cwd, this.eventBus);\n\n\t\t// Keep exactly one visible task list. A user-installed `todo` extension owns\n\t\t// the checklist UI; otherwise fall back to OMK's built-in `update_todo`.\n\t\t// Set OMK_TODO_CHECKLIST=0 to disable the built-in explicitly.\n\t\tconst hasTodoExtension = extensionsResult.extensions.some((extension) => extension.tools.has(\"todo\"));\n\t\tif (!isDisabledByEnv(process.env.OMK_TODO_CHECKLIST) && !hasTodoExtension) {\n\t\t\tconst todoExtension = await loadExtensionFromFactory(\n\t\t\t\ttodoChecklist,\n\t\t\t\tthis.cwd,\n\t\t\t\tthis.eventBus,\n\t\t\t\textensionsResult.runtime,\n\t\t\t\t\"<builtin:todo-checklist>\",\n\t\t\t);\n\t\t\textensionsResult.extensions.unshift(todoExtension);\n\t\t}\n\n\t\t// Built-in command-safety gate runs FIRST (unless YOLO / OMK_COMMAND_SAFETY=0).\n\t\t// tool_call block short-circuits on first block; user_bash on first truthy result.\n\t\tconst yoloOff =\n\t\t\tprocess.env.OMK_YOLO === \"1\" ||\n\t\t\tprocess.env.OMK_YOLO === \"true\" ||\n\t\t\tprocess.env.OMK_DISABLE_COMMAND_SAFETY === \"1\" ||\n\t\t\tprocess.env.OMK_DISABLE_COMMAND_SAFETY === \"true\" ||\n\t\t\tisDisabledByEnv(process.env.OMK_COMMAND_SAFETY);\n\t\tif (!yoloOff) {\n\t\t\tconst commandSafetyExtension = await loadExtensionFromFactory(\n\t\t\t\tcommandSafetyGate,\n\t\t\t\tthis.cwd,\n\t\t\t\tthis.eventBus,\n\t\t\t\textensionsResult.runtime,\n\t\t\t\t\"<builtin:command-safety>\",\n\t\t\t);\n\t\t\textensionsResult.extensions.unshift(commandSafetyExtension);\n\t\t}\n\n\t\tconst harnessFactories: Array<{ factory: ExtensionFactory; path: string; disabled: boolean }> = [\n\t\t\t{\n\t\t\t\tfactory: identicalLoop,\n\t\t\t\tpath: \"<builtin:identical-loop>\",\n\t\t\t\tdisabled: isDisabledByEnv(process.env.OMK_IDENTICAL_LOOP),\n\t\t\t},\n\t\t\t{\n\t\t\t\tfactory: toolPairRepair,\n\t\t\t\tpath: \"<builtin:tool-pair-repair>\",\n\t\t\t\tdisabled: isDisabledByEnv(process.env.OMK_TOOL_PAIR_REPAIR),\n\t\t\t},\n\t\t\t{\n\t\t\t\tfactory: promptPreset,\n\t\t\t\tpath: \"<builtin:prompt-preset>\",\n\t\t\t\tdisabled: isDisabledByEnv(process.env.OMK_PROMPT_PRESET),\n\t\t\t},\n\t\t\t{\n\t\t\t\tfactory: goalController,\n\t\t\t\tpath: \"<builtin:goal-controller>\",\n\t\t\t\tdisabled: isDisabledByEnv(process.env.OMK_GOAL_CONTROLLER),\n\t\t\t},\n\t\t];\n\t\tfor (const { factory, path, disabled } of harnessFactories) {\n\t\t\tif (disabled) continue;\n\t\t\tconst extension = await loadExtensionFromFactory(\n\t\t\t\tfactory,\n\t\t\t\tthis.cwd,\n\t\t\t\tthis.eventBus,\n\t\t\t\textensionsResult.runtime,\n\t\t\t\tpath,\n\t\t\t);\n\t\t\textensionsResult.extensions.push(extension);\n\t\t}\n\n\t\tconst inlineExtensions = await this.loadExtensionFactories(extensionsResult.runtime);\n\t\textensionsResult.extensions.push(...inlineExtensions.extensions);\n\t\textensionsResult.errors.push(...inlineExtensions.errors);\n\t\tthis.loadedExtensions = [...extensionsResult.extensions];\n\n\t\t// Detect extension conflicts (tools, commands, flags with same names from different extensions)\n\t\t// Keep all extensions loaded. Conflicts are reported as diagnostics, and precedence is handled by load order.\n\t\tconst conflicts = this.detectExtensionConflicts(extensionsResult.extensions);\n\t\tfor (const conflict of conflicts) {\n\t\t\textensionsResult.errors.push({ path: conflict.path, error: conflict.message });\n\t\t}\n\n\t\tfor (const p of this.additionalExtensionPaths) {\n\t\t\tif (isLocalPath(p)) {\n\t\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\t\tif (!existsSync(resolved)) {\n\t\t\t\t\textensionsResult.errors.push({ path: resolved, error: `Extension path does not exist: ${resolved}` });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tthis.extensionsResult = this.extensionsOverride ? this.extensionsOverride(extensionsResult) : extensionsResult;\n\t\tthis.applyExtensionSourceInfo(this.extensionsResult.extensions, metadataByPath);\n\n\t\tconst skillPaths = this.noSkills\n\t\t\t? this.mergePaths(cliEnabledSkills, this.additionalSkillPaths)\n\t\t\t: this.mergePaths([...cliEnabledSkills, ...enabledSkills], this.additionalSkillPaths);\n\n\t\tthis.lastSkillPaths = skillPaths;\n\t\tthis.updateSkillsFromPaths(skillPaths, metadataByPath);\n\t\tfor (const p of this.additionalSkillPaths) {\n\t\t\tif (isLocalPath(p)) {\n\t\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\t\tif (!existsSync(resolved) && !this.skillDiagnostics.some((d) => d.path === resolved)) {\n\t\t\t\t\tthis.skillDiagnostics.push({ type: \"error\", message: \"Skill path does not exist\", path: resolved });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst promptPaths = this.noPromptTemplates\n\t\t\t? this.mergePaths(cliEnabledPrompts, this.additionalPromptTemplatePaths)\n\t\t\t: this.mergePaths([...cliEnabledPrompts, ...enabledPrompts], this.additionalPromptTemplatePaths);\n\n\t\tthis.lastPromptPaths = promptPaths;\n\t\tthis.updatePromptsFromPaths(promptPaths, metadataByPath);\n\t\tfor (const p of this.additionalPromptTemplatePaths) {\n\t\t\tif (isLocalPath(p)) {\n\t\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\t\tif (!existsSync(resolved) && !this.promptDiagnostics.some((d) => d.path === resolved)) {\n\t\t\t\t\tthis.promptDiagnostics.push({\n\t\t\t\t\t\ttype: \"error\",\n\t\t\t\t\t\tmessage: \"Prompt template path does not exist\",\n\t\t\t\t\t\tpath: resolved,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst themePaths = this.noThemes\n\t\t\t? this.mergePaths(cliEnabledThemes, this.additionalThemePaths)\n\t\t\t: this.mergePaths([...cliEnabledThemes, ...enabledThemes], this.additionalThemePaths);\n\n\t\tthis.lastThemePaths = themePaths;\n\t\tthis.updateThemesFromPaths(themePaths, metadataByPath);\n\t\tfor (const p of this.additionalThemePaths) {\n\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\tif (!existsSync(resolved) && !this.themeDiagnostics.some((d) => d.path === resolved)) {\n\t\t\t\tthis.themeDiagnostics.push({ type: \"error\", message: \"Theme path does not exist\", path: resolved });\n\t\t\t}\n\t\t}\n\n\t\tconst agentsFiles = {\n\t\t\tagentsFiles: this.noContextFiles ? [] : loadProjectContextFiles({ cwd: this.cwd, agentDir: this.agentDir }),\n\t\t};\n\t\tconst resolvedAgentsFiles = this.agentsFilesOverride ? this.agentsFilesOverride(agentsFiles) : agentsFiles;\n\t\tthis.agentsFiles = resolvedAgentsFiles.agentsFiles;\n\n\t\tconst baseSystemPrompt = resolvePromptInput(\n\t\t\tthis.systemPromptSource ?? this.discoverSystemPromptFile(),\n\t\t\t\"system prompt\",\n\t\t);\n\t\tthis.systemPrompt = this.systemPromptOverride ? this.systemPromptOverride(baseSystemPrompt) : baseSystemPrompt;\n\n\t\tconst appendSources =\n\t\t\tthis.appendSystemPromptSource ??\n\t\t\t(this.discoverAppendSystemPromptFile() ? [this.discoverAppendSystemPromptFile()!] : []);\n\t\tconst baseAppend = appendSources\n\t\t\t.map((s) => resolvePromptInput(s, \"append system prompt\"))\n\t\t\t.filter((s): s is string => s !== undefined);\n\t\tthis.appendSystemPrompt = this.appendSystemPromptOverride\n\t\t\t? this.appendSystemPromptOverride(baseAppend)\n\t\t\t: baseAppend;\n\t}\n\n\tprivate normalizeExtensionPaths(\n\t\tentries: Array<{ path: string; metadata: PathMetadata }>,\n\t): Array<{ path: string; metadata: PathMetadata }> {\n\t\treturn entries.map((entry) => {\n\t\t\tconst metadata = entry.metadata.baseDir\n\t\t\t\t? { ...entry.metadata, baseDir: this.resolveResourcePath(entry.metadata.baseDir) }\n\t\t\t\t: entry.metadata;\n\t\t\treturn {\n\t\t\t\tpath: this.resolveResourcePath(entry.path),\n\t\t\t\tmetadata,\n\t\t\t};\n\t\t});\n\t}\n\n\tprivate updateSkillsFromPaths(skillPaths: string[], metadataByPath?: Map<string, PathMetadata>): void {\n\t\tlet skillsResult: { skills: Skill[]; diagnostics: ResourceDiagnostic[] };\n\t\tif (this.noSkills && skillPaths.length === 0) {\n\t\t\tskillsResult = { skills: [], diagnostics: [] };\n\t\t} else {\n\t\t\tskillsResult = loadSkillsWithBundled(this.noSkills, {\n\t\t\t\tcwd: this.cwd,\n\t\t\t\tagentDir: this.agentDir,\n\t\t\t\tskillPaths,\n\t\t\t\tincludeDefaults: false,\n\t\t\t\tresolveSourceInfo: (filePath) =>\n\t\t\t\t\tthis.findSourceInfoForPath(filePath, this.extensionSkillSourceInfos, metadataByPath),\n\t\t\t});\n\t\t}\n\t\tconst resolvedSkills = this.skillsOverride ? this.skillsOverride(skillsResult) : skillsResult;\n\t\tthis.skills = resolvedSkills.skills.map((skill) => ({\n\t\t\t...skill,\n\t\t\tsourceInfo:\n\t\t\t\tthis.findSourceInfoForPath(skill.filePath, this.extensionSkillSourceInfos, metadataByPath) ??\n\t\t\t\tskill.sourceInfo ??\n\t\t\t\tthis.getDefaultSourceInfoForPath(skill.filePath),\n\t\t}));\n\t\tthis.skillDiagnostics = this.enrichCollisionDiagnostics(\n\t\t\tresolvedSkills.diagnostics,\n\t\t\tthis.extensionSkillSourceInfos,\n\t\t\tmetadataByPath,\n\t\t);\n\t}\n\n\tprivate enrichCollisionDiagnostics(\n\t\tdiagnostics: ResourceDiagnostic[],\n\t\textraSourceInfos?: Map<string, SourceInfo>,\n\t\tmetadataByPath?: Map<string, PathMetadata>,\n\t): ResourceDiagnostic[] {\n\t\treturn diagnostics.map((diagnostic) => {\n\t\t\tconst collision = diagnostic.collision;\n\t\t\tif (!collision) {\n\t\t\t\treturn diagnostic;\n\t\t\t}\n\n\t\t\tconst winnerSourceInfo = this.findSourceInfoForPath(collision.winnerPath, extraSourceInfos, metadataByPath);\n\t\t\tconst loserSourceInfo = this.findSourceInfoForPath(collision.loserPath, extraSourceInfos, metadataByPath);\n\t\t\tif (!winnerSourceInfo && !loserSourceInfo) {\n\t\t\t\treturn diagnostic;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\t...diagnostic,\n\t\t\t\tcollision: {\n\t\t\t\t\t...collision,\n\t\t\t\t\twinnerSource: winnerSourceInfo?.source ?? collision.winnerSource,\n\t\t\t\t\tloserSource: loserSourceInfo?.source ?? collision.loserSource,\n\t\t\t\t\twinnerScope: winnerSourceInfo?.scope ?? collision.winnerScope,\n\t\t\t\t\tloserScope: loserSourceInfo?.scope ?? collision.loserScope,\n\t\t\t\t\twinnerOrigin: winnerSourceInfo?.origin ?? collision.winnerOrigin,\n\t\t\t\t\tloserOrigin: loserSourceInfo?.origin ?? collision.loserOrigin,\n\t\t\t\t},\n\t\t\t};\n\t\t});\n\t}\n\n\tprivate updatePromptsFromPaths(promptPaths: string[], metadataByPath?: Map<string, PathMetadata>): void {\n\t\tlet promptsResult: { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] };\n\t\tif (this.noPromptTemplates && promptPaths.length === 0) {\n\t\t\tpromptsResult = { prompts: [], diagnostics: [] };\n\t\t} else {\n\t\t\tconst allPrompts = loadPromptTemplates({\n\t\t\t\tcwd: this.cwd,\n\t\t\t\tagentDir: this.agentDir,\n\t\t\t\tpromptPaths,\n\t\t\t\tincludeDefaults: false,\n\t\t\t});\n\t\t\tpromptsResult = this.dedupePrompts(allPrompts);\n\t\t}\n\t\tconst resolvedPrompts = this.promptsOverride ? this.promptsOverride(promptsResult) : promptsResult;\n\t\tthis.prompts = resolvedPrompts.prompts.map((prompt) => ({\n\t\t\t...prompt,\n\t\t\tsourceInfo:\n\t\t\t\tthis.findSourceInfoForPath(prompt.filePath, this.extensionPromptSourceInfos, metadataByPath) ??\n\t\t\t\tprompt.sourceInfo ??\n\t\t\t\tthis.getDefaultSourceInfoForPath(prompt.filePath),\n\t\t}));\n\t\tthis.promptDiagnostics = resolvedPrompts.diagnostics;\n\t}\n\n\tprivate updateThemesFromPaths(themePaths: string[], metadataByPath?: Map<string, PathMetadata>): void {\n\t\tlet themesResult: { themes: Theme[]; diagnostics: ResourceDiagnostic[] };\n\t\tif (this.noThemes && themePaths.length === 0) {\n\t\t\tthemesResult = { themes: [], diagnostics: [] };\n\t\t} else {\n\t\t\tconst loaded = this.loadThemes(themePaths, false);\n\t\t\tconst deduped = this.dedupeThemes(loaded.themes);\n\t\t\tthemesResult = { themes: deduped.themes, diagnostics: [...loaded.diagnostics, ...deduped.diagnostics] };\n\t\t}\n\t\tconst resolvedThemes = this.themesOverride ? this.themesOverride(themesResult) : themesResult;\n\t\tthis.themes = resolvedThemes.themes.map((theme) => {\n\t\t\tconst sourcePath = theme.sourcePath;\n\t\t\ttheme.sourceInfo = sourcePath\n\t\t\t\t? (this.findSourceInfoForPath(sourcePath, this.extensionThemeSourceInfos, metadataByPath) ??\n\t\t\t\t\ttheme.sourceInfo ??\n\t\t\t\t\tthis.getDefaultSourceInfoForPath(sourcePath))\n\t\t\t\t: theme.sourceInfo;\n\t\t\treturn theme;\n\t\t});\n\t\tthis.themeDiagnostics = resolvedThemes.diagnostics;\n\t}\n\n\tprivate applyExtensionSourceInfo(extensions: Extension[], metadataByPath: Map<string, PathMetadata>): void {\n\t\tfor (const extension of extensions) {\n\t\t\textension.sourceInfo =\n\t\t\t\tthis.findSourceInfoForPath(extension.path, undefined, metadataByPath) ??\n\t\t\t\tthis.getDefaultSourceInfoForPath(extension.path);\n\t\t\tfor (const command of extension.commands.values()) {\n\t\t\t\tcommand.sourceInfo = extension.sourceInfo;\n\t\t\t}\n\t\t\tfor (const tool of extension.tools.values()) {\n\t\t\t\ttool.sourceInfo = extension.sourceInfo;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate findSourceInfoForPath(\n\t\tresourcePath: string,\n\t\textraSourceInfos?: Map<string, SourceInfo>,\n\t\tmetadataByPath?: Map<string, PathMetadata>,\n\t): SourceInfo | undefined {\n\t\tif (!resourcePath) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (resourcePath.startsWith(\"<\")) {\n\t\t\treturn this.getDefaultSourceInfoForPath(resourcePath);\n\t\t}\n\n\t\tconst normalizedResourcePath = resolve(resourcePath);\n\t\tif (extraSourceInfos) {\n\t\t\tfor (const [sourcePath, sourceInfo] of extraSourceInfos.entries()) {\n\t\t\t\tconst normalizedSourcePath = resolve(sourcePath);\n\t\t\t\tif (\n\t\t\t\t\tnormalizedResourcePath === normalizedSourcePath ||\n\t\t\t\t\tnormalizedResourcePath.startsWith(`${normalizedSourcePath}${sep}`)\n\t\t\t\t) {\n\t\t\t\t\treturn { ...sourceInfo, path: resourcePath };\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (metadataByPath) {\n\t\t\tconst exact = metadataByPath.get(normalizedResourcePath) ?? metadataByPath.get(resourcePath);\n\t\t\tif (exact) {\n\t\t\t\treturn createSourceInfo(resourcePath, exact);\n\t\t\t}\n\n\t\t\tfor (const [sourcePath, metadata] of metadataByPath.entries()) {\n\t\t\t\tconst normalizedSourcePath = resolve(sourcePath);\n\t\t\t\tif (\n\t\t\t\t\tnormalizedResourcePath === normalizedSourcePath ||\n\t\t\t\t\tnormalizedResourcePath.startsWith(`${normalizedSourcePath}${sep}`)\n\t\t\t\t) {\n\t\t\t\t\treturn createSourceInfo(resourcePath, metadata);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate getDefaultSourceInfoForPath(filePath: string): SourceInfo {\n\t\tif (filePath.startsWith(\"<\") && filePath.endsWith(\">\")) {\n\t\t\treturn {\n\t\t\t\tpath: filePath,\n\t\t\t\tsource: filePath.slice(1, -1).split(\":\")[0] || \"temporary\",\n\t\t\t\tscope: \"temporary\",\n\t\t\t\torigin: \"top-level\",\n\t\t\t};\n\t\t}\n\n\t\tconst normalizedPath = resolve(filePath);\n\t\tconst agentRoots = [\n\t\t\tjoin(this.agentDir, \"skills\"),\n\t\t\tjoin(this.agentDir, \"prompts\"),\n\t\t\tjoin(this.agentDir, \"themes\"),\n\t\t\tjoin(this.agentDir, \"extensions\"),\n\t\t];\n\t\tconst projectRoots = [\n\t\t\tjoin(this.cwd, CONFIG_DIR_NAME, \"skills\"),\n\t\t\tjoin(this.cwd, CONFIG_DIR_NAME, \"prompts\"),\n\t\t\tjoin(this.cwd, CONFIG_DIR_NAME, \"themes\"),\n\t\t\tjoin(this.cwd, CONFIG_DIR_NAME, \"extensions\"),\n\t\t];\n\n\t\tfor (const root of agentRoots) {\n\t\t\tif (this.isUnderPath(normalizedPath, root)) {\n\t\t\t\treturn { path: filePath, source: \"local\", scope: \"user\", origin: \"top-level\", baseDir: root };\n\t\t\t}\n\t\t}\n\n\t\tfor (const root of projectRoots) {\n\t\t\tif (this.isUnderPath(normalizedPath, root)) {\n\t\t\t\treturn { path: filePath, source: \"local\", scope: \"project\", origin: \"top-level\", baseDir: root };\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tpath: filePath,\n\t\t\tsource: \"local\",\n\t\t\tscope: \"temporary\",\n\t\t\torigin: \"top-level\",\n\t\t\tbaseDir: statSync(normalizedPath).isDirectory() ? normalizedPath : resolve(normalizedPath, \"..\"),\n\t\t};\n\t}\n\n\tprivate mergePaths(primary: string[], additional: string[]): string[] {\n\t\tconst merged: string[] = [];\n\t\tconst seen = new Set<string>();\n\n\t\tfor (const p of [...primary, ...additional]) {\n\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\tconst canonicalPath = canonicalizePath(resolved);\n\t\t\tif (seen.has(canonicalPath)) continue;\n\t\t\tseen.add(canonicalPath);\n\t\t\tmerged.push(resolved);\n\t\t}\n\n\t\treturn merged;\n\t}\n\n\tprivate resolveResourcePath(p: string): string {\n\t\treturn resolvePath(p, this.cwd, { trim: true });\n\t}\n\n\tprivate loadThemes(\n\t\tpaths: string[],\n\t\tincludeDefaults: boolean = true,\n\t): {\n\t\tthemes: Theme[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t} {\n\t\tconst themes: Theme[] = [];\n\t\tconst diagnostics: ResourceDiagnostic[] = [];\n\t\tif (includeDefaults) {\n\t\t\tconst defaultDirs = [join(this.agentDir, \"themes\"), join(this.cwd, CONFIG_DIR_NAME, \"themes\")];\n\n\t\t\tfor (const dir of defaultDirs) {\n\t\t\t\tthis.loadThemesFromDir(dir, themes, diagnostics);\n\t\t\t}\n\t\t}\n\n\t\tfor (const p of paths) {\n\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\tif (!existsSync(resolved)) {\n\t\t\t\tdiagnostics.push({ type: \"warning\", message: \"theme path does not exist\", path: resolved });\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst stats = statSync(resolved);\n\t\t\t\tif (stats.isDirectory()) {\n\t\t\t\t\tthis.loadThemesFromDir(resolved, themes, diagnostics);\n\t\t\t\t} else if (stats.isFile() && resolved.endsWith(\".json\")) {\n\t\t\t\t\tthis.loadThemeFromFile(resolved, themes, diagnostics);\n\t\t\t\t} else {\n\t\t\t\t\tdiagnostics.push({ type: \"warning\", message: \"theme path is not a json file\", path: resolved });\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : \"failed to read theme path\";\n\t\t\t\tdiagnostics.push({ type: \"warning\", message, path: resolved });\n\t\t\t}\n\t\t}\n\n\t\treturn { themes, diagnostics };\n\t}\n\n\tprivate loadThemesFromDir(dir: string, themes: Theme[], diagnostics: ResourceDiagnostic[]): void {\n\t\tif (!existsSync(dir)) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\t\t\tfor (const entry of entries) {\n\t\t\t\tlet isFile = entry.isFile();\n\t\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tisFile = statSync(join(dir, entry.name)).isFile();\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!isFile) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (!entry.name.endsWith(\".json\")) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tthis.loadThemeFromFile(join(dir, entry.name), themes, diagnostics);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to read theme directory\";\n\t\t\tdiagnostics.push({ type: \"warning\", message, path: dir });\n\t\t}\n\t}\n\n\tprivate loadThemeFromFile(filePath: string, themes: Theme[], diagnostics: ResourceDiagnostic[]): void {\n\t\ttry {\n\t\t\tthemes.push(loadThemeFromPath(filePath));\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to load theme\";\n\t\t\tdiagnostics.push({ type: \"warning\", message, path: filePath });\n\t\t}\n\t}\n\n\t/**\n\t * Unsubscribe event listeners registered by the previously loaded\n\t * extensions. Only subscriptions made through the extension API are\n\t * removed, so listeners added directly on an injected event bus survive.\n\t */\n\tprivate disposeExtensionEventSubscriptions(): void {\n\t\tfor (const extension of this.loadedExtensions) {\n\t\t\tconst subscriptions = extension.eventBusSubscriptions;\n\t\t\tif (!subscriptions) continue;\n\t\t\tfor (const unsubscribe of subscriptions) {\n\t\t\t\tunsubscribe();\n\t\t\t}\n\t\t\tsubscriptions.length = 0;\n\t\t}\n\t\tthis.loadedExtensions = [];\n\t}\n\n\tprivate async loadExtensionFactories(runtime: ExtensionRuntime): Promise<{\n\t\textensions: Extension[];\n\t\terrors: Array<{ path: string; error: string }>;\n\t}> {\n\t\tconst extensions: Extension[] = [];\n\t\tconst errors: Array<{ path: string; error: string }> = [];\n\n\t\tfor (const [index, factory] of this.extensionFactories.entries()) {\n\t\t\tconst extensionPath = `<inline:${index + 1}>`;\n\t\t\ttry {\n\t\t\t\tconst extension = await loadExtensionFromFactory(factory, this.cwd, this.eventBus, runtime, extensionPath);\n\t\t\t\textensions.push(extension);\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : \"failed to load extension\";\n\t\t\t\terrors.push({ path: extensionPath, error: message });\n\t\t\t}\n\t\t}\n\n\t\treturn { extensions, errors };\n\t}\n\n\tprivate dedupePrompts(prompts: PromptTemplate[]): { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] } {\n\t\tconst seen = new Map<string, PromptTemplate>();\n\t\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\t\tfor (const prompt of prompts) {\n\t\t\tconst existing = seen.get(prompt.name);\n\t\t\tif (existing) {\n\t\t\t\tdiagnostics.push({\n\t\t\t\t\ttype: \"collision\",\n\t\t\t\t\tmessage: `name \"/${prompt.name}\" collision`,\n\t\t\t\t\tpath: prompt.filePath,\n\t\t\t\t\tcollision: {\n\t\t\t\t\t\tresourceType: \"prompt\",\n\t\t\t\t\t\tname: prompt.name,\n\t\t\t\t\t\twinnerPath: existing.filePath,\n\t\t\t\t\t\tloserPath: prompt.filePath,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tseen.set(prompt.name, prompt);\n\t\t\t}\n\t\t}\n\n\t\treturn { prompts: Array.from(seen.values()), diagnostics };\n\t}\n\n\tprivate dedupeThemes(themes: Theme[]): { themes: Theme[]; diagnostics: ResourceDiagnostic[] } {\n\t\tconst seen = new Map<string, Theme>();\n\t\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\t\tfor (const t of themes) {\n\t\t\tconst name = t.name ?? \"unnamed\";\n\t\t\tconst existing = seen.get(name);\n\t\t\tif (existing) {\n\t\t\t\tdiagnostics.push({\n\t\t\t\t\ttype: \"collision\",\n\t\t\t\t\tmessage: `name \"${name}\" collision`,\n\t\t\t\t\tpath: t.sourcePath,\n\t\t\t\t\tcollision: {\n\t\t\t\t\t\tresourceType: \"theme\",\n\t\t\t\t\t\tname,\n\t\t\t\t\t\twinnerPath: existing.sourcePath ?? \"<builtin>\",\n\t\t\t\t\t\tloserPath: t.sourcePath ?? \"<builtin>\",\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tseen.set(name, t);\n\t\t\t}\n\t\t}\n\n\t\treturn { themes: Array.from(seen.values()), diagnostics };\n\t}\n\n\tprivate discoverSystemPromptFile(): string | undefined {\n\t\tconst projectPath = join(this.cwd, CONFIG_DIR_NAME, \"SYSTEM.md\");\n\t\tif (existsSync(projectPath)) {\n\t\t\treturn projectPath;\n\t\t}\n\n\t\tconst globalPath = join(this.agentDir, \"SYSTEM.md\");\n\t\tif (existsSync(globalPath)) {\n\t\t\treturn globalPath;\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate discoverAppendSystemPromptFile(): string | undefined {\n\t\tconst projectPath = join(this.cwd, CONFIG_DIR_NAME, \"APPEND_SYSTEM.md\");\n\t\tif (existsSync(projectPath)) {\n\t\t\treturn projectPath;\n\t\t}\n\n\t\tconst globalPath = join(this.agentDir, \"APPEND_SYSTEM.md\");\n\t\tif (existsSync(globalPath)) {\n\t\t\treturn globalPath;\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate isUnderPath(target: string, root: string): boolean {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) {\n\t\t\treturn true;\n\t\t}\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t}\n\n\tprivate detectExtensionConflicts(extensions: Extension[]): Array<{ path: string; message: string }> {\n\t\tconst conflicts: Array<{ path: string; message: string }> = [];\n\n\t\t// Track which extension registered each tool and flag\n\t\tconst toolOwners = new Map<string, string>();\n\t\tconst flagOwners = new Map<string, string>();\n\n\t\tfor (const ext of extensions) {\n\t\t\t// Check tools\n\t\t\tfor (const toolName of ext.tools.keys()) {\n\t\t\t\tconst existingOwner = toolOwners.get(toolName);\n\t\t\t\tif (existingOwner && existingOwner !== ext.path) {\n\t\t\t\t\tconflicts.push({\n\t\t\t\t\t\tpath: ext.path,\n\t\t\t\t\t\tmessage: `Tool \"${toolName}\" conflicts with ${existingOwner}`,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\ttoolOwners.set(toolName, ext.path);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Check flags\n\t\t\tfor (const flagName of ext.flags.keys()) {\n\t\t\t\tconst existingOwner = flagOwners.get(flagName);\n\t\t\t\tif (existingOwner && existingOwner !== ext.path) {\n\t\t\t\t\tconflicts.push({\n\t\t\t\t\t\tpath: ext.path,\n\t\t\t\t\t\tmessage: `Flag \"--${flagName}\" conflicts with ${existingOwner}`,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tflagOwners.set(flagName, ext.path);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn conflicts;\n\t}\n}\n"]}
@@ -4,6 +4,7 @@ import chalk from "chalk";
4
4
  import { CONFIG_DIR_NAME } from "../config.js";
5
5
  import { loadThemeFromPath } from "../modes/interactive/theme/theme.js";
6
6
  import { canonicalizePath, isLocalPath, resolvePath } from "../utils/paths.js";
7
+ import { loadSkillsWithBundled } from "./bundled-skills.js";
7
8
  import { createEventBus } from "./event-bus.js";
8
9
  import commandSafetyGate from "./extensions/builtin/command-safety-gate.js";
9
10
  import goalController from "./extensions/builtin/goal-controller.js";
@@ -15,7 +16,6 @@ import { createExtensionRuntime, loadExtensionFromFactory, loadExtensions } from
15
16
  import { DefaultPackageManager } from "./package-manager.js";
16
17
  import { loadPromptTemplates } from "./prompt-templates.js";
17
18
  import { SettingsManager } from "./settings-manager.js";
18
- import { loadSkills } from "./skills.js";
19
19
  import { createSourceInfo } from "./source-info.js";
20
20
  /** Values that turn a built-in extension off via its environment variable. */
21
21
  const DISABLED_ENV_VALUES = new Set(["0", "false", "off", "disable", "disabled"]);
@@ -491,7 +491,7 @@ export class DefaultResourceLoader {
491
491
  skillsResult = { skills: [], diagnostics: [] };
492
492
  }
493
493
  else {
494
- skillsResult = loadSkills({
494
+ skillsResult = loadSkillsWithBundled(this.noSkills, {
495
495
  cwd: this.cwd,
496
496
  agentDir: this.agentDir,
497
497
  skillPaths,