@ixo/oracle-runtime-workers 0.13.0 → 0.16.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 +13 -8
  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 +310 -0
  18. package/src/channels/contract.ts +246 -0
  19. package/src/channels/turns.ts +280 -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 +170 -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 +2003 -274
  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 +504 -108
  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 +562 -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/env.ts CHANGED
@@ -1,7 +1,17 @@
1
+ import {
2
+ capabilityRouterEnvShape,
3
+ decisionProviderEnvShape,
4
+ } from '@ixo/common/ai/decisions';
1
5
  import { z } from 'zod';
2
6
  import type { OraclePlugin } from '../plugin-api/oracle-plugin';
3
7
  import type { Logger } from '../plugin-api/types';
4
8
  import { NOOP_LOGGER } from './utils';
9
+ import { FEEDBACK_ENV_PREFIX } from '../feedback/contract';
10
+ import {
11
+ isSecureUrl,
12
+ MAX_ARTIFACT_SWEEP_INTERVAL_HOURS,
13
+ MAX_ARTIFACT_TTL_DAYS,
14
+ } from '../artifacts/config';
5
15
 
6
16
  /**
7
17
  * Tier-0 (core) environment for the Workers runtime. Mirrors
@@ -29,6 +39,11 @@ import { NOOP_LOGGER } from './utils';
29
39
  */
30
40
  export const TURN_RECURSION_LIMIT_DEFAULT = 600;
31
41
 
42
+ /** An https URL, or http on localhost / 127.0.0.1 (local harness). */
43
+ const secureUrlSchema = z.string().refine((raw) => isSecureUrl(raw), {
44
+ message: 'must be an https URL (http only on localhost)',
45
+ });
46
+
32
47
  export const baseEnvSchema = z.object({
33
48
  // --- identity -----------------------------------------------------------
34
49
  ORACLE_NAME: z.string().min(1),
@@ -50,6 +65,13 @@ export const baseEnvSchema = z.object({
50
65
  * declares. Default 15 minutes.
51
66
  */
52
67
  UCAN_AUTH_MAX_TTL_SECONDS: z.coerce.number().int().positive().default(900),
68
+ /**
69
+ * `true` lets a bare `x-ucan-delegation` (no invocation) authenticate a
70
+ * request — the legacy fallback, for clients that do not send a UCAN
71
+ * invocation yet. Off by default: a delegation travels on as proof in
72
+ * downstream invocations, so it must not also be a login credential.
73
+ */
74
+ UCAN_ALLOW_BARE_DELEGATION_AUTH: z.enum(['true', 'false']).optional(),
53
75
 
54
76
  // --- matrix -------------------------------------------------------------
55
77
  MATRIX_BASE_URL: z.string().min(1),
@@ -114,6 +136,45 @@ export const baseEnvSchema = z.object({
114
136
  .int()
115
137
  .min(1)
116
138
  .default(TURN_RECURSION_LIMIT_DEFAULT),
139
+ /**
140
+ * The turn budget (docs/plans/workers-harness-hardening.md): cumulative
141
+ * model tokens across the main agent, sub-agents and helper models; tool
142
+ * attempts; and the wall-clock deadline. Exhaustion ends the turn and keeps
143
+ * the work done so far. Parsed by `turnLimitsFromEnv`.
144
+ */
145
+ TURN_MAX_TOKENS: z.coerce.number().int().positive().default(500_000),
146
+ TURN_MAX_TOOL_CALLS: z.coerce.number().int().positive().default(120),
147
+ TURN_TIMEOUT_MS: z.coerce.number().int().positive().default(600_000),
148
+ /**
149
+ * The repetition guard's per-turn caps on identical successful calls
150
+ * (same tool, same arguments): a write, and a read or `repeatable` tool.
151
+ * Parsed by `repetitionCapsFromEnv`.
152
+ */
153
+ TURN_MAX_IDENTICAL_WRITES: z.coerce.number().int().positive().default(1),
154
+ TURN_MAX_IDENTICAL_READS: z.coerce.number().int().positive().default(5),
155
+ /**
156
+ * Time limit per plugin `getRequestAdmission` handler (ms). A handler that
157
+ * has not answered by then counts as `pass` and the turn goes on to the
158
+ * next handler or the agent. See docs/architecture/request-admission.md.
159
+ */
160
+ REQUEST_ADMISSION_TIMEOUT_MS: z.coerce
161
+ .number()
162
+ .int()
163
+ .positive()
164
+ .default(2_000),
165
+
166
+ // --- decisions ----------------------------------------------------------
167
+ // Bounded semantic Decision provider (`DECISION_PROVIDER`, `DECISION_MODEL`,
168
+ // Cloudflare credentials), shared with the Node runtime and optional so
169
+ // oracles without Decisions keep their behaviour. `openrouter-jev` reuses
170
+ // `OPEN_ROUTER_API_KEY` (required above). On Workers `cloudflare-jev`
171
+ // prefers the `AI` binding over the account credentials, which are only
172
+ // needed when the binding is not declared.
173
+ ...decisionProviderEnvShape,
174
+ // `CAPABILITY_ROUTER` (`off` | `shadow` | `on`, default `off`): the first
175
+ // runtime consumer of Decisions. It predicts the on-demand plugin a message
176
+ // needs and preloads it for the turn; it needs a `DECISION_PROVIDER`.
177
+ ...capabilityRouterEnvShape,
117
178
 
118
179
  // --- durable runs (docs/plans/durable-runs.md) ---------------------------
119
180
  // Parsed by `runDurabilityConfig` (src/do/run-store.ts) with defaults:
@@ -144,9 +205,49 @@ export const baseEnvSchema = z.object({
144
205
  TOOL_RESULT_TTL_HOURS: z.string().optional(),
145
206
  TOOL_RESULT_R2_MIN_BYTES: z.string().optional(),
146
207
 
208
+ // --- chat delivery (docs/chat-delivery.md) -------------------------------
209
+ /**
210
+ * `true` gives Matrix room turns the chat style (several short messages,
211
+ * long content as artefacts); unset keeps `OracleConfig.delivery.matrixChat`,
212
+ * which defaults to one message per reply.
213
+ */
214
+ MATRIX_CHAT_DELIVERY: z.enum(['true', 'false']).optional(),
215
+ /**
216
+ * Artefact links (the bucket is a binding, `ARTIFACT_BUCKET`): the oracle's
217
+ * public origin, an optional shared viewer page, and the link lifetime in
218
+ * days. Links carry the decryption key, so both URLs must be https (http
219
+ * only on localhost).
220
+ */
221
+ ORACLE_PUBLIC_URL: secureUrlSchema.optional(),
222
+ ARTIFACT_VIEWER_URL: secureUrlSchema.optional(),
223
+ ARTIFACT_LINK_TTL_DAYS: z.coerce
224
+ .number()
225
+ .int()
226
+ .min(1)
227
+ .max(MAX_ARTIFACT_TTL_DAYS)
228
+ .optional(),
229
+ /**
230
+ * Hours between the cron's sweeps of expired share copies in the bucket
231
+ * (unset = 24). Only runs where `ARTIFACT_BUCKET` is bound and a cron
232
+ * trigger is declared.
233
+ */
234
+ ARTIFACT_SWEEP_INTERVAL_HOURS: z.coerce
235
+ .number()
236
+ .int()
237
+ .min(1)
238
+ .max(MAX_ARTIFACT_SWEEP_INTERVAL_HOURS)
239
+ .optional(),
240
+
147
241
  // --- misc ---------------------------------------------------------------
148
242
  LOG_LEVEL: z.string().default('info'),
149
243
  CORS_ORIGIN: z.string().default('*'),
244
+ /**
245
+ * Comma-separated DIDs allowed onto the gateway-wide operator routes
246
+ * (`/debug/matrix/*`). Unset: any authenticated caller (with
247
+ * `ORACLE_DEBUG_ROUTES=true`). A list with an entry that is not a DID
248
+ * admits nobody.
249
+ */
250
+ ORACLE_OPERATOR_DIDS: z.string().optional(),
150
251
  });
151
252
 
152
253
  export type BaseEnv = z.infer<typeof baseEnvSchema>;
@@ -207,8 +308,18 @@ export function composeEnvSchema(
207
308
  if (!plugin.configSchema) continue;
208
309
  const shape = plugin.configSchema.shape as Record<string, unknown>;
209
310
  for (const key of Object.keys(shape)) {
311
+ // Runtime-only secrets (the feedback sink's Linear key and pseudonym
312
+ // secret) must never reach a plugin's `ctx.config`.
313
+ if (key.startsWith(FEEDBACK_ENV_PREFIX))
314
+ throw new Error(
315
+ `Plugin '${plugin.name}' declares env key '${key}': the '${FEEDBACK_ENV_PREFIX}' prefix is reserved for the runtime`,
316
+ );
210
317
  const previous = pluginOwnership.get(key);
211
318
  if (previous !== undefined && previous !== plugin.name) {
319
+ // Plugins that declare the very same schema object share the key on
320
+ // purpose (the wallet-signing chain ids): nothing is replaced, and
321
+ // the first declarer stays its owner.
322
+ if (merged.shape[key] === shape[key]) continue;
212
323
  logger.warn(
213
324
  `[boot] env key '${key}' is defined by both '${previous}' and '${plugin.name}'; '${plugin.name}' wins.`,
214
325
  );
@@ -0,0 +1,17 @@
1
+ export const TASK_EXECUTION_PROFILES = ['supplied-context-markdown'] as const;
2
+
3
+ export type TaskExecutionProfile = (typeof TASK_EXECUTION_PROFILES)[number];
4
+
5
+ export function isTaskExecutionProfile(
6
+ value: unknown,
7
+ ): value is TaskExecutionProfile {
8
+ return (TASK_EXECUTION_PROFILES as readonly unknown[]).includes(value);
9
+ }
10
+
11
+ export function taskExecutionProfile(
12
+ value: unknown,
13
+ ): TaskExecutionProfile | undefined {
14
+ if (value === undefined || value === null) return undefined;
15
+ if (isTaskExecutionProfile(value)) return value;
16
+ throw new Error('Unsupported task execution profile');
17
+ }
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';