@circuitwall/jarela 0.14.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 (164) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +1 -1
  3. package/.next/standalone/.next/build-manifest.json +2 -2
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  6. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  15. package/.next/standalone/.next/server/app/_not-found.html +2 -2
  16. package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
  17. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  18. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  20. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/standalone/.next/server/app/api/v1/agents/[id]/route.js +6 -1
  24. package/.next/standalone/.next/server/app/api/v1/agents/[id]/route.js.map +1 -1
  25. package/.next/standalone/.next/server/app/api/v1/agents/route.js +6 -1
  26. package/.next/standalone/.next/server/app/api/v1/agents/route.js.map +1 -1
  27. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/route.js +9 -1
  28. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/route.js.map +1 -1
  29. package/.next/standalone/.next/server/app/api/v1/bridges/route.js +9 -1
  30. package/.next/standalone/.next/server/app/api/v1/bridges/route.js.map +1 -1
  31. package/.next/standalone/.next/server/app/api/v1/builtin-tools/route.js +36 -29
  32. package/.next/standalone/.next/server/app/api/v1/builtin-tools/route.js.map +1 -1
  33. package/.next/standalone/.next/server/app/api/v1/events/route.js +7 -1
  34. package/.next/standalone/.next/server/app/api/v1/events/route.js.map +1 -1
  35. package/.next/standalone/.next/server/app/api/v1/extensions/route.js +3 -3
  36. package/.next/standalone/.next/server/app/api/v1/extensions/route.js.map +1 -1
  37. package/.next/standalone/.next/server/app/api/v1/extensions/tools/[name]/secrets/route.js +4 -4
  38. package/.next/standalone/.next/server/app/api/v1/extensions/tools/[name]/secrets/route.js.map +1 -1
  39. package/.next/standalone/.next/server/app/api/v1/health/route.js +7 -1
  40. package/.next/standalone/.next/server/app/api/v1/health/route.js.map +1 -1
  41. package/.next/standalone/.next/server/app/api/v1/mcp-servers/[name]/route.js +9 -1
  42. package/.next/standalone/.next/server/app/api/v1/mcp-servers/[name]/route.js.map +1 -1
  43. package/.next/standalone/.next/server/app/api/v1/mcp-servers/route.js +9 -1
  44. package/.next/standalone/.next/server/app/api/v1/mcp-servers/route.js.map +1 -1
  45. package/.next/standalone/.next/server/app/api/v1/models/route.js +6 -1
  46. package/.next/standalone/.next/server/app/api/v1/models/route.js.map +1 -1
  47. package/.next/standalone/.next/server/app/api/v1/page-capture/route.js +7 -1
  48. package/.next/standalone/.next/server/app/api/v1/page-capture/route.js.map +1 -1
  49. package/.next/standalone/.next/server/app/api/v1/pending-actions/[id]/approve/route.js +14 -7
  50. package/.next/standalone/.next/server/app/api/v1/pending-actions/[id]/approve/route.js.map +1 -1
  51. package/.next/standalone/.next/server/app/api/v1/providers/[provider]/models/route.js +28 -0
  52. package/.next/standalone/.next/server/app/api/v1/providers/[provider]/models/route.js.map +1 -1
  53. package/.next/standalone/.next/server/app/api/v1/providers/route.js +7 -1
  54. package/.next/standalone/.next/server/app/api/v1/providers/route.js.map +1 -1
  55. package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/route.js +16 -2
  56. package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/route.js.map +1 -1
  57. package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/run/route.js +8 -1
  58. package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/run/route.js.map +1 -1
  59. package/.next/standalone/.next/server/app/api/v1/threads/route.js +6 -1
  60. package/.next/standalone/.next/server/app/api/v1/threads/route.js.map +1 -1
  61. package/.next/standalone/.next/server/app/api/v1/tools/route.js +10 -3
  62. package/.next/standalone/.next/server/app/api/v1/tools/route.js.map +1 -1
  63. package/.next/standalone/.next/server/app/index.html +2 -2
  64. package/.next/standalone/.next/server/app/index.rsc +3 -3
  65. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  66. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
  67. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  68. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
  69. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  70. package/.next/standalone/.next/server/app/page.js +56 -0
  71. package/.next/standalone/.next/server/app/page.js.map +1 -1
  72. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  73. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  74. package/.next/standalone/.next/server/app/setup.html +1 -1
  75. package/.next/standalone/.next/server/app/setup.rsc +2 -2
  76. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +2 -2
  77. package/.next/standalone/.next/server/app/setup.segments/_head.segment.rsc +1 -1
  78. package/.next/standalone/.next/server/app/setup.segments/_index.segment.rsc +2 -2
  79. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
  80. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +1 -1
  81. package/.next/standalone/.next/server/app/setup.segments/setup.segment.rsc +1 -1
  82. package/.next/standalone/.next/server/app-paths-manifest.json +1 -1
  83. package/.next/standalone/.next/server/chunks/1683.js +2 -2
  84. package/.next/standalone/.next/server/chunks/2082.js +122 -13
  85. package/.next/standalone/.next/server/chunks/2082.js.map +1 -1
  86. package/.next/standalone/.next/server/chunks/210.js +3 -3
  87. package/.next/standalone/.next/server/chunks/210.js.map +1 -1
  88. package/.next/standalone/.next/server/chunks/239.js +1902 -1487
  89. package/.next/standalone/.next/server/chunks/239.js.map +1 -1
  90. package/.next/standalone/.next/server/chunks/2447.js +9 -1
  91. package/.next/standalone/.next/server/chunks/2447.js.map +1 -1
  92. package/.next/standalone/.next/server/chunks/423.js +125 -16
  93. package/.next/standalone/.next/server/chunks/423.js.map +1 -1
  94. package/.next/standalone/.next/server/chunks/4631.js +36 -29
  95. package/.next/standalone/.next/server/chunks/4631.js.map +1 -1
  96. package/.next/standalone/.next/server/chunks/5937.js +3 -2
  97. package/.next/standalone/.next/server/chunks/5937.js.map +1 -1
  98. package/.next/standalone/.next/server/chunks/{947.js → 8866.js} +11321 -10883
  99. package/.next/standalone/.next/server/chunks/8866.js.map +1 -0
  100. package/.next/standalone/.next/server/chunks/9032.js +3 -3
  101. package/.next/standalone/.next/server/chunks/9032.js.map +1 -1
  102. package/.next/standalone/.next/server/middleware-build-manifest.js +2 -2
  103. package/.next/standalone/.next/server/middleware.js +122 -13
  104. package/.next/standalone/.next/server/pages/404.html +2 -2
  105. package/.next/standalone/.next/server/pages/500.html +1 -1
  106. package/.next/standalone/.next/server/proxy.js.map +1 -1
  107. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  108. package/.next/standalone/.next/static/chunks/app/{page-473b39ec30c7f569.js → page-a7cae65f235e2942.js} +57 -1
  109. package/.next/standalone/.next/static/chunks/app/page-a7cae65f235e2942.js.map +1 -0
  110. package/.next/standalone/.next/static/css/{6f8b1a84bcbcd467.css → e57bdbbbb5a05779.css} +2 -2
  111. package/.next/standalone/.next/static/css/e57bdbbbb5a05779.css.map +1 -0
  112. package/.next/standalone/package.json +9 -1
  113. package/CHANGELOG.md +90 -0
  114. package/README.md +30 -2
  115. package/api/types.ts +8 -0
  116. package/app/api/v1/agents/[id]/route.ts +7 -0
  117. package/app/api/v1/agents/route.ts +7 -0
  118. package/app/api/v1/events/route.ts +8 -0
  119. package/app/api/v1/extensions/route.ts +2 -2
  120. package/app/api/v1/extensions/tools/[name]/secrets/route.ts +3 -3
  121. package/app/api/v1/health/route.ts +8 -0
  122. package/app/api/v1/models/route.ts +7 -0
  123. package/app/api/v1/page-capture/route.ts +8 -0
  124. package/app/api/v1/providers/route.ts +8 -0
  125. package/app/api/v1/threads/[thread_id]/route.ts +8 -0
  126. package/app/api/v1/threads/[thread_id]/run/route.ts +9 -0
  127. package/app/api/v1/threads/route.ts +7 -0
  128. package/app/api/v1/tools/route.ts +9 -0
  129. package/components/chat/ContextUsageBar.tsx +44 -0
  130. package/lib/agents/llm.ts +25 -2
  131. package/lib/agents/run-thread.ts +13 -1
  132. package/lib/agents/stream-collector.ts +9 -1
  133. package/lib/api/serializers.test.ts +15 -0
  134. package/lib/api/serializers.ts +8 -0
  135. package/lib/db/migrations.ts +15 -0
  136. package/lib/health/runner.test.ts +24 -2
  137. package/lib/mcp/registry.ts +14 -6
  138. package/lib/providers/anthropic.test.ts +95 -0
  139. package/lib/providers/anthropic.ts +106 -10
  140. package/lib/providers/jarela-chat-model.ts +9 -1
  141. package/lib/providers/known-context-windows.ts +21 -0
  142. package/lib/providers/types.ts +21 -1
  143. package/lib/stores/message-usage.test.ts +34 -0
  144. package/lib/stores/message-usage.ts +15 -3
  145. package/lib/stores/pricing.test.ts +52 -0
  146. package/lib/stores/pricing.ts +26 -1
  147. package/lib/tools/builtins.ts +4 -0
  148. package/lib/tools/extension-surfaces.test.ts +79 -0
  149. package/lib/tools/extension-surfaces.ts +153 -0
  150. package/lib/tools/index.ts +27 -8
  151. package/lib/tools/list-tools.test.ts +76 -0
  152. package/lib/tools/list-tools.ts +84 -0
  153. package/lib/tools/mcp-servers-info.test.ts +73 -0
  154. package/lib/tools/mcp-servers-info.ts +71 -0
  155. package/lib/tools/providers-info.test.ts +73 -0
  156. package/lib/tools/providers-info.ts +106 -0
  157. package/lib/tools/registry.ts +36 -25
  158. package/lib/tools/types.ts +13 -0
  159. package/package.json +9 -1
  160. package/.next/standalone/.next/server/chunks/947.js.map +0 -1
  161. package/.next/standalone/.next/static/chunks/app/page-473b39ec30c7f569.js.map +0 -1
  162. package/.next/standalone/.next/static/css/6f8b1a84bcbcd467.css.map +0 -1
  163. /package/.next/standalone/.next/static/{T0p2VVPsJPj44rwbmjaFb → d_vhp-lJqfdjRFpnLVIqZ}/_buildManifest.js +0 -0
  164. /package/.next/standalone/.next/static/{T0p2VVPsJPj44rwbmjaFb → d_vhp-lJqfdjRFpnLVIqZ}/_ssgManifest.js +0 -0
@@ -1638,8 +1638,8 @@ var dist_messages = __webpack_require__(12016);
1638
1638
  var providers = __webpack_require__(68866);
1639
1639
  // EXTERNAL MODULE: ./lib/stores/model-config.ts
1640
1640
  var model_config = __webpack_require__(80937);
1641
- // EXTERNAL MODULE: ./lib/tools/index.ts + 23 modules
1642
- var lib_tools = __webpack_require__(64011);
1641
+ // EXTERNAL MODULE: ./lib/tools/index.ts + 27 modules
1642
+ var lib_tools = __webpack_require__(36141);
1643
1643
  // EXTERNAL MODULE: ./node_modules/@langchain/core/dist/language_models/chat_models.js + 1 modules
1644
1644
  var chat_models = __webpack_require__(87233);
1645
1645
  // EXTERNAL MODULE: ./node_modules/@langchain/core/dist/outputs.js
@@ -1832,15 +1832,26 @@ class JarelaChatModel extends chat_models/* BaseChatModel */.xV {
1832
1832
  } else if (event.type === "usage") {
1833
1833
  // ADR-0041: surface real provider token counts on the final
1834
1834
  // AIMessageChunk via LangChain's standard `usage_metadata` field so
1835
- // the agent loop can snapshot them into message_usage.
1835
+ // the agent loop can snapshot them into message_usage. PR #181 added
1836
+ // Anthropic prompt caching; carry the cache breakdown through
1837
+ // `input_token_details` (LangChain's standard channel) so cost
1838
+ // attribution downstream can apply the 1.25× / 0.1× rates.
1836
1839
  emittedAny = true;
1840
+ const cacheCreation = event.cache_creation_input_tokens ?? 0;
1841
+ const cacheRead = event.cache_read_input_tokens ?? 0;
1837
1842
  yield new outputs/* ChatGenerationChunk */.Cf({
1838
1843
  message: new dist_messages/* AIMessageChunk */.H({
1839
1844
  content: "",
1840
1845
  usage_metadata: {
1841
1846
  input_tokens: event.input_tokens ?? 0,
1842
1847
  output_tokens: event.output_tokens ?? 0,
1843
- total_tokens: event.total_tokens ?? (event.input_tokens ?? 0) + (event.output_tokens ?? 0)
1848
+ total_tokens: event.total_tokens ?? (event.input_tokens ?? 0) + (event.output_tokens ?? 0),
1849
+ ...cacheCreation > 0 || cacheRead > 0 ? {
1850
+ input_token_details: {
1851
+ cache_creation: cacheCreation,
1852
+ cache_read: cacheRead
1853
+ }
1854
+ } : {}
1844
1855
  }
1845
1856
  }),
1846
1857
  text: ""
@@ -2215,6 +2226,12 @@ async function* streamWithConfig(threadId, messages, options, signal) {
2215
2226
  // JarelaChatModel; we sum them so the final figure covers the whole turn.
2216
2227
  let usageInputTokens = 0;
2217
2228
  let usageOutputTokens = 0;
2229
+ // PR #181 + cache-fidelity follow-up: Anthropic prompt-cache reads/writes
2230
+ // arrive as a separate breakdown via `input_token_details`. Sum them
2231
+ // independently so the dashboard can report cost correctly (cache reads
2232
+ // are 10× cheaper, cache writes 1.25× more expensive than fresh input).
2233
+ let usageCacheCreationTokens = 0;
2234
+ let usageCacheReadTokens = 0;
2218
2235
  let sawUsage = false;
2219
2236
  // Tracks whether the model hit max_tokens mid-stream. JarelaChatModel tags
2220
2237
  // the final chunk with additional_kwargs.stop_reason="length" when this
@@ -2277,6 +2294,11 @@ async function* streamWithConfig(threadId, messages, options, signal) {
2277
2294
  if (usage && (usage.input_tokens > 0 || usage.output_tokens > 0)) {
2278
2295
  usageInputTokens += usage.input_tokens ?? 0;
2279
2296
  usageOutputTokens += usage.output_tokens ?? 0;
2297
+ const details = usage.input_token_details;
2298
+ if (details) {
2299
+ usageCacheCreationTokens += details.cache_creation ?? 0;
2300
+ usageCacheReadTokens += details.cache_read ?? 0;
2301
+ }
2280
2302
  sawUsage = true;
2281
2303
  }
2282
2304
  if (typeof chunk.content === "string" && chunk.content) {
@@ -2376,6 +2398,8 @@ async function* streamWithConfig(threadId, messages, options, signal) {
2376
2398
  usage: sawUsage ? {
2377
2399
  input_tokens: usageInputTokens,
2378
2400
  output_tokens: usageOutputTokens,
2401
+ cache_creation_input_tokens: usageCacheCreationTokens,
2402
+ cache_read_input_tokens: usageCacheReadTokens,
2379
2403
  source: "provider"
2380
2404
  } : {
2381
2405
  input_tokens: 0,
@@ -2472,6 +2496,8 @@ async function* streamWithConfig(threadId, messages, options, signal) {
2472
2496
  usage: sawUsage ? {
2473
2497
  input_tokens: usageInputTokens,
2474
2498
  output_tokens: usageOutputTokens,
2499
+ cache_creation_input_tokens: usageCacheCreationTokens,
2500
+ cache_read_input_tokens: usageCacheReadTokens,
2475
2501
  source: "provider"
2476
2502
  } : {
2477
2503
  input_tokens: 0,
@@ -3909,13 +3935,31 @@ function inferRatesFromSignals(signals) {
3909
3935
  confidence: tokenRates.length >= 2 ? "medium" : "low"
3910
3936
  };
3911
3937
  }
3912
- function estimateCostUsd(inputTokens, outputTokens, rates) {
3938
+ // Anthropic prompt-cache multipliers, applied against the standard input
3939
+ // rate. Cache writes are billed at 1.25× input ("cache create"); cache
3940
+ // reads are billed at 0.1× input ("cache hit"). Source:
3941
+ // https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching#pricing
3942
+ // We apply the same multipliers to other providers that publish a
3943
+ // cache-token breakdown — OpenAI's prompt caching, for example, also
3944
+ // quotes a 0.5× read multiplier, but its API surfaces only `cached_tokens`
3945
+ // (no separate write count) and a future PR can split that out. For now
3946
+ // any provider that emits both fields will be priced as above.
3947
+ const CACHE_CREATION_INPUT_RATE_MULTIPLIER = 1.25;
3948
+ const CACHE_READ_INPUT_RATE_MULTIPLIER = 0.1;
3949
+ function estimateCostUsd(inputTokens, outputTokens, rates, cache) {
3913
3950
  const inputRate = rates.inputPer1M;
3914
3951
  const outputRate = rates.outputPer1M;
3915
3952
  if (inputRate == null && outputRate == null) return 0;
3916
3953
  const inCost = inputRate == null ? 0 : inputTokens / 1000000 * inputRate;
3917
3954
  const outCost = outputRate == null ? 0 : outputTokens / 1000000 * outputRate;
3918
- return inCost + outCost;
3955
+ let cacheCost = 0;
3956
+ if (cache && inputRate != null) {
3957
+ const create = cache.cache_creation_input_tokens ?? 0;
3958
+ const read = cache.cache_read_input_tokens ?? 0;
3959
+ if (create > 0) cacheCost += create / 1000000 * inputRate * CACHE_CREATION_INPUT_RATE_MULTIPLIER;
3960
+ if (read > 0) cacheCost += read / 1000000 * inputRate * CACHE_READ_INPUT_RATE_MULTIPLIER;
3961
+ }
3962
+ return inCost + outCost + cacheCost;
3919
3963
  }
3920
3964
 
3921
3965
  ;// ./lib/agents/hallucination-classifier.ts
@@ -4832,7 +4876,12 @@ function persistAssistantMessage(thread_id, content, usedTools, toolEvents, cate
4832
4876
  inputPer1M: null,
4833
4877
  outputPer1M: null
4834
4878
  };
4835
- const cost = hasProviderUsage ? estimateCostUsd(usage.input_tokens, usage.output_tokens, rates) : 0;
4879
+ const cacheCreation = hasProviderUsage ? usage.cache_creation_input_tokens ?? 0 : 0;
4880
+ const cacheRead = hasProviderUsage ? usage.cache_read_input_tokens ?? 0 : 0;
4881
+ const cost = hasProviderUsage ? estimateCostUsd(usage.input_tokens, usage.output_tokens, rates, {
4882
+ cache_creation_input_tokens: cacheCreation,
4883
+ cache_read_input_tokens: cacheRead
4884
+ }) : 0;
4836
4885
  (0,message_usage/* recordMessageUsage */.up)({
4837
4886
  message_id: row.msg_id,
4838
4887
  thread_id,
@@ -4846,6 +4895,8 @@ function persistAssistantMessage(thread_id, content, usedTools, toolEvents, cate
4846
4895
  input_rate_usd_per_mtok: rates.inputPer1M,
4847
4896
  output_rate_usd_per_mtok: rates.outputPer1M,
4848
4897
  cost_usd: cost,
4898
+ cache_creation_input_tokens: cacheCreation > 0 ? cacheCreation : null,
4899
+ cache_read_input_tokens: cacheRead > 0 ? cacheRead : null,
4849
4900
  tier_usage: contextSnapshot ? {
4850
4901
  hot_tokens: contextSnapshot.hot_tokens,
4851
4902
  warm_tokens: contextSnapshot.warm_tokens,
@@ -5564,6 +5615,8 @@ function loadExternalTools(builtinNames) {
5564
5615
  result.usage = {
5565
5616
  input_tokens: d.usage.input_tokens ?? 0,
5566
5617
  output_tokens: d.usage.output_tokens ?? 0,
5618
+ cache_creation_input_tokens: d.usage.cache_creation_input_tokens ?? 0,
5619
+ cache_read_input_tokens: d.usage.cache_read_input_tokens ?? 0,
5567
5620
  provider: d.provider,
5568
5621
  model_id: d.model_id,
5569
5622
  model_config_name: d.model_config_name ?? null
@@ -5583,539 +5636,33 @@ function loadExternalTools(builtinNames) {
5583
5636
 
5584
5637
  /***/ }),
5585
5638
 
5586
- /***/ 45552:
5639
+ /***/ 36141:
5587
5640
  /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
5588
5641
 
5589
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
5590
- /* harmony export */ pN: () => (/* binding */ getKnownMaxOutputTokens),
5591
- /* harmony export */ zJ: () => (/* binding */ getKnownContextLength)
5592
- /* harmony export */ });
5593
- /* unused harmony export getKnownModelLimits */
5594
- // Web-sourced fallback context-window sizes per (provider, model_id).
5595
- // Used when the live `/models` endpoint doesn't report `context_length`
5596
- // (OpenAI's catalog and GitHub Copilot's catalog both omit it) AND the
5597
- // user hasn't pinned `context_window_tokens` on the model config.
5598
- //
5599
- // Resolution order at runtime is:
5600
- // 1. user-set `params.context_window_tokens` (explicit override)
5601
- // 2. live API value via `listModels()` / catalog cache (when populated)
5602
- // 3. this static fallback (this file)
5603
- // 4. `DEFAULT_CONTEXT_WINDOW_TOKENS` in lib/agents/context-budget.ts
5604
- //
5605
- // Verified from each vendor's public docs as of 2026-06. Bump when vendors
5606
- // publish new families.
5607
- const ANTHROPIC = {
5608
- "claude-opus-4-7": {
5609
- context_length: 1000000,
5610
- max_output_tokens: 8192
5611
- },
5612
- "claude-opus-4": {
5613
- context_length: 200000,
5614
- max_output_tokens: 8192
5615
- },
5616
- "claude-sonnet-4-6": {
5617
- context_length: 200000,
5618
- max_output_tokens: 8192
5619
- },
5620
- "claude-sonnet-4": {
5621
- context_length: 200000,
5622
- max_output_tokens: 8192
5623
- },
5624
- "claude-3.7-sonnet": {
5625
- context_length: 200000,
5626
- max_output_tokens: 8192
5627
- },
5628
- "claude-3-7-sonnet": {
5629
- context_length: 200000,
5630
- max_output_tokens: 8192
5631
- },
5632
- "claude-3-5-sonnet": {
5633
- context_length: 200000,
5634
- max_output_tokens: 8192
5635
- },
5636
- "claude-3-5-haiku": {
5637
- context_length: 200000,
5638
- max_output_tokens: 8192
5639
- },
5640
- "claude-haiku-4-5": {
5641
- context_length: 200000,
5642
- max_output_tokens: 4096
5643
- },
5644
- "claude-haiku-4": {
5645
- context_length: 200000,
5646
- max_output_tokens: 4096
5647
- },
5648
- "claude-3-opus": {
5649
- context_length: 200000,
5650
- max_output_tokens: 4096
5651
- }
5652
- };
5653
- const GEMINI = {
5654
- "gemini-2.5-pro": {
5655
- context_length: 1048576,
5656
- max_output_tokens: 65536
5657
- },
5658
- "gemini-2.5-flash": {
5659
- context_length: 1048576,
5660
- max_output_tokens: 65536
5661
- },
5662
- "gemini-2.0-flash-lite": {
5663
- context_length: 1048576,
5664
- max_output_tokens: 8192
5665
- },
5666
- "gemini-2.0-flash": {
5667
- context_length: 1048576,
5668
- max_output_tokens: 8192
5669
- },
5670
- "gemini-1.5-pro": {
5671
- context_length: 2097152,
5672
- max_output_tokens: 8192
5673
- },
5674
- "gemini-1.5-flash": {
5675
- context_length: 1048576,
5676
- max_output_tokens: 8192
5677
- }
5678
- };
5679
- const OPENAI = {
5680
- "gpt-5-mini": {
5681
- context_length: 400000,
5682
- max_output_tokens: 128000
5683
- },
5684
- "gpt-5": {
5685
- context_length: 400000,
5686
- max_output_tokens: 128000
5687
- },
5688
- "gpt-4.1-mini": {
5689
- context_length: 1047576,
5690
- max_output_tokens: 32768
5691
- },
5692
- "gpt-4.1": {
5693
- context_length: 1047576,
5694
- max_output_tokens: 32768
5695
- },
5696
- "gpt-4o-mini": {
5697
- context_length: 128000,
5698
- max_output_tokens: 16384
5699
- },
5700
- "gpt-4o": {
5701
- context_length: 128000,
5702
- max_output_tokens: 16384
5703
- },
5704
- "gpt-4-turbo": {
5705
- context_length: 128000,
5706
- max_output_tokens: 4096
5707
- },
5708
- "gpt-4": {
5709
- context_length: 8192,
5710
- max_output_tokens: 4096
5711
- },
5712
- "gpt-3.5-turbo": {
5713
- context_length: 16385,
5714
- max_output_tokens: 4096
5715
- },
5716
- "chatgpt-4o": {
5717
- context_length: 128000,
5718
- max_output_tokens: 16384
5719
- },
5720
- "o4-mini": {
5721
- context_length: 200000,
5722
- max_output_tokens: 100000
5723
- },
5724
- "o3-mini": {
5725
- context_length: 200000,
5726
- max_output_tokens: 100000
5727
- },
5728
- "o3": {
5729
- context_length: 200000,
5730
- max_output_tokens: 100000
5731
- },
5732
- "o1-mini": {
5733
- context_length: 128000,
5734
- max_output_tokens: 65536
5735
- },
5736
- "o1": {
5737
- context_length: 200000,
5738
- max_output_tokens: 100000
5739
- }
5740
- };
5741
- const DEEPSEEK = {
5742
- "deepseek-v4-flash": {
5743
- context_length: 65536,
5744
- max_output_tokens: 8192
5745
- },
5746
- "deepseek-v4-pro": {
5747
- context_length: 65536,
5748
- max_output_tokens: 8192
5749
- },
5750
- "deepseek-chat": {
5751
- context_length: 65536,
5752
- max_output_tokens: 8192
5753
- },
5754
- "deepseek-reasoner": {
5755
- context_length: 65536,
5756
- max_output_tokens: 8192
5757
- }
5758
- };
5759
- // GitHub Copilot proxies vendor models — sometimes under a transformed id
5760
- // (e.g. `Github-Opus4.6`, `copilot-claude-3.5-sonnet`). Strip the proxy
5761
- // prefix and dispatch to the underlying vendor table.
5762
- function resolveCopilot(model_id) {
5763
- const normalized = model_id.replace(/^(?:Github-|copilot-)/i, "").toLowerCase();
5764
- // Canonicalise a few proxy-specific spellings.
5765
- const canon = normalized.replace(/^opus4\.6/, "claude-opus-4-7").replace(/^opus4/, "claude-opus-4").replace(/^sonnet4\.6/, "claude-sonnet-4-6").replace(/^sonnet4/, "claude-sonnet-4").replace(/^haiku4\.5/, "claude-haiku-4-5");
5766
- if (canon.startsWith("claude")) return matchPrefix(ANTHROPIC, canon);
5767
- if (canon.startsWith("gemini")) return matchPrefix(GEMINI, canon);
5768
- if (canon.startsWith("gpt") || /^o[134](?:-|$)/.test(canon)) return matchPrefix(OPENAI, canon);
5769
- return null;
5770
- }
5771
- function matchPrefix(table, id) {
5772
- const lower = id.toLowerCase();
5773
- const exact = table[lower];
5774
- if (exact) return exact;
5775
- // Longest-prefix match so `gpt-4o-2024-08-06` resolves to `gpt-4o` and
5776
- // not `gpt-4` (which would also prefix-match).
5777
- let best = null;
5778
- for (const [k, v] of Object.entries(table)){
5779
- if (lower.startsWith(k) && (!best || k.length > best.key.length)) {
5780
- best = {
5781
- key: k,
5782
- v
5783
- };
5784
- }
5785
- }
5786
- return best?.v ?? null;
5787
- }
5788
- function getKnownModelLimits(provider, model_id) {
5789
- if (!provider || !model_id) return null;
5790
- switch(provider){
5791
- case "anthropic":
5792
- return matchPrefix(ANTHROPIC, model_id);
5793
- case "gemini":
5794
- return matchPrefix(GEMINI, model_id);
5795
- case "openai":
5796
- return matchPrefix(OPENAI, model_id);
5797
- case "deepseek":
5798
- return matchPrefix(DEEPSEEK, model_id);
5799
- case "github-copilot":
5800
- return resolveCopilot(model_id);
5801
- default:
5802
- return null;
5803
- }
5804
- }
5805
- function getKnownContextLength(provider, model_id) {
5806
- return getKnownModelLimits(provider, model_id)?.context_length ?? null;
5807
- }
5808
- function getKnownMaxOutputTokens(provider, model_id) {
5809
- return getKnownModelLimits(provider, model_id)?.max_output_tokens ?? null;
5810
- }
5811
5642
 
5643
+ // EXPORTS
5644
+ __webpack_require__.d(__webpack_exports__, {
5645
+ n0: () => (/* binding */ getAllToolsAsync),
5646
+ sT: () => (/* binding */ getBuiltinToolNames),
5647
+ Dk: () => (/* binding */ getToolCapability),
5648
+ $Z: () => (/* binding */ getToolCategory),
5649
+ y: () => (/* binding */ getToolGroup),
5650
+ s7: () => (/* binding */ getToolSource)
5651
+ });
5812
5652
 
5813
- /***/ }),
5653
+ // UNUSED EXPORTS: executeTool, getAllTools, getToolsDir, initTools, registerTools, toOpenAITools
5814
5654
 
5815
- /***/ 49899:
5816
- /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
5817
-
5818
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
5819
- /* harmony export */ FR: () => (/* binding */ defaultToolStats),
5820
- /* harmony export */ W7: () => (/* binding */ getToolStatsMap),
5821
- /* harmony export */ _r: () => (/* binding */ listToolStats),
5822
- /* harmony export */ hA: () => (/* binding */ recordToolUsage),
5823
- /* harmony export */ jg: () => (/* binding */ toStats)
5824
- /* harmony export */ });
5825
- /* unused harmony export summarizeToolUsage */
5826
- /* harmony import */ var _lib_db__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(46);
5827
-
5828
- const now = ()=>new Date().toISOString();
5829
- const UPSERT_SQL = `
5830
- INSERT INTO tool_stats
5831
- (tool_name, call_count, success_count, error_count, used_count, last_called_at, updated_at)
5832
- VALUES
5833
- (?, ?, ?, ?, ?, ?, ?)
5834
- ON CONFLICT(tool_name) DO UPDATE SET
5835
- call_count = tool_stats.call_count + excluded.call_count,
5836
- success_count = tool_stats.success_count + excluded.success_count,
5837
- error_count = tool_stats.error_count + excluded.error_count,
5838
- used_count = tool_stats.used_count + excluded.used_count,
5839
- last_called_at = COALESCE(excluded.last_called_at, tool_stats.last_called_at),
5840
- updated_at = excluded.updated_at
5841
- `;
5842
- function recordToolUsage(toolEvents, assistantContent) {
5843
- const deltas = summarizeToolUsage(toolEvents, assistantContent);
5844
- if (deltas.length === 0) return;
5845
- const db = (0,_lib_db__WEBPACK_IMPORTED_MODULE_0__/* .getDb */ .Lf)();
5846
- const stamp = now();
5847
- const stmt = db.prepare(UPSERT_SQL);
5848
- db.exec("BEGIN");
5849
- try {
5850
- for (const row of deltas){
5851
- stmt.run(row.name, row.calls, row.successes, row.errors, row.used, row.calls > 0 ? stamp : null, stamp);
5852
- }
5853
- db.exec("COMMIT");
5854
- } catch (err) {
5855
- try {
5856
- db.exec("ROLLBACK");
5857
- } catch {}
5858
- throw err;
5859
- }
5860
- }
5861
- function listToolStats() {
5862
- return (0,_lib_db__WEBPACK_IMPORTED_MODULE_0__/* .getDb */ .Lf)().prepare(`SELECT tool_name, call_count, success_count, error_count, used_count, last_called_at, updated_at
5863
- FROM tool_stats`).all();
5864
- }
5865
- function getToolStatsMap(names) {
5866
- const rows = names && names.length > 0 ? (0,_lib_db__WEBPACK_IMPORTED_MODULE_0__/* .getDb */ .Lf)().prepare(`SELECT tool_name, call_count, success_count, error_count, used_count, last_called_at, updated_at
5867
- FROM tool_stats
5868
- WHERE tool_name IN (${names.map(()=>"?").join(",")})`).all(...names) : listToolStats();
5869
- const out = new Map();
5870
- for (const row of rows)out.set(row.tool_name, toStats(row));
5871
- return out;
5872
- }
5873
- function defaultToolStats() {
5874
- return {
5875
- call_count: 0,
5876
- success_count: 0,
5877
- error_count: 0,
5878
- used_count: 0,
5879
- success_rate: 1,
5880
- usefulness_rate: 1,
5881
- score: 1,
5882
- never_used: true,
5883
- last_called_at: null
5884
- };
5885
- }
5886
- function toStats(row) {
5887
- const calls = Math.max(0, row.call_count);
5888
- if (calls === 0) return defaultToolStats();
5889
- const successRate = clamp01(row.success_count / calls);
5890
- const usefulnessRate = clamp01(row.used_count / calls);
5891
- const score = clamp01(successRate * 0.65 + usefulnessRate * 0.35);
5892
- return {
5893
- call_count: calls,
5894
- success_count: row.success_count,
5895
- error_count: row.error_count,
5896
- used_count: row.used_count,
5897
- success_rate: successRate,
5898
- usefulness_rate: usefulnessRate,
5899
- score,
5900
- never_used: false,
5901
- last_called_at: row.last_called_at
5902
- };
5903
- }
5904
- function summarizeToolUsage(toolEvents, assistantContent) {
5905
- const byId = new Map();
5906
- const assistantTerms = normalizeText(assistantContent);
5907
- for (const ev of toolEvents){
5908
- if (!ev.name) continue;
5909
- const key = ev.id || `${ev.phase}:${ev.name}`;
5910
- const existing = byId.get(key) ?? {
5911
- name: ev.name,
5912
- successful: false,
5913
- used: false
5914
- };
5915
- if (ev.phase === "call") {
5916
- existing.name = ev.name;
5917
- } else if (ev.phase === "result") {
5918
- const successful = !isErrorPayload(ev.payload);
5919
- existing.successful = successful;
5920
- existing.used = successful && payloadLooksUsed(ev.payload, assistantTerms);
5921
- }
5922
- byId.set(key, existing);
5923
- }
5924
- const totals = new Map();
5925
- for (const ev of toolEvents){
5926
- if (ev.phase !== "call" || !ev.name) continue;
5927
- const key = ev.id || `${ev.phase}:${ev.name}`;
5928
- const outcome = byId.get(key);
5929
- const next = totals.get(ev.name) ?? {
5930
- name: ev.name,
5931
- calls: 0,
5932
- successes: 0,
5933
- errors: 0,
5934
- used: 0
5935
- };
5936
- next.calls += 1;
5937
- if (outcome?.successful) next.successes += 1;
5938
- else next.errors += 1;
5939
- if (outcome?.used) next.used += 1;
5940
- totals.set(ev.name, next);
5941
- }
5942
- return [
5943
- ...totals.values()
5944
- ];
5945
- }
5946
- function payloadLooksUsed(payload, assistantTerms) {
5947
- if (assistantTerms.size === 0) return false;
5948
- const candidates = extractPayloadTerms(payload);
5949
- if (candidates.length === 0) return false;
5950
- let matched = 0;
5951
- for (const term of candidates){
5952
- if (assistantTerms.has(term)) matched += 1;
5953
- if (matched >= 2) return true;
5954
- }
5955
- return false;
5956
- }
5957
- function extractPayloadTerms(payload) {
5958
- const raw = stringifyPayload(payload);
5959
- if (!raw) return [];
5960
- const unique = new Set();
5961
- for (const token of raw.toLowerCase().split(/[^a-z0-9_./:-]+/)){
5962
- if (token.length < 4) continue;
5963
- if (/^\d+$/.test(token) && token.length < 6) continue;
5964
- unique.add(token);
5965
- if (unique.size >= 24) break;
5966
- }
5967
- return [
5968
- ...unique
5969
- ];
5970
- }
5971
- function stringifyPayload(payload) {
5972
- if (typeof payload === "string") return payload;
5973
- try {
5974
- return JSON.stringify(payload);
5975
- } catch {
5976
- return "";
5977
- }
5978
- }
5979
- function normalizeText(text) {
5980
- const out = new Set();
5981
- for (const token of text.toLowerCase().split(/[^a-z0-9_./:-]+/)){
5982
- if (token.length < 4) continue;
5983
- out.add(token);
5984
- }
5985
- return out;
5986
- }
5987
- function isErrorPayload(payload) {
5988
- if (typeof payload === "string") return /\berror\b|\bfailed\b|\bexception\b/i.test(payload);
5989
- if (!payload || typeof payload !== "object") return false;
5990
- if ("error" in payload || "errors" in payload) return true;
5991
- const status = "status" in payload ? payload.status : undefined;
5992
- if (typeof status === "string" && /error|failed/i.test(status)) return true;
5993
- return false;
5994
- }
5995
- function clamp01(value) {
5996
- return Math.max(0, Math.min(1, value));
5997
- }
5998
-
5999
-
6000
- /***/ }),
6001
-
6002
- /***/ 52907:
6003
- /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
6004
-
6005
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
6006
- /* harmony export */ LK: () => (/* binding */ writeBinaryFile),
6007
- /* harmony export */ Uc: () => (/* binding */ fileAbsPath)
6008
- /* harmony export */ });
6009
- /* unused harmony exports FILES_DIR, isSafeFileName */
6010
- /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(73024);
6011
- /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(node_fs__WEBPACK_IMPORTED_MODULE_0__);
6012
- /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(76760);
6013
- /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(node_path__WEBPACK_IMPORTED_MODULE_1__);
6014
- /* harmony import */ var _lib_db_data_dir__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(57477);
6015
- // Local file store for binary artifacts produced by tools (generated images,
6016
- // downloads, etc.). Files live under ~/.jarela/files/ and are served by
6017
- // GET /api/v1/files/[name]. The tool returns a relative URL the chat
6018
- // renderer can embed as <img src="/api/v1/files/...">.
6019
-
6020
-
6021
-
6022
- const FILES_DIR = (0,node_path__WEBPACK_IMPORTED_MODULE_1__.join)((0,_lib_db_data_dir__WEBPACK_IMPORTED_MODULE_2__/* .getDataDir */ .o)(), "files");
6023
- (0,node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync)(FILES_DIR, {
6024
- recursive: true
6025
- });
6026
- // Name must be a single path segment with no separators or "..". Callers
6027
- // generate names from randomUUID() so this is mostly defense-in-depth.
6028
- const SAFE_NAME = /^[A-Za-z0-9._-]+$/;
6029
- function isSafeFileName(name) {
6030
- return SAFE_NAME.test(name) && !name.includes("..");
6031
- }
6032
- function fileAbsPath(name) {
6033
- if (!isSafeFileName(name)) return null;
6034
- return (0,node_path__WEBPACK_IMPORTED_MODULE_1__.join)(FILES_DIR, name);
6035
- }
6036
- function writeBinaryFile(name, data) {
6037
- if (!isSafeFileName(name)) throw new Error(`unsafe file name: ${name}`);
6038
- const p = (0,node_path__WEBPACK_IMPORTED_MODULE_1__.join)(FILES_DIR, name);
6039
- (0,node_fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync)(p, data);
6040
- return p;
6041
- }
6042
-
6043
-
6044
- /***/ }),
6045
-
6046
- /***/ 56718:
6047
- /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
6048
-
6049
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
6050
- /* harmony export */ HP: () => (/* binding */ disabledCategories),
6051
- /* harmony export */ MD: () => (/* binding */ setCategoryEnabled)
6052
- /* harmony export */ });
6053
- /* unused harmony exports isCategoryEnabled, listCategoryStates */
6054
- /* harmony import */ var _lib_db__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(46);
6055
- // Built-in tool category toggles.
6056
- //
6057
- // A category is "enabled" unless an explicit row in `builtin_tool_categories`
6058
- // says otherwise. Default-enabled semantics mean upgrading installs keep
6059
- // every category working with zero migration work.
6060
- //
6061
- // Disabled categories are filtered at three layers:
6062
- // - GET /api/v1/tools (so the agent editor never offers them as permissions)
6063
- // - getAllTools / getAllToolsAsync (so the agent runtime can't see them)
6064
- // - executeTool (defense in depth, blocks stale agent configs)
6065
-
6066
- const now = ()=>new Date().toISOString();
6067
- function isCategoryEnabled(category) {
6068
- const row = getDb().prepare("SELECT enabled FROM builtin_tool_categories WHERE category=?").get(category);
6069
- return row ? row.enabled === 1 : true;
6070
- }
6071
- function disabledCategories() {
6072
- const rows = (0,_lib_db__WEBPACK_IMPORTED_MODULE_0__/* .getDb */ .Lf)().prepare("SELECT category FROM builtin_tool_categories WHERE enabled=0").all();
6073
- return new Set(rows.map((r)=>r.category));
6074
- }
6075
- function listCategoryStates() {
6076
- const rows = getDb().prepare("SELECT category, enabled, updated_at FROM builtin_tool_categories").all();
6077
- return rows.map((r)=>({
6078
- category: r.category,
6079
- enabled: r.enabled === 1,
6080
- updated_at: r.updated_at
6081
- }));
6082
- }
6083
- function setCategoryEnabled(category, enabled) {
6084
- (0,_lib_db__WEBPACK_IMPORTED_MODULE_0__/* .getDb */ .Lf)().prepare(`INSERT INTO builtin_tool_categories (category, enabled, updated_at)
6085
- VALUES (?, ?, ?)
6086
- ON CONFLICT(category) DO UPDATE SET enabled=excluded.enabled, updated_at=excluded.updated_at`).run(category, enabled ? 1 : 0, now());
6087
- }
6088
-
6089
-
6090
- /***/ }),
6091
-
6092
- /***/ 64011:
6093
- /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
6094
-
6095
-
6096
- // EXPORTS
6097
- __webpack_require__.d(__webpack_exports__, {
6098
- Wi: () => (/* binding */ BUILTIN_TOOL_NAMES),
6099
- n0: () => (/* binding */ getAllToolsAsync),
6100
- Dk: () => (/* binding */ getToolCapability),
6101
- $Z: () => (/* binding */ getToolCategory),
6102
- y: () => (/* binding */ getToolGroup),
6103
- s7: () => (/* binding */ getToolSource)
6104
- });
6105
-
6106
- // UNUSED EXPORTS: executeTool, getAllTools, getToolsDir, initTools, registerTools, toOpenAITools
6107
-
6108
- // EXTERNAL MODULE: ./node_modules/@langchain/core/dist/utils/function_calling.js
6109
- var function_calling = __webpack_require__(46329);
6110
- // EXTERNAL MODULE: ./node_modules/@langchain/core/dist/tools/index.js
6111
- var tools = __webpack_require__(71890);
6112
- // EXTERNAL MODULE: ./node_modules/zod/v4/classic/schemas.js + 3 modules
6113
- var schemas = __webpack_require__(49883);
6114
- // EXTERNAL MODULE: ./lib/stores/memory.ts
6115
- var memory = __webpack_require__(80956);
6116
- // EXTERNAL MODULE: ./lib/tools/registry.ts + 1 modules
6117
- var registry = __webpack_require__(18995);
6118
- ;// ./lib/tools/memory.ts
5655
+ // EXTERNAL MODULE: ./node_modules/@langchain/core/dist/utils/function_calling.js
5656
+ var function_calling = __webpack_require__(46329);
5657
+ // EXTERNAL MODULE: ./node_modules/@langchain/core/dist/tools/index.js
5658
+ var tools = __webpack_require__(71890);
5659
+ // EXTERNAL MODULE: ./node_modules/zod/v4/classic/schemas.js + 3 modules
5660
+ var schemas = __webpack_require__(49883);
5661
+ // EXTERNAL MODULE: ./lib/stores/memory.ts
5662
+ var memory = __webpack_require__(80956);
5663
+ // EXTERNAL MODULE: ./lib/tools/registry.ts + 1 modules
5664
+ var registry = __webpack_require__(18995);
5665
+ ;// ./lib/tools/memory.ts
6119
5666
 
6120
5667
 
6121
5668
 
@@ -9831,371 +9378,672 @@ const proposeConfigChangeTool = (0,tools/* tool */.z6)(async ({ kind, payload, r
9831
9378
  ts: Date.now()
9832
9379
  });
9833
9380
  return JSON.stringify({
9834
- proposal_id: row.id,
9835
- status: "pending",
9836
- message: "Proposal queued for the user's approval. Tell the user briefly what you proposed and that " + "they need to approve it in the chat. Do not retry; check status later with check_proposal."
9381
+ proposal_id: row.id,
9382
+ status: "pending",
9383
+ message: "Proposal queued for the user's approval. Tell the user briefly what you proposed and that " + "they need to approve it in the chat. Do not retry; check status later with check_proposal."
9384
+ });
9385
+ }, {
9386
+ name: "propose_config_change",
9387
+ description: "Propose a configuration change that requires user approval. Use this when you'd benefit from " + "installing/toggling an MCP server, or modifying agent settings (tool allowlist, identity, instructions, history window). " + "The proposal is queued and shown to the user; they explicitly approve or deny. " + "Don't propose changes the user didn't ask for — only when the current task clearly needs it.",
9388
+ schema: schemas/* object */.Ik({
9389
+ kind: schemas/* enum */.k5([
9390
+ "install_mcp",
9391
+ "toggle_mcp",
9392
+ "update_agent_tools",
9393
+ "update_agent",
9394
+ "start_oauth",
9395
+ "set_provider_key",
9396
+ "enable_integration",
9397
+ "upsert_harness"
9398
+ ]).describe("Type of change being proposed"),
9399
+ payload: schemas/* record */.g1(schemas/* string */.Yj(), schemas/* unknown */.L5()).describe("Parameters for the change. NEVER put secrets, API keys, OAuth tokens, " + "or passwords in this object — those are collected by the approval UI " + "directly. Examples by kind:\n" + "- install_mcp: { registry_id: 'github', variables: { GITHUB_TOKEN: 'asks-user-for-it' } } " + " OR { name, transport, spec }\n" + "- toggle_mcp: { name: 'github', enabled: true }\n" + "- update_agent_tools: { agent_id: '<this-agent>', tools: ['web_search', 'memory_*'] }\n" + "- update_agent: { agent_id, identity?, instructions?, history_limit?, history_window_hours?, harness_id? } " + " — harness_id accepts an existing harness id ('builtin:default' or 'custom:<uuid>'), or null to inherit the global default\n" + "- start_oauth: { integration_id: 'gmail' } — only after enable_integration saved client_id/secret\n" + "- set_provider_key: { name: 'anthropic-default', provider: 'anthropic', model_id: 'claude-opus-4-7', is_default?: true } " + " — the user pastes the API key into the approval modal; do NOT include it here\n" + "- enable_integration: { id: 'gmail' } — the user fills credentials in the approval modal\n" + "- upsert_harness: { id?: 'custom:<uuid>', name, description?, sections: { capabilities?: {enabled,body}, plan_first?: {enabled,body}, presentation?: {enabled,body}, citation?: {enabled,body}, self_config?: {enabled,body} } } " + " — omit `id` to create; pass an existing custom:* id to edit. Built-in harnesses ('builtin:*') are read-only and rejected."),
9400
+ reason: schemas/* string */.Yj().describe("Short human-readable reason shown to the user (≤100 chars)")
9401
+ })
9402
+ });
9403
+ const checkProposalTool = (0,tools/* tool */.z6)(async ({ proposal_id })=>{
9404
+ const row = (0,pending_actions/* getPendingAction */.wv)(proposal_id);
9405
+ if (!row) return JSON.stringify({
9406
+ error: `proposal ${proposal_id} not found`
9407
+ });
9408
+ return JSON.stringify({
9409
+ id: row.id,
9410
+ kind: row.kind,
9411
+ status: row.status,
9412
+ result: row.result ? propose_safeParse(row.result) : null,
9413
+ created_at: row.created_at,
9414
+ decided_at: row.decided_at
9415
+ });
9416
+ }, {
9417
+ name: "check_proposal",
9418
+ description: "Check the status of a previously-submitted config-change proposal by its id.",
9419
+ schema: schemas/* object */.Ik({
9420
+ proposal_id: schemas/* string */.Yj()
9421
+ })
9422
+ });
9423
+ function propose_safeParse(s) {
9424
+ try {
9425
+ return JSON.parse(s);
9426
+ } catch {
9427
+ return s;
9428
+ }
9429
+ }
9430
+ (0,registry/* registerTools */.Fd)("Config", "read", [
9431
+ checkProposalTool
9432
+ ]);
9433
+ (0,registry/* registerTools */.Fd)("Config", "write", [
9434
+ proposeConfigChangeTool
9435
+ ]);
9436
+
9437
+ // EXTERNAL MODULE: ./lib/integrations/registry.ts + 6 modules
9438
+ var integrations_registry = __webpack_require__(91384);
9439
+ // EXTERNAL MODULE: ./lib/stores/integrations.ts
9440
+ var integrations = __webpack_require__(78228);
9441
+ ;// ./lib/tools/integrations.ts
9442
+ // Read-only tools that expose integration manifests to the agent. See
9443
+ // ADR-0010 — these power the "user asks how to connect X → agent narrates
9444
+ // the steps and proposes actions" loop without giving the agent any new
9445
+ // privileges.
9446
+
9447
+
9448
+
9449
+
9450
+
9451
+ function manifestStatus(id) {
9452
+ if (!(0,integrations/* isKnownIntegration */.GV)(id)) return "no_credentials_schema";
9453
+ return (0,integrations/* getIntegrationStatus */.v9)(id)?.configured ? "configured" : "not_configured";
9454
+ }
9455
+ const listIntegrationsTool = (0,tools/* tool */.z6)(async ()=>{
9456
+ const summary = (0,integrations_registry/* listManifests */.po)().map((m)=>({
9457
+ id: m.id,
9458
+ name: m.name,
9459
+ category: m.category,
9460
+ summary: m.summary,
9461
+ status: manifestStatus(m.id)
9462
+ }));
9463
+ return JSON.stringify({
9464
+ integrations: summary
9465
+ });
9466
+ }, {
9467
+ name: "list_integrations",
9468
+ description: "List every integration Jarela knows how to set up, with a one-line summary and " + "current configuration status. Read-only. Use this when the user asks 'what can I connect?' " + "or before proposing enable_integration, so you know what's available and whether it's " + "already configured.",
9469
+ schema: schemas/* object */.Ik({})
9470
+ });
9471
+ const getIntegrationSetupTool = (0,tools/* tool */.z6)(async ({ id })=>{
9472
+ const manifest = (0,integrations_registry/* getManifest */.c2)(id);
9473
+ if (!manifest) {
9474
+ return JSON.stringify({
9475
+ error: `unknown integration "${id}". Call list_integrations to see available ids.`
9476
+ });
9477
+ }
9478
+ return JSON.stringify({
9479
+ id: manifest.id,
9480
+ name: manifest.name,
9481
+ summary: manifest.summary,
9482
+ category: manifest.category,
9483
+ status: manifestStatus(manifest.id),
9484
+ prerequisites: manifest.prerequisites,
9485
+ steps: manifest.steps,
9486
+ troubleshooting: manifest.troubleshooting,
9487
+ notes: [
9488
+ "Walk the user through `prerequisites` first; some require browser actions outside Jarela.",
9489
+ "For each step that has a `proposes` field, call propose_config_change with that kind when ready.",
9490
+ "When a step has a `verify.tool`, call that tool after applying the step to confirm success."
9491
+ ]
9492
+ });
9493
+ }, {
9494
+ name: "get_integration_setup",
9495
+ description: "Return the full setup manifest for one integration — prerequisites, ordered steps, and " + "troubleshooting hints. Read-only. Use this before walking the user through a setup, or " + "when a tool fails and you need the integration-specific recovery hint.",
9496
+ schema: schemas/* object */.Ik({
9497
+ id: schemas/* string */.Yj().describe("Integration id from list_integrations (e.g. 'gmail', 'atlassian')")
9498
+ })
9499
+ });
9500
+ (0,registry/* registerTools */.Fd)("Config", "read", [
9501
+ listIntegrationsTool,
9502
+ getIntegrationSetupTool
9503
+ ]);
9504
+
9505
+ // EXTERNAL MODULE: ./lib/tools/atlassian.ts
9506
+ var atlassian = __webpack_require__(65856);
9507
+ // EXTERNAL MODULE: ./lib/tools/jira-align.ts
9508
+ var jira_align = __webpack_require__(55738);
9509
+ // EXTERNAL MODULE: ./lib/tools/github.ts
9510
+ var github = __webpack_require__(70729);
9511
+ // EXTERNAL MODULE: ./lib/tools/gmail.ts
9512
+ var gmail = __webpack_require__(77414);
9513
+ // EXTERNAL MODULE: ./lib/integrations/gmail-oauth.ts
9514
+ var gmail_oauth = __webpack_require__(87246);
9515
+ ;// ./lib/tools/calendar.ts
9516
+ /**
9517
+ * Native Google Calendar tools — direct REST calls to
9518
+ * calendar.googleapis.com, sharing the Gmail OAuth client (one Google
9519
+ * account, one refresh token, one access-token cache; see
9520
+ * lib/integrations/gmail-oauth.ts).
9521
+ *
9522
+ * Scope-wise this surface is **events on existing calendars**:
9523
+ * - list / get / create / update / delete events
9524
+ * - list the user's calendars (read-only)
9525
+ * There is deliberately no calendar-list create/delete and no ACL
9526
+ * editing. To enable a wider surface, add the broader scope to
9527
+ * GMAIL_SCOPES and (force users to) reconnect.
9528
+ *
9529
+ * v1 design notes:
9530
+ * - calendarId defaults to "primary" everywhere so the agent can
9531
+ * create a quick event without first calling list_calendars.
9532
+ * - All datetimes are RFC3339 strings (e.g. "2026-05-19T16:00:00-07:00").
9533
+ * When the caller omits a timezone offset, Google falls back to the
9534
+ * calendar's default TZ. The tools surface a `time_zone` field that
9535
+ * forwards an IANA name in case the caller wants to be explicit.
9536
+ * - `calendar_create_event` can provision a Google Meet link via the
9537
+ * conferenceData createRequest dance — gated behind `add_meet_link`
9538
+ * so unused calls don't trip Google's quota.
9539
+ */
9540
+
9541
+
9542
+
9543
+ const CALENDAR_BASE = "https://www.googleapis.com/calendar/v3";
9544
+ const calendarFetch = (auth, path, init)=>(0,gmail_oauth/* googleFetch */.sI)(auth, "Calendar", CALENDAR_BASE, path, init);
9545
+ // Slim down a full event to the fields the agent actually needs in a
9546
+ // thread response. Keeps tool outputs small enough not to blow context.
9547
+ function slimEvent(e) {
9548
+ return {
9549
+ id: e.id,
9550
+ status: e.status,
9551
+ summary: e.summary ?? null,
9552
+ description: e.description ?? null,
9553
+ location: e.location ?? null,
9554
+ start: e.start?.dateTime ?? e.start?.date ?? null,
9555
+ end: e.end?.dateTime ?? e.end?.date ?? null,
9556
+ time_zone: e.start?.timeZone ?? e.end?.timeZone ?? null,
9557
+ attendees: (e.attendees ?? []).map((a)=>({
9558
+ email: a.email,
9559
+ name: a.displayName ?? null,
9560
+ response: a.responseStatus ?? null
9561
+ })),
9562
+ meet_link: e.hangoutLink ?? null,
9563
+ html_link: e.htmlLink ?? null
9564
+ };
9565
+ }
9566
+ // ── Tools ───────────────────────────────────────────────────────────────────
9567
+ const calendarListCalendarsTool = (0,tools/* tool */.z6)(async ()=>{
9568
+ const auth = (0,gmail_oauth/* resolveGoogleAuth */.wq)();
9569
+ if ("error" in auth) return JSON.stringify({
9570
+ error: auth.error
9571
+ });
9572
+ const data = await calendarFetch(auth, "/users/me/calendarList");
9573
+ if ("error" in data && data.error) return JSON.stringify(data);
9574
+ return JSON.stringify({
9575
+ calendars: (data.items ?? []).map((c)=>({
9576
+ id: c.id,
9577
+ summary: c.summary ?? null,
9578
+ primary: c.primary === true,
9579
+ time_zone: c.timeZone ?? null,
9580
+ access_role: c.accessRole ?? null
9581
+ }))
9582
+ });
9583
+ }, {
9584
+ name: "calendar_list_calendars",
9585
+ description: "List the user's Google calendars (primary + any they've subscribed to). " + "Returns id, summary, primary flag, time zone, access role per entry. The " + "`id` is what other calendar_* tools expect as `calendar_id`. Use this when " + "the user mentions a non-primary calendar by name (e.g. 'put it on my Work " + "calendar') — match against `summary` to find the right id.",
9586
+ schema: schemas/* object */.Ik({})
9587
+ });
9588
+ const calendarListEventsTool = (0,tools/* tool */.z6)(async ({ calendar_id, time_min, time_max, query, max_results })=>{
9589
+ const auth = (0,gmail_oauth/* resolveGoogleAuth */.wq)();
9590
+ if ("error" in auth) return JSON.stringify({
9591
+ error: auth.error
9592
+ });
9593
+ const cal = encodeURIComponent(calendar_id ?? "primary");
9594
+ const params = new URLSearchParams({
9595
+ singleEvents: "true",
9596
+ orderBy: "startTime",
9597
+ maxResults: String(Math.min(Math.max(max_results ?? 25, 1), 100))
9598
+ });
9599
+ // Default window: now → +7 days. Keeps "what's on my schedule" cheap.
9600
+ const now = new Date();
9601
+ const inAWeek = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
9602
+ params.set("timeMin", time_min ?? now.toISOString());
9603
+ params.set("timeMax", time_max ?? inAWeek.toISOString());
9604
+ if (query) params.set("q", query);
9605
+ const data = await calendarFetch(auth, `/calendars/${cal}/events?${params.toString()}`);
9606
+ if ("error" in data && data.error) return JSON.stringify(data);
9607
+ return JSON.stringify({
9608
+ events: (data.items ?? []).map(slimEvent)
9837
9609
  });
9838
9610
  }, {
9839
- name: "propose_config_change",
9840
- description: "Propose a configuration change that requires user approval. Use this when you'd benefit from " + "installing/toggling an MCP server, or modifying agent settings (tool allowlist, identity, instructions, history window). " + "The proposal is queued and shown to the user; they explicitly approve or deny. " + "Don't propose changes the user didn't ask for only when the current task clearly needs it.",
9611
+ name: "calendar_list_events",
9612
+ description: "List events on a calendar within a time window. Defaults: primary calendar, " + "now +7 days, ordered by start, recurring events expanded into single " + "instances. **Use this before creating an event** to avoid double-booking, " + "and to look up existing event ids before updating/deleting. " + "Datetimes are RFC3339 (e.g. '2026-05-19T16:00:00-07:00').",
9841
9613
  schema: schemas/* object */.Ik({
9842
- kind: schemas/* enum */.k5([
9843
- "install_mcp",
9844
- "toggle_mcp",
9845
- "update_agent_tools",
9846
- "update_agent",
9847
- "start_oauth",
9848
- "set_provider_key",
9849
- "enable_integration",
9850
- "upsert_harness"
9851
- ]).describe("Type of change being proposed"),
9852
- payload: schemas/* record */.g1(schemas/* string */.Yj(), schemas/* unknown */.L5()).describe("Parameters for the change. NEVER put secrets, API keys, OAuth tokens, " + "or passwords in this object — those are collected by the approval UI " + "directly. Examples by kind:\n" + "- install_mcp: { registry_id: 'github', variables: { GITHUB_TOKEN: 'asks-user-for-it' } } " + " OR { name, transport, spec }\n" + "- toggle_mcp: { name: 'github', enabled: true }\n" + "- update_agent_tools: { agent_id: '<this-agent>', tools: ['web_search', 'memory_*'] }\n" + "- update_agent: { agent_id, identity?, instructions?, history_limit?, history_window_hours?, harness_id? } " + " — harness_id accepts an existing harness id ('builtin:default' or 'custom:<uuid>'), or null to inherit the global default\n" + "- start_oauth: { integration_id: 'gmail' } — only after enable_integration saved client_id/secret\n" + "- set_provider_key: { name: 'anthropic-default', provider: 'anthropic', model_id: 'claude-opus-4-7', is_default?: true } " + " — the user pastes the API key into the approval modal; do NOT include it here\n" + "- enable_integration: { id: 'gmail' } — the user fills credentials in the approval modal\n" + "- upsert_harness: { id?: 'custom:<uuid>', name, description?, sections: { capabilities?: {enabled,body}, plan_first?: {enabled,body}, presentation?: {enabled,body}, citation?: {enabled,body}, self_config?: {enabled,body} } } " + " — omit `id` to create; pass an existing custom:* id to edit. Built-in harnesses ('builtin:*') are read-only and rejected."),
9853
- reason: schemas/* string */.Yj().describe("Short human-readable reason shown to the user (≤100 chars)")
9614
+ calendar_id: schemas/* string */.Yj().optional().describe("Calendar id (default: 'primary')"),
9615
+ time_min: schemas/* string */.Yj().optional().describe("RFC3339 lower bound (default: now)"),
9616
+ time_max: schemas/* string */.Yj().optional().describe("RFC3339 upper bound (default: now + 7 days)"),
9617
+ query: schemas/* string */.Yj().optional().describe("Free-text search across event fields"),
9618
+ max_results: schemas/* number */.ai().int().optional().describe("Max events (default 25, max 100)")
9854
9619
  })
9855
9620
  });
9856
- const checkProposalTool = (0,tools/* tool */.z6)(async ({ proposal_id })=>{
9857
- const row = (0,pending_actions/* getPendingAction */.wv)(proposal_id);
9858
- if (!row) return JSON.stringify({
9859
- error: `proposal ${proposal_id} not found`
9621
+ const calendarGetEventTool = (0,tools/* tool */.z6)(async ({ calendar_id, event_id })=>{
9622
+ const auth = (0,gmail_oauth/* resolveGoogleAuth */.wq)();
9623
+ if ("error" in auth) return JSON.stringify({
9624
+ error: auth.error
9860
9625
  });
9861
- return JSON.stringify({
9862
- id: row.id,
9863
- kind: row.kind,
9864
- status: row.status,
9865
- result: row.result ? propose_safeParse(row.result) : null,
9866
- created_at: row.created_at,
9867
- decided_at: row.decided_at
9626
+ const cal = encodeURIComponent(calendar_id ?? "primary");
9627
+ const eid = encodeURIComponent(event_id);
9628
+ const data = await calendarFetch(auth, `/calendars/${cal}/events/${eid}`);
9629
+ if (data.error) return JSON.stringify({
9630
+ error: data.error
9868
9631
  });
9632
+ return JSON.stringify(slimEvent(data));
9869
9633
  }, {
9870
- name: "check_proposal",
9871
- description: "Check the status of a previously-submitted config-change proposal by its id.",
9634
+ name: "calendar_get_event",
9635
+ description: "Fetch one Google Calendar event by id. Returns the same slim shape as " + "calendar_list_events but for a single event.",
9872
9636
  schema: schemas/* object */.Ik({
9873
- proposal_id: schemas/* string */.Yj()
9637
+ calendar_id: schemas/* string */.Yj().optional().describe("Calendar id (default: 'primary')"),
9638
+ event_id: schemas/* string */.Yj().describe("Event id (from calendar_list_events results)")
9874
9639
  })
9875
9640
  });
9876
- function propose_safeParse(s) {
9877
- try {
9878
- return JSON.parse(s);
9879
- } catch {
9880
- return s;
9641
+ const calendarCreateEventTool = (0,tools/* tool */.z6)(async ({ calendar_id, summary, subject, start_iso, end_iso, description, location, attendees, time_zone, add_meet_link })=>{
9642
+ const auth = (0,gmail_oauth/* resolveGoogleAuth */.wq)();
9643
+ if ("error" in auth) return JSON.stringify({
9644
+ error: auth.error
9645
+ });
9646
+ const cal = encodeURIComponent(calendar_id ?? "primary");
9647
+ // `subject` is accepted as an alias for `summary` so agents trained on
9648
+ // Outlook vocabulary don't fail the schema on first try.
9649
+ const eventSummary = summary ?? subject;
9650
+ if (!eventSummary) {
9651
+ return JSON.stringify({
9652
+ error: "summary (or subject) is required"
9653
+ });
9881
9654
  }
9882
- }
9883
- (0,registry/* registerTools */.Fd)("Config", "read", [
9884
- checkProposalTool
9885
- ]);
9886
- (0,registry/* registerTools */.Fd)("Config", "write", [
9887
- proposeConfigChangeTool
9888
- ]);
9889
-
9890
- // EXTERNAL MODULE: ./lib/integrations/registry.ts + 6 modules
9891
- var integrations_registry = __webpack_require__(91384);
9892
- // EXTERNAL MODULE: ./lib/stores/integrations.ts
9893
- var integrations = __webpack_require__(78228);
9894
- ;// ./lib/tools/integrations.ts
9895
- // Read-only tools that expose integration manifests to the agent. See
9896
- // ADR-0010 — these power the "user asks how to connect X → agent narrates
9897
- // the steps and proposes actions" loop without giving the agent any new
9898
- // privileges.
9899
-
9900
-
9901
-
9902
-
9903
-
9904
- function manifestStatus(id) {
9905
- if (!(0,integrations/* isKnownIntegration */.GV)(id)) return "no_credentials_schema";
9906
- return (0,integrations/* getIntegrationStatus */.v9)(id)?.configured ? "configured" : "not_configured";
9907
- }
9908
- const listIntegrationsTool = (0,tools/* tool */.z6)(async ()=>{
9909
- const summary = (0,integrations_registry/* listManifests */.po)().map((m)=>({
9910
- id: m.id,
9911
- name: m.name,
9912
- category: m.category,
9913
- summary: m.summary,
9914
- status: manifestStatus(m.id)
9915
- }));
9916
- return JSON.stringify({
9917
- integrations: summary
9655
+ const body = {
9656
+ summary: eventSummary,
9657
+ start: {
9658
+ dateTime: start_iso,
9659
+ ...time_zone ? {
9660
+ timeZone: time_zone
9661
+ } : {}
9662
+ },
9663
+ end: {
9664
+ dateTime: end_iso,
9665
+ ...time_zone ? {
9666
+ timeZone: time_zone
9667
+ } : {}
9668
+ }
9669
+ };
9670
+ if (description) body.description = description;
9671
+ if (location) body.location = location;
9672
+ if (attendees && attendees.length > 0) {
9673
+ body.attendees = attendees.map((email)=>({
9674
+ email
9675
+ }));
9676
+ }
9677
+ if (add_meet_link) {
9678
+ body.conferenceData = {
9679
+ createRequest: {
9680
+ // requestId must be unique per createRequest; Google echoes it back.
9681
+ requestId: `jarela-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
9682
+ conferenceSolutionKey: {
9683
+ type: "hangoutsMeet"
9684
+ }
9685
+ }
9686
+ };
9687
+ }
9688
+ // conferenceDataVersion=1 is required for Google to honor the
9689
+ // createRequest and provision a Meet URL. Harmless to set without it.
9690
+ const params = add_meet_link ? "?conferenceDataVersion=1" : "";
9691
+ const data = await calendarFetch(auth, `/calendars/${cal}/events${params}`, {
9692
+ method: "POST",
9693
+ body: JSON.stringify(body)
9694
+ });
9695
+ if (data.error) return JSON.stringify({
9696
+ error: data.error
9918
9697
  });
9698
+ return JSON.stringify(slimEvent(data));
9919
9699
  }, {
9920
- name: "list_integrations",
9921
- description: "List every integration Jarela knows how to set up, with a one-line summary and " + "current configuration status. Read-only. Use this when the user asks 'what can I connect?' " + "or before proposing enable_integration, so you know what's available and whether it's " + "already configured.",
9922
- schema: schemas/* object */.Ik({})
9700
+ name: "calendar_create_event",
9701
+ description: "Create a Google Calendar event on a calendar (default: 'primary'). " + "Datetimes are RFC3339 strings — include a timezone offset (e.g. " + "'2026-05-19T16:00:00-07:00') or set `time_zone` to an IANA name (e.g. " + "'America/Los_Angeles') so the user's calendar interprets the time " + "correctly. Attendees are emailed an invite automatically. Set " + "`add_meet_link: true` to provision a Google Meet URL on the event.",
9702
+ schema: schemas/* object */.Ik({
9703
+ calendar_id: schemas/* string */.Yj().optional().describe("Calendar id (default: 'primary')"),
9704
+ summary: schemas/* string */.Yj().min(1).optional().describe("Event title shown on the calendar (alias: subject)"),
9705
+ subject: schemas/* string */.Yj().min(1).optional().describe("Alias for summary (Outlook vocabulary)"),
9706
+ start_iso: schemas/* string */.Yj().describe("Start datetime, RFC3339"),
9707
+ end_iso: schemas/* string */.Yj().describe("End datetime, RFC3339 (must be after start)"),
9708
+ description: schemas/* string */.Yj().optional().describe("Long-form event body (markdown not rendered)"),
9709
+ location: schemas/* string */.Yj().optional().describe("Free-text location string"),
9710
+ attendees: schemas/* array */.YO(schemas/* string */.Yj().email()).optional().describe("Invitee email addresses"),
9711
+ time_zone: schemas/* string */.Yj().optional().describe("IANA timezone, e.g. 'America/Los_Angeles'"),
9712
+ add_meet_link: schemas/* boolean */.zM().optional().describe("Provision a Google Meet URL on this event")
9713
+ })
9923
9714
  });
9924
- const getIntegrationSetupTool = (0,tools/* tool */.z6)(async ({ id })=>{
9925
- const manifest = (0,integrations_registry/* getManifest */.c2)(id);
9926
- if (!manifest) {
9715
+ const calendarUpdateEventTool = (0,tools/* tool */.z6)(async ({ calendar_id, event_id, summary, subject, start_iso, end_iso, description, location, attendees, time_zone })=>{
9716
+ const auth = (0,gmail_oauth/* resolveGoogleAuth */.wq)();
9717
+ if ("error" in auth) return JSON.stringify({
9718
+ error: auth.error
9719
+ });
9720
+ const cal = encodeURIComponent(calendar_id ?? "primary");
9721
+ const eid = encodeURIComponent(event_id);
9722
+ const patch = {};
9723
+ const eventSummary = summary ?? subject;
9724
+ if (eventSummary !== undefined) patch.summary = eventSummary;
9725
+ if (description !== undefined) patch.description = description;
9726
+ if (location !== undefined) patch.location = location;
9727
+ if (start_iso !== undefined) {
9728
+ patch.start = {
9729
+ dateTime: start_iso,
9730
+ ...time_zone ? {
9731
+ timeZone: time_zone
9732
+ } : {}
9733
+ };
9734
+ }
9735
+ if (end_iso !== undefined) {
9736
+ patch.end = {
9737
+ dateTime: end_iso,
9738
+ ...time_zone ? {
9739
+ timeZone: time_zone
9740
+ } : {}
9741
+ };
9742
+ }
9743
+ if (attendees !== undefined) {
9744
+ patch.attendees = attendees.map((email)=>({
9745
+ email
9746
+ }));
9747
+ }
9748
+ if (Object.keys(patch).length === 0) {
9927
9749
  return JSON.stringify({
9928
- error: `unknown integration "${id}". Call list_integrations to see available ids.`
9750
+ error: "Provide at least one field to update"
9929
9751
  });
9930
9752
  }
9931
- return JSON.stringify({
9932
- id: manifest.id,
9933
- name: manifest.name,
9934
- summary: manifest.summary,
9935
- category: manifest.category,
9936
- status: manifestStatus(manifest.id),
9937
- prerequisites: manifest.prerequisites,
9938
- steps: manifest.steps,
9939
- troubleshooting: manifest.troubleshooting,
9940
- notes: [
9941
- "Walk the user through `prerequisites` first; some require browser actions outside Jarela.",
9942
- "For each step that has a `proposes` field, call propose_config_change with that kind when ready.",
9943
- "When a step has a `verify.tool`, call that tool after applying the step to confirm success."
9944
- ]
9753
+ const data = await calendarFetch(auth, `/calendars/${cal}/events/${eid}`, {
9754
+ method: "PATCH",
9755
+ body: JSON.stringify(patch)
9756
+ });
9757
+ if (data.error) return JSON.stringify({
9758
+ error: data.error
9759
+ });
9760
+ return JSON.stringify(slimEvent(data));
9761
+ }, {
9762
+ name: "calendar_update_event",
9763
+ description: "Patch a Google Calendar event. Only the fields you supply are changed; " + "omit fields to leave them alone. Use this to reschedule (`start_iso`/`end_iso`), " + "rename (`summary`), or adjust the invitee list (`attendees` — note this " + "**replaces** the full list, so include everyone who should remain).",
9764
+ schema: schemas/* object */.Ik({
9765
+ calendar_id: schemas/* string */.Yj().optional().describe("Calendar id (default: 'primary')"),
9766
+ event_id: schemas/* string */.Yj().describe("Event id"),
9767
+ summary: schemas/* string */.Yj().optional().describe("New event title (alias: subject)"),
9768
+ subject: schemas/* string */.Yj().optional().describe("Alias for summary (Outlook vocabulary)"),
9769
+ start_iso: schemas/* string */.Yj().optional().describe("New start datetime, RFC3339"),
9770
+ end_iso: schemas/* string */.Yj().optional().describe("New end datetime, RFC3339"),
9771
+ description: schemas/* string */.Yj().optional().describe("New event body"),
9772
+ location: schemas/* string */.Yj().optional().describe("New location string"),
9773
+ attendees: schemas/* array */.YO(schemas/* string */.Yj().email()).optional().describe("Replacement attendee list"),
9774
+ time_zone: schemas/* string */.Yj().optional().describe("IANA timezone applied to start/end")
9775
+ })
9776
+ });
9777
+ const calendarDeleteEventTool = (0,tools/* tool */.z6)(async ({ calendar_id, event_id })=>{
9778
+ const auth = (0,gmail_oauth/* resolveGoogleAuth */.wq)();
9779
+ if ("error" in auth) return JSON.stringify({
9780
+ error: auth.error
9781
+ });
9782
+ const cal = encodeURIComponent(calendar_id ?? "primary");
9783
+ const eid = encodeURIComponent(event_id);
9784
+ const data = await calendarFetch(auth, `/calendars/${cal}/events/${eid}`, {
9785
+ method: "DELETE"
9786
+ });
9787
+ if (data.error) return JSON.stringify({
9788
+ error: data.error
9789
+ });
9790
+ return JSON.stringify({
9791
+ ok: true,
9792
+ id: event_id
9945
9793
  });
9946
9794
  }, {
9947
- name: "get_integration_setup",
9948
- description: "Return the full setup manifest for one integration prerequisites, ordered steps, and " + "troubleshooting hints. Read-only. Use this before walking the user through a setup, or " + "when a tool fails and you need the integration-specific recovery hint.",
9795
+ name: "calendar_delete_event",
9796
+ description: "Delete a Google Calendar event by id. Attendees are notified by default. " + "Returns `{ ok: true, id }` on success. Use `calendar_list_events` to find " + "the event id first if you only know the title/time.",
9949
9797
  schema: schemas/* object */.Ik({
9950
- id: schemas/* string */.Yj().describe("Integration id from list_integrations (e.g. 'gmail', 'atlassian')")
9798
+ calendar_id: schemas/* string */.Yj().optional().describe("Calendar id (default: 'primary')"),
9799
+ event_id: schemas/* string */.Yj().describe("Event id to delete")
9951
9800
  })
9952
9801
  });
9953
- (0,registry/* registerTools */.Fd)("Config", "read", [
9954
- listIntegrationsTool,
9955
- getIntegrationSetupTool
9802
+ (0,registry/* registerTools */.Fd)("Calendar", "read", [
9803
+ calendarListCalendarsTool,
9804
+ calendarListEventsTool,
9805
+ calendarGetEventTool
9806
+ ]);
9807
+ (0,registry/* registerTools */.Fd)("Calendar", "execute", [
9808
+ calendarCreateEventTool,
9809
+ calendarUpdateEventTool,
9810
+ calendarDeleteEventTool
9956
9811
  ]);
9957
9812
 
9958
- // EXTERNAL MODULE: ./lib/tools/atlassian.ts
9959
- var atlassian = __webpack_require__(65856);
9960
- // EXTERNAL MODULE: ./lib/tools/jira-align.ts
9961
- var jira_align = __webpack_require__(55738);
9962
- // EXTERNAL MODULE: ./lib/tools/github.ts
9963
- var github = __webpack_require__(70729);
9964
- // EXTERNAL MODULE: ./lib/tools/gmail.ts
9965
- var gmail = __webpack_require__(77414);
9966
- // EXTERNAL MODULE: ./lib/integrations/gmail-oauth.ts
9967
- var gmail_oauth = __webpack_require__(87246);
9968
- ;// ./lib/tools/calendar.ts
9813
+ // EXTERNAL MODULE: ./lib/tools/outlook.ts
9814
+ var outlook = __webpack_require__(77467);
9815
+ // EXTERNAL MODULE: ./lib/integrations/microsoft-oauth.ts
9816
+ var microsoft_oauth = __webpack_require__(89080);
9817
+ ;// ./lib/tools/outlook-calendar.ts
9969
9818
  /**
9970
- * Native Google Calendar tools direct REST calls to
9971
- * calendar.googleapis.com, sharing the Gmail OAuth client (one Google
9972
- * account, one refresh token, one access-token cache; see
9973
- * lib/integrations/gmail-oauth.ts).
9974
- *
9975
- * Scope-wise this surface is **events on existing calendars**:
9976
- * - list / get / create / update / delete events
9977
- * - list the user's calendars (read-only)
9978
- * There is deliberately no calendar-list create/delete and no ACL
9979
- * editing. To enable a wider surface, add the broader scope to
9980
- * GMAIL_SCOPES and (force users to) reconnect.
9819
+ * Outlook Calendar tools via Microsoft Graph. Mirrors lib/tools/calendar.ts
9820
+ * (the Google Calendar set) one-for-one so the agent's mental model
9821
+ * carries over between providers.
9981
9822
  *
9982
- * v1 design notes:
9983
- * - calendarId defaults to "primary" everywhere so the agent can
9984
- * create a quick event without first calling list_calendars.
9985
- * - All datetimes are RFC3339 strings (e.g. "2026-05-19T16:00:00-07:00").
9986
- * When the caller omits a timezone offset, Google falls back to the
9987
- * calendar's default TZ. The tools surface a `time_zone` field that
9988
- * forwards an IANA name in case the caller wants to be explicit.
9989
- * - `calendar_create_event` can provision a Google Meet link via the
9990
- * conferenceData createRequest dance gated behind `add_meet_link`
9991
- * so unused calls don't trip Google's quota.
9823
+ * Graph differences vs Google Calendar worth knowing:
9824
+ * - Each event's start/end is { dateTime, timeZone } where timeZone is
9825
+ * a Windows-time-zone string by default ("Pacific Standard Time"),
9826
+ * not IANA. We pass an explicit `Prefer: outlook.timezone="UTC"`
9827
+ * header so reads come back in UTC and the agent doesn't have to
9828
+ * navigate two TZ namespaces.
9829
+ * - Listing events in a window uses /calendarView (auto-expands
9830
+ * recurring series), parallel to Google's `singleEvents=true`.
9831
+ * - Online meetings use `isOnlineMeeting:true` +
9832
+ * `onlineMeetingProvider:"teamsForBusiness"`. Provisioning a Teams
9833
+ * link is gated behind `add_teams_link` to keep create_event cheap
9834
+ * when the agent just wants a plain event.
9992
9835
  */
9993
9836
 
9994
9837
 
9995
9838
 
9996
- const CALENDAR_BASE = "https://www.googleapis.com/calendar/v3";
9997
- const calendarFetch = (auth, path, init)=>(0,gmail_oauth/* googleFetch */.sI)(auth, "Calendar", CALENDAR_BASE, path, init);
9998
- // Slim down a full event to the fields the agent actually needs in a
9999
- // thread response. Keeps tool outputs small enough not to blow context.
10000
- function slimEvent(e) {
9839
+ function outlook_calendar_slimEvent(e) {
10001
9840
  return {
10002
9841
  id: e.id,
10003
- status: e.status,
10004
- summary: e.summary ?? null,
10005
- description: e.description ?? null,
10006
- location: e.location ?? null,
10007
- start: e.start?.dateTime ?? e.start?.date ?? null,
10008
- end: e.end?.dateTime ?? e.end?.date ?? null,
9842
+ subject: e.subject ?? null,
9843
+ description: e.bodyPreview ?? null,
9844
+ location: e.location?.displayName ?? null,
9845
+ start: e.start?.dateTime ?? null,
9846
+ end: e.end?.dateTime ?? null,
10009
9847
  time_zone: e.start?.timeZone ?? e.end?.timeZone ?? null,
10010
9848
  attendees: (e.attendees ?? []).map((a)=>({
10011
- email: a.email,
10012
- name: a.displayName ?? null,
10013
- response: a.responseStatus ?? null
9849
+ email: a.emailAddress?.address ?? null,
9850
+ name: a.emailAddress?.name ?? null,
9851
+ response: a.status?.response ?? null
10014
9852
  })),
10015
- meet_link: e.hangoutLink ?? null,
10016
- html_link: e.htmlLink ?? null
9853
+ is_all_day: e.isAllDay === true,
9854
+ is_cancelled: e.isCancelled === true,
9855
+ show_as: e.showAs ?? null,
9856
+ teams_link: e.onlineMeeting?.joinUrl ?? null,
9857
+ web_link: e.webLink ?? null
10017
9858
  };
10018
9859
  }
10019
9860
  // ── Tools ───────────────────────────────────────────────────────────────────
10020
- const calendarListCalendarsTool = (0,tools/* tool */.z6)(async ()=>{
10021
- const auth = (0,gmail_oauth/* resolveGoogleAuth */.wq)();
9861
+ const outlookCalendarListCalendarsTool = (0,tools/* tool */.z6)(async ()=>{
9862
+ const auth = (0,microsoft_oauth/* resolveMicrosoftAuth */.jG)();
10022
9863
  if ("error" in auth) return JSON.stringify({
10023
9864
  error: auth.error
10024
9865
  });
10025
- const data = await calendarFetch(auth, "/users/me/calendarList");
9866
+ const data = await (0,microsoft_oauth/* graphFetch */.z$)(auth, "/me/calendars");
10026
9867
  if ("error" in data && data.error) return JSON.stringify(data);
10027
9868
  return JSON.stringify({
10028
- calendars: (data.items ?? []).map((c)=>({
9869
+ calendars: (data.value ?? []).map((c)=>({
10029
9870
  id: c.id,
10030
- summary: c.summary ?? null,
10031
- primary: c.primary === true,
10032
- time_zone: c.timeZone ?? null,
10033
- access_role: c.accessRole ?? null
9871
+ name: c.name ?? null,
9872
+ is_default: c.isDefaultCalendar === true,
9873
+ can_edit: c.canEdit === true,
9874
+ owner: c.owner?.address ?? null
10034
9875
  }))
10035
9876
  });
10036
9877
  }, {
10037
- name: "calendar_list_calendars",
10038
- description: "List the user's Google calendars (primary + any they've subscribed to). " + "Returns id, summary, primary flag, time zone, access role per entry. The " + "`id` is what other calendar_* tools expect as `calendar_id`. Use this when " + "the user mentions a non-primary calendar by name (e.g. 'put it on my Work " + "calendar') — match against `summary` to find the right id.",
9878
+ name: "outlook_calendar_list_calendars",
9879
+ description: "List the user's Outlook calendars (primary + shared). Returns id, name, " + "is_default flag, edit permission, owner. The `id` is what other " + "outlook_calendar_* tools expect as `calendar_id`. Omit `calendar_id` " + "in those tools to use the user's default calendar.",
10039
9880
  schema: schemas/* object */.Ik({})
10040
9881
  });
10041
- const calendarListEventsTool = (0,tools/* tool */.z6)(async ({ calendar_id, time_min, time_max, query, max_results })=>{
10042
- const auth = (0,gmail_oauth/* resolveGoogleAuth */.wq)();
9882
+ const outlookCalendarListEventsTool = (0,tools/* tool */.z6)(async ({ calendar_id, time_min, time_max, max_results })=>{
9883
+ const auth = (0,microsoft_oauth/* resolveMicrosoftAuth */.jG)();
10043
9884
  if ("error" in auth) return JSON.stringify({
10044
9885
  error: auth.error
10045
9886
  });
10046
- const cal = encodeURIComponent(calendar_id ?? "primary");
10047
- const params = new URLSearchParams({
10048
- singleEvents: "true",
10049
- orderBy: "startTime",
10050
- maxResults: String(Math.min(Math.max(max_results ?? 25, 1), 100))
10051
- });
10052
- // Default window: now → +7 days. Keeps "what's on my schedule" cheap.
10053
9887
  const now = new Date();
10054
9888
  const inAWeek = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
10055
- params.set("timeMin", time_min ?? now.toISOString());
10056
- params.set("timeMax", time_max ?? inAWeek.toISOString());
10057
- if (query) params.set("q", query);
10058
- const data = await calendarFetch(auth, `/calendars/${cal}/events?${params.toString()}`);
9889
+ const startISO = time_min ?? now.toISOString();
9890
+ const endISO = time_max ?? inAWeek.toISOString();
9891
+ const top = Math.min(Math.max(max_results ?? 25, 1), 100);
9892
+ // /calendarView expands recurring series into individual instances —
9893
+ // the closest parallel to Google's singleEvents=true.
9894
+ const base = calendar_id ? `/me/calendars/${encodeURIComponent(calendar_id)}/calendarView` : "/me/calendarView";
9895
+ const params = new URLSearchParams({
9896
+ startDateTime: startISO,
9897
+ endDateTime: endISO,
9898
+ $top: String(top),
9899
+ $orderby: "start/dateTime"
9900
+ });
9901
+ const data = await (0,microsoft_oauth/* graphFetch */.z$)(auth, `${base}?${params.toString()}`, {
9902
+ // Force UTC in responses so the agent doesn't juggle Windows-TZ names.
9903
+ headers: {
9904
+ Prefer: 'outlook.timezone="UTC"'
9905
+ }
9906
+ });
10059
9907
  if ("error" in data && data.error) return JSON.stringify(data);
10060
9908
  return JSON.stringify({
10061
- events: (data.items ?? []).map(slimEvent)
9909
+ events: (data.value ?? []).map(outlook_calendar_slimEvent)
10062
9910
  });
10063
9911
  }, {
10064
- name: "calendar_list_events",
10065
- description: "List events on a calendar within a time window. Defaults: primary calendar, " + "now → +7 days, ordered by start, recurring events expanded into single " + "instances. **Use this before creating an event** to avoid double-booking, " + "and to look up existing event ids before updating/deleting. " + "Datetimes are RFC3339 (e.g. '2026-05-19T16:00:00-07:00').",
9912
+ name: "outlook_calendar_list_events",
9913
+ description: "List Outlook calendar events within a time window. Defaults: default " + "calendar, now → +7 days, ordered by start, recurring series expanded. " + "**Use this before creating an event** to avoid double-booking, and to " + "look up event ids before updating/deleting. All datetimes returned in " + "UTC (RFC3339 'Z'-suffixed).",
10066
9914
  schema: schemas/* object */.Ik({
10067
- calendar_id: schemas/* string */.Yj().optional().describe("Calendar id (default: 'primary')"),
9915
+ calendar_id: schemas/* string */.Yj().optional().describe("Calendar id (default: user's default calendar)"),
10068
9916
  time_min: schemas/* string */.Yj().optional().describe("RFC3339 lower bound (default: now)"),
10069
9917
  time_max: schemas/* string */.Yj().optional().describe("RFC3339 upper bound (default: now + 7 days)"),
10070
- query: schemas/* string */.Yj().optional().describe("Free-text search across event fields"),
10071
9918
  max_results: schemas/* number */.ai().int().optional().describe("Max events (default 25, max 100)")
10072
9919
  })
10073
9920
  });
10074
- const calendarGetEventTool = (0,tools/* tool */.z6)(async ({ calendar_id, event_id })=>{
10075
- const auth = (0,gmail_oauth/* resolveGoogleAuth */.wq)();
9921
+ const outlookCalendarGetEventTool = (0,tools/* tool */.z6)(async ({ event_id })=>{
9922
+ const auth = (0,microsoft_oauth/* resolveMicrosoftAuth */.jG)();
10076
9923
  if ("error" in auth) return JSON.stringify({
10077
9924
  error: auth.error
10078
9925
  });
10079
- const cal = encodeURIComponent(calendar_id ?? "primary");
10080
- const eid = encodeURIComponent(event_id);
10081
- const data = await calendarFetch(auth, `/calendars/${cal}/events/${eid}`);
10082
- if (data.error) return JSON.stringify({
10083
- error: data.error
9926
+ const e = await (0,microsoft_oauth/* graphFetch */.z$)(auth, `/me/events/${encodeURIComponent(event_id)}`, {
9927
+ headers: {
9928
+ Prefer: 'outlook.timezone="UTC"'
9929
+ }
10084
9930
  });
10085
- return JSON.stringify(slimEvent(data));
9931
+ if (e.error) return JSON.stringify({
9932
+ error: e.error
9933
+ });
9934
+ return JSON.stringify(outlook_calendar_slimEvent(e));
10086
9935
  }, {
10087
- name: "calendar_get_event",
10088
- description: "Fetch one Google Calendar event by id. Returns the same slim shape as " + "calendar_list_events but for a single event.",
9936
+ name: "outlook_calendar_get_event",
9937
+ description: "Fetch one Outlook calendar event by id. Returns the same slim shape as " + "outlook_calendar_list_events but for a single event.",
10089
9938
  schema: schemas/* object */.Ik({
10090
- calendar_id: schemas/* string */.Yj().optional().describe("Calendar id (default: 'primary')"),
10091
- event_id: schemas/* string */.Yj().describe("Event id (from calendar_list_events results)")
9939
+ event_id: schemas/* string */.Yj().describe("Event id (from outlook_calendar_list_events)")
10092
9940
  })
10093
9941
  });
10094
- const calendarCreateEventTool = (0,tools/* tool */.z6)(async ({ calendar_id, summary, subject, start_iso, end_iso, description, location, attendees, time_zone, add_meet_link })=>{
10095
- const auth = (0,gmail_oauth/* resolveGoogleAuth */.wq)();
9942
+ const outlookCalendarCreateEventTool = (0,tools/* tool */.z6)(async ({ calendar_id, subject, summary, start_iso, end_iso, description, location, attendees, time_zone, add_teams_link })=>{
9943
+ const auth = (0,microsoft_oauth/* resolveMicrosoftAuth */.jG)();
10096
9944
  if ("error" in auth) return JSON.stringify({
10097
9945
  error: auth.error
10098
9946
  });
10099
- const cal = encodeURIComponent(calendar_id ?? "primary");
10100
- // `subject` is accepted as an alias for `summary` so agents trained on
10101
- // Outlook vocabulary don't fail the schema on first try.
10102
- const eventSummary = summary ?? subject;
10103
- if (!eventSummary) {
9947
+ // `summary` is accepted as an alias for `subject` so agents trained on
9948
+ // Google Calendar's vocabulary don't fail the schema on first try.
9949
+ const eventSubject = subject ?? summary;
9950
+ if (!eventSubject) {
10104
9951
  return JSON.stringify({
10105
- error: "summary (or subject) is required"
9952
+ error: "subject (or summary) is required"
10106
9953
  });
10107
9954
  }
9955
+ // Graph uses Windows TZ names by default but also accepts IANA names
9956
+ // when the body specifies them per-field. Default to UTC if the
9957
+ // caller omits time_zone (matches the list endpoint's behavior).
9958
+ const tz = time_zone ?? "UTC";
10108
9959
  const body = {
10109
- summary: eventSummary,
9960
+ subject: eventSubject,
10110
9961
  start: {
10111
9962
  dateTime: start_iso,
10112
- ...time_zone ? {
10113
- timeZone: time_zone
10114
- } : {}
9963
+ timeZone: tz
10115
9964
  },
10116
9965
  end: {
10117
9966
  dateTime: end_iso,
10118
- ...time_zone ? {
10119
- timeZone: time_zone
10120
- } : {}
9967
+ timeZone: tz
10121
9968
  }
10122
9969
  };
10123
- if (description) body.description = description;
10124
- if (location) body.location = location;
9970
+ if (description) body.body = {
9971
+ contentType: "text",
9972
+ content: description
9973
+ };
9974
+ if (location) body.location = {
9975
+ displayName: location
9976
+ };
10125
9977
  if (attendees && attendees.length > 0) {
10126
- body.attendees = attendees.map((email)=>({
10127
- email
9978
+ body.attendees = attendees.map((address)=>({
9979
+ emailAddress: {
9980
+ address
9981
+ },
9982
+ type: "required"
10128
9983
  }));
10129
9984
  }
10130
- if (add_meet_link) {
10131
- body.conferenceData = {
10132
- createRequest: {
10133
- // requestId must be unique per createRequest; Google echoes it back.
10134
- requestId: `jarela-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
10135
- conferenceSolutionKey: {
10136
- type: "hangoutsMeet"
10137
- }
10138
- }
10139
- };
9985
+ if (add_teams_link) {
9986
+ body.isOnlineMeeting = true;
9987
+ body.onlineMeetingProvider = "teamsForBusiness";
10140
9988
  }
10141
- // conferenceDataVersion=1 is required for Google to honor the
10142
- // createRequest and provision a Meet URL. Harmless to set without it.
10143
- const params = add_meet_link ? "?conferenceDataVersion=1" : "";
10144
- const data = await calendarFetch(auth, `/calendars/${cal}/events${params}`, {
9989
+ const path = calendar_id ? `/me/calendars/${encodeURIComponent(calendar_id)}/events` : "/me/events";
9990
+ const r = await (0,microsoft_oauth/* graphFetch */.z$)(auth, path, {
10145
9991
  method: "POST",
10146
- body: JSON.stringify(body)
9992
+ body: JSON.stringify(body),
9993
+ headers: {
9994
+ Prefer: 'outlook.timezone="UTC"'
9995
+ }
10147
9996
  });
10148
- if (data.error) return JSON.stringify({
10149
- error: data.error
9997
+ if (r.error) return JSON.stringify({
9998
+ error: r.error
10150
9999
  });
10151
- return JSON.stringify(slimEvent(data));
10000
+ return JSON.stringify(outlook_calendar_slimEvent(r));
10152
10001
  }, {
10153
- name: "calendar_create_event",
10154
- description: "Create a Google Calendar event on a calendar (default: 'primary'). " + "Datetimes are RFC3339 strings include a timezone offset (e.g. " + "'2026-05-19T16:00:00-07:00') or set `time_zone` to an IANA name (e.g. " + "'America/Los_Angeles') so the user's calendar interprets the time " + "correctly. Attendees are emailed an invite automatically. Set " + "`add_meet_link: true` to provision a Google Meet URL on the event.",
10002
+ name: "outlook_calendar_create_event",
10003
+ description: "Create an Outlook calendar event. Datetimes are RFC3339 strings; pair " + "with `time_zone` (IANA like 'America/Los_Angeles' or Windows TZ like " + "'Pacific Standard Time') so Outlook interprets them correctly. Defaults " + "to UTC if omitted. Attendees are automatically sent invites. Set " + "`add_teams_link: true` to provision a Teams meeting URL (requires a " + "work/school M365 account; personal Microsoft accounts cannot create " + "Teams meetings via Graph).",
10155
10004
  schema: schemas/* object */.Ik({
10156
- calendar_id: schemas/* string */.Yj().optional().describe("Calendar id (default: 'primary')"),
10157
- summary: schemas/* string */.Yj().min(1).optional().describe("Event title shown on the calendar (alias: subject)"),
10158
- subject: schemas/* string */.Yj().min(1).optional().describe("Alias for summary (Outlook vocabulary)"),
10005
+ calendar_id: schemas/* string */.Yj().optional().describe("Calendar id (default: user's default calendar)"),
10006
+ subject: schemas/* string */.Yj().min(1).optional().describe("Event title shown on the calendar (alias: summary)"),
10007
+ summary: schemas/* string */.Yj().min(1).optional().describe("Alias for subject (Google Calendar vocabulary)"),
10159
10008
  start_iso: schemas/* string */.Yj().describe("Start datetime, RFC3339"),
10160
10009
  end_iso: schemas/* string */.Yj().describe("End datetime, RFC3339 (must be after start)"),
10161
- description: schemas/* string */.Yj().optional().describe("Long-form event body (markdown not rendered)"),
10010
+ description: schemas/* string */.Yj().optional().describe("Long-form event body (plain text)"),
10162
10011
  location: schemas/* string */.Yj().optional().describe("Free-text location string"),
10163
10012
  attendees: schemas/* array */.YO(schemas/* string */.Yj().email()).optional().describe("Invitee email addresses"),
10164
- time_zone: schemas/* string */.Yj().optional().describe("IANA timezone, e.g. 'America/Los_Angeles'"),
10165
- add_meet_link: schemas/* boolean */.zM().optional().describe("Provision a Google Meet URL on this event")
10013
+ time_zone: schemas/* string */.Yj().optional().describe("IANA or Windows timezone (default 'UTC')"),
10014
+ add_teams_link: schemas/* boolean */.zM().optional().describe("Provision a Teams meeting URL on this event")
10166
10015
  })
10167
10016
  });
10168
- const calendarUpdateEventTool = (0,tools/* tool */.z6)(async ({ calendar_id, event_id, summary, subject, start_iso, end_iso, description, location, attendees, time_zone })=>{
10169
- const auth = (0,gmail_oauth/* resolveGoogleAuth */.wq)();
10017
+ const outlookCalendarUpdateEventTool = (0,tools/* tool */.z6)(async ({ event_id, subject, summary, start_iso, end_iso, description, location, attendees, time_zone })=>{
10018
+ const auth = (0,microsoft_oauth/* resolveMicrosoftAuth */.jG)();
10170
10019
  if ("error" in auth) return JSON.stringify({
10171
10020
  error: auth.error
10172
10021
  });
10173
- const cal = encodeURIComponent(calendar_id ?? "primary");
10174
- const eid = encodeURIComponent(event_id);
10022
+ const tz = time_zone ?? "UTC";
10175
10023
  const patch = {};
10176
- const eventSummary = summary ?? subject;
10177
- if (eventSummary !== undefined) patch.summary = eventSummary;
10178
- if (description !== undefined) patch.description = description;
10179
- if (location !== undefined) patch.location = location;
10180
- if (start_iso !== undefined) {
10181
- patch.start = {
10182
- dateTime: start_iso,
10183
- ...time_zone ? {
10184
- timeZone: time_zone
10185
- } : {}
10186
- };
10187
- }
10188
- if (end_iso !== undefined) {
10189
- patch.end = {
10190
- dateTime: end_iso,
10191
- ...time_zone ? {
10192
- timeZone: time_zone
10193
- } : {}
10194
- };
10195
- }
10024
+ const eventSubject = subject ?? summary;
10025
+ if (eventSubject !== undefined) patch.subject = eventSubject;
10026
+ if (description !== undefined) patch.body = {
10027
+ contentType: "text",
10028
+ content: description
10029
+ };
10030
+ if (location !== undefined) patch.location = {
10031
+ displayName: location
10032
+ };
10033
+ if (start_iso !== undefined) patch.start = {
10034
+ dateTime: start_iso,
10035
+ timeZone: tz
10036
+ };
10037
+ if (end_iso !== undefined) patch.end = {
10038
+ dateTime: end_iso,
10039
+ timeZone: tz
10040
+ };
10196
10041
  if (attendees !== undefined) {
10197
- patch.attendees = attendees.map((email)=>({
10198
- email
10042
+ patch.attendees = attendees.map((address)=>({
10043
+ emailAddress: {
10044
+ address
10045
+ },
10046
+ type: "required"
10199
10047
  }));
10200
10048
  }
10201
10049
  if (Object.keys(patch).length === 0) {
@@ -10203,373 +10051,678 @@ const calendarUpdateEventTool = (0,tools/* tool */.z6)(async ({ calendar_id, eve
10203
10051
  error: "Provide at least one field to update"
10204
10052
  });
10205
10053
  }
10206
- const data = await calendarFetch(auth, `/calendars/${cal}/events/${eid}`, {
10054
+ const r = await (0,microsoft_oauth/* graphFetch */.z$)(auth, `/me/events/${encodeURIComponent(event_id)}`, {
10207
10055
  method: "PATCH",
10208
- body: JSON.stringify(patch)
10056
+ body: JSON.stringify(patch),
10057
+ headers: {
10058
+ Prefer: 'outlook.timezone="UTC"'
10059
+ }
10209
10060
  });
10210
- if (data.error) return JSON.stringify({
10211
- error: data.error
10061
+ if (r.error) return JSON.stringify({
10062
+ error: r.error
10212
10063
  });
10213
- return JSON.stringify(slimEvent(data));
10064
+ return JSON.stringify(outlook_calendar_slimEvent(r));
10214
10065
  }, {
10215
- name: "calendar_update_event",
10216
- description: "Patch a Google Calendar event. Only the fields you supply are changed; " + "omit fields to leave them alone. Use this to reschedule (`start_iso`/`end_iso`), " + "rename (`summary`), or adjust the invitee list (`attendees` — note this " + "**replaces** the full list, so include everyone who should remain).",
10066
+ name: "outlook_calendar_update_event",
10067
+ description: "Patch an Outlook calendar event. Only supplied fields are changed; omit " + "fields to leave them alone. Note `attendees` REPLACES the full invitee " + "list, so include everyone who should remain.",
10217
10068
  schema: schemas/* object */.Ik({
10218
- calendar_id: schemas/* string */.Yj().optional().describe("Calendar id (default: 'primary')"),
10219
10069
  event_id: schemas/* string */.Yj().describe("Event id"),
10220
- summary: schemas/* string */.Yj().optional().describe("New event title (alias: subject)"),
10221
- subject: schemas/* string */.Yj().optional().describe("Alias for summary (Outlook vocabulary)"),
10070
+ subject: schemas/* string */.Yj().optional().describe("New event title (alias: summary)"),
10071
+ summary: schemas/* string */.Yj().optional().describe("Alias for subject (Google Calendar vocabulary)"),
10222
10072
  start_iso: schemas/* string */.Yj().optional().describe("New start datetime, RFC3339"),
10223
10073
  end_iso: schemas/* string */.Yj().optional().describe("New end datetime, RFC3339"),
10224
- description: schemas/* string */.Yj().optional().describe("New event body"),
10074
+ description: schemas/* string */.Yj().optional().describe("New event body (plain text)"),
10225
10075
  location: schemas/* string */.Yj().optional().describe("New location string"),
10226
10076
  attendees: schemas/* array */.YO(schemas/* string */.Yj().email()).optional().describe("Replacement attendee list"),
10227
- time_zone: schemas/* string */.Yj().optional().describe("IANA timezone applied to start/end")
10077
+ time_zone: schemas/* string */.Yj().optional().describe("IANA or Windows TZ applied to start/end (default 'UTC')")
10228
10078
  })
10229
10079
  });
10230
- const calendarDeleteEventTool = (0,tools/* tool */.z6)(async ({ calendar_id, event_id })=>{
10231
- const auth = (0,gmail_oauth/* resolveGoogleAuth */.wq)();
10080
+ const outlookCalendarDeleteEventTool = (0,tools/* tool */.z6)(async ({ event_id })=>{
10081
+ const auth = (0,microsoft_oauth/* resolveMicrosoftAuth */.jG)();
10232
10082
  if ("error" in auth) return JSON.stringify({
10233
10083
  error: auth.error
10234
10084
  });
10235
- const cal = encodeURIComponent(calendar_id ?? "primary");
10236
- const eid = encodeURIComponent(event_id);
10237
- const data = await calendarFetch(auth, `/calendars/${cal}/events/${eid}`, {
10085
+ const r = await (0,microsoft_oauth/* graphFetch */.z$)(auth, `/me/events/${encodeURIComponent(event_id)}`, {
10238
10086
  method: "DELETE"
10239
10087
  });
10240
- if (data.error) return JSON.stringify({
10241
- error: data.error
10088
+ if (r.error) return JSON.stringify({
10089
+ error: r.error
10242
10090
  });
10243
10091
  return JSON.stringify({
10244
10092
  ok: true,
10245
10093
  id: event_id
10246
10094
  });
10247
10095
  }, {
10248
- name: "calendar_delete_event",
10249
- description: "Delete a Google Calendar event by id. Attendees are notified by default. " + "Returns `{ ok: true, id }` on success. Use `calendar_list_events` to find " + "the event id first if you only know the title/time.",
10096
+ name: "outlook_calendar_delete_event",
10097
+ description: "Delete an Outlook calendar event by id. Attendees are notified. Returns " + "`{ ok: true, id }` on success. Use outlook_calendar_list_events to find " + "the event id first if you only know the title/time.",
10098
+ schema: schemas/* object */.Ik({
10099
+ event_id: schemas/* string */.Yj().describe("Event id to delete")
10100
+ })
10101
+ });
10102
+ (0,registry/* registerTools */.Fd)("Calendar", "read", [
10103
+ outlookCalendarListCalendarsTool,
10104
+ outlookCalendarListEventsTool,
10105
+ outlookCalendarGetEventTool
10106
+ ]);
10107
+ (0,registry/* registerTools */.Fd)("Calendar", "execute", [
10108
+ outlookCalendarCreateEventTool,
10109
+ outlookCalendarUpdateEventTool,
10110
+ outlookCalendarDeleteEventTool
10111
+ ]);
10112
+
10113
+ // EXTERNAL MODULE: ./lib/agents/run-thread.ts + 17 modules
10114
+ var run_thread = __webpack_require__(8459);
10115
+ // EXTERNAL MODULE: ./lib/agents/stream-collector.ts
10116
+ var stream_collector = __webpack_require__(30685);
10117
+ // EXTERNAL MODULE: ./lib/agents/run-queue.ts
10118
+ var run_queue = __webpack_require__(3944);
10119
+ ;// ./lib/tools/delegate.ts
10120
+
10121
+
10122
+
10123
+
10124
+
10125
+
10126
+
10127
+
10128
+ function readDelegateContext(config) {
10129
+ const threadId = config?.configurable?.thread_id;
10130
+ if (!threadId) return null;
10131
+ const thread = (0,threads/* getThread */.fG)(threadId);
10132
+ if (!thread) return null;
10133
+ const depth = config?.configurable?.delegation_depth ?? 0;
10134
+ const ancestorsRaw = config?.configurable?.delegation_ancestors;
10135
+ const ancestors = Array.isArray(ancestorsRaw) ? ancestorsRaw : [];
10136
+ return {
10137
+ parentAgentId: thread.agent_id,
10138
+ depth,
10139
+ ancestors
10140
+ };
10141
+ }
10142
+ function fail(code, message, extra = {}) {
10143
+ return JSON.stringify({
10144
+ ok: false,
10145
+ error_code: code,
10146
+ message,
10147
+ ...extra
10148
+ });
10149
+ }
10150
+ const delegateToAgentTool = (0,tools/* tool */.z6)(async ({ agent_id, task }, config)=>{
10151
+ const ctx = readDelegateContext(config);
10152
+ if (!ctx) return fail("no_context", "No agent context (missing thread_id)");
10153
+ const parent = (0,agent_configs/* getAgentConfig */.mJ)(ctx.parentAgentId);
10154
+ if (!parent) return fail("agent_not_found", `Parent agent "${ctx.parentAgentId}" not found`);
10155
+ if (agent_id === ctx.parentAgentId) {
10156
+ return fail("cycle_detected", "Cannot delegate to self");
10157
+ }
10158
+ if (ctx.ancestors.includes(agent_id)) {
10159
+ return fail("cycle_detected", `Delegation cycle: ${agent_id} is already in the chain [${ctx.ancestors.join(" -> ")}]`);
10160
+ }
10161
+ if (ctx.depth >= run_thread/* MAX_DELEGATION_DEPTH */.Ye) {
10162
+ return fail("depth_exceeded", `Maximum delegation depth (${run_thread/* MAX_DELEGATION_DEPTH */.Ye}) reached. Chain: [${[
10163
+ ...ctx.ancestors,
10164
+ ctx.parentAgentId
10165
+ ].join(" -> ")}]`);
10166
+ }
10167
+ const roster = (0,agent_configs/* parseDelegateTargets */.bg)(parent.delegate_targets);
10168
+ if (!roster.includes(agent_id)) {
10169
+ return fail("not_in_roster", `Agent "${agent_id}" is not in the delegate roster for "${parent.id}". Available: [${roster.join(", ") || "none"}]`);
10170
+ }
10171
+ const child = (0,agent_configs/* getAgentConfig */.mJ)(agent_id);
10172
+ if (!child) return fail("agent_not_found", `Delegate agent "${agent_id}" not found`);
10173
+ const childThread = (0,threads/* getOrCreateAgentThread */.xH)(agent_id);
10174
+ const startedAt = Date.now();
10175
+ try {
10176
+ // Serialise on the child thread_id with every other entry point
10177
+ // (HTTP POST, scheduler, watcher, bridge, sibling delegations) —
10178
+ // see lib/agents/run-queue.ts. A delegate fired while the child is
10179
+ // already running waits in the child's queue instead of racing the
10180
+ // checkpoint store.
10181
+ const queued = await (0,run_queue/* enqueueThreadRun */.M6)(childThread.thread_id, "delegate", async ()=>{
10182
+ const prepared = await (0,run_thread/* prepareThreadRun */.fn)({
10183
+ thread_id: childThread.thread_id,
10184
+ message: task,
10185
+ user_category: "delegation",
10186
+ _delegation_depth: ctx.depth + 1,
10187
+ _delegation_ancestors: [
10188
+ ...ctx.ancestors,
10189
+ ctx.parentAgentId
10190
+ ]
10191
+ });
10192
+ const collected = await (0,stream_collector/* collectStream */.V)(prepared.stream);
10193
+ if (collected.terminal !== "error") {
10194
+ (0,run_thread/* persistAssistantMessage */._w)(childThread.thread_id, collected.assistantContent, collected.usedTools, collected.toolEvents, "delegation", collected.usage ?? null, prepared.context_snapshot ?? null, prepared.source_manifest ?? null);
10195
+ }
10196
+ return collected;
10197
+ }).result;
10198
+ const elapsed_ms = Date.now() - startedAt;
10199
+ if (queued.terminal === "error") {
10200
+ return fail("child_error", queued.errorMessage ?? "Child agent run failed", {
10201
+ agent_id,
10202
+ agent_name: child.name,
10203
+ thread_id: childThread.thread_id
10204
+ });
10205
+ }
10206
+ return JSON.stringify({
10207
+ ok: true,
10208
+ agent_id: child.id,
10209
+ agent_name: child.name,
10210
+ thread_id: childThread.thread_id,
10211
+ depth: ctx.depth + 1,
10212
+ result: queued.assistantContent.trim(),
10213
+ used_tools: Array.from(new Set(queued.usedTools)),
10214
+ elapsed_ms
10215
+ });
10216
+ } catch (err) {
10217
+ return fail("child_error", err instanceof Error ? err.message : String(err), {
10218
+ agent_id,
10219
+ agent_name: child.name,
10220
+ thread_id: childThread.thread_id
10221
+ });
10222
+ }
10223
+ }, {
10224
+ name: "delegate_to_agent",
10225
+ description: "Hand a subtask to another agent that has specialized knowledge or tools you lack. " + "The delegate runs in its own thread; the user sees a tool card with the delegate's name, " + "the task you sent, and the delegate's final answer (plus a link to open the delegate's thread). " + "Only delegate when the target agent is genuinely better-suited — do NOT delegate trivial subtasks " + "you can handle yourself. Before calling this tool, tell the user in one sentence which agent you " + "are handing to and why. Available delegate ids are listed in the 'Available delegates' system " + "section; calling with an id outside that list will be refused.",
10250
10226
  schema: schemas/* object */.Ik({
10251
- calendar_id: schemas/* string */.Yj().optional().describe("Calendar id (default: 'primary')"),
10252
- event_id: schemas/* string */.Yj().describe("Event id to delete")
10227
+ agent_id: schemas/* string */.Yj().describe("The id of the delegate agent. Must appear in the 'Available delegates' list in your system prompt."),
10228
+ task: schemas/* string */.Yj().min(1).describe("A self-contained task description for the delegate. Include all context the delegate needs — " + "the delegate does NOT see your conversation history with the user, only this task string.")
10253
10229
  })
10254
10230
  });
10255
- (0,registry/* registerTools */.Fd)("Calendar", "read", [
10256
- calendarListCalendarsTool,
10257
- calendarListEventsTool,
10258
- calendarGetEventTool
10259
- ]);
10260
- (0,registry/* registerTools */.Fd)("Calendar", "execute", [
10261
- calendarCreateEventTool,
10262
- calendarUpdateEventTool,
10263
- calendarDeleteEventTool
10231
+ (0,registry/* registerTools */.Fd)("Agent", "execute", [
10232
+ delegateToAgentTool
10264
10233
  ]);
10265
10234
 
10266
- // EXTERNAL MODULE: ./lib/tools/outlook.ts
10267
- var outlook = __webpack_require__(77467);
10268
- // EXTERNAL MODULE: ./lib/integrations/microsoft-oauth.ts
10269
- var microsoft_oauth = __webpack_require__(89080);
10270
- ;// ./lib/tools/outlook-calendar.ts
10271
- /**
10272
- * Outlook Calendar tools via Microsoft Graph. Mirrors lib/tools/calendar.ts
10273
- * (the Google Calendar set) one-for-one so the agent's mental model
10274
- * carries over between providers.
10275
- *
10276
- * Graph differences vs Google Calendar worth knowing:
10277
- * - Each event's start/end is { dateTime, timeZone } where timeZone is
10278
- * a Windows-time-zone string by default ("Pacific Standard Time"),
10279
- * not IANA. We pass an explicit `Prefer: outlook.timezone="UTC"`
10280
- * header so reads come back in UTC and the agent doesn't have to
10281
- * navigate two TZ namespaces.
10282
- * - Listing events in a window uses /calendarView (auto-expands
10283
- * recurring series), parallel to Google's `singleEvents=true`.
10284
- * - Online meetings use `isOnlineMeeting:true` +
10285
- * `onlineMeetingProvider:"teamsForBusiness"`. Provisioning a Teams
10286
- * link is gated behind `add_teams_link` to keep create_event cheap
10287
- * when the agent just wants a plain event.
10288
- */
10235
+ // EXTERNAL MODULE: ./lib/env/schema.ts
10236
+ var schema = __webpack_require__(69353);
10237
+ // EXTERNAL MODULE: ./lib/env/overrides.ts
10238
+ var overrides = __webpack_require__(86037);
10239
+ ;// ./lib/tools/system_config.ts
10240
+ // Agent-callable knobs for the JARELA_* override store.
10241
+ //
10242
+ // Both tools are gated by the per-var schema flag `agentWritable`
10243
+ // agents cannot write to anything not flagged true (defaults to false).
10244
+ // `restart_server` is only wired here; agents still need the tool to be
10245
+ // in their selected toolset to call it (default agents do not include
10246
+ // these).
10247
+ //
10248
+ // Use cases the user explicitly asked to support:
10249
+ // - "agent, lower the run idle timeout to 30s and restart"
10250
+ // - "agent, switch the log level to debug for the next hour"
10251
+ //
10252
+ // We surface the schema's tier/restart flags in the response so the
10253
+ // agent's reply can tell the user what changed and whether a restart
10254
+ // fired (or is needed but skipped).
10289
10255
 
10290
10256
 
10291
10257
 
10292
- function outlook_calendar_slimEvent(e) {
10293
- return {
10294
- id: e.id,
10295
- subject: e.subject ?? null,
10296
- description: e.bodyPreview ?? null,
10297
- location: e.location?.displayName ?? null,
10298
- start: e.start?.dateTime ?? null,
10299
- end: e.end?.dateTime ?? null,
10300
- time_zone: e.start?.timeZone ?? e.end?.timeZone ?? null,
10301
- attendees: (e.attendees ?? []).map((a)=>({
10302
- email: a.emailAddress?.address ?? null,
10303
- name: a.emailAddress?.name ?? null,
10304
- response: a.status?.response ?? null
10305
- })),
10306
- is_all_day: e.isAllDay === true,
10307
- is_cancelled: e.isCancelled === true,
10308
- show_as: e.showAs ?? null,
10309
- teams_link: e.onlineMeeting?.joinUrl ?? null,
10310
- web_link: e.webLink ?? null
10311
- };
10312
- }
10313
- // ── Tools ───────────────────────────────────────────────────────────────────
10314
- const outlookCalendarListCalendarsTool = (0,tools/* tool */.z6)(async ()=>{
10315
- const auth = (0,microsoft_oauth/* resolveMicrosoftAuth */.jG)();
10316
- if ("error" in auth) return JSON.stringify({
10317
- error: auth.error
10318
- });
10319
- const data = await (0,microsoft_oauth/* graphFetch */.z$)(auth, "/me/calendars");
10320
- if ("error" in data && data.error) return JSON.stringify(data);
10321
- return JSON.stringify({
10322
- calendars: (data.value ?? []).map((c)=>({
10323
- id: c.id,
10324
- name: c.name ?? null,
10325
- is_default: c.isDefaultCalendar === true,
10326
- can_edit: c.canEdit === true,
10327
- owner: c.owner?.address ?? null
10328
- }))
10329
- });
10330
- }, {
10331
- name: "outlook_calendar_list_calendars",
10332
- description: "List the user's Outlook calendars (primary + shared). Returns id, name, " + "is_default flag, edit permission, owner. The `id` is what other " + "outlook_calendar_* tools expect as `calendar_id`. Omit `calendar_id` " + "in those tools to use the user's default calendar.",
10333
- schema: schemas/* object */.Ik({})
10258
+
10259
+
10260
+
10261
+ const setEnvSchema = schemas/* object */.Ik({
10262
+ name: schemas/* string */.Yj().describe("Env var name. Must be a JARELA_* knob the schema flags as agent-writable; otherwise the call returns code=forbidden."),
10263
+ value: schemas/* string */.Yj().nullable().describe("New value as a string (numbers/bools come as their string form: '5000' / 'true'). Pass null to clear the override and revert to the default."),
10264
+ reason: schemas/* string */.Yj().optional().describe("Short note explaining the change — logged for postmortems.")
10334
10265
  });
10335
- const outlookCalendarListEventsTool = (0,tools/* tool */.z6)(async ({ calendar_id, time_min, time_max, max_results })=>{
10336
- const auth = (0,microsoft_oauth/* resolveMicrosoftAuth */.jG)();
10337
- if ("error" in auth) return JSON.stringify({
10338
- error: auth.error
10339
- });
10340
- const now = new Date();
10341
- const inAWeek = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
10342
- const startISO = time_min ?? now.toISOString();
10343
- const endISO = time_max ?? inAWeek.toISOString();
10344
- const top = Math.min(Math.max(max_results ?? 25, 1), 100);
10345
- // /calendarView expands recurring series into individual instances —
10346
- // the closest parallel to Google's singleEvents=true.
10347
- const base = calendar_id ? `/me/calendars/${encodeURIComponent(calendar_id)}/calendarView` : "/me/calendarView";
10348
- const params = new URLSearchParams({
10349
- startDateTime: startISO,
10350
- endDateTime: endISO,
10351
- $top: String(top),
10352
- $orderby: "start/dateTime"
10353
- });
10354
- const data = await (0,microsoft_oauth/* graphFetch */.z$)(auth, `${base}?${params.toString()}`, {
10355
- // Force UTC in responses so the agent doesn't juggle Windows-TZ names.
10356
- headers: {
10357
- Prefer: 'outlook.timezone="UTC"'
10266
+ const setEnvVar = (0,tools/* tool */.z6)(async ({ name, value, reason })=>{
10267
+ const def = (0,schema/* envSchemaByName */.Db)().get(name);
10268
+ if (!def) {
10269
+ return JSON.stringify({
10270
+ ok: false,
10271
+ code: "unknown_var",
10272
+ error: `unknown env var: ${name}`,
10273
+ hint: "List the schema with /api/v1/env (GET) before guessing names. Only JARELA_* keys defined in lib/env/schema.ts are accepted."
10274
+ });
10275
+ }
10276
+ if (!def.agentWritable) {
10277
+ return JSON.stringify({
10278
+ ok: false,
10279
+ code: "forbidden",
10280
+ error: `${name} is not flagged agentWritable in the schema`,
10281
+ hint: "Agents cannot edit infra knobs (port, hostname, dataDir, …). Tell the user to change this from the Environment panel themselves."
10282
+ });
10283
+ }
10284
+ if (value !== null) {
10285
+ const verr = (0,overrides/* validateForSchema */.xg)(def, value);
10286
+ if (verr) {
10287
+ return JSON.stringify({
10288
+ ok: false,
10289
+ code: "invalid_value",
10290
+ error: `${name}: ${verr}`,
10291
+ hint: "Re-issue the call with a value that matches the schema's type/min/max/enum."
10292
+ });
10358
10293
  }
10359
- });
10360
- if ("error" in data && data.error) return JSON.stringify(data);
10294
+ }
10295
+ await (0,overrides/* patchOverride */.PJ)(name, value);
10296
+ if (value === null) delete process.env[name];
10297
+ else process.env[name] = value;
10298
+ (0,config/* resetConfigCache */.y)();
10361
10299
  return JSON.stringify({
10362
- events: (data.value ?? []).map(outlook_calendar_slimEvent)
10300
+ ok: true,
10301
+ name,
10302
+ value,
10303
+ requiresRestart: def.requiresRestart,
10304
+ reason: reason ?? null,
10305
+ hint: def.requiresRestart ? "Override persisted. The change requires a server restart — call restart_server to apply, OR tell the user to click Restart in the Environment panel." : "Override persisted and is in effect immediately. No restart needed."
10363
10306
  });
10364
10307
  }, {
10365
- name: "outlook_calendar_list_events",
10366
- description: "List Outlook calendar events within a time window. Defaults: default " + "calendar, now +7 days, ordered by start, recurring series expanded. " + "**Use this before creating an event** to avoid double-booking, and to " + "look up event ids before updating/deleting. All datetimes returned in " + "UTC (RFC3339 'Z'-suffixed).",
10367
- schema: schemas/* object */.Ik({
10368
- calendar_id: schemas/* string */.Yj().optional().describe("Calendar id (default: user's default calendar)"),
10369
- time_min: schemas/* string */.Yj().optional().describe("RFC3339 lower bound (default: now)"),
10370
- time_max: schemas/* string */.Yj().optional().describe("RFC3339 upper bound (default: now + 7 days)"),
10371
- max_results: schemas/* number */.ai().int().optional().describe("Max events (default 25, max 100)")
10372
- })
10308
+ name: "set_env_var",
10309
+ description: "Set or unset a JARELA_* runtime override. Only schema-flagged agent-writable knobs are accepted; everything else returns code=forbidden. Use this when the user explicitly asks to change a runtime setting (e.g. log level, retry budget). Pair with restart_server when the result reports requiresRestart=true.",
10310
+ schema: setEnvSchema
10373
10311
  });
10374
- const outlookCalendarGetEventTool = (0,tools/* tool */.z6)(async ({ event_id })=>{
10375
- const auth = (0,microsoft_oauth/* resolveMicrosoftAuth */.jG)();
10376
- if ("error" in auth) return JSON.stringify({
10377
- error: auth.error
10378
- });
10379
- const e = await (0,microsoft_oauth/* graphFetch */.z$)(auth, `/me/events/${encodeURIComponent(event_id)}`, {
10380
- headers: {
10381
- Prefer: 'outlook.timezone="UTC"'
10382
- }
10383
- });
10384
- if (e.error) return JSON.stringify({
10385
- error: e.error
10386
- });
10387
- return JSON.stringify(outlook_calendar_slimEvent(e));
10388
- }, {
10389
- name: "outlook_calendar_get_event",
10390
- description: "Fetch one Outlook calendar event by id. Returns the same slim shape as " + "outlook_calendar_list_events but for a single event.",
10391
- schema: schemas/* object */.Ik({
10392
- event_id: schemas/* string */.Yj().describe("Event id (from outlook_calendar_list_events)")
10393
- })
10312
+ const restartSchema = schemas/* object */.Ik({
10313
+ reason: schemas/* string */.Yj().min(1).describe("Why the restart is needed (logged + included in /api/v1/system/restart payload). Required.")
10394
10314
  });
10395
- const outlookCalendarCreateEventTool = (0,tools/* tool */.z6)(async ({ calendar_id, subject, summary, start_iso, end_iso, description, location, attendees, time_zone, add_teams_link })=>{
10396
- const auth = (0,microsoft_oauth/* resolveMicrosoftAuth */.jG)();
10397
- if ("error" in auth) return JSON.stringify({
10398
- error: auth.error
10399
- });
10400
- // `summary` is accepted as an alias for `subject` so agents trained on
10401
- // Google Calendar's vocabulary don't fail the schema on first try.
10402
- const eventSubject = subject ?? summary;
10403
- if (!eventSubject) {
10315
+ const restartServer = (0,tools/* tool */.z6)(async ({ reason })=>{
10316
+ // Use the same endpoint the UI hits. Doing it via fetch instead of
10317
+ // calling process.exit() directly here means the request response
10318
+ // flushes back to the agent (and through it, the user) before the
10319
+ // process tears down.
10320
+ try {
10321
+ const port = Number(process.env.JARELA_PORT ?? process.env.PORT ?? 4312);
10322
+ const host = process.env.JARELA_HOSTNAME ?? process.env.HOSTNAME ?? "127.0.0.1";
10323
+ const r = await fetch(`http://${host}:${port}/api/v1/system/restart`, {
10324
+ method: "POST",
10325
+ headers: {
10326
+ "Content-Type": "application/json"
10327
+ },
10328
+ body: JSON.stringify({
10329
+ reason: `[agent] ${reason}`
10330
+ })
10331
+ });
10332
+ const body = await r.json().catch(()=>({}));
10404
10333
  return JSON.stringify({
10405
- error: "subject (or summary) is required"
10334
+ ok: r.ok,
10335
+ status: r.status,
10336
+ reason,
10337
+ hint: body.hint ?? "Restart request sent. The supervisor will relaunch the process; the user's UI will reconnect automatically."
10338
+ });
10339
+ } catch (e) {
10340
+ return JSON.stringify({
10341
+ ok: false,
10342
+ code: "restart_failed",
10343
+ error: e.message,
10344
+ hint: "Could not reach /api/v1/system/restart from inside the same process. Tell the user they need to restart manually."
10406
10345
  });
10407
10346
  }
10408
- // Graph uses Windows TZ names by default but also accepts IANA names
10409
- // when the body specifies them per-field. Default to UTC if the
10410
- // caller omits time_zone (matches the list endpoint's behavior).
10411
- const tz = time_zone ?? "UTC";
10412
- const body = {
10413
- subject: eventSubject,
10414
- start: {
10415
- dateTime: start_iso,
10416
- timeZone: tz
10347
+ }, {
10348
+ name: "restart_server",
10349
+ description: "Restart the Jarela server. Only call this when the user explicitly asks for a restart, OR when set_env_var returned requiresRestart=true and the user agreed. The UI reconnects automatically once the supervisor (launchd/systemd/Task Scheduler) relaunches.",
10350
+ schema: restartSchema
10351
+ });
10352
+ (0,registry/* registerTools */.Fd)("Config", "execute", [
10353
+ setEnvVar,
10354
+ restartServer
10355
+ ]);
10356
+
10357
+ ;// ./lib/tools/list-tools.ts
10358
+ // Read-only introspection tool — lets the agent enumerate every tool it has
10359
+ // access to right now (built-in + external + MCP), so it can answer
10360
+ // "what's in my toolbox / is X available" without the user having to
10361
+ // describe the project's tool surface in the prompt.
10362
+
10363
+
10364
+
10365
+
10366
+ const listToolsTool = (0,tools/* tool */.z6)(async ({ category, capability, source })=>{
10367
+ const all = await getAllToolsAsync();
10368
+ const summaries = all.map((t)=>({
10369
+ name: t.name,
10370
+ description: typeof t.description === "string" ? t.description : "",
10371
+ category: getToolCategory(t.name),
10372
+ capability: getToolCapability(t.name),
10373
+ source: getToolSource(t.name),
10374
+ group: getToolGroup(t.name)
10375
+ }));
10376
+ const filtered = summaries.filter((s)=>(!category || s.category === category) && (!capability || s.capability === capability) && (!source || s.source === source));
10377
+ const counts = {
10378
+ total: filtered.length,
10379
+ by_source: {
10380
+ builtin: 0,
10381
+ external: 0,
10382
+ mcp: 0
10417
10383
  },
10418
- end: {
10419
- dateTime: end_iso,
10420
- timeZone: tz
10384
+ by_capability: {
10385
+ read: 0,
10386
+ write: 0,
10387
+ execute: 0
10421
10388
  }
10422
10389
  };
10423
- if (description) body.body = {
10424
- contentType: "text",
10425
- content: description
10426
- };
10427
- if (location) body.location = {
10428
- displayName: location
10429
- };
10430
- if (attendees && attendees.length > 0) {
10431
- body.attendees = attendees.map((address)=>({
10432
- emailAddress: {
10433
- address
10434
- },
10435
- type: "required"
10436
- }));
10437
- }
10438
- if (add_teams_link) {
10439
- body.isOnlineMeeting = true;
10440
- body.onlineMeetingProvider = "teamsForBusiness";
10390
+ for (const s of filtered){
10391
+ counts.by_source[s.source]++;
10392
+ counts.by_capability[s.capability]++;
10441
10393
  }
10442
- const path = calendar_id ? `/me/calendars/${encodeURIComponent(calendar_id)}/events` : "/me/events";
10443
- const r = await (0,microsoft_oauth/* graphFetch */.z$)(auth, path, {
10444
- method: "POST",
10445
- body: JSON.stringify(body),
10446
- headers: {
10447
- Prefer: 'outlook.timezone="UTC"'
10448
- }
10449
- });
10450
- if (r.error) return JSON.stringify({
10451
- error: r.error
10394
+ return JSON.stringify({
10395
+ tools: filtered,
10396
+ counts
10452
10397
  });
10453
- return JSON.stringify(outlook_calendar_slimEvent(r));
10454
10398
  }, {
10455
- name: "outlook_calendar_create_event",
10456
- description: "Create an Outlook calendar event. Datetimes are RFC3339 strings; pair " + "with `time_zone` (IANA like 'America/Los_Angeles' or Windows TZ like " + "'Pacific Standard Time') so Outlook interprets them correctly. Defaults " + "to UTC if omitted. Attendees are automatically sent invites. Set " + "`add_teams_link: true` to provision a Teams meeting URL (requires a " + "work/school M365 account; personal Microsoft accounts cannot create " + "Teams meetings via Graph).",
10399
+ name: "list_tools",
10400
+ description: "List every tool currently available to this agent built-in tools, " + "external (~/.jarela/providers JS plugins), and MCP server tools — with " + "category, capability (read/write/execute), and source. Read-only. " + "Use this when the user asks 'what can you do?', when picking between " + "tools for a task, or when troubleshooting whether a specific tool is " + "registered. Optional filters narrow by category, capability, or source.",
10457
10401
  schema: schemas/* object */.Ik({
10458
- calendar_id: schemas/* string */.Yj().optional().describe("Calendar id (default: user's default calendar)"),
10459
- subject: schemas/* string */.Yj().min(1).optional().describe("Event title shown on the calendar (alias: summary)"),
10460
- summary: schemas/* string */.Yj().min(1).optional().describe("Alias for subject (Google Calendar vocabulary)"),
10461
- start_iso: schemas/* string */.Yj().describe("Start datetime, RFC3339"),
10462
- end_iso: schemas/* string */.Yj().describe("End datetime, RFC3339 (must be after start)"),
10463
- description: schemas/* string */.Yj().optional().describe("Long-form event body (plain text)"),
10464
- location: schemas/* string */.Yj().optional().describe("Free-text location string"),
10465
- attendees: schemas/* array */.YO(schemas/* string */.Yj().email()).optional().describe("Invitee email addresses"),
10466
- time_zone: schemas/* string */.Yj().optional().describe("IANA or Windows timezone (default 'UTC')"),
10467
- add_teams_link: schemas/* boolean */.zM().optional().describe("Provision a Teams meeting URL on this event")
10402
+ category: schemas/* string */.Yj().optional().describe("Optional category filter (e.g. 'Files', 'Mail', 'GitHub', 'MCP')"),
10403
+ capability: schemas/* enum */.k5([
10404
+ "read",
10405
+ "write",
10406
+ "execute"
10407
+ ]).optional().describe("Optional capability filter the safety class of the tool"),
10408
+ source: schemas/* enum */.k5([
10409
+ "builtin",
10410
+ "external",
10411
+ "mcp"
10412
+ ]).optional().describe("Optional source filter — where the tool came from")
10468
10413
  })
10469
10414
  });
10470
- const outlookCalendarUpdateEventTool = (0,tools/* tool */.z6)(async ({ event_id, subject, summary, start_iso, end_iso, description, location, attendees, time_zone })=>{
10471
- const auth = (0,microsoft_oauth/* resolveMicrosoftAuth */.jG)();
10472
- if ("error" in auth) return JSON.stringify({
10473
- error: auth.error
10415
+ (0,registry/* registerTools */.Fd)("Config", "read", [
10416
+ listToolsTool
10417
+ ]);
10418
+
10419
+ // EXTERNAL MODULE: ./lib/providers/index.ts + 8 modules
10420
+ var lib_providers = __webpack_require__(68866);
10421
+ // EXTERNAL MODULE: ./lib/providers/known-context-windows.ts
10422
+ var known_context_windows = __webpack_require__(45552);
10423
+ ;// ./lib/tools/providers-info.ts
10424
+ // Read-only introspection for LLM provider adapters. Two paired tools:
10425
+ // - list_providers: enumerate every adapter (built-in + external).
10426
+ // - describe_provider: capability map + known model context windows for one.
10427
+ // Together they let the agent answer "what can we route to" or pick a model
10428
+ // by capability without hand-coded knowledge of the provider catalog.
10429
+
10430
+
10431
+
10432
+
10433
+
10434
+ function sourceOf(name) {
10435
+ return lib_providers/* BUILTIN_PROVIDER_NAMES */.X4.has(name) ? "builtin" : "external";
10436
+ }
10437
+ const listProvidersTool = (0,tools/* tool */.z6)(async ()=>{
10438
+ const names = (0,lib_providers/* listProviderNames */.Yb)();
10439
+ const providers = names.map((name)=>({
10440
+ name,
10441
+ source: sourceOf(name)
10442
+ }));
10443
+ return JSON.stringify({
10444
+ providers,
10445
+ count: providers.length,
10446
+ builtin_count: providers.filter((p)=>p.source === "builtin").length,
10447
+ external_count: providers.filter((p)=>p.source === "external").length
10474
10448
  });
10475
- const tz = time_zone ?? "UTC";
10476
- const patch = {};
10477
- const eventSubject = subject ?? summary;
10478
- if (eventSubject !== undefined) patch.subject = eventSubject;
10479
- if (description !== undefined) patch.body = {
10480
- contentType: "text",
10481
- content: description
10482
- };
10483
- if (location !== undefined) patch.location = {
10484
- displayName: location
10485
- };
10486
- if (start_iso !== undefined) patch.start = {
10487
- dateTime: start_iso,
10488
- timeZone: tz
10489
- };
10490
- if (end_iso !== undefined) patch.end = {
10491
- dateTime: end_iso,
10492
- timeZone: tz
10493
- };
10494
- if (attendees !== undefined) {
10495
- patch.attendees = attendees.map((address)=>({
10496
- emailAddress: {
10497
- address
10498
- },
10499
- type: "required"
10500
- }));
10501
- }
10502
- if (Object.keys(patch).length === 0) {
10449
+ }, {
10450
+ name: "list_providers",
10451
+ description: "List every LLM provider adapter registered in this app (built-in like " + "anthropic / openai / gemini / deepseek / github-copilot / langchain, plus " + "any external `.cjs` plugins under ~/.jarela/providers/). Read-only. " + "Use this before suggesting a model swap, or when answering 'what can " + "we route to.' Call describe_provider afterwards for capability details.",
10452
+ schema: schemas/* object */.Ik({})
10453
+ });
10454
+ const describeProviderTool = (0,tools/* tool */.z6)(async ({ name })=>{
10455
+ let provider;
10456
+ try {
10457
+ provider = (0,lib_providers/* getProvider */.sO)(name);
10458
+ } catch (err) {
10503
10459
  return JSON.stringify({
10504
- error: "Provide at least one field to update"
10460
+ error: err instanceof Error ? err.message : String(err),
10461
+ hint: "Call list_providers to see registered names."
10505
10462
  });
10506
10463
  }
10507
- const r = await (0,microsoft_oauth/* graphFetch */.z$)(auth, `/me/events/${encodeURIComponent(event_id)}`, {
10508
- method: "PATCH",
10509
- body: JSON.stringify(patch),
10510
- headers: {
10511
- Prefer: 'outlook.timezone="UTC"'
10512
- }
10513
- });
10514
- if (r.error) return JSON.stringify({
10515
- error: r.error
10464
+ // Capability inferred from which methods the adapter implements. We
10465
+ // intentionally don't call listModels() here — that would hit the
10466
+ // network and require valid credentials, which makes this a "read"-class
10467
+ // tool that sometimes fails for credential reasons. Static introspection
10468
+ // only.
10469
+ const capabilities = {
10470
+ chat: typeof provider.chat === "function",
10471
+ invoke: typeof provider.invoke === "function",
10472
+ stream_invoke: typeof provider.streamInvoke === "function",
10473
+ embed: typeof provider.embed === "function",
10474
+ list_models: typeof provider.listModels === "function"
10475
+ };
10476
+ const models = (0,known_context_windows/* listKnownModels */.YY)(name);
10477
+ return JSON.stringify({
10478
+ name,
10479
+ source: sourceOf(name),
10480
+ capabilities,
10481
+ known_models: models,
10482
+ notes: [
10483
+ models.length === 0 ? "No static model catalog for this provider — call /api/v1/models?provider=" + name + " for live discovery if the adapter implements list_models." : "Static catalog only. Live `list_models` (when supported) may include " + "newer models not listed here."
10484
+ ]
10516
10485
  });
10517
- return JSON.stringify(outlook_calendar_slimEvent(r));
10518
10486
  }, {
10519
- name: "outlook_calendar_update_event",
10520
- description: "Patch an Outlook calendar event. Only supplied fields are changed; omit " + "fields to leave them alone. Note `attendees` REPLACES the full invitee " + "list, so include everyone who should remain.",
10487
+ name: "describe_provider",
10488
+ description: "Return capability flags (chat / invoke / stream / embed / list_models) " + "and the static known-models catalog (with context windows) for one " + "registered provider. Read-only. Use this to choose a model by capability, " + "or to explain provider differences to the user. Call list_providers first " + "if you don't know the provider name.",
10521
10489
  schema: schemas/* object */.Ik({
10522
- event_id: schemas/* string */.Yj().describe("Event id"),
10523
- subject: schemas/* string */.Yj().optional().describe("New event title (alias: summary)"),
10524
- summary: schemas/* string */.Yj().optional().describe("Alias for subject (Google Calendar vocabulary)"),
10525
- start_iso: schemas/* string */.Yj().optional().describe("New start datetime, RFC3339"),
10526
- end_iso: schemas/* string */.Yj().optional().describe("New end datetime, RFC3339"),
10527
- description: schemas/* string */.Yj().optional().describe("New event body (plain text)"),
10528
- location: schemas/* string */.Yj().optional().describe("New location string"),
10529
- attendees: schemas/* array */.YO(schemas/* string */.Yj().email()).optional().describe("Replacement attendee list"),
10530
- time_zone: schemas/* string */.Yj().optional().describe("IANA or Windows TZ applied to start/end (default 'UTC')")
10490
+ name: schemas/* string */.Yj().describe("Provider name from list_providers, e.g. 'anthropic', 'openai', 'gemini'")
10531
10491
  })
10532
10492
  });
10533
- const outlookCalendarDeleteEventTool = (0,tools/* tool */.z6)(async ({ event_id })=>{
10534
- const auth = (0,microsoft_oauth/* resolveMicrosoftAuth */.jG)();
10535
- if ("error" in auth) return JSON.stringify({
10536
- error: auth.error
10537
- });
10538
- const r = await (0,microsoft_oauth/* graphFetch */.z$)(auth, `/me/events/${encodeURIComponent(event_id)}`, {
10539
- method: "DELETE"
10540
- });
10541
- if (r.error) return JSON.stringify({
10542
- error: r.error
10543
- });
10493
+ (0,registry/* registerTools */.Fd)("Config", "read", [
10494
+ listProvidersTool,
10495
+ describeProviderTool
10496
+ ]);
10497
+
10498
+ // EXTERNAL MODULE: ./lib/stores/mcp-servers.ts
10499
+ var mcp_servers = __webpack_require__(42666);
10500
+ ;// ./lib/tools/mcp-servers-info.ts
10501
+ // Read-only introspection of configured MCP servers — names, transports,
10502
+ // enabled/disabled state, last connection error, and the count of tools each
10503
+ // server contributes to the live tool pool. Lets the agent answer "why is
10504
+ // my Jira tool unavailable?" without bouncing the user through the UI.
10505
+
10506
+
10507
+
10508
+
10509
+
10510
+
10511
+ const listMcpServersTool = (0,tools/* tool */.z6)(async ()=>{
10512
+ const rows = (0,mcp_servers/* listMcpServers */.n7)();
10513
+ const allTools = await getAllToolsAsync();
10514
+ // MCP tools don't carry their server name as a structured field — for
10515
+ // the per-server count we count every tool whose source is "mcp", and
10516
+ // we surface the total separately. Per-server attribution would require
10517
+ // a deeper hook into MultiServerMCPClient's tool-namespace metadata,
10518
+ // which is a bigger change than this introspection step warrants today.
10519
+ const totalMcp = allTools.filter((t)=>getToolSource(t.name) === "mcp").length;
10520
+ const servers = rows.map((r)=>({
10521
+ name: r.name,
10522
+ transport: r.transport,
10523
+ enabled: r.enabled === 1,
10524
+ last_error: r.last_error,
10525
+ // Per-server tool count not yet attributable; surface total on every
10526
+ // row for now and document the limitation in `notes` below.
10527
+ tool_count: r.enabled === 1 ? totalMcp : 0,
10528
+ created_at: r.created_at,
10529
+ updated_at: r.updated_at
10530
+ }));
10544
10531
  return JSON.stringify({
10545
- ok: true,
10546
- id: event_id
10532
+ servers,
10533
+ count: servers.length,
10534
+ enabled_count: servers.filter((s)=>s.enabled).length,
10535
+ total_mcp_tool_count: totalMcp,
10536
+ notes: [
10537
+ "tool_count is an aggregate across all enabled MCP servers — per-server " + "attribution requires deeper namespace tracking that's not implemented yet.",
10538
+ "last_error shows the most recent connection error if any; null means " + "the server has never failed since last config change."
10539
+ ]
10547
10540
  });
10548
10541
  }, {
10549
- name: "outlook_calendar_delete_event",
10550
- description: "Delete an Outlook calendar event by id. Attendees are notified. Returns " + "`{ ok: true, id }` on success. Use outlook_calendar_list_events to find " + "the event id first if you only know the title/time.",
10551
- schema: schemas/* object */.Ik({
10552
- event_id: schemas/* string */.Yj().describe("Event id to delete")
10553
- })
10542
+ name: "list_mcp_servers",
10543
+ description: "List every configured MCP server (stdio or http transport) with its " + "enabled state, last connection error, and the aggregate count of MCP " + "tools currently in the agent's pool. Read-only. Use this when " + "diagnosing 'my <X> tool isn't working' check enabled and last_error " + "before assuming the tool itself is broken.",
10544
+ schema: schemas/* object */.Ik({})
10554
10545
  });
10555
- (0,registry/* registerTools */.Fd)("Calendar", "read", [
10556
- outlookCalendarListCalendarsTool,
10557
- outlookCalendarListEventsTool,
10558
- outlookCalendarGetEventTool
10546
+ (0,registry/* registerTools */.Fd)("Config", "read", [
10547
+ listMcpServersTool
10559
10548
  ]);
10560
- (0,registry/* registerTools */.Fd)("Calendar", "execute", [
10561
- outlookCalendarCreateEventTool,
10562
- outlookCalendarUpdateEventTool,
10563
- outlookCalendarDeleteEventTool
10549
+
10550
+ ;// ./lib/tools/extension-surfaces.ts
10551
+ // Meta-introspection: a hand-curated catalog of every place a third party
10552
+ // (or the agent itself, on behalf of the user) can extend or customize the
10553
+ // app. Read-only. The tool returns a structured map that points to the
10554
+ // registration entrypoint, the integration guide section, and an example
10555
+ // file for each surface — so the agent can answer "how do I add an X?"
10556
+ // or "what can be customized" without needing the docs in its context.
10557
+ //
10558
+ // SOURCE OF TRUTH: this file. When a new extension surface lands, add an
10559
+ // entry below — it's a single source for both the agent runtime and the
10560
+ // `docs/EXTENDING.md` table-of-contents anchor.
10561
+
10562
+
10563
+
10564
+ const SURFACES = [
10565
+ {
10566
+ id: "llm_provider_builtin",
10567
+ name: "Built-in LLM provider",
10568
+ summary: "Add a new in-tree adapter (Anthropic, OpenAI, Gemini, … pattern). " + "Implements the ModelProvider interface and is registered via the static " + "BUILTINS map in lib/providers/index.ts.",
10569
+ registration_entrypoint: "lib/providers/index.ts (BUILTINS map)",
10570
+ doc_section: "docs/EXTENDING.md#adding-an-llm-provider-built-in",
10571
+ example_path: "lib/providers/anthropic.ts",
10572
+ introspection_tool: "list_providers",
10573
+ related_adrs: [
10574
+ "ADR-0013"
10575
+ ]
10576
+ },
10577
+ {
10578
+ id: "llm_provider_external",
10579
+ name: "External LLM provider plugin",
10580
+ summary: "Drop a `.cjs` plugin into ~/.jarela/providers/ that exports an object " + "matching ModelProvider. Hot-loaded per call (no rebuild).",
10581
+ registration_entrypoint: "~/.jarela/providers/<name>.cjs",
10582
+ doc_section: "docs/EXTENDING.md#adding-an-llm-provider-external",
10583
+ example_path: "lib/providers/template-external.cjs.example",
10584
+ introspection_tool: "list_providers",
10585
+ related_adrs: [
10586
+ "ADR-0013"
10587
+ ]
10588
+ },
10589
+ {
10590
+ id: "builtin_tool",
10591
+ name: "Built-in tool",
10592
+ summary: "Add a tool callable by the agent. Implement with @langchain/core/tools, " + "register with `registerTools(category, capability, [tool])`, and add a " + "side-effect import in lib/tools/builtins.ts. Capability gating is " + "read | write | execute.",
10593
+ registration_entrypoint: "lib/tools/<name>.ts (registerTools call) + lib/tools/builtins.ts",
10594
+ doc_section: "docs/EXTENDING.md#adding-a-built-in-tool",
10595
+ example_path: "lib/tools/template.ts",
10596
+ introspection_tool: "list_tools",
10597
+ related_adrs: [
10598
+ "ADR-0038"
10599
+ ]
10600
+ },
10601
+ {
10602
+ id: "mcp_server",
10603
+ name: "MCP server",
10604
+ summary: "Connect a Model Context Protocol server (stdio or http transport). " + "Configured via the UI (mcp_servers DB table) or programmatically through " + "lib/stores/mcp-servers.ts. Tools auto-merge into the same pool as " + "built-ins. Online discovery via the MCP registry is also supported.",
10605
+ registration_entrypoint: "lib/stores/mcp-servers.ts (upsertMcpServer)",
10606
+ doc_section: "docs/EXTENDING.md#adding-an-mcp-server",
10607
+ introspection_tool: "list_mcp_servers",
10608
+ related_adrs: [
10609
+ "ADR-0014"
10610
+ ]
10611
+ },
10612
+ {
10613
+ id: "agent_harness",
10614
+ name: "Agent harness",
10615
+ summary: "Compose system-prompt sections that shape an agent's behaviour " + "(plan-first, self-config, capability-listing, etc.). Built-in presets " + "live in lib/agents/harness/presets.ts; custom harnesses are stored in " + "the memory_store under the `app-settings` namespace.",
10616
+ registration_entrypoint: "lib/agents/harness/presets.ts (built-in) or memory_store (custom)",
10617
+ doc_section: "docs/EXTENDING.md#adding-a-custom-harness",
10618
+ related_adrs: [
10619
+ "ADR-0036"
10620
+ ]
10621
+ },
10622
+ {
10623
+ id: "integration_manifest",
10624
+ name: "Integration manifest",
10625
+ summary: "Add a new agent-led setup recipe — prerequisites, ordered steps, " + "troubleshooting hints. The agent narrates the recipe and proposes " + "the corresponding config changes through propose_config_change.",
10626
+ registration_entrypoint: "lib/integrations/registry.ts",
10627
+ doc_section: "docs/EXTENDING.md#adding-an-integration-manifest",
10628
+ introspection_tool: "list_integrations",
10629
+ related_adrs: [
10630
+ "ADR-0010"
10631
+ ]
10632
+ },
10633
+ {
10634
+ id: "brand_overlay",
10635
+ name: "Brand / app identity",
10636
+ summary: "Customize the app's name, description, and issue URL via environment " + "variables. No code change required. Used by downstream packages that " + "consume @circuitwall/jarela as an npm dep (e.g. brand overlays).",
10637
+ registration_entrypoint: "NEXT_PUBLIC_APP_NAME, NEXT_PUBLIC_APP_DESCRIPTION, NEXT_PUBLIC_APP_ISSUE_URL",
10638
+ doc_section: "docs/EXTENDING.md#branding-the-app",
10639
+ related_adrs: [
10640
+ "ADR-0005"
10641
+ ]
10642
+ }
10643
+ ];
10644
+ const describeExtensionSurfacesTool = (0,tools/* tool */.z6)(async ()=>{
10645
+ return JSON.stringify({
10646
+ surfaces: SURFACES,
10647
+ count: SURFACES.length,
10648
+ guide_path: "docs/EXTENDING.md",
10649
+ contract_paths: [
10650
+ "lib/providers/types.ts (ModelProvider interface)",
10651
+ "lib/tools/types.ts (OpenAITool, ToolContext, InvokeMessage)",
10652
+ "lib/tools/registry.ts (registerTools, ToolCategory, Capability)",
10653
+ "lib/mcp/registry.ts (RegistryEntry, applyVariables)"
10654
+ ],
10655
+ notes: [
10656
+ "Each surface's introspection_tool, when set, lets you enumerate what's " + "currently registered. Call those when the user asks 'what's available' " + "rather than describing the type from memory.",
10657
+ "A surface can have either a static example_path (for external plugins) or " + "no example (for surfaces wired entirely through the UI / DB). When in " + "doubt, the doc_section under EXTENDING.md is the canonical walk-through."
10658
+ ]
10659
+ });
10660
+ }, {
10661
+ name: "describe_extension_surfaces",
10662
+ description: "Return the curated catalog of every extension surface in this app — " + "providers, tools, MCP servers, harnesses, integration manifests, brand " + "overlay. Each entry has a registration entrypoint, an EXTENDING.md " + "section anchor, and (when applicable) the introspection tool that lists " + "what's currently registered. Read-only. Call this when the user asks " + "'how do I add an X?' or 'what can be customized?' so you can guide " + "them with the right files and docs.",
10663
+ schema: schemas/* object */.Ik({})
10664
+ });
10665
+ (0,registry/* registerTools */.Fd)("Config", "read", [
10666
+ describeExtensionSurfacesTool
10564
10667
  ]);
10565
10668
 
10566
- // EXTERNAL MODULE: ./lib/agents/run-thread.ts + 17 modules
10567
- var run_thread = __webpack_require__(8459);
10568
- // EXTERNAL MODULE: ./lib/agents/stream-collector.ts
10569
- var stream_collector = __webpack_require__(30685);
10570
- // EXTERNAL MODULE: ./lib/agents/run-queue.ts
10571
- var run_queue = __webpack_require__(3944);
10572
- ;// ./lib/tools/delegate.ts
10669
+ ;// ./lib/tools/builtins.ts
10670
+ // Barrel of built-in tool modules. Each side-effect import triggers the
10671
+ // module's `registerTools(...)` call (see ./registry.ts). Adding a new
10672
+ // built-in tool: add the file under lib/tools/ and append a line here.
10673
+ //
10674
+ // Order matters only for deterministic logging / UI ordering — registry
10675
+ // preserves insertion order.
10676
+
10677
+
10678
+
10679
+
10680
+
10681
+
10682
+
10683
+
10684
+
10685
+
10686
+
10687
+
10688
+
10689
+
10690
+
10691
+
10692
+
10693
+
10694
+
10695
+
10696
+
10697
+
10698
+
10699
+
10700
+
10701
+
10702
+
10703
+
10704
+
10705
+ // EXTERNAL MODULE: ./lib/mcp/client.ts
10706
+ var client = __webpack_require__(30572);
10707
+ // EXTERNAL MODULE: ./lib/tools/external.ts
10708
+ var external = __webpack_require__(18689);
10709
+ // EXTERNAL MODULE: ./lib/stores/builtin-tools.ts
10710
+ var builtin_tools = __webpack_require__(56718);
10711
+ ;// ./lib/tools/index.ts
10712
+ // Public tool surface for the agent runtime.
10713
+ //
10714
+ // Built-in tools register themselves at module load (see ./registry.ts and
10715
+ // ./builtins.ts). External tools live under JARELA_TOOLS_DIR and are
10716
+ // loaded per-call (hot-reload). MCP tools come from lib/mcp/client.ts.
10717
+ //
10718
+ // To add a new built-in tool:
10719
+ // 1. Copy lib/tools/template.ts to lib/tools/<name>.ts and implement it.
10720
+ // 2. Call `registerTools("<Category>", "<read|write|execute>", [yourTool, ...])` at the bottom.
10721
+ // 3. Add `import "./<name>";` to lib/tools/builtins.ts.
10722
+ //
10723
+ // That's it — no central array to update, no parallel category map.
10724
+
10725
+ // Side-effect import: triggers registerTools() in every built-in module.
10573
10726
 
10574
10727
 
10575
10728
 
@@ -10578,438 +10731,700 @@ var run_queue = __webpack_require__(3944);
10578
10731
 
10579
10732
 
10580
10733
 
10581
- function readDelegateContext(config) {
10582
- const threadId = config?.configurable?.thread_id;
10583
- if (!threadId) return null;
10584
- const thread = (0,threads/* getThread */.fG)(threadId);
10585
- if (!thread) return null;
10586
- const depth = config?.configurable?.delegation_depth ?? 0;
10587
- const ancestorsRaw = config?.configurable?.delegation_ancestors;
10588
- const ancestors = Array.isArray(ancestorsRaw) ? ancestorsRaw : [];
10589
- return {
10590
- parentAgentId: thread.agent_id,
10591
- depth,
10592
- ancestors
10593
- };
10734
+ // Live accessors — DO NOT snapshot at module-load time. Some tool modules
10735
+ // import back from this file (for `getAllToolsAsync` etc.) and would create
10736
+ // a circular import cycle: their `registerTools(...)` call runs AFTER this
10737
+ // module finishes evaluating, so any captured snapshot here would miss them
10738
+ // and they'd silently disappear from the agent's tool pool. Live calls dodge
10739
+ // the problem by the time anyone INVOKES `getAllTools()` / etc., every
10740
+ // builtin has registered.
10741
+ function allBuiltins() {
10742
+ return (0,registry/* registeredTools */.gN)();
10594
10743
  }
10595
- function fail(code, message, extra = {}) {
10596
- return JSON.stringify({
10597
- ok: false,
10598
- error_code: code,
10599
- message,
10600
- ...extra
10744
+ function builtinNames() {
10745
+ return (0,registry/* registeredNames */.nE)();
10746
+ }
10747
+ // Backwards-compatible export. Callers that import this Set get its current
10748
+ // contents at the moment they read it (a fresh Set each access). Internal
10749
+ // code prefers `builtinNames()` directly; this stays for external consumers
10750
+ // like the extensions API route.
10751
+ function getBuiltinToolNames() {
10752
+ return builtinNames();
10753
+ }
10754
+ // Per-call recompute so files dropped in $JARELA_TOOLS_DIR are picked up
10755
+ // without restart. loadExternalTools cache-busts require() per file.
10756
+ function loadExternal() {
10757
+ return (0,external/* loadExternalTools */.H)(builtinNames());
10758
+ }
10759
+ // Resolve a tool's origin from its name. Used to label rows in the tools
10760
+ // API and to route metadata lookups. Returns "mcp" for any name that is
10761
+ // neither a registered built-in nor an external (JARELA_TOOLS_DIR) tool —
10762
+ // matches today's behavior where MCP tools are everything else.
10763
+ function getToolSource(name) {
10764
+ if (builtinNames().has(name)) return "builtin";
10765
+ if (loadExternal().tools.some((t)=>t.name === name)) return "external";
10766
+ return "mcp";
10767
+ }
10768
+ // Look up a tool's safety class. Built-in tools have a declared capability;
10769
+ // external (JARELA_TOOLS_DIR) and MCP tools default to "execute" — the
10770
+ // conservative choice until manifest-level overrides land (ADR-0038).
10771
+ // Source is derived internally so callers can't mis-tag external tools as
10772
+ // MCP (or vice versa).
10773
+ function getToolCapability(name) {
10774
+ return (0,registry/* registeredCapability */.RL)(name) ?? "execute";
10775
+ }
10776
+ function getToolCategory(name) {
10777
+ const builtin = (0,registry/* registeredCategory */.RY)(name);
10778
+ if (builtin) return builtin;
10779
+ const ext = loadExternal().categories.get(name);
10780
+ if (ext) return ext;
10781
+ return getToolSource(name) === "mcp" ? "MCP" : "Config";
10782
+ }
10783
+ function getToolGroup(name) {
10784
+ const cat = getToolCategory(name);
10785
+ if (cat === "MCP") return null;
10786
+ return (0,registry/* registeredGroup */.Oi)(name) ?? null;
10787
+ }
10788
+ function applyPolicy(tools, policy) {
10789
+ const allowSet = policy?.allow?.length ? new Set(policy.allow) : null;
10790
+ const denySet = policy?.deny?.length ? new Set(policy.deny) : null;
10791
+ return tools.filter((t)=>{
10792
+ if (allowSet && !allowSet.has(t.name)) return false;
10793
+ if (denySet && denySet.has(t.name)) return false;
10794
+ return true;
10601
10795
  });
10602
10796
  }
10603
- const delegateToAgentTool = (0,tools/* tool */.z6)(async ({ agent_id, task }, config)=>{
10604
- const ctx = readDelegateContext(config);
10605
- if (!ctx) return fail("no_context", "No agent context (missing thread_id)");
10606
- const parent = (0,agent_configs/* getAgentConfig */.mJ)(ctx.parentAgentId);
10607
- if (!parent) return fail("agent_not_found", `Parent agent "${ctx.parentAgentId}" not found`);
10608
- if (agent_id === ctx.parentAgentId) {
10609
- return fail("cycle_detected", "Cannot delegate to self");
10610
- }
10611
- if (ctx.ancestors.includes(agent_id)) {
10612
- return fail("cycle_detected", `Delegation cycle: ${agent_id} is already in the chain [${ctx.ancestors.join(" -> ")}]`);
10797
+ // Filter built-in tools whose category is disabled in the toggle store.
10798
+ // External + MCP tools are untouched (they have their own enable surfaces).
10799
+ function applyCategoryToggles(tools) {
10800
+ const disabled = (0,builtin_tools/* disabledCategories */.HP)();
10801
+ if (disabled.size === 0) return tools;
10802
+ return tools.filter((t)=>{
10803
+ const cat = (0,registry/* registeredCategory */.RY)(t.name);
10804
+ if (!cat) return true; // not a built-in (or unregistered) → leave it
10805
+ return !disabled.has(cat);
10806
+ });
10807
+ }
10808
+ // Synchronous: built-in + external tools (no MCP). Used by GET /api/v1/tools
10809
+ // and any code path that can't await.
10810
+ function getAllTools(policy) {
10811
+ return applyPolicy([
10812
+ ...applyCategoryToggles(allBuiltins()),
10813
+ ...loadExternal().tools
10814
+ ], policy);
10815
+ }
10816
+ // Async: built-in + external + MCP tools.
10817
+ // Use this anywhere the agent might invoke tools (createReactAgent input).
10818
+ // External tools are loaded per-call (hot-reload). MCP tools are cached by
10819
+ // lib/mcp/client.ts and only re-resolved when the mcp_servers table changes.
10820
+ async function getAllToolsAsync(policy) {
10821
+ let mcpTools = [];
10822
+ try {
10823
+ mcpTools = await (0,client/* getMcpTools */.a)();
10824
+ } catch (err) {
10825
+ console.error("[tools] MCP load failed, continuing with built-ins only:", err);
10613
10826
  }
10614
- if (ctx.depth >= run_thread/* MAX_DELEGATION_DEPTH */.Ye) {
10615
- return fail("depth_exceeded", `Maximum delegation depth (${run_thread/* MAX_DELEGATION_DEPTH */.Ye}) reached. Chain: [${[
10616
- ...ctx.ancestors,
10617
- ctx.parentAgentId
10618
- ].join(" -> ")}]`);
10827
+ return applyPolicy([
10828
+ ...applyCategoryToggles(allBuiltins()),
10829
+ ...loadExternal().tools,
10830
+ ...mcpTools
10831
+ ], policy);
10832
+ }
10833
+ function toOpenAITools(tools) {
10834
+ return tools.map((t)=>{
10835
+ const oai = convertToOpenAITool(t);
10836
+ return {
10837
+ type: "function",
10838
+ function: {
10839
+ name: oai.function.name,
10840
+ description: oai.function.description ?? "",
10841
+ parameters: oai.function.parameters
10842
+ }
10843
+ };
10844
+ });
10845
+ }
10846
+ async function executeTool(name, args, context = {}) {
10847
+ let t = allBuiltins().find((x)=>x.name === name);
10848
+ if (t) {
10849
+ const cat = registeredCategory(name);
10850
+ if (cat && disabledCategories().has(cat)) {
10851
+ throw new Error(`Tool "${name}" is disabled (category ${cat} is turned off)`);
10852
+ }
10619
10853
  }
10620
- const roster = (0,agent_configs/* parseDelegateTargets */.bg)(parent.delegate_targets);
10621
- if (!roster.includes(agent_id)) {
10622
- return fail("not_in_roster", `Agent "${agent_id}" is not in the delegate roster for "${parent.id}". Available: [${roster.join(", ") || "none"}]`);
10854
+ if (!t) {
10855
+ t = loadExternal().tools.find((x)=>x.name === name);
10623
10856
  }
10624
- const child = (0,agent_configs/* getAgentConfig */.mJ)(agent_id);
10625
- if (!child) return fail("agent_not_found", `Delegate agent "${agent_id}" not found`);
10626
- const childThread = (0,threads/* getOrCreateAgentThread */.xH)(agent_id);
10627
- const startedAt = Date.now();
10628
- try {
10629
- // Serialise on the child thread_id with every other entry point
10630
- // (HTTP POST, scheduler, watcher, bridge, sibling delegations)
10631
- // see lib/agents/run-queue.ts. A delegate fired while the child is
10632
- // already running waits in the child's queue instead of racing the
10633
- // checkpoint store.
10634
- const queued = await (0,run_queue/* enqueueThreadRun */.M6)(childThread.thread_id, "delegate", async ()=>{
10635
- const prepared = await (0,run_thread/* prepareThreadRun */.fn)({
10636
- thread_id: childThread.thread_id,
10637
- message: task,
10638
- user_category: "delegation",
10639
- _delegation_depth: ctx.depth + 1,
10640
- _delegation_ancestors: [
10641
- ...ctx.ancestors,
10642
- ctx.parentAgentId
10643
- ]
10644
- });
10645
- const collected = await (0,stream_collector/* collectStream */.V)(prepared.stream);
10646
- if (collected.terminal !== "error") {
10647
- (0,run_thread/* persistAssistantMessage */._w)(childThread.thread_id, collected.assistantContent, collected.usedTools, collected.toolEvents, "delegation", collected.usage ?? null, prepared.context_snapshot ?? null, prepared.source_manifest ?? null);
10648
- }
10649
- return collected;
10650
- }).result;
10651
- const elapsed_ms = Date.now() - startedAt;
10652
- if (queued.terminal === "error") {
10653
- return fail("child_error", queued.errorMessage ?? "Child agent run failed", {
10654
- agent_id,
10655
- agent_name: child.name,
10656
- thread_id: childThread.thread_id
10657
- });
10857
+ if (!t) throw new Error(`Unknown tool: ${name}`);
10858
+ const config = context.thread_id ? {
10859
+ configurable: {
10860
+ thread_id: context.thread_id
10861
+ }
10862
+ } : {};
10863
+ const result = await t.invoke(args, config);
10864
+ // Tools return JSON strings per LangChain convention; parse back for downstream use.
10865
+ if (typeof result === "string") {
10866
+ try {
10867
+ return JSON.parse(result);
10868
+ } catch {
10869
+ return result;
10658
10870
  }
10659
- return JSON.stringify({
10660
- ok: true,
10661
- agent_id: child.id,
10662
- agent_name: child.name,
10663
- thread_id: childThread.thread_id,
10664
- depth: ctx.depth + 1,
10665
- result: queued.assistantContent.trim(),
10666
- used_tools: Array.from(new Set(queued.usedTools)),
10667
- elapsed_ms
10668
- });
10669
- } catch (err) {
10670
- return fail("child_error", err instanceof Error ? err.message : String(err), {
10671
- agent_id,
10672
- agent_name: child.name,
10673
- thread_id: childThread.thread_id
10674
- });
10675
10871
  }
10676
- }, {
10677
- name: "delegate_to_agent",
10678
- description: "Hand a subtask to another agent that has specialized knowledge or tools you lack. " + "The delegate runs in its own thread; the user sees a tool card with the delegate's name, " + "the task you sent, and the delegate's final answer (plus a link to open the delegate's thread). " + "Only delegate when the target agent is genuinely better-suited do NOT delegate trivial subtasks " + "you can handle yourself. Before calling this tool, tell the user in one sentence which agent you " + "are handing to and why. Available delegate ids are listed in the 'Available delegates' system " + "section; calling with an id outside that list will be refused.",
10679
- schema: schemas/* object */.Ik({
10680
- agent_id: schemas/* string */.Yj().describe("The id of the delegate agent. Must appear in the 'Available delegates' list in your system prompt."),
10681
- task: schemas/* string */.Yj().min(1).describe("A self-contained task description for the delegate. Include all context the delegate needs — " + "the delegate does NOT see your conversation history with the user, only this task string.")
10682
- })
10683
- });
10684
- (0,registry/* registerTools */.Fd)("Agent", "execute", [
10685
- delegateToAgentTool
10686
- ]);
10687
-
10688
- // EXTERNAL MODULE: ./lib/env/schema.ts
10689
- var schema = __webpack_require__(69353);
10690
- // EXTERNAL MODULE: ./lib/env/overrides.ts
10691
- var overrides = __webpack_require__(86037);
10692
- ;// ./lib/tools/system_config.ts
10693
- // Agent-callable knobs for the JARELA_* override store.
10694
- //
10695
- // Both tools are gated by the per-var schema flag `agentWritable` —
10696
- // agents cannot write to anything not flagged true (defaults to false).
10697
- // `restart_server` is only wired here; agents still need the tool to be
10698
- // in their selected toolset to call it (default agents do not include
10699
- // these).
10700
- //
10701
- // Use cases the user explicitly asked to support:
10702
- // - "agent, lower the run idle timeout to 30s and restart"
10703
- // - "agent, switch the log level to debug for the next hour"
10704
- //
10705
- // We surface the schema's tier/restart flags in the response so the
10706
- // agent's reply can tell the user what changed and whether a restart
10707
- // fired (or is needed but skipped).
10708
-
10709
-
10872
+ return result;
10873
+ }
10874
+ // One-shot startup loader. Call from instrumentation.ts so external tools
10875
+ // load + log their status at boot rather than lazily on first agent turn.
10876
+ let _initialized = false;
10877
+ function initTools() {
10878
+ const toolsDir = getToolsDir();
10879
+ const result = loadExternal();
10880
+ const summary = {
10881
+ builtinCount: allBuiltins().length,
10882
+ externalCount: result.tools.length,
10883
+ errors: result.errors,
10884
+ toolsDir
10885
+ };
10886
+ if (!_initialized) {
10887
+ console.info(`[tools] ${summary.builtinCount} built-in tool(s) registered; ` + `${summary.externalCount} external tool(s) loaded from ${toolsDir}`);
10888
+ for (const err of summary.errors){
10889
+ console.error(`[tools] external ${err.file}: ${err.error}`);
10890
+ }
10891
+ _initialized = true;
10892
+ }
10893
+ return summary;
10894
+ }
10710
10895
 
10711
10896
 
10897
+ /***/ }),
10712
10898
 
10899
+ /***/ 45552:
10900
+ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
10713
10901
 
10714
- const setEnvSchema = schemas/* object */.Ik({
10715
- name: schemas/* string */.Yj().describe("Env var name. Must be a JARELA_* knob the schema flags as agent-writable; otherwise the call returns code=forbidden."),
10716
- value: schemas/* string */.Yj().nullable().describe("New value as a string (numbers/bools come as their string form: '5000' / 'true'). Pass null to clear the override and revert to the default."),
10717
- reason: schemas/* string */.Yj().optional().describe("Short note explaining the change — logged for postmortems.")
10718
- });
10719
- const setEnvVar = (0,tools/* tool */.z6)(async ({ name, value, reason })=>{
10720
- const def = (0,schema/* envSchemaByName */.Db)().get(name);
10721
- if (!def) {
10722
- return JSON.stringify({
10723
- ok: false,
10724
- code: "unknown_var",
10725
- error: `unknown env var: ${name}`,
10726
- hint: "List the schema with /api/v1/env (GET) before guessing names. Only JARELA_* keys defined in lib/env/schema.ts are accepted."
10727
- });
10902
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
10903
+ /* harmony export */ YY: () => (/* binding */ listKnownModels),
10904
+ /* harmony export */ pN: () => (/* binding */ getKnownMaxOutputTokens),
10905
+ /* harmony export */ zJ: () => (/* binding */ getKnownContextLength)
10906
+ /* harmony export */ });
10907
+ /* unused harmony export getKnownModelLimits */
10908
+ // Web-sourced fallback context-window sizes per (provider, model_id).
10909
+ // Used when the live `/models` endpoint doesn't report `context_length`
10910
+ // (OpenAI's catalog and GitHub Copilot's catalog both omit it) AND the
10911
+ // user hasn't pinned `context_window_tokens` on the model config.
10912
+ //
10913
+ // Resolution order at runtime is:
10914
+ // 1. user-set `params.context_window_tokens` (explicit override)
10915
+ // 2. live API value via `listModels()` / catalog cache (when populated)
10916
+ // 3. this static fallback (this file)
10917
+ // 4. `DEFAULT_CONTEXT_WINDOW_TOKENS` in lib/agents/context-budget.ts
10918
+ //
10919
+ // Verified from each vendor's public docs as of 2026-06. Bump when vendors
10920
+ // publish new families.
10921
+ const ANTHROPIC = {
10922
+ "claude-opus-4-7": {
10923
+ context_length: 1000000,
10924
+ max_output_tokens: 8192
10925
+ },
10926
+ "claude-opus-4": {
10927
+ context_length: 200000,
10928
+ max_output_tokens: 8192
10929
+ },
10930
+ "claude-sonnet-4-6": {
10931
+ context_length: 200000,
10932
+ max_output_tokens: 8192
10933
+ },
10934
+ "claude-sonnet-4": {
10935
+ context_length: 200000,
10936
+ max_output_tokens: 8192
10937
+ },
10938
+ "claude-3.7-sonnet": {
10939
+ context_length: 200000,
10940
+ max_output_tokens: 8192
10941
+ },
10942
+ "claude-3-7-sonnet": {
10943
+ context_length: 200000,
10944
+ max_output_tokens: 8192
10945
+ },
10946
+ "claude-3-5-sonnet": {
10947
+ context_length: 200000,
10948
+ max_output_tokens: 8192
10949
+ },
10950
+ "claude-3-5-haiku": {
10951
+ context_length: 200000,
10952
+ max_output_tokens: 8192
10953
+ },
10954
+ "claude-haiku-4-5": {
10955
+ context_length: 200000,
10956
+ max_output_tokens: 4096
10957
+ },
10958
+ "claude-haiku-4": {
10959
+ context_length: 200000,
10960
+ max_output_tokens: 4096
10961
+ },
10962
+ "claude-3-opus": {
10963
+ context_length: 200000,
10964
+ max_output_tokens: 4096
10728
10965
  }
10729
- if (!def.agentWritable) {
10730
- return JSON.stringify({
10731
- ok: false,
10732
- code: "forbidden",
10733
- error: `${name} is not flagged agentWritable in the schema`,
10734
- hint: "Agents cannot edit infra knobs (port, hostname, dataDir, …). Tell the user to change this from the Environment panel themselves."
10735
- });
10966
+ };
10967
+ const GEMINI = {
10968
+ "gemini-2.5-pro": {
10969
+ context_length: 1048576,
10970
+ max_output_tokens: 65536
10971
+ },
10972
+ "gemini-2.5-flash": {
10973
+ context_length: 1048576,
10974
+ max_output_tokens: 65536
10975
+ },
10976
+ "gemini-2.0-flash-lite": {
10977
+ context_length: 1048576,
10978
+ max_output_tokens: 8192
10979
+ },
10980
+ "gemini-2.0-flash": {
10981
+ context_length: 1048576,
10982
+ max_output_tokens: 8192
10983
+ },
10984
+ "gemini-1.5-pro": {
10985
+ context_length: 2097152,
10986
+ max_output_tokens: 8192
10987
+ },
10988
+ "gemini-1.5-flash": {
10989
+ context_length: 1048576,
10990
+ max_output_tokens: 8192
10736
10991
  }
10737
- if (value !== null) {
10738
- const verr = (0,overrides/* validateForSchema */.xg)(def, value);
10739
- if (verr) {
10740
- return JSON.stringify({
10741
- ok: false,
10742
- code: "invalid_value",
10743
- error: `${name}: ${verr}`,
10744
- hint: "Re-issue the call with a value that matches the schema's type/min/max/enum."
10745
- });
10992
+ };
10993
+ const OPENAI = {
10994
+ "gpt-5-mini": {
10995
+ context_length: 400000,
10996
+ max_output_tokens: 128000
10997
+ },
10998
+ "gpt-5": {
10999
+ context_length: 400000,
11000
+ max_output_tokens: 128000
11001
+ },
11002
+ "gpt-4.1-mini": {
11003
+ context_length: 1047576,
11004
+ max_output_tokens: 32768
11005
+ },
11006
+ "gpt-4.1": {
11007
+ context_length: 1047576,
11008
+ max_output_tokens: 32768
11009
+ },
11010
+ "gpt-4o-mini": {
11011
+ context_length: 128000,
11012
+ max_output_tokens: 16384
11013
+ },
11014
+ "gpt-4o": {
11015
+ context_length: 128000,
11016
+ max_output_tokens: 16384
11017
+ },
11018
+ "gpt-4-turbo": {
11019
+ context_length: 128000,
11020
+ max_output_tokens: 4096
11021
+ },
11022
+ "gpt-4": {
11023
+ context_length: 8192,
11024
+ max_output_tokens: 4096
11025
+ },
11026
+ "gpt-3.5-turbo": {
11027
+ context_length: 16385,
11028
+ max_output_tokens: 4096
11029
+ },
11030
+ "chatgpt-4o": {
11031
+ context_length: 128000,
11032
+ max_output_tokens: 16384
11033
+ },
11034
+ "o4-mini": {
11035
+ context_length: 200000,
11036
+ max_output_tokens: 100000
11037
+ },
11038
+ "o3-mini": {
11039
+ context_length: 200000,
11040
+ max_output_tokens: 100000
11041
+ },
11042
+ "o3": {
11043
+ context_length: 200000,
11044
+ max_output_tokens: 100000
11045
+ },
11046
+ "o1-mini": {
11047
+ context_length: 128000,
11048
+ max_output_tokens: 65536
11049
+ },
11050
+ "o1": {
11051
+ context_length: 200000,
11052
+ max_output_tokens: 100000
11053
+ }
11054
+ };
11055
+ const DEEPSEEK = {
11056
+ "deepseek-v4-flash": {
11057
+ context_length: 65536,
11058
+ max_output_tokens: 8192
11059
+ },
11060
+ "deepseek-v4-pro": {
11061
+ context_length: 65536,
11062
+ max_output_tokens: 8192
11063
+ },
11064
+ "deepseek-chat": {
11065
+ context_length: 65536,
11066
+ max_output_tokens: 8192
11067
+ },
11068
+ "deepseek-reasoner": {
11069
+ context_length: 65536,
11070
+ max_output_tokens: 8192
11071
+ }
11072
+ };
11073
+ // GitHub Copilot proxies vendor models — sometimes under a transformed id
11074
+ // (e.g. `Github-Opus4.6`, `copilot-claude-3.5-sonnet`). Strip the proxy
11075
+ // prefix and dispatch to the underlying vendor table.
11076
+ function resolveCopilot(model_id) {
11077
+ const normalized = model_id.replace(/^(?:Github-|copilot-)/i, "").toLowerCase();
11078
+ // Canonicalise a few proxy-specific spellings.
11079
+ const canon = normalized.replace(/^opus4\.6/, "claude-opus-4-7").replace(/^opus4/, "claude-opus-4").replace(/^sonnet4\.6/, "claude-sonnet-4-6").replace(/^sonnet4/, "claude-sonnet-4").replace(/^haiku4\.5/, "claude-haiku-4-5");
11080
+ if (canon.startsWith("claude")) return matchPrefix(ANTHROPIC, canon);
11081
+ if (canon.startsWith("gemini")) return matchPrefix(GEMINI, canon);
11082
+ if (canon.startsWith("gpt") || /^o[134](?:-|$)/.test(canon)) return matchPrefix(OPENAI, canon);
11083
+ return null;
11084
+ }
11085
+ function matchPrefix(table, id) {
11086
+ const lower = id.toLowerCase();
11087
+ const exact = table[lower];
11088
+ if (exact) return exact;
11089
+ // Longest-prefix match so `gpt-4o-2024-08-06` resolves to `gpt-4o` and
11090
+ // not `gpt-4` (which would also prefix-match).
11091
+ let best = null;
11092
+ for (const [k, v] of Object.entries(table)){
11093
+ if (lower.startsWith(k) && (!best || k.length > best.key.length)) {
11094
+ best = {
11095
+ key: k,
11096
+ v
11097
+ };
10746
11098
  }
10747
11099
  }
10748
- await (0,overrides/* patchOverride */.PJ)(name, value);
10749
- if (value === null) delete process.env[name];
10750
- else process.env[name] = value;
10751
- (0,config/* resetConfigCache */.y)();
10752
- return JSON.stringify({
10753
- ok: true,
10754
- name,
10755
- value,
10756
- requiresRestart: def.requiresRestart,
10757
- reason: reason ?? null,
10758
- hint: def.requiresRestart ? "Override persisted. The change requires a server restart — call restart_server to apply, OR tell the user to click Restart in the Environment panel." : "Override persisted and is in effect immediately. No restart needed."
10759
- });
10760
- }, {
10761
- name: "set_env_var",
10762
- description: "Set or unset a JARELA_* runtime override. Only schema-flagged agent-writable knobs are accepted; everything else returns code=forbidden. Use this when the user explicitly asks to change a runtime setting (e.g. log level, retry budget). Pair with restart_server when the result reports requiresRestart=true.",
10763
- schema: setEnvSchema
10764
- });
10765
- const restartSchema = schemas/* object */.Ik({
10766
- reason: schemas/* string */.Yj().min(1).describe("Why the restart is needed (logged + included in /api/v1/system/restart payload). Required.")
10767
- });
10768
- const restartServer = (0,tools/* tool */.z6)(async ({ reason })=>{
10769
- // Use the same endpoint the UI hits. Doing it via fetch instead of
10770
- // calling process.exit() directly here means the request response
10771
- // flushes back to the agent (and through it, the user) before the
10772
- // process tears down.
10773
- try {
10774
- const port = Number(process.env.JARELA_PORT ?? process.env.PORT ?? 4312);
10775
- const host = process.env.JARELA_HOSTNAME ?? process.env.HOSTNAME ?? "127.0.0.1";
10776
- const r = await fetch(`http://${host}:${port}/api/v1/system/restart`, {
10777
- method: "POST",
10778
- headers: {
10779
- "Content-Type": "application/json"
10780
- },
10781
- body: JSON.stringify({
10782
- reason: `[agent] ${reason}`
10783
- })
10784
- });
10785
- const body = await r.json().catch(()=>({}));
10786
- return JSON.stringify({
10787
- ok: r.ok,
10788
- status: r.status,
10789
- reason,
10790
- hint: body.hint ?? "Restart request sent. The supervisor will relaunch the process; the user's UI will reconnect automatically."
10791
- });
10792
- } catch (e) {
10793
- return JSON.stringify({
10794
- ok: false,
10795
- code: "restart_failed",
10796
- error: e.message,
10797
- hint: "Could not reach /api/v1/system/restart from inside the same process. Tell the user they need to restart manually."
10798
- });
11100
+ return best?.v ?? null;
11101
+ }
11102
+ function getKnownModelLimits(provider, model_id) {
11103
+ if (!provider || !model_id) return null;
11104
+ switch(provider){
11105
+ case "anthropic":
11106
+ return matchPrefix(ANTHROPIC, model_id);
11107
+ case "gemini":
11108
+ return matchPrefix(GEMINI, model_id);
11109
+ case "openai":
11110
+ return matchPrefix(OPENAI, model_id);
11111
+ case "deepseek":
11112
+ return matchPrefix(DEEPSEEK, model_id);
11113
+ case "github-copilot":
11114
+ return resolveCopilot(model_id);
11115
+ default:
11116
+ return null;
11117
+ }
11118
+ }
11119
+ function getKnownContextLength(provider, model_id) {
11120
+ return getKnownModelLimits(provider, model_id)?.context_length ?? null;
11121
+ }
11122
+ function getKnownMaxOutputTokens(provider, model_id) {
11123
+ return getKnownModelLimits(provider, model_id)?.max_output_tokens ?? null;
11124
+ }
11125
+ // Flat catalog snapshot for one provider — used by introspection tools so
11126
+ // the agent can enumerate what's known statically. Returns [] for providers
11127
+ // without a static table (e.g. `langchain`, `mock`, externals).
11128
+ function listKnownModels(provider) {
11129
+ let table = null;
11130
+ switch(provider){
11131
+ case "anthropic":
11132
+ table = ANTHROPIC;
11133
+ break;
11134
+ case "gemini":
11135
+ table = GEMINI;
11136
+ break;
11137
+ case "openai":
11138
+ table = OPENAI;
11139
+ break;
11140
+ case "deepseek":
11141
+ table = DEEPSEEK;
11142
+ break;
11143
+ default:
11144
+ return [];
10799
11145
  }
10800
- }, {
10801
- name: "restart_server",
10802
- description: "Restart the Jarela server. Only call this when the user explicitly asks for a restart, OR when set_env_var returned requiresRestart=true and the user agreed. The UI reconnects automatically once the supervisor (launchd/systemd/Task Scheduler) relaunches.",
10803
- schema: restartSchema
10804
- });
10805
- (0,registry/* registerTools */.Fd)("Config", "execute", [
10806
- setEnvVar,
10807
- restartServer
10808
- ]);
10809
-
10810
- ;// ./lib/tools/builtins.ts
10811
- // Barrel of built-in tool modules. Each side-effect import triggers the
10812
- // module's `registerTools(...)` call (see ./registry.ts). Adding a new
10813
- // built-in tool: add the file under lib/tools/ and append a line here.
10814
- //
10815
- // Order matters only for deterministic logging / UI ordering — registry
10816
- // preserves insertion order.
10817
-
10818
-
10819
-
10820
-
10821
-
10822
-
10823
-
10824
-
10825
-
11146
+ return Object.entries(table).map(([model_id, l])=>({
11147
+ model_id,
11148
+ context_length: l.context_length,
11149
+ max_output_tokens: l.max_output_tokens ?? null
11150
+ }));
11151
+ }
10826
11152
 
10827
11153
 
11154
+ /***/ }),
10828
11155
 
11156
+ /***/ 49899:
11157
+ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
10829
11158
 
11159
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
11160
+ /* harmony export */ FR: () => (/* binding */ defaultToolStats),
11161
+ /* harmony export */ W7: () => (/* binding */ getToolStatsMap),
11162
+ /* harmony export */ _r: () => (/* binding */ listToolStats),
11163
+ /* harmony export */ hA: () => (/* binding */ recordToolUsage),
11164
+ /* harmony export */ jg: () => (/* binding */ toStats)
11165
+ /* harmony export */ });
11166
+ /* unused harmony export summarizeToolUsage */
11167
+ /* harmony import */ var _lib_db__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(46);
10830
11168
 
11169
+ const now = ()=>new Date().toISOString();
11170
+ const UPSERT_SQL = `
11171
+ INSERT INTO tool_stats
11172
+ (tool_name, call_count, success_count, error_count, used_count, last_called_at, updated_at)
11173
+ VALUES
11174
+ (?, ?, ?, ?, ?, ?, ?)
11175
+ ON CONFLICT(tool_name) DO UPDATE SET
11176
+ call_count = tool_stats.call_count + excluded.call_count,
11177
+ success_count = tool_stats.success_count + excluded.success_count,
11178
+ error_count = tool_stats.error_count + excluded.error_count,
11179
+ used_count = tool_stats.used_count + excluded.used_count,
11180
+ last_called_at = COALESCE(excluded.last_called_at, tool_stats.last_called_at),
11181
+ updated_at = excluded.updated_at
11182
+ `;
11183
+ function recordToolUsage(toolEvents, assistantContent) {
11184
+ const deltas = summarizeToolUsage(toolEvents, assistantContent);
11185
+ if (deltas.length === 0) return;
11186
+ const db = (0,_lib_db__WEBPACK_IMPORTED_MODULE_0__/* .getDb */ .Lf)();
11187
+ const stamp = now();
11188
+ const stmt = db.prepare(UPSERT_SQL);
11189
+ db.exec("BEGIN");
11190
+ try {
11191
+ for (const row of deltas){
11192
+ stmt.run(row.name, row.calls, row.successes, row.errors, row.used, row.calls > 0 ? stamp : null, stamp);
11193
+ }
11194
+ db.exec("COMMIT");
11195
+ } catch (err) {
11196
+ try {
11197
+ db.exec("ROLLBACK");
11198
+ } catch {}
11199
+ throw err;
11200
+ }
11201
+ }
11202
+ function listToolStats() {
11203
+ return (0,_lib_db__WEBPACK_IMPORTED_MODULE_0__/* .getDb */ .Lf)().prepare(`SELECT tool_name, call_count, success_count, error_count, used_count, last_called_at, updated_at
11204
+ FROM tool_stats`).all();
11205
+ }
11206
+ function getToolStatsMap(names) {
11207
+ const rows = names && names.length > 0 ? (0,_lib_db__WEBPACK_IMPORTED_MODULE_0__/* .getDb */ .Lf)().prepare(`SELECT tool_name, call_count, success_count, error_count, used_count, last_called_at, updated_at
11208
+ FROM tool_stats
11209
+ WHERE tool_name IN (${names.map(()=>"?").join(",")})`).all(...names) : listToolStats();
11210
+ const out = new Map();
11211
+ for (const row of rows)out.set(row.tool_name, toStats(row));
11212
+ return out;
11213
+ }
11214
+ function defaultToolStats() {
11215
+ return {
11216
+ call_count: 0,
11217
+ success_count: 0,
11218
+ error_count: 0,
11219
+ used_count: 0,
11220
+ success_rate: 1,
11221
+ usefulness_rate: 1,
11222
+ score: 1,
11223
+ never_used: true,
11224
+ last_called_at: null
11225
+ };
11226
+ }
11227
+ function toStats(row) {
11228
+ const calls = Math.max(0, row.call_count);
11229
+ if (calls === 0) return defaultToolStats();
11230
+ const successRate = clamp01(row.success_count / calls);
11231
+ const usefulnessRate = clamp01(row.used_count / calls);
11232
+ const score = clamp01(successRate * 0.65 + usefulnessRate * 0.35);
11233
+ return {
11234
+ call_count: calls,
11235
+ success_count: row.success_count,
11236
+ error_count: row.error_count,
11237
+ used_count: row.used_count,
11238
+ success_rate: successRate,
11239
+ usefulness_rate: usefulnessRate,
11240
+ score,
11241
+ never_used: false,
11242
+ last_called_at: row.last_called_at
11243
+ };
11244
+ }
11245
+ function summarizeToolUsage(toolEvents, assistantContent) {
11246
+ const byId = new Map();
11247
+ const assistantTerms = normalizeText(assistantContent);
11248
+ for (const ev of toolEvents){
11249
+ if (!ev.name) continue;
11250
+ const key = ev.id || `${ev.phase}:${ev.name}`;
11251
+ const existing = byId.get(key) ?? {
11252
+ name: ev.name,
11253
+ successful: false,
11254
+ used: false
11255
+ };
11256
+ if (ev.phase === "call") {
11257
+ existing.name = ev.name;
11258
+ } else if (ev.phase === "result") {
11259
+ const successful = !isErrorPayload(ev.payload);
11260
+ existing.successful = successful;
11261
+ existing.used = successful && payloadLooksUsed(ev.payload, assistantTerms);
11262
+ }
11263
+ byId.set(key, existing);
11264
+ }
11265
+ const totals = new Map();
11266
+ for (const ev of toolEvents){
11267
+ if (ev.phase !== "call" || !ev.name) continue;
11268
+ const key = ev.id || `${ev.phase}:${ev.name}`;
11269
+ const outcome = byId.get(key);
11270
+ const next = totals.get(ev.name) ?? {
11271
+ name: ev.name,
11272
+ calls: 0,
11273
+ successes: 0,
11274
+ errors: 0,
11275
+ used: 0
11276
+ };
11277
+ next.calls += 1;
11278
+ if (outcome?.successful) next.successes += 1;
11279
+ else next.errors += 1;
11280
+ if (outcome?.used) next.used += 1;
11281
+ totals.set(ev.name, next);
11282
+ }
11283
+ return [
11284
+ ...totals.values()
11285
+ ];
11286
+ }
11287
+ function payloadLooksUsed(payload, assistantTerms) {
11288
+ if (assistantTerms.size === 0) return false;
11289
+ const candidates = extractPayloadTerms(payload);
11290
+ if (candidates.length === 0) return false;
11291
+ let matched = 0;
11292
+ for (const term of candidates){
11293
+ if (assistantTerms.has(term)) matched += 1;
11294
+ if (matched >= 2) return true;
11295
+ }
11296
+ return false;
11297
+ }
11298
+ function extractPayloadTerms(payload) {
11299
+ const raw = stringifyPayload(payload);
11300
+ if (!raw) return [];
11301
+ const unique = new Set();
11302
+ for (const token of raw.toLowerCase().split(/[^a-z0-9_./:-]+/)){
11303
+ if (token.length < 4) continue;
11304
+ if (/^\d+$/.test(token) && token.length < 6) continue;
11305
+ unique.add(token);
11306
+ if (unique.size >= 24) break;
11307
+ }
11308
+ return [
11309
+ ...unique
11310
+ ];
11311
+ }
11312
+ function stringifyPayload(payload) {
11313
+ if (typeof payload === "string") return payload;
11314
+ try {
11315
+ return JSON.stringify(payload);
11316
+ } catch {
11317
+ return "";
11318
+ }
11319
+ }
11320
+ function normalizeText(text) {
11321
+ const out = new Set();
11322
+ for (const token of text.toLowerCase().split(/[^a-z0-9_./:-]+/)){
11323
+ if (token.length < 4) continue;
11324
+ out.add(token);
11325
+ }
11326
+ return out;
11327
+ }
11328
+ function isErrorPayload(payload) {
11329
+ if (typeof payload === "string") return /\berror\b|\bfailed\b|\bexception\b/i.test(payload);
11330
+ if (!payload || typeof payload !== "object") return false;
11331
+ if ("error" in payload || "errors" in payload) return true;
11332
+ const status = "status" in payload ? payload.status : undefined;
11333
+ if (typeof status === "string" && /error|failed/i.test(status)) return true;
11334
+ return false;
11335
+ }
11336
+ function clamp01(value) {
11337
+ return Math.max(0, Math.min(1, value));
11338
+ }
10831
11339
 
10832
11340
 
11341
+ /***/ }),
10833
11342
 
11343
+ /***/ 52907:
11344
+ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
10834
11345
 
11346
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
11347
+ /* harmony export */ LK: () => (/* binding */ writeBinaryFile),
11348
+ /* harmony export */ Uc: () => (/* binding */ fileAbsPath)
11349
+ /* harmony export */ });
11350
+ /* unused harmony exports FILES_DIR, isSafeFileName */
11351
+ /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(73024);
11352
+ /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(node_fs__WEBPACK_IMPORTED_MODULE_0__);
11353
+ /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(76760);
11354
+ /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(node_path__WEBPACK_IMPORTED_MODULE_1__);
11355
+ /* harmony import */ var _lib_db_data_dir__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(57477);
11356
+ // Local file store for binary artifacts produced by tools (generated images,
11357
+ // downloads, etc.). Files live under ~/.jarela/files/ and are served by
11358
+ // GET /api/v1/files/[name]. The tool returns a relative URL the chat
11359
+ // renderer can embed as <img src="/api/v1/files/...">.
10835
11360
 
10836
11361
 
10837
11362
 
11363
+ const FILES_DIR = (0,node_path__WEBPACK_IMPORTED_MODULE_1__.join)((0,_lib_db_data_dir__WEBPACK_IMPORTED_MODULE_2__/* .getDataDir */ .o)(), "files");
11364
+ (0,node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync)(FILES_DIR, {
11365
+ recursive: true
11366
+ });
11367
+ // Name must be a single path segment with no separators or "..". Callers
11368
+ // generate names from randomUUID() so this is mostly defense-in-depth.
11369
+ const SAFE_NAME = /^[A-Za-z0-9._-]+$/;
11370
+ function isSafeFileName(name) {
11371
+ return SAFE_NAME.test(name) && !name.includes("..");
11372
+ }
11373
+ function fileAbsPath(name) {
11374
+ if (!isSafeFileName(name)) return null;
11375
+ return (0,node_path__WEBPACK_IMPORTED_MODULE_1__.join)(FILES_DIR, name);
11376
+ }
11377
+ function writeBinaryFile(name, data) {
11378
+ if (!isSafeFileName(name)) throw new Error(`unsafe file name: ${name}`);
11379
+ const p = (0,node_path__WEBPACK_IMPORTED_MODULE_1__.join)(FILES_DIR, name);
11380
+ (0,node_fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync)(p, data);
11381
+ return p;
11382
+ }
10838
11383
 
10839
11384
 
11385
+ /***/ }),
10840
11386
 
11387
+ /***/ 56718:
11388
+ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
10841
11389
 
10842
- // EXTERNAL MODULE: ./lib/mcp/client.ts
10843
- var client = __webpack_require__(30572);
10844
- // EXTERNAL MODULE: ./lib/tools/external.ts
10845
- var external = __webpack_require__(18689);
10846
- // EXTERNAL MODULE: ./lib/stores/builtin-tools.ts
10847
- var builtin_tools = __webpack_require__(56718);
10848
- ;// ./lib/tools/index.ts
10849
- // Public tool surface for the agent runtime.
10850
- //
10851
- // Built-in tools register themselves at module load (see ./registry.ts and
10852
- // ./builtins.ts). External tools live under JARELA_TOOLS_DIR and are
10853
- // loaded per-call (hot-reload). MCP tools come from lib/mcp/client.ts.
11390
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
11391
+ /* harmony export */ HP: () => (/* binding */ disabledCategories),
11392
+ /* harmony export */ MD: () => (/* binding */ setCategoryEnabled)
11393
+ /* harmony export */ });
11394
+ /* unused harmony exports isCategoryEnabled, listCategoryStates */
11395
+ /* harmony import */ var _lib_db__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(46);
11396
+ // Built-in tool category toggles.
10854
11397
  //
10855
- // To add a new built-in tool:
10856
- // 1. Copy lib/tools/template.ts to lib/tools/<name>.ts and implement it.
10857
- // 2. Call `registerTools("<Category>", "<read|write|execute>", [yourTool, ...])` at the bottom.
10858
- // 3. Add `import "./<name>";` to lib/tools/builtins.ts.
11398
+ // A category is "enabled" unless an explicit row in `builtin_tool_categories`
11399
+ // says otherwise. Default-enabled semantics mean upgrading installs keep
11400
+ // every category working with zero migration work.
10859
11401
  //
10860
- // That's it no central array to update, no parallel category map.
10861
-
10862
- // Side-effect import: triggers registerTools() in every built-in module.
10863
-
10864
-
10865
-
10866
-
10867
-
10868
-
10869
-
11402
+ // Disabled categories are filtered at three layers:
11403
+ // - GET /api/v1/tools (so the agent editor never offers them as permissions)
11404
+ // - getAllTools / getAllToolsAsync (so the agent runtime can't see them)
11405
+ // - executeTool (defense in depth, blocks stale agent configs)
10870
11406
 
10871
- const ALL_BUILTINS = (0,registry/* registeredTools */.gN)();
10872
- const BUILTIN_TOOL_NAMES = (0,registry/* registeredNames */.nE)();
10873
- // Per-call recompute so files dropped in $JARELA_TOOLS_DIR are picked up
10874
- // without restart. loadExternalTools cache-busts require() per file.
10875
- function loadExternal() {
10876
- return (0,external/* loadExternalTools */.H)(BUILTIN_TOOL_NAMES);
10877
- }
10878
- // Resolve a tool's origin from its name. Used to label rows in the tools
10879
- // API and to route metadata lookups. Returns "mcp" for any name that is
10880
- // neither a registered built-in nor an external (JARELA_TOOLS_DIR) tool —
10881
- // matches today's behavior where MCP tools are everything else.
10882
- function getToolSource(name) {
10883
- if (BUILTIN_TOOL_NAMES.has(name)) return "builtin";
10884
- if (loadExternal().tools.some((t)=>t.name === name)) return "external";
10885
- return "mcp";
10886
- }
10887
- // Look up a tool's safety class. Built-in tools have a declared capability;
10888
- // external (JARELA_TOOLS_DIR) and MCP tools default to "execute" — the
10889
- // conservative choice until manifest-level overrides land (ADR-0038).
10890
- // Source is derived internally so callers can't mis-tag external tools as
10891
- // MCP (or vice versa).
10892
- function getToolCapability(name) {
10893
- return (0,registry/* registeredCapability */.RL)(name) ?? "execute";
10894
- }
10895
- function getToolCategory(name) {
10896
- const builtin = (0,registry/* registeredCategory */.RY)(name);
10897
- if (builtin) return builtin;
10898
- const ext = loadExternal().categories.get(name);
10899
- if (ext) return ext;
10900
- return getToolSource(name) === "mcp" ? "MCP" : "Config";
10901
- }
10902
- function getToolGroup(name) {
10903
- const cat = getToolCategory(name);
10904
- if (cat === "MCP") return null;
10905
- return (0,registry/* registeredGroup */.Oi)(name) ?? null;
10906
- }
10907
- function applyPolicy(tools, policy) {
10908
- const allowSet = policy?.allow?.length ? new Set(policy.allow) : null;
10909
- const denySet = policy?.deny?.length ? new Set(policy.deny) : null;
10910
- return tools.filter((t)=>{
10911
- if (allowSet && !allowSet.has(t.name)) return false;
10912
- if (denySet && denySet.has(t.name)) return false;
10913
- return true;
10914
- });
10915
- }
10916
- // Filter built-in tools whose category is disabled in the toggle store.
10917
- // External + MCP tools are untouched (they have their own enable surfaces).
10918
- function applyCategoryToggles(tools) {
10919
- const disabled = (0,builtin_tools/* disabledCategories */.HP)();
10920
- if (disabled.size === 0) return tools;
10921
- return tools.filter((t)=>{
10922
- const cat = (0,registry/* registeredCategory */.RY)(t.name);
10923
- if (!cat) return true; // not a built-in (or unregistered) → leave it
10924
- return !disabled.has(cat);
10925
- });
10926
- }
10927
- // Synchronous: built-in + external tools (no MCP). Used by GET /api/v1/tools
10928
- // and any code path that can't await.
10929
- function getAllTools(policy) {
10930
- return applyPolicy([
10931
- ...applyCategoryToggles(ALL_BUILTINS),
10932
- ...loadExternal().tools
10933
- ], policy);
10934
- }
10935
- // Async: built-in + external + MCP tools.
10936
- // Use this anywhere the agent might invoke tools (createReactAgent input).
10937
- // External tools are loaded per-call (hot-reload). MCP tools are cached by
10938
- // lib/mcp/client.ts and only re-resolved when the mcp_servers table changes.
10939
- async function getAllToolsAsync(policy) {
10940
- let mcpTools = [];
10941
- try {
10942
- mcpTools = await (0,client/* getMcpTools */.a)();
10943
- } catch (err) {
10944
- console.error("[tools] MCP load failed, continuing with built-ins only:", err);
10945
- }
10946
- return applyPolicy([
10947
- ...applyCategoryToggles(ALL_BUILTINS),
10948
- ...loadExternal().tools,
10949
- ...mcpTools
10950
- ], policy);
11407
+ const now = ()=>new Date().toISOString();
11408
+ function isCategoryEnabled(category) {
11409
+ const row = getDb().prepare("SELECT enabled FROM builtin_tool_categories WHERE category=?").get(category);
11410
+ return row ? row.enabled === 1 : true;
10951
11411
  }
10952
- function toOpenAITools(tools) {
10953
- return tools.map((t)=>{
10954
- const oai = convertToOpenAITool(t);
10955
- return {
10956
- type: "function",
10957
- function: {
10958
- name: oai.function.name,
10959
- description: oai.function.description ?? "",
10960
- parameters: oai.function.parameters
10961
- }
10962
- };
10963
- });
11412
+ function disabledCategories() {
11413
+ const rows = (0,_lib_db__WEBPACK_IMPORTED_MODULE_0__/* .getDb */ .Lf)().prepare("SELECT category FROM builtin_tool_categories WHERE enabled=0").all();
11414
+ return new Set(rows.map((r)=>r.category));
10964
11415
  }
10965
- async function executeTool(name, args, context = {}) {
10966
- let t = ALL_BUILTINS.find((x)=>x.name === name);
10967
- if (t) {
10968
- const cat = registeredCategory(name);
10969
- if (cat && disabledCategories().has(cat)) {
10970
- throw new Error(`Tool "${name}" is disabled (category ${cat} is turned off)`);
10971
- }
10972
- }
10973
- if (!t) {
10974
- t = loadExternal().tools.find((x)=>x.name === name);
10975
- }
10976
- if (!t) throw new Error(`Unknown tool: ${name}`);
10977
- const config = context.thread_id ? {
10978
- configurable: {
10979
- thread_id: context.thread_id
10980
- }
10981
- } : {};
10982
- const result = await t.invoke(args, config);
10983
- // Tools return JSON strings per LangChain convention; parse back for downstream use.
10984
- if (typeof result === "string") {
10985
- try {
10986
- return JSON.parse(result);
10987
- } catch {
10988
- return result;
10989
- }
10990
- }
10991
- return result;
11416
+ function listCategoryStates() {
11417
+ const rows = getDb().prepare("SELECT category, enabled, updated_at FROM builtin_tool_categories").all();
11418
+ return rows.map((r)=>({
11419
+ category: r.category,
11420
+ enabled: r.enabled === 1,
11421
+ updated_at: r.updated_at
11422
+ }));
10992
11423
  }
10993
- // One-shot startup loader. Call from instrumentation.ts so external tools
10994
- // load + log their status at boot rather than lazily on first agent turn.
10995
- let _initialized = false;
10996
- function initTools() {
10997
- const toolsDir = getToolsDir();
10998
- const result = loadExternal();
10999
- const summary = {
11000
- builtinCount: ALL_BUILTINS.length,
11001
- externalCount: result.tools.length,
11002
- errors: result.errors,
11003
- toolsDir
11004
- };
11005
- if (!_initialized) {
11006
- console.info(`[tools] ${summary.builtinCount} built-in tool(s) registered; ` + `${summary.externalCount} external tool(s) loaded from ${toolsDir}`);
11007
- for (const err of summary.errors){
11008
- console.error(`[tools] external ${err.file}: ${err.error}`);
11009
- }
11010
- _initialized = true;
11011
- }
11012
- return summary;
11424
+ function setCategoryEnabled(category, enabled) {
11425
+ (0,_lib_db__WEBPACK_IMPORTED_MODULE_0__/* .getDb */ .Lf)().prepare(`INSERT INTO builtin_tool_categories (category, enabled, updated_at)
11426
+ VALUES (?, ?, ?)
11427
+ ON CONFLICT(category) DO UPDATE SET enabled=excluded.enabled, updated_at=excluded.updated_at`).run(category, enabled ? 1 : 0, now());
11013
11428
  }
11014
11429
 
11015
11430