@decibelsystems/tools 2.1.4 → 3.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 (295) hide show
  1. package/CHANGELOG.md +160 -0
  2. package/README.md +1 -1
  3. package/dist/agentPresence.d.ts +80 -0
  4. package/dist/agentPresence.d.ts.map +1 -1
  5. package/dist/agentPresence.js +177 -0
  6. package/dist/agentPresence.js.map +1 -1
  7. package/dist/agentic/index.d.ts +18 -8
  8. package/dist/agentic/index.d.ts.map +1 -1
  9. package/dist/agentic/index.js +18 -144
  10. package/dist/agentic/index.js.map +1 -1
  11. package/dist/agentic/types.d.ts +106 -0
  12. package/dist/agentic/types.d.ts.map +1 -1
  13. package/dist/agentic/types.js +77 -1
  14. package/dist/agentic/types.js.map +1 -1
  15. package/dist/architectAdrs.d.ts +14 -2
  16. package/dist/architectAdrs.d.ts.map +1 -1
  17. package/dist/architectAdrs.js +9 -3
  18. package/dist/architectAdrs.js.map +1 -1
  19. package/dist/client/transports.d.ts.map +1 -1
  20. package/dist/client/transports.js +13 -3
  21. package/dist/client/transports.js.map +1 -1
  22. package/dist/daemon.d.ts +19 -0
  23. package/dist/daemon.d.ts.map +1 -1
  24. package/dist/daemon.js +181 -38
  25. package/dist/daemon.js.map +1 -1
  26. package/dist/daemonConfig.d.ts +30 -0
  27. package/dist/daemonConfig.d.ts.map +1 -1
  28. package/dist/daemonConfig.js +56 -9
  29. package/dist/daemonConfig.js.map +1 -1
  30. package/dist/decibelPaths.d.ts.map +1 -1
  31. package/dist/decibelPaths.js +39 -6
  32. package/dist/decibelPaths.js.map +1 -1
  33. package/dist/domain/issue.d.ts +77 -0
  34. package/dist/domain/issue.d.ts.map +1 -0
  35. package/dist/domain/issue.js +134 -0
  36. package/dist/domain/issue.js.map +1 -0
  37. package/dist/domain/issueCodec.d.ts +39 -0
  38. package/dist/domain/issueCodec.d.ts.map +1 -0
  39. package/dist/domain/issueCodec.js +298 -0
  40. package/dist/domain/issueCodec.js.map +1 -0
  41. package/dist/domain/issueRepository.d.ts +91 -0
  42. package/dist/domain/issueRepository.d.ts.map +1 -0
  43. package/dist/domain/issueRepository.js +257 -0
  44. package/dist/domain/issueRepository.js.map +1 -0
  45. package/dist/facades/definitions.d.ts +0 -1
  46. package/dist/facades/definitions.d.ts.map +1 -1
  47. package/dist/facades/definitions.js +94 -98
  48. package/dist/facades/definitions.js.map +1 -1
  49. package/dist/facades/index.d.ts +2 -1
  50. package/dist/facades/index.d.ts.map +1 -1
  51. package/dist/facades/index.js +1 -1
  52. package/dist/facades/index.js.map +1 -1
  53. package/dist/facades/types.d.ts +17 -0
  54. package/dist/facades/types.d.ts.map +1 -1
  55. package/dist/httpArgs.d.ts +11 -0
  56. package/dist/httpArgs.d.ts.map +1 -0
  57. package/dist/httpArgs.js +39 -0
  58. package/dist/httpArgs.js.map +1 -0
  59. package/dist/httpServer.d.ts +1 -12
  60. package/dist/httpServer.d.ts.map +1 -1
  61. package/dist/httpServer.js +173 -98
  62. package/dist/httpServer.js.map +1 -1
  63. package/dist/kernel.d.ts +55 -1
  64. package/dist/kernel.d.ts.map +1 -1
  65. package/dist/kernel.js +366 -31
  66. package/dist/kernel.js.map +1 -1
  67. package/dist/lib/agent-services/assumptions.d.ts.map +1 -1
  68. package/dist/lib/agent-services/assumptions.js +8 -2
  69. package/dist/lib/agent-services/assumptions.js.map +1 -1
  70. package/dist/lib/atomicWrite.d.ts +26 -0
  71. package/dist/lib/atomicWrite.d.ts.map +1 -0
  72. package/dist/lib/atomicWrite.js +73 -0
  73. package/dist/lib/atomicWrite.js.map +1 -0
  74. package/dist/lib/envelope.d.ts +31 -0
  75. package/dist/lib/envelope.d.ts.map +1 -0
  76. package/dist/lib/envelope.js +80 -0
  77. package/dist/lib/envelope.js.map +1 -0
  78. package/dist/lib/fileLock.d.ts +38 -0
  79. package/dist/lib/fileLock.d.ts.map +1 -0
  80. package/dist/lib/fileLock.js +124 -0
  81. package/dist/lib/fileLock.js.map +1 -0
  82. package/dist/lib/recordIdAllocator.d.ts +86 -0
  83. package/dist/lib/recordIdAllocator.d.ts.map +1 -0
  84. package/dist/lib/recordIdAllocator.js +161 -0
  85. package/dist/lib/recordIdAllocator.js.map +1 -0
  86. package/dist/license.js +3 -3
  87. package/dist/license.js.map +1 -1
  88. package/dist/projectRegistry.d.ts +65 -3
  89. package/dist/projectRegistry.d.ts.map +1 -1
  90. package/dist/projectRegistry.js +233 -33
  91. package/dist/projectRegistry.js.map +1 -1
  92. package/dist/runtime/circuitBreaker.d.ts +95 -0
  93. package/dist/runtime/circuitBreaker.d.ts.map +1 -0
  94. package/dist/runtime/circuitBreaker.js +178 -0
  95. package/dist/runtime/circuitBreaker.js.map +1 -0
  96. package/dist/runtime/ensureRuntime.d.ts +103 -0
  97. package/dist/runtime/ensureRuntime.d.ts.map +1 -0
  98. package/dist/runtime/ensureRuntime.js +281 -0
  99. package/dist/runtime/ensureRuntime.js.map +1 -0
  100. package/dist/runtime/extensions.d.ts +57 -0
  101. package/dist/runtime/extensions.d.ts.map +1 -0
  102. package/dist/runtime/extensions.js +216 -0
  103. package/dist/runtime/extensions.js.map +1 -0
  104. package/dist/runtime/localHttp.d.ts +24 -0
  105. package/dist/runtime/localHttp.d.ts.map +1 -0
  106. package/dist/runtime/localHttp.js +84 -0
  107. package/dist/runtime/localHttp.js.map +1 -0
  108. package/dist/runtime/projectResolution.d.ts +29 -0
  109. package/dist/runtime/projectResolution.d.ts.map +1 -0
  110. package/dist/runtime/projectResolution.js +67 -0
  111. package/dist/runtime/projectResolution.js.map +1 -0
  112. package/dist/runtime/protocol.d.ts +62 -0
  113. package/dist/runtime/protocol.d.ts.map +1 -0
  114. package/dist/runtime/protocol.js +74 -0
  115. package/dist/runtime/protocol.js.map +1 -0
  116. package/dist/sentinelIssues.d.ts +26 -2
  117. package/dist/sentinelIssues.d.ts.map +1 -1
  118. package/dist/sentinelIssues.js +163 -76
  119. package/dist/sentinelIssues.js.map +1 -1
  120. package/dist/server.js +65 -12
  121. package/dist/server.js.map +1 -1
  122. package/dist/setup.d.ts +83 -0
  123. package/dist/setup.d.ts.map +1 -0
  124. package/dist/setup.js +445 -0
  125. package/dist/setup.js.map +1 -0
  126. package/dist/store/markdown.d.ts +14 -1
  127. package/dist/store/markdown.d.ts.map +1 -1
  128. package/dist/store/markdown.js +36 -3
  129. package/dist/store/markdown.js.map +1 -1
  130. package/dist/tools/agentic/index.d.ts.map +1 -1
  131. package/dist/tools/agentic/index.js +28 -2
  132. package/dist/tools/agentic/index.js.map +1 -1
  133. package/dist/tools/architect/index.d.ts.map +1 -1
  134. package/dist/tools/architect/index.js +7 -2
  135. package/dist/tools/architect/index.js.map +1 -1
  136. package/dist/tools/conductor/index.d.ts +9 -0
  137. package/dist/tools/conductor/index.d.ts.map +1 -0
  138. package/dist/tools/conductor/index.js +242 -0
  139. package/dist/tools/conductor/index.js.map +1 -0
  140. package/dist/tools/context.d.ts +10 -5
  141. package/dist/tools/context.d.ts.map +1 -1
  142. package/dist/tools/context.js +45 -18
  143. package/dist/tools/context.js.map +1 -1
  144. package/dist/tools/coordinator/coordinator.d.ts.map +1 -1
  145. package/dist/tools/coordinator/coordinator.js +48 -19
  146. package/dist/tools/coordinator/coordinator.js.map +1 -1
  147. package/dist/tools/crit.d.ts +2 -1
  148. package/dist/tools/crit.d.ts.map +1 -1
  149. package/dist/tools/crit.js +19 -4
  150. package/dist/tools/crit.js.map +1 -1
  151. package/dist/tools/designer/index.d.ts.map +1 -1
  152. package/dist/tools/designer/index.js +4 -3
  153. package/dist/tools/designer/index.js.map +1 -1
  154. package/dist/tools/designer/lateral-tools.d.ts.map +1 -1
  155. package/dist/tools/designer/lateral-tools.js +4 -2
  156. package/dist/tools/designer/lateral-tools.js.map +1 -1
  157. package/dist/tools/designer.d.ts +2 -1
  158. package/dist/tools/designer.d.ts.map +1 -1
  159. package/dist/tools/designer.js +24 -12
  160. package/dist/tools/designer.js.map +1 -1
  161. package/dist/tools/dojo.d.ts +3 -2
  162. package/dist/tools/dojo.d.ts.map +1 -1
  163. package/dist/tools/dojo.js +64 -22
  164. package/dist/tools/dojo.js.map +1 -1
  165. package/dist/tools/dojoPolicy.js +1 -1
  166. package/dist/tools/dojoPolicy.js.map +1 -1
  167. package/dist/tools/friction.d.ts.map +1 -1
  168. package/dist/tools/friction.js +16 -6
  169. package/dist/tools/friction.js.map +1 -1
  170. package/dist/tools/guardian.d.ts +29 -7
  171. package/dist/tools/guardian.d.ts.map +1 -1
  172. package/dist/tools/guardian.js +90 -75
  173. package/dist/tools/guardian.js.map +1 -1
  174. package/dist/tools/hygiene/oracle-hygiene.d.ts.map +1 -1
  175. package/dist/tools/hygiene/oracle-hygiene.js +8 -2
  176. package/dist/tools/hygiene/oracle-hygiene.js.map +1 -1
  177. package/dist/tools/index.d.ts.map +1 -1
  178. package/dist/tools/index.js +29 -18
  179. package/dist/tools/index.js.map +1 -1
  180. package/dist/tools/learnings.d.ts +2 -1
  181. package/dist/tools/learnings.d.ts.map +1 -1
  182. package/dist/tools/learnings.js +31 -15
  183. package/dist/tools/learnings.js.map +1 -1
  184. package/dist/tools/oracle.d.ts.map +1 -1
  185. package/dist/tools/oracle.js +19 -6
  186. package/dist/tools/oracle.js.map +1 -1
  187. package/dist/tools/policy.d.ts +2 -1
  188. package/dist/tools/policy.d.ts.map +1 -1
  189. package/dist/tools/policy.js +28 -9
  190. package/dist/tools/policy.js.map +1 -1
  191. package/dist/tools/postoffice/index.d.ts +10 -0
  192. package/dist/tools/postoffice/index.d.ts.map +1 -0
  193. package/dist/tools/postoffice/index.js +218 -0
  194. package/dist/tools/postoffice/index.js.map +1 -0
  195. package/dist/tools/postoffice.d.ts +78 -0
  196. package/dist/tools/postoffice.d.ts.map +1 -0
  197. package/dist/tools/postoffice.js +199 -0
  198. package/dist/tools/postoffice.js.map +1 -0
  199. package/dist/tools/provenance.d.ts +7 -0
  200. package/dist/tools/provenance.d.ts.map +1 -1
  201. package/dist/tools/provenance.js +42 -16
  202. package/dist/tools/provenance.js.map +1 -1
  203. package/dist/tools/registry/index.d.ts.map +1 -1
  204. package/dist/tools/registry/index.js +41 -12
  205. package/dist/tools/registry/index.js.map +1 -1
  206. package/dist/tools/sentinel/index.d.ts +1 -0
  207. package/dist/tools/sentinel/index.d.ts.map +1 -1
  208. package/dist/tools/sentinel/index.js +88 -4
  209. package/dist/tools/sentinel/index.js.map +1 -1
  210. package/dist/tools/sentinel.d.ts +117 -1
  211. package/dist/tools/sentinel.d.ts.map +1 -1
  212. package/dist/tools/sentinel.js +761 -339
  213. package/dist/tools/sentinel.js.map +1 -1
  214. package/dist/tools/shared/index.d.ts +2 -1
  215. package/dist/tools/shared/index.d.ts.map +1 -1
  216. package/dist/tools/shared/index.js +2 -1
  217. package/dist/tools/shared/index.js.map +1 -1
  218. package/dist/tools/shared/response.d.ts +24 -0
  219. package/dist/tools/shared/response.d.ts.map +1 -1
  220. package/dist/tools/shared/response.js +61 -7
  221. package/dist/tools/shared/response.js.map +1 -1
  222. package/dist/tools/shared/storeRead.d.ts +153 -0
  223. package/dist/tools/shared/storeRead.d.ts.map +1 -0
  224. package/dist/tools/shared/storeRead.js +242 -0
  225. package/dist/tools/shared/storeRead.js.map +1 -0
  226. package/dist/tools/testSpec.d.ts +3 -2
  227. package/dist/tools/testSpec.d.ts.map +1 -1
  228. package/dist/tools/testSpec.js +51 -14
  229. package/dist/tools/testSpec.js.map +1 -1
  230. package/dist/tools/vector.d.ts +33 -1
  231. package/dist/tools/vector.d.ts.map +1 -1
  232. package/dist/tools/vector.js +31 -5
  233. package/dist/tools/vector.js.map +1 -1
  234. package/dist/tools/zoom/index.d.ts +15 -0
  235. package/dist/tools/zoom/index.d.ts.map +1 -0
  236. package/dist/tools/zoom/index.js +174 -0
  237. package/dist/tools/zoom/index.js.map +1 -0
  238. package/dist/tools/zoom.d.ts +234 -0
  239. package/dist/tools/zoom.d.ts.map +1 -0
  240. package/dist/tools/zoom.js +627 -0
  241. package/dist/tools/zoom.js.map +1 -0
  242. package/dist/transports/bridge.d.ts +1 -1
  243. package/dist/transports/bridge.d.ts.map +1 -1
  244. package/dist/transports/bridge.js +2 -0
  245. package/dist/transports/bridge.js.map +1 -1
  246. package/dist/transports/http.d.ts +1 -1
  247. package/dist/transports/http.d.ts.map +1 -1
  248. package/dist/transports/http.js +3 -1
  249. package/dist/transports/http.js.map +1 -1
  250. package/dist/transports/index.d.ts +1 -0
  251. package/dist/transports/index.d.ts.map +1 -1
  252. package/dist/transports/index.js +1 -0
  253. package/dist/transports/index.js.map +1 -1
  254. package/dist/transports/mcp.d.ts +1 -1
  255. package/dist/transports/mcp.d.ts.map +1 -1
  256. package/dist/transports/mcp.js +7 -3
  257. package/dist/transports/mcp.js.map +1 -1
  258. package/dist/transports/stdio.d.ts +1 -1
  259. package/dist/transports/stdio.d.ts.map +1 -1
  260. package/dist/transports/stdio.js +2 -0
  261. package/dist/transports/stdio.js.map +1 -1
  262. package/dist/transports/thinStdio.d.ts +22 -0
  263. package/dist/transports/thinStdio.d.ts.map +1 -0
  264. package/dist/transports/thinStdio.js +218 -0
  265. package/dist/transports/thinStdio.js.map +1 -0
  266. package/dist/transports/types.d.ts +8 -1
  267. package/dist/transports/types.d.ts.map +1 -1
  268. package/dist/types/agent-services.d.ts +7 -0
  269. package/dist/types/agent-services.d.ts.map +1 -1
  270. package/package.json +9 -4
  271. package/templates/com.decibel.daemon.plist +6 -1
  272. package/dist/tools/deck.d.ts +0 -11
  273. package/dist/tools/deck.d.ts.map +0 -1
  274. package/dist/tools/deck.js +0 -1656
  275. package/dist/tools/deck.js.map +0 -1
  276. package/dist/tools/mother.d.ts +0 -11
  277. package/dist/tools/mother.d.ts.map +0 -1
  278. package/dist/tools/mother.js +0 -503
  279. package/dist/tools/mother.js.map +0 -1
  280. package/dist/tools/registry.d.ts +0 -3
  281. package/dist/tools/registry.d.ts.map +0 -1
  282. package/dist/tools/registry.js +0 -189
  283. package/dist/tools/registry.js.map +0 -1
  284. package/dist/tools/senken.d.ts +0 -11
  285. package/dist/tools/senken.d.ts.map +0 -1
  286. package/dist/tools/senken.js +0 -482
  287. package/dist/tools/senken.js.map +0 -1
  288. package/dist/tools/terminal.d.ts +0 -17
  289. package/dist/tools/terminal.d.ts.map +0 -1
  290. package/dist/tools/terminal.js +0 -968
  291. package/dist/tools/terminal.js.map +0 -1
  292. package/dist/tools/toolsIndex.d.ts +0 -5
  293. package/dist/tools/toolsIndex.d.ts.map +0 -1
  294. package/dist/tools/toolsIndex.js +0 -37
  295. package/dist/tools/toolsIndex.js.map +0 -1
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Per-facade circuit breaker.
3
+ *
4
+ * The failure this exists for: one facade's dependency wedges — Mother's
5
+ * Postgres stops accepting connections, Supabase hangs, a disk mount goes
6
+ * away — and every call into that facade sits on a socket until it times out.
7
+ * With six clients sharing one runtime that is enough to make the whole
8
+ * runtime feel dead, even though twenty other facades are fine. The breaker
9
+ * turns a slow, repeated failure into a fast, local one.
10
+ *
11
+ * WHAT COUNTS AS A FAULT is the whole design, because Decibel's tools rarely
12
+ * throw. `senken_trade_summary` catches its own Postgres error and returns
13
+ * `{isError: true}` (src/tools/senken.ts:117), so a breaker that only counted
14
+ * exceptions would never trip on the exact case it was built for. But counting
15
+ * every `isError` is worse: `create_issue` missing a title is an `isError`
16
+ * too, and five bad calls in a row from one confused agent must not take the
17
+ * sentinel facade offline for everyone.
18
+ *
19
+ * So the signal is *unresponsiveness*, not *unhappiness*:
20
+ *
21
+ * - a thrown exception is always a fault (the handler didn't handle it)
22
+ * - an `isError` result that took longer than `slowFailureMs` is a fault
23
+ * (a dependency that times out is slow; a rejected argument is not)
24
+ * - an `isError` result that came back fast is neither fault nor proof of
25
+ * health — it leaves the counters exactly where they were
26
+ * - a success closes the circuit and clears the count
27
+ *
28
+ * State machine: closed → (threshold consecutive faults) → open → (cooldown
29
+ * elapses) → half-open → success closes it, fault re-opens it. Half-open
30
+ * admits exactly one probe; concurrent callers are rejected until it lands, so
31
+ * a recovering database gets one connection attempt rather than six.
32
+ *
33
+ * The breaker never converts a working call into a failure — it only refuses
34
+ * calls it has good reason to believe would fail slowly.
35
+ */
36
+ const DEFAULTS = {
37
+ failureThreshold: 5,
38
+ cooldownMs: 30_000,
39
+ slowFailureMs: 2_000,
40
+ };
41
+ export class CircuitBreakerRegistry {
42
+ circuits = new Map();
43
+ failureThreshold;
44
+ cooldownMs;
45
+ slowFailureMs;
46
+ now;
47
+ constructor(opts = {}) {
48
+ this.failureThreshold = opts.failureThreshold ?? DEFAULTS.failureThreshold;
49
+ this.cooldownMs = opts.cooldownMs ?? DEFAULTS.cooldownMs;
50
+ this.slowFailureMs = opts.slowFailureMs ?? DEFAULTS.slowFailureMs;
51
+ this.now = opts.now ?? Date.now;
52
+ }
53
+ /**
54
+ * Ask whether a call to `key` may proceed. Transitions open → half_open when
55
+ * the cooldown has elapsed, and reserves the half-open probe for the caller
56
+ * it says yes to.
57
+ */
58
+ beforeCall(key) {
59
+ const c = this.circuits.get(key);
60
+ if (!c || c.state === 'closed')
61
+ return { allowed: true };
62
+ if (c.state === 'open') {
63
+ const elapsed = this.now() - c.openedAt;
64
+ if (elapsed < this.cooldownMs) {
65
+ return {
66
+ allowed: false,
67
+ retryAfterMs: this.cooldownMs - elapsed,
68
+ lastError: c.lastError,
69
+ openedAt: new Date(c.openedAt).toISOString(),
70
+ };
71
+ }
72
+ // Cooldown elapsed — this caller becomes the probe.
73
+ c.state = 'half_open';
74
+ c.probeInFlight = true;
75
+ return { allowed: true };
76
+ }
77
+ // half_open: one probe at a time.
78
+ if (c.probeInFlight) {
79
+ return {
80
+ allowed: false,
81
+ retryAfterMs: this.cooldownMs,
82
+ lastError: c.lastError,
83
+ openedAt: new Date(c.openedAt).toISOString(),
84
+ };
85
+ }
86
+ c.probeInFlight = true;
87
+ return { allowed: true };
88
+ }
89
+ /**
90
+ * Report what happened. `threw` and `durationMs` decide whether this was a
91
+ * fault; see the module comment for why `isError` alone is not enough and
92
+ * not too much.
93
+ */
94
+ afterCall(key, outcome) {
95
+ const fault = outcome.threw || (outcome.isError && outcome.durationMs >= this.slowFailureMs);
96
+ if (fault) {
97
+ this.recordFault(key, outcome.error);
98
+ return;
99
+ }
100
+ if (outcome.isError) {
101
+ // Fast domain error: says nothing about the dependency's health. Release
102
+ // the probe if we were half-open, but leave the fault count alone.
103
+ const c = this.circuits.get(key);
104
+ if (c?.state === 'half_open')
105
+ this.close(key);
106
+ else if (c)
107
+ c.probeInFlight = false;
108
+ return;
109
+ }
110
+ this.recordSuccess(key);
111
+ }
112
+ recordSuccess(key) {
113
+ const c = this.circuits.get(key);
114
+ if (!c)
115
+ return;
116
+ this.close(key);
117
+ }
118
+ recordFault(key, error) {
119
+ const c = this.circuits.get(key) ?? {
120
+ state: 'closed',
121
+ faults: 0,
122
+ openedAt: 0,
123
+ probeInFlight: false,
124
+ };
125
+ c.lastError = error;
126
+ c.probeInFlight = false;
127
+ if (c.state === 'half_open') {
128
+ // The probe failed — straight back to open, full cooldown.
129
+ c.state = 'open';
130
+ c.openedAt = this.now();
131
+ this.circuits.set(key, c);
132
+ return;
133
+ }
134
+ c.faults += 1;
135
+ if (c.faults >= this.failureThreshold) {
136
+ c.state = 'open';
137
+ c.openedAt = this.now();
138
+ }
139
+ this.circuits.set(key, c);
140
+ }
141
+ state(key) {
142
+ return this.circuits.get(key)?.state ?? 'closed';
143
+ }
144
+ /** Open circuits only — a healthy runtime reports `{}`. */
145
+ snapshot() {
146
+ const out = {};
147
+ for (const [key, c] of this.circuits) {
148
+ if (c.state === 'closed' && c.faults === 0)
149
+ continue;
150
+ out[key] = {
151
+ state: c.state,
152
+ consecutive_faults: c.faults,
153
+ ...(c.state === 'closed' ? {} : {
154
+ opened_at: new Date(c.openedAt).toISOString(),
155
+ retry_after_ms: Math.max(0, this.cooldownMs - (this.now() - c.openedAt)),
156
+ }),
157
+ ...(c.lastError ? { last_error: c.lastError } : {}),
158
+ };
159
+ }
160
+ return out;
161
+ }
162
+ reset(key) {
163
+ if (key === undefined)
164
+ this.circuits.clear();
165
+ else
166
+ this.circuits.delete(key);
167
+ }
168
+ close(key) {
169
+ const c = this.circuits.get(key);
170
+ if (!c)
171
+ return;
172
+ c.state = 'closed';
173
+ c.faults = 0;
174
+ c.probeInFlight = false;
175
+ c.openedAt = 0;
176
+ }
177
+ }
178
+ //# sourceMappingURL=circuitBreaker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"circuitBreaker.js","sourceRoot":"","sources":["../../src/runtime/circuitBreaker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * ensureRuntime — the one lifecycle primitive.
3
+ *
4
+ * Every surface that needs the Decibel runtime (MCP adapters, the CLI, the
5
+ * VS Code extension, shell hooks) calls this. None of them should carry their
6
+ * own "is the daemon up? should I start it?" logic — that duplication is how
7
+ * five subtly different startup behaviours end up in one codebase.
8
+ *
9
+ * Two things this deliberately does NOT do:
10
+ *
11
+ * 1. It does not use the PID file as the source of truth. `~/.decibel/daemon.pid`
12
+ * goes stale — an unclean exit leaves a pid pointing at nothing, or worse,
13
+ * at a recycled pid belonging to an unrelated process. A successful
14
+ * `/health` response is the only proof the runtime is actually serving.
15
+ *
16
+ * 2. It does not check-then-spawn. When six clients start at once, six
17
+ * "is it running? no → start it" sequences race and you get six daemons
18
+ * fighting over one port. Startup is arbitrated by an atomic O_EXCL lock
19
+ * file: exactly one caller wins the right to spawn, and the losers wait
20
+ * for the winner's daemon rather than starting their own.
21
+ */
22
+ /** Default port the daemon binds. Overridden by daemon.meta or DECIBEL_DAEMON_PORT. */
23
+ export declare const DEFAULT_RUNTIME_PORT = 4888;
24
+ export interface RuntimeHealth {
25
+ status: string;
26
+ version: string;
27
+ protocol_version?: string;
28
+ pid: number;
29
+ uptime_ms: number;
30
+ facade_count: number;
31
+ license_tier: string;
32
+ [key: string]: unknown;
33
+ }
34
+ export interface RuntimeHandle {
35
+ /** Base URL, e.g. `http://127.0.0.1:4888`. No trailing slash. */
36
+ url: string;
37
+ port: number;
38
+ /** PID of the serving runtime — reported by /health, not read from a pid file. */
39
+ pid: number;
40
+ /** Package version of the running runtime. */
41
+ version: string;
42
+ /** Wire protocol the running runtime speaks. */
43
+ protocolVersion: string;
44
+ licenseTier: string;
45
+ /** True when this call started the runtime; false when it was already up. */
46
+ spawned: boolean;
47
+ health: RuntimeHealth;
48
+ }
49
+ export interface EnsureRuntimeOptions {
50
+ /** Protocol version this client requires. Defaults to the compiled-in version. */
51
+ protocolVersion?: string;
52
+ /** Port override. Falls back to daemon.meta, then DECIBEL_DAEMON_PORT, then 4888. */
53
+ port?: number;
54
+ /** Total time to wait for a runtime to become healthy. */
55
+ timeoutMs?: number;
56
+ /**
57
+ * When false, never spawn — probe only, and throw if nothing is serving.
58
+ * Used by callers that must not have side effects (status commands, health
59
+ * checks, anything running inside a hook with a hard timeout).
60
+ */
61
+ autoStart?: boolean;
62
+ /** Absolute path to the runtime entry point. Defaults to this package's dist/server.js. */
63
+ serverEntry?: string;
64
+ }
65
+ /** Thrown when a runtime is serving but speaks an incompatible protocol. */
66
+ export declare class RuntimeProtocolMismatchError extends Error {
67
+ readonly serverVersion: string | undefined;
68
+ readonly clientVersion: string;
69
+ constructor(serverVersion: string | undefined, clientVersion: string, reason: string);
70
+ }
71
+ /** Thrown when no runtime is serving and one could not be started. */
72
+ export declare class RuntimeUnavailableError extends Error {
73
+ constructor(port: number, detail: string);
74
+ }
75
+ /**
76
+ * Resolve the port to talk to, in precedence order:
77
+ * explicit option → DECIBEL_DAEMON_PORT → daemon.meta → default.
78
+ *
79
+ * daemon.meta is a *hint*, not proof of life — it records the port the daemon
80
+ * last bound. We still confirm with /health before trusting anything.
81
+ */
82
+ export declare function resolveRuntimePort(explicit?: number): number;
83
+ /**
84
+ * GET /health. Returns null when nothing is listening or the response is not a
85
+ * usable health payload — callers treat null as "no runtime here", never as an
86
+ * error worth surfacing.
87
+ */
88
+ export declare function probeRuntime(port: number): Promise<RuntimeHealth | null>;
89
+ /**
90
+ * Return a handle to a healthy, protocol-compatible runtime, starting one if
91
+ * needed.
92
+ *
93
+ * Throws rather than degrading:
94
+ * - RuntimeProtocolMismatchError when something is serving but speaks a
95
+ * contract this client does not understand.
96
+ * - RuntimeUnavailableError when nothing is serving and none could be started.
97
+ *
98
+ * Callers must not silently fall back to doing the work in-process. A client
99
+ * that quietly becomes a second writer is exactly the failure this runtime
100
+ * exists to prevent.
101
+ */
102
+ export declare function ensureRuntime(options?: EnsureRuntimeOptions): Promise<RuntimeHandle>;
103
+ //# sourceMappingURL=ensureRuntime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ensureRuntime.d.ts","sourceRoot":"","sources":["../../src/runtime/ensureRuntime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAcH,uFAAuF;AACvF,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAczC,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,kFAAkF;IAClF,GAAG,EAAE,MAAM,CAAC;IACZ,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,aAAa,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,kFAAkF;IAClF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qFAAqF;IACrF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,2FAA2F;IAC3F,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,4EAA4E;AAC5E,qBAAa,4BAA6B,SAAQ,KAAK;IAEnD,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS;IAC1C,QAAQ,CAAC,aAAa,EAAE,MAAM;gBADrB,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,aAAa,EAAE,MAAM,EAC9B,MAAM,EAAE,MAAM;CASjB;AAED,sEAAsE;AACtE,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAQzC;AAMD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAiB5D;AAMD;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAW9E;AAgHD;;;;;;;;;;;;GAYG;AACH,wBAAsB,aAAa,CACjC,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,aAAa,CAAC,CAkExB"}
@@ -0,0 +1,281 @@
1
+ /**
2
+ * ensureRuntime — the one lifecycle primitive.
3
+ *
4
+ * Every surface that needs the Decibel runtime (MCP adapters, the CLI, the
5
+ * VS Code extension, shell hooks) calls this. None of them should carry their
6
+ * own "is the daemon up? should I start it?" logic — that duplication is how
7
+ * five subtly different startup behaviours end up in one codebase.
8
+ *
9
+ * Two things this deliberately does NOT do:
10
+ *
11
+ * 1. It does not use the PID file as the source of truth. `~/.decibel/daemon.pid`
12
+ * goes stale — an unclean exit leaves a pid pointing at nothing, or worse,
13
+ * at a recycled pid belonging to an unrelated process. A successful
14
+ * `/health` response is the only proof the runtime is actually serving.
15
+ *
16
+ * 2. It does not check-then-spawn. When six clients start at once, six
17
+ * "is it running? no → start it" sequences race and you get six daemons
18
+ * fighting over one port. Startup is arbitrated by an atomic O_EXCL lock
19
+ * file: exactly one caller wins the right to spawn, and the losers wait
20
+ * for the winner's daemon rather than starting their own.
21
+ */
22
+ import { spawn } from 'child_process';
23
+ import { openSync, closeSync, writeSync, readFileSync, unlinkSync, statSync, mkdirSync } from 'fs';
24
+ import { join, dirname } from 'path';
25
+ import { homedir } from 'os';
26
+ import { fileURLToPath } from 'url';
27
+ import { RUNTIME_PROTOCOL_VERSION, isProtocolCompatible } from './protocol.js';
28
+ import { localJson } from './localHttp.js';
29
+ const DECIBEL_HOME = join(homedir(), '.decibel');
30
+ const META_PATH = join(DECIBEL_HOME, 'daemon.meta');
31
+ const LOCK_PATH = join(DECIBEL_HOME, 'runtime.lock');
32
+ /** Default port the daemon binds. Overridden by daemon.meta or DECIBEL_DAEMON_PORT. */
33
+ export const DEFAULT_RUNTIME_PORT = 4888;
34
+ /** A lock older than this is presumed abandoned (holder crashed mid-spawn). */
35
+ const LOCK_STALE_MS = 30_000;
36
+ /** How long to wait for a runtime — ours or someone else's — to become healthy. */
37
+ const DEFAULT_STARTUP_TIMEOUT_MS = 15_000;
38
+ /** Gap between `/health` polls while waiting for startup. */
39
+ const POLL_INTERVAL_MS = 150;
40
+ /** Per-probe HTTP timeout. Short: a healthy local daemon answers in single-digit ms. */
41
+ const PROBE_TIMEOUT_MS = 1_500;
42
+ /** Thrown when a runtime is serving but speaks an incompatible protocol. */
43
+ export class RuntimeProtocolMismatchError extends Error {
44
+ serverVersion;
45
+ clientVersion;
46
+ constructor(serverVersion, clientVersion, reason) {
47
+ super(`Decibel runtime protocol mismatch: ${reason}. ` +
48
+ `Restart the runtime so it matches this client (\`decibel-tools --daemon restart\`), ` +
49
+ `or upgrade the client to match the running runtime.`);
50
+ this.serverVersion = serverVersion;
51
+ this.clientVersion = clientVersion;
52
+ this.name = 'RuntimeProtocolMismatchError';
53
+ }
54
+ }
55
+ /** Thrown when no runtime is serving and one could not be started. */
56
+ export class RuntimeUnavailableError extends Error {
57
+ constructor(port, detail) {
58
+ super(`Decibel runtime is not available on 127.0.0.1:${port}: ${detail}. ` +
59
+ `Start it manually with \`decibel-tools --daemon\` and check ` +
60
+ `~/.decibel/logs/daemon.log for why it did not come up.`);
61
+ this.name = 'RuntimeUnavailableError';
62
+ }
63
+ }
64
+ // ============================================================================
65
+ // Port discovery
66
+ // ============================================================================
67
+ /**
68
+ * Resolve the port to talk to, in precedence order:
69
+ * explicit option → DECIBEL_DAEMON_PORT → daemon.meta → default.
70
+ *
71
+ * daemon.meta is a *hint*, not proof of life — it records the port the daemon
72
+ * last bound. We still confirm with /health before trusting anything.
73
+ */
74
+ export function resolveRuntimePort(explicit) {
75
+ if (explicit)
76
+ return explicit;
77
+ const fromEnv = process.env.DECIBEL_DAEMON_PORT;
78
+ if (fromEnv) {
79
+ const parsed = Number(fromEnv);
80
+ if (Number.isInteger(parsed) && parsed > 0 && parsed < 65536)
81
+ return parsed;
82
+ }
83
+ try {
84
+ const meta = JSON.parse(readFileSync(META_PATH, 'utf-8'));
85
+ if (Number.isInteger(meta?.port))
86
+ return meta.port;
87
+ }
88
+ catch {
89
+ // No meta file, or unreadable — fall through to the default.
90
+ }
91
+ return DEFAULT_RUNTIME_PORT;
92
+ }
93
+ // ============================================================================
94
+ // Health probe
95
+ // ============================================================================
96
+ /**
97
+ * GET /health. Returns null when nothing is listening or the response is not a
98
+ * usable health payload — callers treat null as "no runtime here", never as an
99
+ * error worth surfacing.
100
+ */
101
+ export async function probeRuntime(port) {
102
+ try {
103
+ const res = await localJson(`http://127.0.0.1:${port}/health`, {
104
+ timeoutMs: PROBE_TIMEOUT_MS,
105
+ });
106
+ if (!res.ok)
107
+ return null;
108
+ if (typeof res.body?.status !== 'string')
109
+ return null;
110
+ return res.body;
111
+ }
112
+ catch {
113
+ return null;
114
+ }
115
+ }
116
+ // ============================================================================
117
+ // Startup arbitration
118
+ // ============================================================================
119
+ /**
120
+ * Try to become the process responsible for spawning the runtime.
121
+ *
122
+ * `wx` is O_EXCL: the create either succeeds atomically or fails because
123
+ * someone else holds it. That is the whole arbitration mechanism — no
124
+ * read-then-write window for a second caller to slip through.
125
+ *
126
+ * A lock whose mtime is older than LOCK_STALE_MS is presumed abandoned (its
127
+ * holder crashed between acquiring and spawning) and is removed so startup can
128
+ * proceed. Without that, one crashed spawn would wedge every future client.
129
+ */
130
+ function tryAcquireStartupLock() {
131
+ mkdirSync(DECIBEL_HOME, { recursive: true });
132
+ try {
133
+ const fd = openSync(LOCK_PATH, 'wx');
134
+ writeSync(fd, JSON.stringify({ pid: process.pid, at: new Date().toISOString() }));
135
+ closeSync(fd);
136
+ return true;
137
+ }
138
+ catch (err) {
139
+ if (err.code !== 'EEXIST')
140
+ throw err;
141
+ }
142
+ // Held by someone. Abandoned?
143
+ try {
144
+ const age = Date.now() - statSync(LOCK_PATH).mtimeMs;
145
+ if (age > LOCK_STALE_MS) {
146
+ unlinkSync(LOCK_PATH);
147
+ return tryAcquireStartupLock();
148
+ }
149
+ }
150
+ catch {
151
+ // Vanished between our EEXIST and the stat — the holder finished. Retry once.
152
+ try {
153
+ const fd = openSync(LOCK_PATH, 'wx');
154
+ writeSync(fd, JSON.stringify({ pid: process.pid, at: new Date().toISOString() }));
155
+ closeSync(fd);
156
+ return true;
157
+ }
158
+ catch {
159
+ return false;
160
+ }
161
+ }
162
+ return false;
163
+ }
164
+ function releaseStartupLock() {
165
+ try {
166
+ unlinkSync(LOCK_PATH);
167
+ }
168
+ catch {
169
+ // Already gone (stolen as stale, or cleaned up elsewhere). Nothing to do.
170
+ }
171
+ }
172
+ /** Default runtime entry point: dist/server.js in this package. */
173
+ function defaultServerEntry() {
174
+ // src/runtime/ensureRuntime.ts → dist/runtime/ensureRuntime.js at runtime,
175
+ // so ../server.js resolves to dist/server.js in both layouts.
176
+ return join(dirname(fileURLToPath(import.meta.url)), '..', 'server.js');
177
+ }
178
+ /**
179
+ * Spawn the daemon fully detached so it outlives this client. A runtime that
180
+ * dies when the terminal that happened to start it closes is not a shared
181
+ * runtime.
182
+ */
183
+ function spawnRuntime(port, serverEntry) {
184
+ const child = spawn(process.execPath, [serverEntry, '--daemon', '--port', String(port)], {
185
+ detached: true,
186
+ stdio: 'ignore',
187
+ env: process.env,
188
+ });
189
+ child.unref();
190
+ }
191
+ /** Poll /health until it answers compatibly, or the deadline passes. */
192
+ async function waitForHealthy(port, clientProtocol, deadline) {
193
+ while (Date.now() < deadline) {
194
+ const health = await probeRuntime(port);
195
+ if (health) {
196
+ const compat = isProtocolCompatible(health.protocol_version, clientProtocol);
197
+ if (!compat.compatible) {
198
+ throw new RuntimeProtocolMismatchError(health.protocol_version, clientProtocol, compat.reason);
199
+ }
200
+ return health;
201
+ }
202
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
203
+ }
204
+ return null;
205
+ }
206
+ // ============================================================================
207
+ // Entry point
208
+ // ============================================================================
209
+ /**
210
+ * Return a handle to a healthy, protocol-compatible runtime, starting one if
211
+ * needed.
212
+ *
213
+ * Throws rather than degrading:
214
+ * - RuntimeProtocolMismatchError when something is serving but speaks a
215
+ * contract this client does not understand.
216
+ * - RuntimeUnavailableError when nothing is serving and none could be started.
217
+ *
218
+ * Callers must not silently fall back to doing the work in-process. A client
219
+ * that quietly becomes a second writer is exactly the failure this runtime
220
+ * exists to prevent.
221
+ */
222
+ export async function ensureRuntime(options = {}) {
223
+ const clientProtocol = options.protocolVersion ?? RUNTIME_PROTOCOL_VERSION;
224
+ const port = resolveRuntimePort(options.port);
225
+ const autoStart = options.autoStart !== false;
226
+ const timeoutMs = options.timeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
227
+ // Fast path: something is already serving.
228
+ const existing = await probeRuntime(port);
229
+ if (existing) {
230
+ const compat = isProtocolCompatible(existing.protocol_version, clientProtocol);
231
+ if (!compat.compatible) {
232
+ throw new RuntimeProtocolMismatchError(existing.protocol_version, clientProtocol, compat.reason);
233
+ }
234
+ return toHandle(existing, port, false);
235
+ }
236
+ if (!autoStart) {
237
+ throw new RuntimeUnavailableError(port, 'nothing is listening and autoStart is disabled');
238
+ }
239
+ const deadline = Date.now() + timeoutMs;
240
+ // Arbitrate. Exactly one caller spawns; the rest wait for that one.
241
+ const isSpawner = tryAcquireStartupLock();
242
+ if (!isSpawner) {
243
+ const health = await waitForHealthy(port, clientProtocol, deadline);
244
+ if (health)
245
+ return toHandle(health, port, false);
246
+ throw new RuntimeUnavailableError(port, 'another process holds the startup lock but no runtime became healthy before the timeout');
247
+ }
248
+ try {
249
+ // Re-probe under the lock. Between our first probe and acquiring the lock,
250
+ // a previous holder may have finished starting one.
251
+ const raced = await probeRuntime(port);
252
+ if (raced) {
253
+ const compat = isProtocolCompatible(raced.protocol_version, clientProtocol);
254
+ if (!compat.compatible) {
255
+ throw new RuntimeProtocolMismatchError(raced.protocol_version, clientProtocol, compat.reason);
256
+ }
257
+ return toHandle(raced, port, false);
258
+ }
259
+ spawnRuntime(port, options.serverEntry ?? defaultServerEntry());
260
+ const health = await waitForHealthy(port, clientProtocol, deadline);
261
+ if (health)
262
+ return toHandle(health, port, true);
263
+ throw new RuntimeUnavailableError(port, `spawned a runtime but it did not report healthy within ${timeoutMs}ms`);
264
+ }
265
+ finally {
266
+ releaseStartupLock();
267
+ }
268
+ }
269
+ function toHandle(health, port, spawned) {
270
+ return {
271
+ url: `http://127.0.0.1:${port}`,
272
+ port,
273
+ pid: health.pid,
274
+ version: health.version,
275
+ protocolVersion: health.protocol_version ?? 'unknown',
276
+ licenseTier: health.license_tier,
277
+ spawned,
278
+ health,
279
+ };
280
+ }
281
+ //# sourceMappingURL=ensureRuntime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ensureRuntime.js","sourceRoot":"","sources":["../../src/runtime/ensureRuntime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import type { FacadeSpec } from '../facades/types.js';
2
+ import type { ToolSpec } from '../tools/types.js';
3
+ export interface DecibelExtensionManifest {
4
+ /** Facade-style identifier, e.g. "senken". Must not collide with a core facade. */
5
+ name: string;
6
+ /** The extension's own version, independent of the runtime's. */
7
+ version: string;
8
+ /** Runtime wire contract this extension was built against. See protocol.ts. */
9
+ protocolVersion: string;
10
+ /** Extensions are never core — core is what ships in the package. */
11
+ tier: 'pro' | 'apps';
12
+ }
13
+ export interface DecibelExtension {
14
+ manifest: DecibelExtensionManifest;
15
+ facades: FacadeSpec[];
16
+ tools: ToolSpec[];
17
+ }
18
+ export interface RejectedExtension {
19
+ /** The allowlist entry as written, so the owner can find it in their config. */
20
+ entry: string;
21
+ reason: string;
22
+ }
23
+ export interface ExtensionLoadResult {
24
+ extensions: DecibelExtension[];
25
+ facades: FacadeSpec[];
26
+ tools: ToolSpec[];
27
+ rejected: RejectedExtension[];
28
+ }
29
+ /**
30
+ * Read the extension allowlist from ~/.decibel/config.yaml.
31
+ *
32
+ * Deliberately config-only. Reading a path from the environment would let
33
+ * anything that can set an env var — a plist, a shell profile, a parent
34
+ * process — choose code that runs in-process with full access, which is
35
+ * exactly the property the allowlist exists to provide.
36
+ */
37
+ export declare function readAllowlist(): string[];
38
+ /** Path rules, split out so the test suite can exercise them without a filesystem. */
39
+ export declare function checkAllowlistEntry(entry: string): {
40
+ ok: true;
41
+ path: string;
42
+ } | {
43
+ ok: false;
44
+ reason: string;
45
+ };
46
+ /**
47
+ * Load every allowlisted extension.
48
+ *
49
+ * Never throws. A broken extension is reported in `rejected` and the runtime
50
+ * boots without it — one bad private module must not take down core. Callers
51
+ * that care (the daemon's startup log, /health) surface the rejections.
52
+ *
53
+ * @param reservedNames Facade names already registered. An extension may not
54
+ * shadow one; that would let a private module silently replace `sentinel`.
55
+ */
56
+ export declare function loadExtensions(reservedNames?: ReadonlySet<string>): Promise<ExtensionLoadResult>;
57
+ //# sourceMappingURL=extensions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extensions.d.ts","sourceRoot":"","sources":["../../src/runtime/extensions.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AASlD,MAAM,WAAW,wBAAwB;IACvC,mFAAmF;IACnF,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,eAAe,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,IAAI,EAAE,KAAK,GAAG,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,wBAAwB,CAAC;IACnC,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,gBAAgB,EAAE,CAAC;IAC/B,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B;AA6FD;;;;;;;GAOG;AACH,wBAAgB,aAAa,IAAI,MAAM,EAAE,CAKxC;AAED,sFAAsF;AACtF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAsB7G;AAMD;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,aAAa,GAAE,WAAW,CAAC,MAAM,CAAa,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAyDjH"}