@ixo/oracle-runtime-workers 0.2.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 (215) hide show
  1. package/README.md +110 -0
  2. package/package.json +77 -0
  3. package/src/attachments/classify.ts +114 -0
  4. package/src/attachments/content-blocks.ts +51 -0
  5. package/src/attachments/download.ts +235 -0
  6. package/src/attachments/extract.ts +241 -0
  7. package/src/attachments/index.ts +84 -0
  8. package/src/attachments/magic.ts +133 -0
  9. package/src/attachments/pipeline.ts +409 -0
  10. package/src/attachments/retention.ts +205 -0
  11. package/src/attachments/route.ts +41 -0
  12. package/src/attachments/sandbox-archive.ts +90 -0
  13. package/src/attachments/sanitize.ts +35 -0
  14. package/src/attachments/types.ts +86 -0
  15. package/src/attachments/view.ts +107 -0
  16. package/src/core/env.ts +200 -0
  17. package/src/core/index.ts +513 -0
  18. package/src/core/llm.ts +842 -0
  19. package/src/core/main-agent-types.ts +123 -0
  20. package/src/core/main-agent.ts +399 -0
  21. package/src/core/manifest.ts +365 -0
  22. package/src/core/meta-tools.ts +232 -0
  23. package/src/core/middlewares/byo-history-sanitizer.ts +165 -0
  24. package/src/core/middlewares/capability-gate.ts +76 -0
  25. package/src/core/middlewares/index.ts +18 -0
  26. package/src/core/middlewares/page-context.ts +98 -0
  27. package/src/core/middlewares/safety-guardrail.ts +117 -0
  28. package/src/core/middlewares/summarization.ts +99 -0
  29. package/src/core/middlewares/tool-repetition-guard.ts +125 -0
  30. package/src/core/middlewares/tool-validation.ts +83 -0
  31. package/src/core/middlewares/work-status.ts +57 -0
  32. package/src/core/openrouter-pricing.ts +124 -0
  33. package/src/core/plugin-loader.ts +192 -0
  34. package/src/core/plugins/index.ts +6 -0
  35. package/src/core/plugins/skills/index.ts +8 -0
  36. package/src/core/plugins/skills/skills-tools.ts +356 -0
  37. package/src/core/plugins/skills/skills.plugin.ts +113 -0
  38. package/src/core/plugins/weather/index.ts +8 -0
  39. package/src/core/plugins/weather/weather-client.ts +153 -0
  40. package/src/core/plugins/weather/weather-middleware.ts +27 -0
  41. package/src/core/plugins/weather/weather-sub-agent.ts +65 -0
  42. package/src/core/plugins/weather/weather-tools.ts +103 -0
  43. package/src/core/plugins/weather/weather.plugin.ts +227 -0
  44. package/src/core/prompt-composer.ts +469 -0
  45. package/src/core/registries.ts +642 -0
  46. package/src/core/runtime-context.ts +619 -0
  47. package/src/core/state.ts +108 -0
  48. package/src/core/sub-agent-fallback.ts +187 -0
  49. package/src/core/subagent-as-tool.ts +322 -0
  50. package/src/core/template.ts +245 -0
  51. package/src/core/utils.ts +246 -0
  52. package/src/core/wrap-plugin-tool.ts +100 -0
  53. package/src/do/ambient.ts +266 -0
  54. package/src/do/contracts.ts +562 -0
  55. package/src/do/debug-timers.ts +120 -0
  56. package/src/do/fresh-stub.ts +32 -0
  57. package/src/do/gateway-retry.ts +73 -0
  58. package/src/do/secrets-adapter.ts +32 -0
  59. package/src/do/size-alerts.ts +76 -0
  60. package/src/do/sse-stream.ts +472 -0
  61. package/src/do/transcript.ts +192 -0
  62. package/src/do/turn-body.ts +84 -0
  63. package/src/do/turn-metadata.ts +146 -0
  64. package/src/do/ucan-service.ts +339 -0
  65. package/src/do/user-oracle-do.ts +3035 -0
  66. package/src/gateway-worker.ts +82 -0
  67. package/src/index.ts +237 -0
  68. package/src/llm/byo-adapter.ts +59 -0
  69. package/src/llm/byo-catalog.ts +520 -0
  70. package/src/llm/byo-client.ts +280 -0
  71. package/src/llm/byo-routes.ts +255 -0
  72. package/src/llm/byo-service.ts +836 -0
  73. package/src/llm/chatgpt-oauth.ts +430 -0
  74. package/src/llm/provider-error.ts +283 -0
  75. package/src/matrix/gateway-do.ts +918 -0
  76. package/src/matrix/ingest.ts +209 -0
  77. package/src/matrix/replay-format.ts +34 -0
  78. package/src/matrix/reply-chain.ts +109 -0
  79. package/src/matrix/room-state-codec.ts +115 -0
  80. package/src/matrix/user-homeserver.ts +106 -0
  81. package/src/matrix/work-status.ts +294 -0
  82. package/src/memory/session-history-indexer.ts +328 -0
  83. package/src/owner-store/ixo-vfs-store.ts +570 -0
  84. package/src/owner-store/matrix-media-store.ts +136 -0
  85. package/src/owner-store/migrating-store.ts +185 -0
  86. package/src/owner-store/owner-copy-errors.ts +175 -0
  87. package/src/owner-store/retry.ts +45 -0
  88. package/src/owner-store/tus-upload.ts +333 -0
  89. package/src/owner-store/types.ts +0 -0
  90. package/src/plugin-api/define-plugin.ts +45 -0
  91. package/src/plugin-api/index.ts +56 -0
  92. package/src/plugin-api/oracle-plugin.ts +145 -0
  93. package/src/plugin-api/tool-helper.ts +82 -0
  94. package/src/plugin-api/types.ts +816 -0
  95. package/src/plugin-api/ucan-errors.ts +16 -0
  96. package/src/plugins/agui/agui-agent.ts +115 -0
  97. package/src/plugins/agui/agui.plugin.ts +148 -0
  98. package/src/plugins/agui/index.ts +2 -0
  99. package/src/plugins/attachments/attachments.plugin.ts +54 -0
  100. package/src/plugins/attachments/index.ts +6 -0
  101. package/src/plugins/attachments/view-attachment-tool.ts +91 -0
  102. package/src/plugins/base64.ts +26 -0
  103. package/src/plugins/composio/composio-tools.ts +365 -0
  104. package/src/plugins/composio/composio-ucan.ts +43 -0
  105. package/src/plugins/composio/composio.plugin.ts +214 -0
  106. package/src/plugins/composio/index.ts +15 -0
  107. package/src/plugins/domain-indexer/domain-indexer-agent.ts +57 -0
  108. package/src/plugins/domain-indexer/domain-indexer-tools.ts +253 -0
  109. package/src/plugins/domain-indexer/domain-indexer.plugin.ts +100 -0
  110. package/src/plugins/domain-indexer/index.ts +3 -0
  111. package/src/plugins/editor/blocknote-bridge.ts +120 -0
  112. package/src/plugins/editor/content-session.ts +331 -0
  113. package/src/plugins/editor/content-tools.ts +686 -0
  114. package/src/plugins/editor/document-model.ts +0 -0
  115. package/src/plugins/editor/editor-access-denied-tool.ts +58 -0
  116. package/src/plugins/editor/editor-agent.ts +34 -0
  117. package/src/plugins/editor/editor-config.ts +64 -0
  118. package/src/plugins/editor/editor-mx.ts +128 -0
  119. package/src/plugins/editor/editor.plugin.ts +143 -0
  120. package/src/plugins/editor/failures.ts +154 -0
  121. package/src/plugins/editor/index.ts +31 -0
  122. package/src/plugins/editor/jsdom-shim.ts +55 -0
  123. package/src/plugins/editor/prompts.ts +59 -0
  124. package/src/plugins/editor/prop-policy.ts +147 -0
  125. package/src/plugins/editor/provider.ts +386 -0
  126. package/src/plugins/editor/room-membership.ts +95 -0
  127. package/src/plugins/editor/standalone-editor-tool.ts +200 -0
  128. package/src/plugins/firecrawl/firecrawl-agent.ts +93 -0
  129. package/src/plugins/firecrawl/firecrawl-tools.ts +220 -0
  130. package/src/plugins/firecrawl/firecrawl.plugin.ts +105 -0
  131. package/src/plugins/firecrawl/index.ts +15 -0
  132. package/src/plugins/flows/action-metadata.ts +3505 -0
  133. package/src/plugins/flows/actions.ts +190 -0
  134. package/src/plugins/flows/edit.ts +314 -0
  135. package/src/plugins/flows/errors.ts +38 -0
  136. package/src/plugins/flows/explain.ts +43 -0
  137. package/src/plugins/flows/flow-doc.ts +111 -0
  138. package/src/plugins/flows/flows.plugin.ts +129 -0
  139. package/src/plugins/flows/forms.ts +330 -0
  140. package/src/plugins/flows/index.ts +2 -0
  141. package/src/plugins/flows/linkage.ts +147 -0
  142. package/src/plugins/flows/port-types.ts +53 -0
  143. package/src/plugins/flows/prompts.ts +65 -0
  144. package/src/plugins/flows/read.ts +392 -0
  145. package/src/plugins/flows/references.ts +66 -0
  146. package/src/plugins/flows/test-support.ts +165 -0
  147. package/src/plugins/flows/tools/authoring.ts +516 -0
  148. package/src/plugins/flows/tools/discovery.ts +119 -0
  149. package/src/plugins/flows/tools/forms.ts +142 -0
  150. package/src/plugins/flows/tools/inspect.ts +165 -0
  151. package/src/plugins/flows/tools/linkage.ts +117 -0
  152. package/src/plugins/flows/tools/settings.ts +184 -0
  153. package/src/plugins/flows/translator.ts +328 -0
  154. package/src/plugins/flows/types.ts +210 -0
  155. package/src/plugins/flows/ydoc-helpers.ts +543 -0
  156. package/src/plugins/index.ts +70 -0
  157. package/src/plugins/mcp-call-timeout.ts +38 -0
  158. package/src/plugins/mcp-tool-adapter.ts +87 -0
  159. package/src/plugins/memory/index.ts +17 -0
  160. package/src/plugins/memory/memory-tools.ts +463 -0
  161. package/src/plugins/memory/memory-ucan.ts +51 -0
  162. package/src/plugins/memory/memory.plugin.ts +122 -0
  163. package/src/plugins/memory/types.ts +147 -0
  164. package/src/plugins/portal/action-log.ts +34 -0
  165. package/src/plugins/portal/index.ts +2 -0
  166. package/src/plugins/portal/portal.plugin.ts +203 -0
  167. package/src/plugins/sandbox/index.ts +34 -0
  168. package/src/plugins/sandbox/sandbox-bridge.ts +295 -0
  169. package/src/plugins/sandbox/sandbox-mcp.ts +127 -0
  170. package/src/plugins/sandbox/sandbox-write-blob.ts +103 -0
  171. package/src/plugins/sandbox/sandbox.plugin.ts +599 -0
  172. package/src/plugins/tasks/index.ts +17 -0
  173. package/src/plugins/tasks/manifest.ts +81 -0
  174. package/src/plugins/tasks/middleware.ts +219 -0
  175. package/src/plugins/tasks/tasks-tools.ts +414 -0
  176. package/src/plugins/tasks/tasks.plugin.ts +74 -0
  177. package/src/plugins/ucan-failure.ts +133 -0
  178. package/src/plugins/user-preferences/index.ts +22 -0
  179. package/src/plugins/user-preferences/schema.ts +31 -0
  180. package/src/plugins/user-preferences/user-preferences-store.ts +172 -0
  181. package/src/plugins/user-preferences/user-preferences-tool.ts +131 -0
  182. package/src/plugins/user-preferences/user-preferences.plugin.ts +137 -0
  183. package/src/plugins/vfs/index.ts +40 -0
  184. package/src/plugins/vfs/vfs-auth.ts +56 -0
  185. package/src/plugins/vfs/vfs-client.ts +655 -0
  186. package/src/plugins/vfs/vfs-content.ts +181 -0
  187. package/src/plugins/vfs/vfs-errors.ts +163 -0
  188. package/src/plugins/vfs/vfs-sandbox-tools.ts +272 -0
  189. package/src/plugins/vfs/vfs-tools.ts +635 -0
  190. package/src/plugins/vfs/vfs.plugin.ts +201 -0
  191. package/src/realtime/frontend-call-registry.ts +163 -0
  192. package/src/realtime/realtime-endpoint.ts +619 -0
  193. package/src/realtime/session-socket-hub.ts +249 -0
  194. package/src/realtime/socket-io-codec.ts +187 -0
  195. package/src/secrets/jwe.ts +74 -0
  196. package/src/secrets/pin-cipher.ts +43 -0
  197. package/src/secrets/secrets-service.ts +319 -0
  198. package/src/shell/app.ts +564 -0
  199. package/src/shell/auth.ts +290 -0
  200. package/src/sqlite/blob-codec.ts +100 -0
  201. package/src/sqlite/blob-compactor.ts +146 -0
  202. package/src/sqlite/cache-config.ts +45 -0
  203. package/src/sqlite/database.ts +643 -0
  204. package/src/sqlite/do-vfs.ts +1485 -0
  205. package/src/sqlite/index.ts +61 -0
  206. package/src/sqlite/serialization.ts +370 -0
  207. package/src/sqlite/sessions-store.ts +311 -0
  208. package/src/sqlite/sqlite-saver.ts +919 -0
  209. package/src/sqlite/vacuum-policy.ts +77 -0
  210. package/src/sqlite/wa-sqlite-loader.ts +123 -0
  211. package/src/sqlite/wasm.d.ts +10 -0
  212. package/src/tasks/schedule.ts +174 -0
  213. package/src/tasks/scheduler.ts +753 -0
  214. package/src/tasks/spec.ts +125 -0
  215. package/src/tasks/store.ts +351 -0
@@ -0,0 +1,123 @@
1
+ import type { AgentMiddleware } from 'langchain';
2
+ import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
3
+ import type { BaseCheckpointSaver } from '@langchain/langgraph';
4
+ import type { ReactAgent } from 'langchain';
5
+ import { z } from 'zod';
6
+ import type {
7
+ ChatOpenAIFields,
8
+ MergedConfig,
9
+ ModelRole,
10
+ OracleIdentity,
11
+ } from '../plugin-api/types';
12
+ import type { Registries } from './registries';
13
+ import type { AmbientServices, RunConfigContext } from './runtime-context';
14
+ import type { TMainAgentGraphState } from './state';
15
+
16
+ /** The 6 internal registries the runtime composes. */
17
+ export type MainAgentRegistries = Registries;
18
+
19
+ export const mainAgentRequestContextSchema = z.object({
20
+ user: z.object({
21
+ did: z.string(),
22
+ matrixUserId: z.string(),
23
+ ucanDelegation: z.any(),
24
+ timezone: z.string().optional(),
25
+ currentTime: z.string().optional(),
26
+ }),
27
+ session: z.object({
28
+ id: z.string(),
29
+ client: z.enum(['portal', 'matrix', 'slack']),
30
+ requestId: z.string(),
31
+ wsId: z.string().optional(),
32
+ roomId: z.string().optional(),
33
+ }),
34
+ history: z
35
+ .object({
36
+ userContext: z.record(z.string(), z.unknown()).optional(),
37
+ })
38
+ .optional(),
39
+ /**
40
+ * Per-request model override, already validated against the catalog
41
+ * allow-list (`isAllowedModel`). Absent → the agent uses the default
42
+ * `main` model.
43
+ */
44
+ model: z.string().optional(),
45
+ });
46
+
47
+ /** Per-request shape — exposes only what the main-agent build needs. */
48
+ export type MainAgentRequestContext = z.infer<
49
+ typeof mainAgentRequestContextSchema
50
+ >;
51
+
52
+ /**
53
+ * Optional hooks the consuming host provides to keep host-specific behaviour
54
+ * (model selection, prompt section snippets) out of the runtime.
55
+ */
56
+ export interface MainAgentHooks {
57
+ /** Optional model resolver. Default: `ambient.llm.get`. */
58
+ resolveModel?: (
59
+ role: ModelRole,
60
+ params?: ChatOpenAIFields,
61
+ ) => ReturnType<AmbientServices['llm']['get']>;
62
+ /** Look up a human-readable page title for the page-context middleware. */
63
+ getRoomTitle?: (roomId: string) => Promise<string | undefined>;
64
+ /** Cheap classification model used by the safety-guardrail middleware. */
65
+ safetyModel?: BaseChatModel;
66
+ /** Tool names whose `ToolMessage` outputs should be stripped between turns. */
67
+ validationSkipToolNames?: string[];
68
+ /** Operational-mode block — overridden when a richer mode applies. */
69
+ operationalMode?: string;
70
+ /** Degraded-services notice appended to the system prompt body. */
71
+ degradedServicesBlock?: string;
72
+ /**
73
+ * Host middlewares appended after the always-on stack and the plugins'
74
+ * (e.g. the Matrix `work_status` card driver, which needs a per-object
75
+ * producer the core cannot construct).
76
+ */
77
+ middlewares?: AgentMiddleware[];
78
+ }
79
+
80
+ export interface MainAgentArgs {
81
+ registries: MainAgentRegistries;
82
+ identity: OracleIdentity;
83
+ config: MergedConfig;
84
+ requestCtx: MainAgentRequestContext;
85
+ ambient: AmbientServices;
86
+ state: Partial<TMainAgentGraphState>;
87
+ availablePlugins: ReadonlySet<string>;
88
+ /**
89
+ * Checkpointer for the user's thread state. On Workers the Durable Object
90
+ * owns it (SQLite over DO storage) and passes it in per turn; omit for a
91
+ * stateless build (tests).
92
+ */
93
+ checkpointer?: BaseCheckpointSaver;
94
+ /**
95
+ * The turn's abort signal. Forwarded to the request-time plugin hooks
96
+ * (`getRequestTools` / `getRequestSubAgents`) through their `RuntimeContext`
97
+ * so a cancelled turn also cancels the network calls those hooks make. The
98
+ * caller still passes the same signal to `agent.invoke` / `agent.stream`.
99
+ */
100
+ abortSignal?: AbortSignal;
101
+ /** Provider of a BYO turn (`runtime.context.byo`); absent on platform turns. */
102
+ byoProvider?: string;
103
+ hooks?: MainAgentHooks;
104
+ }
105
+
106
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
107
+ export type CompiledMainAgent = ReactAgent<any>;
108
+
109
+ /** What `createMainAgent` resolves to. */
110
+ export interface MainAgentBuildResult {
111
+ /** The compiled LangChain agent — call `invoke` / `stream` on it. */
112
+ agent: CompiledMainAgent;
113
+ /** The composed system prompt the agent was built with (diagnostics). */
114
+ systemPrompt: string;
115
+ /** Names of every tool bound to the agent, in binding order. */
116
+ boundToolNames: string[];
117
+ /**
118
+ * The request's `{ user, session }` channel. Tools already fall back to it,
119
+ * but pass it as `context` in the `invoke` / `stream` config so LangChain
120
+ * middlewares and sub-agents see the same channel the Node runtime provides.
121
+ */
122
+ context: RunConfigContext;
123
+ }
@@ -0,0 +1,399 @@
1
+ import {
2
+ createAgent,
3
+ toolRetryMiddleware,
4
+ type StructuredTool,
5
+ } from 'langchain';
6
+ import type {
7
+ PluginContext,
8
+ PluginManifest,
9
+ PluginTool,
10
+ RuntimeContext,
11
+ SharedAccessors,
12
+ } from '../plugin-api/types';
13
+ import type { MainAgentArgs, MainAgentBuildResult } from './main-agent-types';
14
+ import { renderTier1, type Tier1Entry } from './manifest';
15
+ import { buildMetaTools } from './meta-tools';
16
+ import {
17
+ createByoHistorySanitizerMiddleware,
18
+ createCapabilityGateMiddleware,
19
+ createPageContextMiddleware,
20
+ createSafetyGuardrailMiddleware,
21
+ createSummarizationMiddleware,
22
+ createToolRepetitionGuardMiddleware,
23
+ createToolValidationMiddleware,
24
+ } from './middlewares';
25
+ import {
26
+ composePrompt,
27
+ formatTimeContext,
28
+ formatUserPreferences,
29
+ } from './prompt-composer';
30
+ import { formatByPlugin, type ManifestRegistry } from './registries';
31
+ import {
32
+ buildPluginContext,
33
+ buildRuntimeContext,
34
+ type RunConfig,
35
+ type RuntimeStateInput,
36
+ } from './runtime-context';
37
+ import { MainAgentGraphState } from './state';
38
+ import { collectSubAgentsWithFallback } from './sub-agent-fallback';
39
+ import { computeSubAgentToolName } from './subagent-as-tool';
40
+ import { wrapPluginTool } from './wrap-plugin-tool';
41
+
42
+ const PLUGIN_LOGGER_COMPONENT = 'main-agent';
43
+
44
+ const DEFAULT_OPERATIONAL_MODE = [
45
+ '**General conversation mode**',
46
+ '',
47
+ 'Default to conversation mode, using available capabilities for recall, search, and task delegation.',
48
+ ].join('\n');
49
+
50
+ type CollectedTool = { pluginName: string; tool: PluginTool };
51
+
52
+ /** Map plugin name → effective `manifest.visibility` (default `on-demand`). */
53
+ function visibilityIndex(
54
+ manifests: ManifestRegistry,
55
+ ): Map<string, NonNullable<PluginManifest['visibility']>> {
56
+ const out = new Map<string, NonNullable<PluginManifest['visibility']>>();
57
+ for (const { pluginName, manifest } of manifests.collect()) {
58
+ out.set(pluginName, manifest.visibility ?? 'on-demand');
59
+ }
60
+ return out;
61
+ }
62
+
63
+ /**
64
+ * Filter collected tools by their effective visibility — per-tool override
65
+ * wins; otherwise the plugin's manifest visibility decides.
66
+ */
67
+ function selectByVisibility(
68
+ tools: CollectedTool[],
69
+ manifestViz: Map<string, NonNullable<PluginManifest['visibility']>>,
70
+ visibility: NonNullable<PluginManifest['visibility']>,
71
+ ): CollectedTool[] {
72
+ return tools.filter(({ pluginName, tool }) => {
73
+ const effective =
74
+ tool.visibility ?? manifestViz.get(pluginName) ?? 'on-demand';
75
+ return effective === visibility;
76
+ });
77
+ }
78
+
79
+ /**
80
+ * Build a compiled main agent from the runtime's registries plus per-request
81
+ * context. Same pipeline as the Node runtime — tools from the registries
82
+ * (boot cache + request hooks, collected concurrently with per-plugin failure
83
+ * isolation), visibility partition, meta-tools, sub-agents as tools, the
84
+ * fixed middleware order, prompt composition — minus the branches that do not
85
+ * exist on Workers (the commerce lane, Slack, Matrix group chats, and the
86
+ * ChatGPT-subscription history sanitizer). The Matrix `work_status` card
87
+ * driver arrives as a host middleware (`hooks.middlewares`); page context and
88
+ * the safety guardrail install when the host supplies `hooks.getRoomTitle` /
89
+ * `hooks.safetyModel`, exactly as on Node.
90
+ */
91
+ export async function createMainAgent(
92
+ args: MainAgentArgs,
93
+ ): Promise<MainAgentBuildResult> {
94
+ const {
95
+ registries,
96
+ identity,
97
+ config,
98
+ requestCtx,
99
+ ambient,
100
+ state,
101
+ availablePlugins,
102
+ checkpointer,
103
+ abortSignal,
104
+ byoProvider,
105
+ hooks,
106
+ } = args;
107
+
108
+ // ── 1. Plugin context (boot-time, no per-request fields) ────────────────
109
+ const buildCtx: PluginContext = buildPluginContext({
110
+ config,
111
+ identity,
112
+ availablePlugins,
113
+ logger: ambient.logger,
114
+ pluginName: PLUGIN_LOGGER_COMPONENT,
115
+ });
116
+
117
+ // ── 2. Request-time runtime context (drives getRequestTools/...SubAgents) ─
118
+ ambient.logger.debug?.(
119
+ `[MainAgent] state.loadedPlugins:`,
120
+ state.loadedPlugins,
121
+ );
122
+
123
+ const loadedSet = new Set<string>(state.loadedPlugins ?? []);
124
+ // Carry the prior request state (browserTools, agActions, …) into the
125
+ // per-request RuntimeContext and the tool-wrapper closures, but NOT the
126
+ // message history: keeping the full `messages` array here would pin the
127
+ // entire history inside every tool closure for the whole run, and it would
128
+ // be the WRONG history anyway — this snapshot is taken before the turn
129
+ // runs. Tools that must describe the conversation read
130
+ // `ctx.history.messages`, which `buildRuntimeContext` fills from
131
+ // LangGraph's live `ToolRuntime.state` at call time.
132
+ //
133
+ // Explicit fields come last so they win over the spread (notably
134
+ // `loadedPlugins`, which must be the de-duped Set, not the raw state array).
135
+ const wrapState: RuntimeStateInput = {
136
+ ...state,
137
+ messages: [],
138
+ userContext: state.userContext,
139
+ loadedPlugins: loadedSet,
140
+ };
141
+
142
+ // `ctx.shared` — read accessors other plugins registered via
143
+ // `getSharedState()`, evaluated lazily against the live context.
144
+ const sharedFactory = (ctx: RuntimeContext): SharedAccessors =>
145
+ registries.sharedState.build(ctx.history.state, ctx);
146
+
147
+ const runConfig: RunConfig = {
148
+ context: {
149
+ user: {
150
+ did: requestCtx.user.did,
151
+ matrixUserId: requestCtx.user.matrixUserId,
152
+ ucanDelegation: requestCtx.user.ucanDelegation,
153
+ timezone: requestCtx.user.timezone,
154
+ currentTime: requestCtx.user.currentTime,
155
+ },
156
+ session: {
157
+ id: requestCtx.session.id,
158
+ client: requestCtx.session.client,
159
+ requestId: requestCtx.session.requestId,
160
+ wsId: requestCtx.session.wsId,
161
+ roomId: requestCtx.session.roomId,
162
+ },
163
+ ...(byoProvider ? { byo: { provider: byoProvider, active: true } } : {}),
164
+ },
165
+ ...(abortSignal ? { signal: abortSignal } : {}),
166
+ };
167
+ const rtCtx = buildRuntimeContext(
168
+ runConfig,
169
+ ambient,
170
+ wrapState,
171
+ sharedFactory,
172
+ );
173
+
174
+ // ── 3. Resolve registries (boot-time + request-time contributions) ──────
175
+ // Tool and sub-agent collection are independent request-time fan-outs
176
+ // (each may open network connections); run them concurrently so the
177
+ // slower of the two — not their sum — gates the build.
178
+ const [allTools, subAgentEntries] = await Promise.all([
179
+ registries.tools.collect(buildCtx, rtCtx),
180
+ registries.subAgents.collect(buildCtx, rtCtx),
181
+ ]);
182
+
183
+ // The per-turn tool-surface line. Request tools are named in full (there
184
+ // are only a handful and they are the ones that vary turn to turn); the
185
+ // full bound list, boot tools included, stays at debug.
186
+ const requestTools = allTools.filter(({ origin }) => origin === 'request');
187
+ ambient.logger.log(
188
+ `[main-agent] tool surface: ${allTools.length} tools ` +
189
+ `(boot ${allTools.length - requestTools.length}, request ${requestTools.length}) ` +
190
+ `— request tools: ${formatByPlugin(requestTools) || '∅'}`,
191
+ );
192
+ ambient.logger.debug?.(
193
+ `[main-agent] full tool surface: ${formatByPlugin(allTools) || '∅'}`,
194
+ );
195
+ const manifestEntries = registries.manifests.collect();
196
+ const manifestViz = visibilityIndex(registries.manifests);
197
+ const titleByPlugin = new Map(
198
+ manifestEntries.map(({ pluginName, manifest }) => [
199
+ pluginName,
200
+ manifest.title,
201
+ ]),
202
+ );
203
+
204
+ const eagerTools = selectByVisibility(allTools, manifestViz, 'always');
205
+ // Bind ALL on-demand tools at compile time — gating happens per model call
206
+ // in `CapabilityGateMiddleware` based on the live `loadedPlugins` state.
207
+ // This lets `load_capability` take effect on the very next LLM call inside
208
+ // the same run, instead of waiting for the next request to rebuild.
209
+ const onDemandTools = selectByVisibility(allTools, manifestViz, 'on-demand');
210
+ const silentTools = selectByVisibility(allTools, manifestViz, 'silent');
211
+
212
+ // ── 4. Wrap tools (meta + plugin) so handlers receive a RuntimeContext ──
213
+ const metaTools = buildMetaTools({
214
+ manifestRegistry: registries.manifests,
215
+ toolRegistry: registries.tools,
216
+ });
217
+
218
+ const fallbackContext = runConfig.context;
219
+ const wrap = (entry: CollectedTool) =>
220
+ wrapPluginTool(entry.tool, {
221
+ ambient,
222
+ state: wrapState,
223
+ pluginTitle: titleByPlugin.get(entry.pluginName),
224
+ sharedFactory,
225
+ fallbackContext,
226
+ });
227
+
228
+ // ── 5. Sub-agents — bind all at compile time; gating happens at runtime ─
229
+ // Sub-agents share the tool namespace with plugin tools, so they go through
230
+ // the same `CapabilityGateMiddleware` filter as plugin tools.
231
+ const subAgentTools = await collectSubAgentsWithFallback({
232
+ registry: registries.subAgents,
233
+ buildCtx,
234
+ ambient,
235
+ state: wrapState,
236
+ userDid: requestCtx.user.did,
237
+ sessionId: requestCtx.session.id,
238
+ rtCtx,
239
+ sharedFactory,
240
+ fallbackContext,
241
+ subAgents: subAgentEntries,
242
+ });
243
+
244
+ ambient.logger.debug?.(
245
+ `[main-agent] binding summary (all bound; gated at runtime): ` +
246
+ `eager=${eagerTools.length} onDemand=${onDemandTools.length} silent=${silentTools.length} ` +
247
+ `subAgents=${subAgentEntries.length} loadedPlugins=[${Array.from(loadedSet).join(', ')}]`,
248
+ );
249
+
250
+ const tools: StructuredTool[] = [
251
+ ...metaTools.map((t) =>
252
+ wrapPluginTool(t, {
253
+ ambient,
254
+ state: wrapState,
255
+ sharedFactory,
256
+ fallbackContext,
257
+ }),
258
+ ),
259
+ ...eagerTools.map(wrap),
260
+ ...onDemandTools.map(wrap),
261
+ ...silentTools.map(wrap),
262
+ ...subAgentTools,
263
+ ];
264
+
265
+ // Lookups used by `CapabilityGateMiddleware` to gate on-demand plugins
266
+ // and sub-agents per model call. Meta-tools omitted from the map are
267
+ // pass-through.
268
+ const pluginByToolName = new Map<string, string>();
269
+ const visibilityByToolName = new Map<
270
+ string,
271
+ NonNullable<PluginManifest['visibility']>
272
+ >();
273
+ for (const { pluginName, tool } of allTools) {
274
+ pluginByToolName.set(tool.name, pluginName);
275
+ const effective =
276
+ tool.visibility ?? manifestViz.get(pluginName) ?? 'on-demand';
277
+ visibilityByToolName.set(tool.name, effective);
278
+ }
279
+ for (const { pluginName, subAgent } of subAgentEntries) {
280
+ const toolName = computeSubAgentToolName(subAgent.name);
281
+ pluginByToolName.set(toolName, pluginName);
282
+ visibilityByToolName.set(
283
+ toolName,
284
+ manifestViz.get(pluginName) ?? 'on-demand',
285
+ );
286
+ }
287
+
288
+ // ── 6. Middleware stack — always-on + plugin contributions ──────────────
289
+ const pluginMiddlewares = registries.middlewares
290
+ .collect(buildCtx)
291
+ .map(({ middleware }) => middleware);
292
+
293
+ // The summarization middleware needs the same resolver so a host's
294
+ // `hooks.resolveModel` override covers the summary model too.
295
+ const resolveModel = hooks?.resolveModel ?? ambient.llm.get.bind(ambient.llm);
296
+
297
+ const middleware = [
298
+ // Outermost, as on Node: rewrite cross-provider reasoning residue before
299
+ // the summarizer condenses (a well-formed) history.
300
+ createByoHistorySanitizerMiddleware({ logger: ambient.logger }),
301
+ // Condense long threads before everything tool-related.
302
+ // Without this, thread state — reloaded, re-serialized, and re-uploaded to
303
+ // the owner store on every turn — grows without bound.
304
+ createSummarizationMiddleware({ model: resolveModel('routing') }),
305
+ createCapabilityGateMiddleware({
306
+ pluginByToolName,
307
+ visibilityByToolName,
308
+ logger: ambient.logger,
309
+ }),
310
+ createToolValidationMiddleware({
311
+ skipToolNames: hooks?.validationSkipToolNames,
312
+ logger: ambient.logger,
313
+ }),
314
+ createToolRepetitionGuardMiddleware({ logger: ambient.logger }),
315
+ toolRetryMiddleware({ onFailure: (error) => error.message }),
316
+ // Same host-gated pair as the Node runtime: the page-context block needs a
317
+ // title lookup, the safety guardrail a classification model.
318
+ ...(hooks?.getRoomTitle
319
+ ? [
320
+ createPageContextMiddleware({
321
+ getRoomTitle: hooks.getRoomTitle,
322
+ logger: ambient.logger,
323
+ }),
324
+ ]
325
+ : []),
326
+ ...(hooks?.safetyModel
327
+ ? [
328
+ createSafetyGuardrailMiddleware({
329
+ safetyModel: hooks.safetyModel,
330
+ logger: ambient.logger,
331
+ }),
332
+ ]
333
+ : []),
334
+ ...pluginMiddlewares,
335
+ ...(hooks?.middlewares ?? []),
336
+ ];
337
+
338
+ // ── 7. Prompt composition ───────────────────────────────────────────────
339
+ const eagerEntries: Tier1Entry[] = manifestEntries.filter(
340
+ ({ manifest }) => manifest.visibility === 'always',
341
+ );
342
+ const tier1 = renderTier1({ manifests: eagerEntries });
343
+ for (const warning of tier1.warnings) ambient.logger.warn(warning);
344
+
345
+ const customInstructions = identity.prompt?.customInstructions?.trim() ?? '';
346
+
347
+ const systemPrompt = await composePrompt({
348
+ identity,
349
+ capabilityBlock: tier1.block,
350
+ customInstructions,
351
+ operationalMode: hooks?.operationalMode ?? DEFAULT_OPERATIONAL_MODE,
352
+ userPreferencesContext: formatUserPreferences(state.userPreferences),
353
+ userContext: state.userContext,
354
+ timeContext: formatTimeContext(
355
+ requestCtx.user.timezone,
356
+ requestCtx.user.currentTime,
357
+ ),
358
+ currentEntityDid: state.currentEntityDid ?? '',
359
+ oracleNameOverride: state.userPreferences?.agentName,
360
+ degradedServicesBlock: hooks?.degradedServicesBlock,
361
+ });
362
+
363
+ // ── 8. Model ────────────────────────────────────────────────────────────
364
+ // A per-request model (already allow-list-validated by the caller) wins
365
+ // over the role default; the adapter honours `params.model`.
366
+ const model = resolveModel(
367
+ 'main',
368
+ requestCtx.model ? { model: requestCtx.model } : undefined,
369
+ );
370
+
371
+ // ── 9. Compile ──────────────────────────────────────────────────────────
372
+ const agent = createAgent({
373
+ model,
374
+ tools,
375
+ middleware,
376
+ stateSchema: MainAgentGraphState,
377
+ systemPrompt,
378
+ ...(checkpointer ? { checkpointer } : {}),
379
+ name: identity.name,
380
+ });
381
+
382
+ return {
383
+ agent,
384
+ systemPrompt,
385
+ boundToolNames: tools.map((t) => t.name),
386
+ context: runConfig.context,
387
+ };
388
+ }
389
+
390
+ export { MainAgentGraphState };
391
+ export type {
392
+ CompiledMainAgent,
393
+ MainAgentArgs,
394
+ MainAgentBuildResult,
395
+ MainAgentHooks,
396
+ MainAgentRegistries,
397
+ MainAgentRequestContext,
398
+ } from './main-agent-types';
399
+ export type { TMainAgentGraphState } from './state';