@ixo/oracle-runtime-workers 0.12.0 → 0.15.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 (205) hide show
  1. package/README.md +32 -10
  2. package/package.json +14 -9
  3. package/src/artifacts/config.ts +158 -0
  4. package/src/artifacts/crypto.ts +94 -0
  5. package/src/artifacts/routes.ts +73 -0
  6. package/src/artifacts/store.ts +238 -0
  7. package/src/artifacts/sweep.ts +200 -0
  8. package/src/artifacts/tool.ts +103 -0
  9. package/src/artifacts/viewer-page.ts +614 -0
  10. package/src/attachments/download.ts +168 -45
  11. package/src/attachments/index.ts +9 -0
  12. package/src/attachments/magic.ts +33 -0
  13. package/src/attachments/pipeline.ts +105 -49
  14. package/src/attachments/sandbox-archive.ts +9 -8
  15. package/src/attachments/view-cache.ts +142 -0
  16. package/src/attachments/view.ts +53 -12
  17. package/src/channels/auth.ts +267 -0
  18. package/src/channels/contract.ts +137 -0
  19. package/src/channels/turns.ts +270 -0
  20. package/src/core/budgeted-llm.ts +223 -0
  21. package/src/core/capability-router.ts +273 -0
  22. package/src/core/context-budget.ts +97 -10
  23. package/src/core/env.ts +111 -0
  24. package/src/core/execution-profile.ts +17 -0
  25. package/src/core/index.ts +267 -9
  26. package/src/core/llm.ts +30 -9
  27. package/src/core/main-agent-types.ts +41 -1
  28. package/src/core/main-agent.ts +367 -65
  29. package/src/core/manifest.ts +34 -0
  30. package/src/core/meta-tools.ts +128 -18
  31. package/src/core/middlewares/capability-gate.ts +130 -12
  32. package/src/core/middlewares/context-guard.ts +55 -24
  33. package/src/core/middlewares/index.ts +20 -2
  34. package/src/core/middlewares/page-context.ts +46 -2
  35. package/src/core/middlewares/return-direct-first.ts +47 -0
  36. package/src/core/middlewares/summarization.ts +152 -10
  37. package/src/core/middlewares/tool-execution.ts +323 -0
  38. package/src/core/middlewares/tool-repetition-guard.ts +202 -35
  39. package/src/core/middlewares/turn-boundary.ts +162 -0
  40. package/src/core/openrouter-pricing.ts +155 -22
  41. package/src/core/prompt-composer.ts +112 -7
  42. package/src/core/registries.ts +253 -48
  43. package/src/core/request-admission.ts +159 -0
  44. package/src/core/runtime-context.ts +107 -8
  45. package/src/core/state.ts +1 -1
  46. package/src/core/sub-agent-fallback.ts +21 -2
  47. package/src/core/subagent-as-tool.ts +78 -43
  48. package/src/core/tool-access.ts +189 -0
  49. package/src/core/tool-scheduler.ts +125 -0
  50. package/src/core/turn-budget.ts +205 -0
  51. package/src/core/turn-time-note.ts +43 -0
  52. package/src/core/user-kv.ts +446 -0
  53. package/src/core/wrap-plugin-tool.ts +20 -0
  54. package/src/delivery/plan.ts +278 -0
  55. package/src/delivery/profile.ts +161 -0
  56. package/src/delivery/prompt.ts +27 -0
  57. package/src/delivery/schema.ts +61 -0
  58. package/src/delivery/shaper.ts +389 -0
  59. package/src/delivery/types.ts +84 -0
  60. package/src/do/ambient.ts +51 -15
  61. package/src/do/contracts.ts +198 -6
  62. package/src/do/idle-eviction.ts +38 -0
  63. package/src/do/matrix-turn-ledger.ts +24 -5
  64. package/src/do/result-store-test-do.ts +137 -3
  65. package/src/do/result-store.ts +239 -58
  66. package/src/do/room-mirror.ts +29 -0
  67. package/src/do/run-buffer.ts +64 -9
  68. package/src/do/run-coordinator.ts +451 -76
  69. package/src/do/run-request.ts +2 -0
  70. package/src/do/run-store-test-do.ts +301 -1
  71. package/src/do/run-store.ts +385 -43
  72. package/src/do/secrets-adapter.ts +22 -3
  73. package/src/do/sse-stream.ts +60 -9
  74. package/src/do/transcript.ts +48 -5
  75. package/src/do/turn-body.ts +105 -5
  76. package/src/do/ucan-service.ts +204 -34
  77. package/src/do/user-oracle-do.ts +1883 -264
  78. package/src/feedback/config.ts +127 -0
  79. package/src/feedback/contract.ts +148 -0
  80. package/src/feedback/linear-sink.ts +285 -0
  81. package/src/feedback/privacy.ts +150 -0
  82. package/src/feedback/reservation.ts +298 -0
  83. package/src/feedback/submit.ts +247 -0
  84. package/src/index.ts +138 -6
  85. package/src/llm/byo-adapter.ts +27 -6
  86. package/src/llm/byo-catalog.ts +13 -0
  87. package/src/llm/byo-model-fallback.ts +248 -0
  88. package/src/llm/byo-service.ts +45 -8
  89. package/src/llm/provider-error.ts +139 -2
  90. package/src/matrix/gateway-do.ts +523 -111
  91. package/src/matrix/group-chat.ts +66 -23
  92. package/src/matrix/inbox-store.ts +16 -1
  93. package/src/matrix/ingest.ts +79 -14
  94. package/src/matrix/media-deadline.ts +130 -0
  95. package/src/matrix/replay-format.ts +5 -3
  96. package/src/matrix/reply-parts.ts +92 -0
  97. package/src/matrix/room-alias.ts +96 -0
  98. package/src/matrix/user-homeserver.ts +166 -7
  99. package/src/owner-store/ixo-vfs-store.ts +170 -43
  100. package/src/owner-store/migrating-store.ts +31 -15
  101. package/src/owner-store/types.ts +18 -3
  102. package/src/plugin-api/index.ts +43 -2
  103. package/src/plugin-api/oracle-plugin.ts +35 -0
  104. package/src/plugin-api/request-admission.ts +41 -0
  105. package/src/plugin-api/tool-helper.ts +25 -1
  106. package/src/plugin-api/tool-plane.ts +92 -0
  107. package/src/plugin-api/types.ts +194 -6
  108. package/src/plugins/agui/agui.plugin.ts +31 -16
  109. package/src/plugins/composio/composio-tools.ts +57 -6
  110. package/src/plugins/composio/composio-ucan.ts +3 -2
  111. package/src/plugins/composio/composio.plugin.ts +2 -0
  112. package/src/plugins/delegated-capabilities.ts +25 -0
  113. package/src/plugins/editor/content-session.ts +173 -83
  114. package/src/plugins/editor/content-tools.ts +76 -17
  115. package/src/plugins/editor/editor-mx.ts +28 -9
  116. package/src/plugins/editor/editor.plugin.ts +3 -1
  117. package/src/plugins/editor/failures.ts +28 -7
  118. package/src/plugins/editor/prompts.ts +2 -1
  119. package/src/plugins/editor/provider.ts +553 -40
  120. package/src/plugins/editor/standalone-editor-tool.ts +61 -18
  121. package/src/plugins/firecrawl/firecrawl-tools.ts +13 -5
  122. package/src/plugins/flows/action-metadata.ts +111 -11
  123. package/src/plugins/flows/actions.ts +18 -0
  124. package/src/plugins/flows/decision-context.ts +31 -0
  125. package/src/plugins/flows/edit.ts +223 -52
  126. package/src/plugins/flows/errors.ts +22 -1
  127. package/src/plugins/flows/flow-doc.ts +92 -9
  128. package/src/plugins/flows/flows.plugin.ts +11 -5
  129. package/src/plugins/flows/forms.ts +34 -0
  130. package/src/plugins/flows/index.ts +2 -0
  131. package/src/plugins/flows/input-policy.ts +140 -0
  132. package/src/plugins/flows/prompts.ts +14 -11
  133. package/src/plugins/flows/read.ts +49 -9
  134. package/src/plugins/flows/ref-syntax.ts +129 -0
  135. package/src/plugins/flows/test-homeserver.ts +245 -0
  136. package/src/plugins/flows/tools/authoring.ts +216 -112
  137. package/src/plugins/flows/tools/discovery.ts +4 -1
  138. package/src/plugins/flows/tools/forms.ts +21 -2
  139. package/src/plugins/flows/tools/settings.ts +34 -13
  140. package/src/plugins/flows/translator.ts +13 -10
  141. package/src/plugins/flows/types.ts +167 -42
  142. package/src/plugins/index.ts +11 -1
  143. package/src/plugins/ixo-transaction/chain-ids.ts +43 -0
  144. package/src/plugins/ixo-transaction/index.ts +2 -0
  145. package/src/plugins/ixo-transaction/ixo-transaction.plugin.ts +119 -0
  146. package/src/plugins/ixo-transaction/receipts.ts +99 -0
  147. package/src/plugins/ixo-transaction/tools.ts +329 -0
  148. package/src/plugins/ixo-transaction/wallet-signing.ts +202 -0
  149. package/src/plugins/mcp-tool-adapter.ts +23 -2
  150. package/src/plugins/memory/memory-tools.ts +7 -2
  151. package/src/plugins/memory/memory-ucan.ts +3 -2
  152. package/src/plugins/memory/memory.plugin.ts +2 -0
  153. package/src/plugins/pod-creator/blueprint-store.ts +163 -0
  154. package/src/plugins/pod-creator/blueprint-types.ts +61 -0
  155. package/src/plugins/pod-creator/bounded-map.ts +87 -0
  156. package/src/plugins/pod-creator/capsule-content-client.ts +285 -0
  157. package/src/plugins/pod-creator/chain-gateway.ts +110 -0
  158. package/src/plugins/pod-creator/config.ts +85 -0
  159. package/src/plugins/pod-creator/create-session-store.ts +180 -0
  160. package/src/plugins/pod-creator/create-tools.ts +538 -0
  161. package/src/plugins/pod-creator/design-pod-roles.ts +111 -0
  162. package/src/plugins/pod-creator/index.ts +65 -0
  163. package/src/plugins/pod-creator/orchestration-tools.ts +172 -0
  164. package/src/plugins/pod-creator/pod-creator.plugin.ts +190 -0
  165. package/src/plugins/pod-creator/stage.ts +201 -0
  166. package/src/plugins/pod-creator/sub-agents.ts +189 -0
  167. package/src/plugins/portal/action-log.ts +21 -1
  168. package/src/plugins/portal/declared-tools.ts +90 -0
  169. package/src/plugins/portal/portal.plugin.ts +42 -16
  170. package/src/plugins/sandbox/sandbox-bridge.ts +79 -10
  171. package/src/plugins/sandbox/sandbox-mcp.ts +3 -2
  172. package/src/plugins/sandbox/sandbox-write-blob.ts +11 -1
  173. package/src/plugins/sandbox/sandbox.plugin.ts +8 -16
  174. package/src/plugins/tasks/manifest.ts +1 -1
  175. package/src/plugins/tasks/middleware.ts +20 -7
  176. package/src/plugins/tasks/tasks-tools.ts +30 -3
  177. package/src/plugins/tasks/tasks.plugin.ts +64 -11
  178. package/src/plugins/user-preferences/schema.ts +12 -1
  179. package/src/plugins/user-preferences/user-preferences-store.ts +38 -13
  180. package/src/plugins/user-preferences/user-preferences-tool.ts +3 -2
  181. package/src/plugins/vfs/vfs-client.ts +250 -71
  182. package/src/plugins/vfs/vfs-content.ts +30 -11
  183. package/src/plugins/vfs/vfs-errors.ts +22 -0
  184. package/src/plugins/vfs/vfs-sandbox-tools.ts +12 -5
  185. package/src/plugins/vfs/vfs-tools.ts +60 -20
  186. package/src/realtime/frontend-call-registry.ts +228 -81
  187. package/src/realtime/realtime-endpoint.ts +215 -44
  188. package/src/realtime/session-socket-hub.ts +47 -2
  189. package/src/secrets/secrets-service.ts +9 -3
  190. package/src/shell/app.ts +529 -82
  191. package/src/shell/auth.ts +131 -37
  192. package/src/shell/turn-body-cap.ts +11 -13
  193. package/src/sqlite/blob-compactor.ts +61 -28
  194. package/src/sqlite/database.ts +156 -90
  195. package/src/sqlite/do-vfs.ts +416 -129
  196. package/src/sqlite/page-tier.ts +133 -40
  197. package/src/sqlite/serialization.ts +40 -0
  198. package/src/sqlite/sessions-store.ts +10 -2
  199. package/src/sqlite/sqlite-saver.ts +139 -19
  200. package/src/sqlite/user-kv-store.ts +317 -0
  201. package/src/tasks/schedule.ts +201 -12
  202. package/src/tasks/scheduler.ts +586 -98
  203. package/src/tasks/spec.ts +67 -3
  204. package/src/tasks/store.ts +292 -27
  205. package/src/tasks/topic-deliverables.ts +70 -0
package/src/core/index.ts CHANGED
@@ -4,6 +4,18 @@
4
4
  * Durable Objects, Matrix, or Hono; the host (`src/do`, `src/shell`) supplies
5
5
  * those through the `AmbientServices` adapters and mounts `pluginRoutes`.
6
6
  */
7
+ import {
8
+ DecisionProviderRegistry,
9
+ DecisionProviderRouter,
10
+ DecisionRuntime,
11
+ HOST_DECISION_PROVIDER_ID,
12
+ resolveDecisionAdapter,
13
+ type DecisionAdapter,
14
+ type DecisionEvaluator,
15
+ type DecisionProviderPolicy,
16
+ type DecisionProviderRegistration,
17
+ type WorkersAiBinding,
18
+ } from '@ixo/common/ai/decisions';
7
19
  import { z } from 'zod';
8
20
  import type { OraclePlugin, PluginRoute } from '../plugin-api/oracle-plugin';
9
21
  import type {
@@ -14,8 +26,17 @@ import type {
14
26
  PluginContext,
15
27
  } from '../plugin-api/types';
16
28
  import { baseEnvSchema, composeEnvSchema, validateEnv } from './env';
17
- import { createLlmAdapter, type OpenRouterLlmAdapter } from './llm';
18
- import { validateManifest, type PluginManifestOverride } from './manifest';
29
+ import {
30
+ createLlmAdapter,
31
+ openRouterAttributionHeaders,
32
+ type OpenRouterLlmAdapter,
33
+ } from './llm';
34
+ import {
35
+ estimateTokensApprox,
36
+ OPERATING_GUIDE_WARN_TOKENS,
37
+ validateManifest,
38
+ type PluginManifestOverride,
39
+ } from './manifest';
19
40
  import {
20
41
  resolvePlugins,
21
42
  type ExcludedPlugin,
@@ -23,6 +44,10 @@ import {
23
44
  type SoftDepGap,
24
45
  } from './plugin-loader';
25
46
  import { createRegistries, type Registries } from './registries';
47
+ import {
48
+ type DeliveryConfig,
49
+ deliveryConfigWithEnv,
50
+ } from '../delivery/profile';
26
51
  import { buildPluginContext } from './runtime-context';
27
52
  import { NOOP_LOGGER } from './utils';
28
53
 
@@ -50,6 +75,7 @@ export type {
50
75
 
51
76
  export {
52
77
  ConfigSchemaRegistry,
78
+ DecisionRegistry,
53
79
  ManifestRegistry,
54
80
  MiddlewareRegistry,
55
81
  SharedStateRegistry,
@@ -61,6 +87,7 @@ export {
61
87
  export type {
62
88
  ManifestCrossCheckResult,
63
89
  RegisteredConfigSchema,
90
+ RegisteredDecision,
64
91
  RegisteredManifest,
65
92
  RegisteredMiddleware,
66
93
  RegisteredSharedAccessor,
@@ -70,8 +97,67 @@ export type {
70
97
  ToolSummary,
71
98
  } from './registries';
72
99
 
100
+ export {
101
+ AmbiguousDecisionProviderError,
102
+ CloudflareJevDecisionAdapter,
103
+ DECISION_PROVIDERS,
104
+ DecisionNotApplicableError,
105
+ DecisionProviderNotFoundError,
106
+ DecisionProviderRegistry,
107
+ DecisionProviderRouter,
108
+ DecisionProviderUnavailableError,
109
+ DecisionRuntime,
110
+ HOST_DECISION_PROVIDER_ID,
111
+ JevDecisionError,
112
+ OpenRouterJevDecisionAdapter,
113
+ StaleDecisionSubjectError,
114
+ UNAVAILABLE_DECISION_EVALUATOR,
115
+ WorkersAiJevDecisionAdapter,
116
+ assertFinalDecisionSubjectUnchanged,
117
+ canonicalizeFinalDecisionSubject,
118
+ createDecisionAuthorityReceipt,
119
+ createDecisionExecutionReceipt,
120
+ decisionProviderEnvShape,
121
+ digestFinalDecisionSubject,
122
+ measureDecisionQuestionIsolation,
123
+ resolveDecisionAdapter,
124
+ } from '@ixo/common/ai/decisions';
125
+ export type {
126
+ CloudflareJevAdapterOptions,
127
+ DecisionAdapter,
128
+ DecisionApplicability,
129
+ DecisionAuthorityReceipt,
130
+ DecisionCalibrationProvenance,
131
+ DecisionEvaluator,
132
+ DecisionExecution,
133
+ DecisionExecutionReceipt,
134
+ DecisionJudgmentMethod,
135
+ DecisionJudgmentProvenance,
136
+ DecisionLookup,
137
+ DecisionProviderConfigIssue,
138
+ DecisionProviderName,
139
+ DecisionProviderPolicy,
140
+ DecisionProviderProvenance,
141
+ DecisionProviderRegistration,
142
+ DecisionProviderResolution,
143
+ DecisionProviderSelection,
144
+ DecisionQuestionIsolationObservation,
145
+ DecisionQuestionIsolationReport,
146
+ DecisionRuntimeLogger,
147
+ FinalDecisionSubject,
148
+ FinalDecisionSubjectBinding,
149
+ FinalDecisionSubjectValue,
150
+ JevProviderName,
151
+ OpenRouterJevAdapterOptions,
152
+ ResolveDecisionAdapterOptions,
153
+ ResolveDecisionAdapterResult,
154
+ WorkersAiBinding,
155
+ WorkersAiJevAdapterOptions,
156
+ } from '@ixo/common/ai/decisions';
157
+
73
158
  export {
74
159
  estimateTokensApprox,
160
+ OPERATING_GUIDE_WARN_TOKENS,
75
161
  manifestCategorySchema,
76
162
  manifestExampleSchema,
77
163
  manifestStabilitySchema,
@@ -114,6 +200,7 @@ export {
114
200
  isAllowedModel,
115
201
  listModelCatalog,
116
202
  listModels,
203
+ openRouterAttributionHeaders,
117
204
  } from './llm';
118
205
  export {
119
206
  OPENROUTER_MODELS_URL,
@@ -146,9 +233,16 @@ export type {
146
233
  export {
147
234
  buildOracleSection,
148
235
  composePrompt,
236
+ formatDateContext,
149
237
  formatTimeContext,
150
238
  formatUserPreferences,
239
+ renderTurnTimeNote,
151
240
  } from './prompt-composer';
241
+ export {
242
+ stripTurnTimeNote,
243
+ TURN_TIME_NOTE_KWARG,
244
+ withTurnTimeNote,
245
+ } from './turn-time-note';
152
246
  export type {
153
247
  ComposePromptInput,
154
248
  MemoryCommunity,
@@ -163,6 +257,8 @@ export type { CompiledTemplate, TemplateValues } from './template';
163
257
 
164
258
  export * from './middlewares';
165
259
 
260
+ export { createMemoryUserKv } from './user-kv';
261
+
166
262
  export {
167
263
  EMPTY_SHARED,
168
264
  EVENT_NAMES,
@@ -253,6 +349,26 @@ export interface RuntimeCoreOptions {
253
349
  * namespaces, …) are stripped from `validatedEnv`.
254
350
  */
255
351
  env: Record<string, unknown>;
352
+ /**
353
+ * Single host-supplied Decision adapter, registered as provider `host` and
354
+ * the default. Wins over the `DECISION_PROVIDER` env configuration, which
355
+ * is then neither checked nor registered. Mutually exclusive with
356
+ * `decisionProviders`.
357
+ */
358
+ decisionAdapter?: DecisionAdapter;
359
+ /**
360
+ * Host-configured Decision providers, registered beside the env-selected
361
+ * provider (`DECISION_PROVIDER`, registered under that value as its id).
362
+ * Ids must be unique across both.
363
+ */
364
+ decisionProviders?: readonly DecisionProviderRegistration[];
365
+ /**
366
+ * Default and per-Decision routes over the registered providers. Without a
367
+ * `defaultProviderId` the env-selected provider is the default. With
368
+ * several providers and neither a route nor a default for a Decision, its
369
+ * evaluation is refused with `AmbiguousDecisionProviderError`.
370
+ */
371
+ decisionProviderPolicy?: DecisionProviderPolicy;
256
372
  /** Boot + runtime logger. Defaults to a silent logger. */
257
373
  logger?: Logger;
258
374
  }
@@ -263,6 +379,8 @@ export interface RuntimeCoreOptions {
263
379
  */
264
380
  export interface RuntimeCore {
265
381
  identity: OracleIdentity;
382
+ /** `OracleConfig.delivery`: how chat surfaces receive replies. */
383
+ delivery: DeliveryConfig;
266
384
  registries: Registries;
267
385
  /** Names of the plugins that loaded, in dependency order. */
268
386
  availablePlugins: ReadonlySet<string>;
@@ -278,13 +396,20 @@ export interface RuntimeCore {
278
396
  authExcludedRoutes: AuthExcludedRoute[];
279
397
  /** OpenRouter-backed LLM adapter (`get(role, params?)`). */
280
398
  llm: OpenRouterLlmAdapter;
399
+ /**
400
+ * Bounded semantic Decision evaluator over the decision registry and the
401
+ * configured adapter — what `ctx.decisions` forwards to. Without a provider
402
+ * every call rejects with `DecisionProviderUnavailableError`.
403
+ */
404
+ decisions: DecisionEvaluator;
281
405
  logger: Logger;
282
406
  /** A boot-time `PluginContext` scoped to `pluginName` (default `__runtime__`). */
283
407
  buildCtx(pluginName?: string): PluginContext;
284
408
  /**
285
409
  * Warm the registries' boot caches (`getTools` / `getSubAgents` /
286
- * `getMiddlewares`) and run the cross-plugin checks that need them: tool,
287
- * sub-agent and shared-state name collisions (fatal), and every manifest's
410
+ * `getMiddlewares` / `getDecisions`) and run the cross-plugin checks that
411
+ * need them: tool, sub-agent, shared-state and decision name collisions
412
+ * (fatal), and every manifest's
288
413
  * `examples[].tool` references (advisory — logged). Memoised — safe to
289
414
  * await on every turn; the first agent build would otherwise perform the
290
415
  * collection lazily without the checks. Rejects with the boot error.
@@ -297,6 +422,19 @@ function reportBootError(logger: Logger, message: string, hint?: string): void {
297
422
  logger.error(`[boot-error] ${body}`);
298
423
  }
299
424
 
425
+ /**
426
+ * The Workers AI binding (`env.AI`) is an object exposing `run`. Only its
427
+ * shape can be checked: bindings carry no brand the runtime could test for.
428
+ */
429
+ function isWorkersAiBinding(value: unknown): value is WorkersAiBinding {
430
+ return (
431
+ typeof value === 'object' &&
432
+ value !== null &&
433
+ 'run' in value &&
434
+ typeof value.run === 'function'
435
+ );
436
+ }
437
+
300
438
  function validateConfig(config: OracleConfig): void {
301
439
  if (!config || typeof config !== 'object') {
302
440
  throw new Error('createRuntimeCore: `config` is required.');
@@ -354,6 +492,24 @@ export function createRuntimeCore(opts: RuntimeCoreOptions): RuntimeCore {
354
492
  for (const warning of result.warnings) logger.warn(`[boot] ${warning}`);
355
493
  if (!result.valid) manifestErrors.push(...result.errors);
356
494
  }
495
+ const guides = registries.manifests.operatingGuides();
496
+ if (guides.length > 0) {
497
+ logger.log(
498
+ `[boot] operating guides: ${guides
499
+ .map(
500
+ ({ pluginName, guide }) =>
501
+ `${pluginName}=${guide.length} chars (~${estimateTokensApprox(guide)} tokens)`,
502
+ )
503
+ .join(', ')}`,
504
+ );
505
+ for (const { pluginName, guide } of guides) {
506
+ const tokens = estimateTokensApprox(guide);
507
+ if (tokens > OPERATING_GUIDE_WARN_TOKENS)
508
+ logger.warn(
509
+ `[boot] operating guide of '${pluginName}' is ~${tokens} tokens (over ${OPERATING_GUIDE_WARN_TOKENS}); it is in the system prompt of every turn that has the plugin loaded`,
510
+ );
511
+ }
512
+ }
357
513
  if (manifestErrors.length > 0) {
358
514
  for (const err of manifestErrors) reportBootError(logger, err);
359
515
  throw new Error(
@@ -386,7 +542,81 @@ export function createRuntimeCore(opts: RuntimeCoreOptions): RuntimeCore {
386
542
  );
387
543
  }
388
544
 
389
- // 4. Identity.
545
+ // 4. Decision providers — resolved here so a selected provider with missing
546
+ // credentials, or a policy naming an unknown provider, fails at boot rather
547
+ // than on the first evaluation. A single host adapter wins over env and
548
+ // skips the env check. The Workers AI binding is not a string, so Zod
549
+ // strips it from `validated.config`; it is read from the raw env.
550
+ if (opts.decisionAdapter && opts.decisionProviders !== undefined) {
551
+ const message =
552
+ 'createRuntimeCore: decisionAdapter and decisionProviders are mutually exclusive.';
553
+ reportBootError(logger, message);
554
+ throw new Error(message);
555
+ }
556
+ const decisionRegistrations: DecisionProviderRegistration[] = [];
557
+ let defaultDecisionProviderId: string | undefined;
558
+ if (opts.decisionAdapter) {
559
+ decisionRegistrations.push({
560
+ id: HOST_DECISION_PROVIDER_ID,
561
+ adapter: opts.decisionAdapter,
562
+ });
563
+ defaultDecisionProviderId = HOST_DECISION_PROVIDER_ID;
564
+ } else {
565
+ const workersAi = opts.env.AI;
566
+ const resolvedAdapter = resolveDecisionAdapter(validated.config, {
567
+ ...(isWorkersAiBinding(workersAi) ? { workersAi } : {}),
568
+ openRouterHeaders: openRouterAttributionHeaders(
569
+ String(validated.config.ORACLE_NAME),
570
+ ),
571
+ });
572
+ if (!resolvedAdapter.ok) {
573
+ const lines: string[] = [];
574
+ for (const issue of resolvedAdapter.issues) {
575
+ const message = `Decision provider env validation failed for '${issue.field}': ${issue.message}`;
576
+ reportBootError(
577
+ logger,
578
+ message,
579
+ `Set '${issue.field}' on the Worker (wrangler secret / vars).`,
580
+ );
581
+ lines.push(message);
582
+ }
583
+ throw new Error(
584
+ `Env validation failed (${lines.length} issues):\n - ${lines.join('\n - ')}`,
585
+ );
586
+ }
587
+ if (resolvedAdapter.adapter) {
588
+ // `resolveDecisionAdapter` only builds an adapter for a set provider.
589
+ const envProviderId = String(validated.config.DECISION_PROVIDER);
590
+ decisionRegistrations.push({
591
+ id: envProviderId,
592
+ adapter: resolvedAdapter.adapter,
593
+ });
594
+ defaultDecisionProviderId = envProviderId;
595
+ }
596
+ decisionRegistrations.push(...(opts.decisionProviders ?? []));
597
+ }
598
+ let decisionProviders: DecisionProviderRouter;
599
+ try {
600
+ const policy = opts.decisionProviderPolicy;
601
+ const defaultProviderId =
602
+ policy?.defaultProviderId ?? defaultDecisionProviderId;
603
+ decisionProviders = new DecisionProviderRouter(
604
+ new DecisionProviderRegistry(decisionRegistrations),
605
+ {
606
+ ...(defaultProviderId !== undefined && { defaultProviderId }),
607
+ ...(policy?.routes !== undefined && { routes: policy.routes }),
608
+ },
609
+ );
610
+ } catch (err) {
611
+ reportBootError(
612
+ logger,
613
+ `Decision provider configuration is invalid: ${err instanceof Error ? err.message : String(err)}`,
614
+ 'Check createOracleWorker({ decisionProviders, decisionProviderPolicy }) against DECISION_PROVIDER.',
615
+ );
616
+ throw err;
617
+ }
618
+
619
+ // 5. Identity.
390
620
  const oracleDid = String(validated.config.ORACLE_DID);
391
621
  const entityDid =
392
622
  typeof validated.config.ORACLE_ENTITY_DID === 'string' &&
@@ -401,14 +631,20 @@ export function createRuntimeCore(opts: RuntimeCoreOptions): RuntimeCore {
401
631
  prompt: opts.config.prompt ?? {},
402
632
  };
403
633
 
404
- // 5. Registries.
634
+ // 6. Registries.
405
635
  for (const plugin of resolved.loaded) {
406
636
  registries.tools.register(plugin);
407
637
  registries.subAgents.register(plugin);
408
638
  registries.middlewares.register(plugin);
409
639
  registries.configSchema.register(plugin);
410
640
  registries.sharedState.register(plugin);
641
+ registries.decisions.register(plugin);
411
642
  }
643
+ const decisions: DecisionEvaluator = new DecisionRuntime(
644
+ registries.decisions,
645
+ decisionProviders,
646
+ logger,
647
+ );
412
648
 
413
649
  const availablePlugins: ReadonlySet<string> = loadedNames;
414
650
  const buildCtx = (pluginName = '__runtime__'): PluginContext =>
@@ -420,7 +656,7 @@ export function createRuntimeCore(opts: RuntimeCoreOptions): RuntimeCore {
420
656
  pluginName,
421
657
  });
422
658
 
423
- // 6. Routes + auth exclusions (sync — plugins close over config here).
659
+ // 7. Routes + auth exclusions (sync — plugins close over config here).
424
660
  const pluginRoutes: PluginRoute[] = resolved.loaded.flatMap(
425
661
  (p) => p.getRoutes?.(buildCtx(p.name)) ?? [],
426
662
  );
@@ -428,7 +664,7 @@ export function createRuntimeCore(opts: RuntimeCoreOptions): RuntimeCore {
428
664
  (p) => p.getAuthExcludedRoutes?.() ?? [],
429
665
  );
430
666
 
431
- // 7. LLM.
667
+ // 8. LLM.
432
668
  const llm = createLlmAdapter(
433
669
  {
434
670
  OPEN_ROUTER_API_KEY: String(validated.config.OPEN_ROUTER_API_KEY),
@@ -446,7 +682,7 @@ export function createRuntimeCore(opts: RuntimeCoreOptions): RuntimeCore {
446
682
  logger,
447
683
  );
448
684
 
449
- // 8. Warm-up (memoised). Collision + manifest cross-checks need the boot
685
+ // 9. Warm-up (memoised). Collision + manifest cross-checks need the boot
450
686
  // caches, which may involve async `getTools`, so they cannot run here.
451
687
  let warmed: Promise<void> | null = null;
452
688
  const warm = (): Promise<void> => {
@@ -455,10 +691,12 @@ export function createRuntimeCore(opts: RuntimeCoreOptions): RuntimeCore {
455
691
  await registries.tools.collectBoot(ctx);
456
692
  registries.subAgents.collectBoot(ctx);
457
693
  registries.middlewares.collect(ctx);
694
+ registries.decisions.collect(ctx);
458
695
  try {
459
696
  registries.tools.assertNoCollisions();
460
697
  registries.subAgents.assertNoCollisions();
461
698
  registries.sharedState.assertNoCollisions();
699
+ registries.decisions.assertNoCollisions();
462
700
  // Advisory only: request-time tools (`getRequestTools`) are invisible
463
701
  // at boot, so an example that names one reads as "unknown" here. The
464
702
  // Node runtime does not run this check at boot at all; surfacing it
@@ -497,6 +735,12 @@ export function createRuntimeCore(opts: RuntimeCoreOptions): RuntimeCore {
497
735
 
498
736
  return {
499
737
  identity,
738
+ delivery: deliveryConfigWithEnv(
739
+ opts.config.delivery,
740
+ typeof validated.config.MATRIX_CHAT_DELIVERY === 'string'
741
+ ? validated.config.MATRIX_CHAT_DELIVERY
742
+ : undefined,
743
+ ),
500
744
  registries,
501
745
  availablePlugins,
502
746
  plugins: resolved.loaded,
@@ -506,8 +750,22 @@ export function createRuntimeCore(opts: RuntimeCoreOptions): RuntimeCore {
506
750
  pluginRoutes,
507
751
  authExcludedRoutes,
508
752
  llm,
753
+ decisions,
509
754
  logger,
510
755
  buildCtx,
511
756
  warm,
512
757
  };
513
758
  }
759
+ export {
760
+ DEFAULT_TURN_LIMITS,
761
+ HarnessLimitError,
762
+ TurnBudget,
763
+ harnessLimitOf,
764
+ isHarnessLimitError,
765
+ turnLimitsFromEnv,
766
+ } from './turn-budget';
767
+ export type { TurnLimits, TurnUsage } from './turn-budget';
768
+ export { budgetedLlm } from './budgeted-llm';
769
+ export type { BudgetedLlm, BudgetedLlmOptions } from './budgeted-llm';
770
+ export { ToolScheduler } from './tool-scheduler';
771
+ export type { ToolLane, ToolSchedulerOptions } from './tool-scheduler';
package/src/core/llm.ts CHANGED
@@ -82,7 +82,7 @@ export const TIER_DISPLAY: Record<
82
82
  export const MODEL_CATALOG: readonly ModelCatalogEntry[] = [
83
83
  // ── $ Everyday — fast and cheap ─────────────────────────────────────────
84
84
  {
85
- id: DEFAULT_MODEL_ID,
85
+ id: 'openai/gpt-5.4-nano',
86
86
  label: 'GPT-5.4 Nano',
87
87
  family: 'openai',
88
88
  tier: 'everyday',
@@ -120,7 +120,7 @@ export const MODEL_CATALOG: readonly ModelCatalogEntry[] = [
120
120
 
121
121
  // ── $$ Balanced — smarter, still affordable ─────────────────────────────
122
122
  {
123
- id: 'openai/gpt-5.6-luna',
123
+ id: DEFAULT_MODEL_ID,
124
124
  label: 'GPT-5.6 Luna',
125
125
  family: 'openai',
126
126
  tier: 'balanced',
@@ -239,7 +239,10 @@ export function getDefaultModelId(
239
239
  return override.length > 0 ? override : DEFAULT_MODEL_ID;
240
240
  }
241
241
 
242
- const MODEL_INPUT_CAPS: Record<string, ModelInputCapabilities> = {
242
+ /** Native input capabilities per catalog id (every key is a `MODEL_CATALOG` id). */
243
+ export const MODEL_INPUT_CAPS: Readonly<
244
+ Record<string, ModelInputCapabilities>
245
+ > = {
243
246
  'openai/gpt-5.4-nano': {
244
247
  image: true,
245
248
  file: true,
@@ -396,12 +399,19 @@ export async function listModels(
396
399
  OPEN_ROUTER_API_KEY?: unknown;
397
400
  }
398
401
  | undefined,
402
+ options: {
403
+ /** Keeps a background price refresh alive past the response (`ctx.waitUntil`). */
404
+ waitUntil?: (work: Promise<unknown>) => void;
405
+ } = {},
399
406
  ): Promise<ModelListing> {
400
407
  const apiKey =
401
408
  typeof env?.OPEN_ROUTER_API_KEY === 'string' && env.OPEN_ROUTER_API_KEY
402
409
  ? env.OPEN_ROUTER_API_KEY
403
410
  : undefined;
404
- const livePrices = await fetchOpenRouterPrices({ apiKey });
411
+ const livePrices = await fetchOpenRouterPrices({
412
+ apiKey,
413
+ ...(options.waitUntil ? { waitUntil: options.waitUntil } : {}),
414
+ });
405
415
  return listModelCatalog(env, livePrices);
406
416
  }
407
417
 
@@ -446,6 +456,20 @@ export const OPENROUTER_MAIN_FALLBACKS: readonly string[] = [
446
456
 
447
457
  export const OPENROUTER_BASE_URL = 'https://openrouter.ai/api/v1';
448
458
 
459
+ /**
460
+ * Attribution headers OpenRouter asks callers to send. Shared by the chat
461
+ * models and the `openrouter-jev` Decision adapter so every request the
462
+ * oracle makes is credited to the same app.
463
+ */
464
+ export function openRouterAttributionHeaders(
465
+ oracleName?: string,
466
+ ): Record<string, string> {
467
+ return {
468
+ 'HTTP-Referer': 'oracle-app.com',
469
+ 'X-Title': oracleName ?? 'Oracle App',
470
+ };
471
+ }
472
+
449
473
  // ── Nebius (self-hosted deployments) ────────────────────────────────────────
450
474
 
451
475
  export const NEBIUS_BASE_URL = 'https://api.tokenfactory.nebius.com/v1/';
@@ -561,10 +585,7 @@ export function createLlmAdapter(
561
585
  if (env.LLM_PROVIDER === 'nebius') return createNebiusAdapter(env, logger);
562
586
  const defaultModelId = getDefaultModelId(env);
563
587
  const mainEffort = env.MAIN_REASONING_EFFORT ?? 'medium';
564
- const headers: Record<string, string> = {
565
- 'HTTP-Referer': 'oracle-app.com',
566
- 'X-Title': env.ORACLE_NAME ?? 'Oracle App',
567
- };
588
+ const headers = openRouterAttributionHeaders(env.ORACLE_NAME);
568
589
 
569
590
  const modelForRole = (role: ModelRole): string => {
570
591
  if (role === 'main') return defaultModelId;
@@ -758,7 +779,7 @@ export interface ResolveLangsmithTracingArgs {
758
779
  /** The turn's user DID — from the authenticated payload, never client-set metadata. */
759
780
  userDid: string;
760
781
  /** Ingress surface, so latency can be sliced per client in LangSmith. */
761
- client: 'portal' | 'matrix' | 'slack';
782
+ client: 'portal' | 'matrix' | 'slack' | 'channel';
762
783
  env: LangsmithTracingEnv;
763
784
  }
764
785
 
@@ -1,8 +1,10 @@
1
+ import type { TaskExecutionProfile } from '../core/execution-profile';
1
2
  import type { ContextBudget } from './context-budget';
2
3
  import type { ContextGuardEvent } from './middlewares/context-guard';
3
4
  import type { ResultCapConfig } from './middlewares/result-cap';
4
5
  import type { ReadResultOutcome } from '../do/result-store';
5
6
  import type { AgentMiddleware } from 'langchain';
7
+ import type { TurnBudget } from './turn-budget';
6
8
  import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
7
9
  import type { BaseCheckpointSaver } from '@langchain/langgraph';
8
10
  import type { ReactAgent } from 'langchain';
@@ -12,7 +14,9 @@ import type {
12
14
  MergedConfig,
13
15
  ModelRole,
14
16
  OracleIdentity,
17
+ PluginTool,
15
18
  } from '../plugin-api/types';
19
+ import type { DeliveryProfile } from '../delivery/types';
16
20
  import type { Registries } from './registries';
17
21
  import type { AmbientServices, RunConfigContext } from './runtime-context';
18
22
  import type { TMainAgentGraphState } from './state';
@@ -30,7 +34,7 @@ export const mainAgentRequestContextSchema = z.object({
30
34
  }),
31
35
  session: z.object({
32
36
  id: z.string(),
33
- client: z.enum(['portal', 'matrix', 'slack']),
37
+ client: z.enum(['portal', 'matrix', 'slack', 'channel']),
34
38
  requestId: z.string(),
35
39
  wsId: z.string().optional(),
36
40
  roomId: z.string().optional(),
@@ -85,6 +89,14 @@ export interface MainAgentHooks {
85
89
  * write-ahead tool marks of durable runs and the result cap live here.
86
90
  */
87
91
  toolMiddlewares?: AgentMiddleware[];
92
+ /**
93
+ * The tool-execution middleware (budget, scheduling, write claims). It
94
+ * must see a tool's own thrown error, so on the main agent it is placed
95
+ * innermost — inside the retry middlewares, which turn a thrown error into
96
+ * an error ToolMessage — and each retry attempt passes through it. Every
97
+ * sub-agent gets it after `toolMiddlewares`.
98
+ */
99
+ toolExecution?: AgentMiddleware;
88
100
  /**
89
101
  * The turn's result cap (result-cap.ts), applied inside every wrapped
90
102
  * tool — plugin, meta and sub-agent inner tools — so a large result is
@@ -110,9 +122,16 @@ export interface MainAgentHooks {
110
122
  offset: number,
111
123
  length: number,
112
124
  ) => Promise<ReadResultOutcome>;
125
+ /**
126
+ * Tools the host binds for this turn only, outside every plugin and the
127
+ * capability gate (chat delivery's `create_artifact`). `returnDirect` ends
128
+ * the run once the tool has answered, without another model call.
129
+ */
130
+ turnTools?: Array<{ tool: PluginTool; returnDirect?: boolean }>;
113
131
  }
114
132
 
115
133
  export interface MainAgentArgs {
134
+ executionProfile?: TaskExecutionProfile;
116
135
  registries: MainAgentRegistries;
117
136
  identity: OracleIdentity;
118
137
  config: MergedConfig;
@@ -120,6 +139,12 @@ export interface MainAgentArgs {
120
139
  ambient: AmbientServices;
121
140
  state: Partial<TMainAgentGraphState>;
122
141
  availablePlugins: ReadonlySet<string>;
142
+ /**
143
+ * Plugins the capability router chose for this turn. The gate admits their
144
+ * tools and tool handlers see them as loaded, but they are never written
145
+ * to the graph's `loadedPlugins` channel: the preload lasts one turn.
146
+ */
147
+ preloadedPlugins?: ReadonlySet<string>;
123
148
  /**
124
149
  * Checkpointer for the user's thread state. On Workers the Durable Object
125
150
  * owns it (SQLite over DO storage) and passes it in per turn; omit for a
@@ -141,6 +166,19 @@ export interface MainAgentArgs {
141
166
  * size. Omitted → the legacy fixed thresholds (tests, stateless builds).
142
167
  */
143
168
  contextBudget?: ContextBudget;
169
+ /**
170
+ * The turn's resource budget (turn-budget.ts). Model calls are charged by
171
+ * the metered LLM adapter and tool calls by the host's tool-execution
172
+ * middleware; the build itself only uses it to skip a summary after the
173
+ * deadline. Omitted → unbudgeted (tests, stateless builds).
174
+ */
175
+ turnBudget?: TurnBudget;
176
+ /**
177
+ * Where the reply lands (chat delivery). A `chat` profile adds the surface
178
+ * section to the prompt; tools see it as `ctx.session.surface`. Omitted →
179
+ * a Portal turn.
180
+ */
181
+ delivery?: DeliveryProfile;
144
182
  hooks?: MainAgentHooks;
145
183
  }
146
184
 
@@ -160,6 +198,8 @@ export interface MainAgentBuildResult {
160
198
  * turn, `write` never does). Declared by the plugin, else by name.
161
199
  */
162
200
  toolEffects: Map<string, 'read' | 'write'>;
201
+ /** Names of the sub-agent dispatch tools (`call_*_agent`): scheduled in their own lane. */
202
+ subAgentToolNames: ReadonlySet<string>;
163
203
  /**
164
204
  * The request's `{ user, session }` channel. Tools already fall back to it,
165
205
  * but pass it as `context` in the `invoke` / `stream` config so LangChain