@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.
- package/README.md +110 -0
- package/package.json +77 -0
- package/src/attachments/classify.ts +114 -0
- package/src/attachments/content-blocks.ts +51 -0
- package/src/attachments/download.ts +235 -0
- package/src/attachments/extract.ts +241 -0
- package/src/attachments/index.ts +84 -0
- package/src/attachments/magic.ts +133 -0
- package/src/attachments/pipeline.ts +409 -0
- package/src/attachments/retention.ts +205 -0
- package/src/attachments/route.ts +41 -0
- package/src/attachments/sandbox-archive.ts +90 -0
- package/src/attachments/sanitize.ts +35 -0
- package/src/attachments/types.ts +86 -0
- package/src/attachments/view.ts +107 -0
- package/src/core/env.ts +200 -0
- package/src/core/index.ts +513 -0
- package/src/core/llm.ts +842 -0
- package/src/core/main-agent-types.ts +123 -0
- package/src/core/main-agent.ts +399 -0
- package/src/core/manifest.ts +365 -0
- package/src/core/meta-tools.ts +232 -0
- package/src/core/middlewares/byo-history-sanitizer.ts +165 -0
- package/src/core/middlewares/capability-gate.ts +76 -0
- package/src/core/middlewares/index.ts +18 -0
- package/src/core/middlewares/page-context.ts +98 -0
- package/src/core/middlewares/safety-guardrail.ts +117 -0
- package/src/core/middlewares/summarization.ts +99 -0
- package/src/core/middlewares/tool-repetition-guard.ts +125 -0
- package/src/core/middlewares/tool-validation.ts +83 -0
- package/src/core/middlewares/work-status.ts +57 -0
- package/src/core/openrouter-pricing.ts +124 -0
- package/src/core/plugin-loader.ts +192 -0
- package/src/core/plugins/index.ts +6 -0
- package/src/core/plugins/skills/index.ts +8 -0
- package/src/core/plugins/skills/skills-tools.ts +356 -0
- package/src/core/plugins/skills/skills.plugin.ts +113 -0
- package/src/core/plugins/weather/index.ts +8 -0
- package/src/core/plugins/weather/weather-client.ts +153 -0
- package/src/core/plugins/weather/weather-middleware.ts +27 -0
- package/src/core/plugins/weather/weather-sub-agent.ts +65 -0
- package/src/core/plugins/weather/weather-tools.ts +103 -0
- package/src/core/plugins/weather/weather.plugin.ts +227 -0
- package/src/core/prompt-composer.ts +469 -0
- package/src/core/registries.ts +642 -0
- package/src/core/runtime-context.ts +619 -0
- package/src/core/state.ts +108 -0
- package/src/core/sub-agent-fallback.ts +187 -0
- package/src/core/subagent-as-tool.ts +322 -0
- package/src/core/template.ts +245 -0
- package/src/core/utils.ts +246 -0
- package/src/core/wrap-plugin-tool.ts +100 -0
- package/src/do/ambient.ts +266 -0
- package/src/do/contracts.ts +562 -0
- package/src/do/debug-timers.ts +120 -0
- package/src/do/fresh-stub.ts +32 -0
- package/src/do/gateway-retry.ts +73 -0
- package/src/do/secrets-adapter.ts +32 -0
- package/src/do/size-alerts.ts +76 -0
- package/src/do/sse-stream.ts +472 -0
- package/src/do/transcript.ts +192 -0
- package/src/do/turn-body.ts +84 -0
- package/src/do/turn-metadata.ts +146 -0
- package/src/do/ucan-service.ts +339 -0
- package/src/do/user-oracle-do.ts +3035 -0
- package/src/gateway-worker.ts +82 -0
- package/src/index.ts +237 -0
- package/src/llm/byo-adapter.ts +59 -0
- package/src/llm/byo-catalog.ts +520 -0
- package/src/llm/byo-client.ts +280 -0
- package/src/llm/byo-routes.ts +255 -0
- package/src/llm/byo-service.ts +836 -0
- package/src/llm/chatgpt-oauth.ts +430 -0
- package/src/llm/provider-error.ts +283 -0
- package/src/matrix/gateway-do.ts +918 -0
- package/src/matrix/ingest.ts +209 -0
- package/src/matrix/replay-format.ts +34 -0
- package/src/matrix/reply-chain.ts +109 -0
- package/src/matrix/room-state-codec.ts +115 -0
- package/src/matrix/user-homeserver.ts +106 -0
- package/src/matrix/work-status.ts +294 -0
- package/src/memory/session-history-indexer.ts +328 -0
- package/src/owner-store/ixo-vfs-store.ts +570 -0
- package/src/owner-store/matrix-media-store.ts +136 -0
- package/src/owner-store/migrating-store.ts +185 -0
- package/src/owner-store/owner-copy-errors.ts +175 -0
- package/src/owner-store/retry.ts +45 -0
- package/src/owner-store/tus-upload.ts +333 -0
- package/src/owner-store/types.ts +0 -0
- package/src/plugin-api/define-plugin.ts +45 -0
- package/src/plugin-api/index.ts +56 -0
- package/src/plugin-api/oracle-plugin.ts +145 -0
- package/src/plugin-api/tool-helper.ts +82 -0
- package/src/plugin-api/types.ts +816 -0
- package/src/plugin-api/ucan-errors.ts +16 -0
- package/src/plugins/agui/agui-agent.ts +115 -0
- package/src/plugins/agui/agui.plugin.ts +148 -0
- package/src/plugins/agui/index.ts +2 -0
- package/src/plugins/attachments/attachments.plugin.ts +54 -0
- package/src/plugins/attachments/index.ts +6 -0
- package/src/plugins/attachments/view-attachment-tool.ts +91 -0
- package/src/plugins/base64.ts +26 -0
- package/src/plugins/composio/composio-tools.ts +365 -0
- package/src/plugins/composio/composio-ucan.ts +43 -0
- package/src/plugins/composio/composio.plugin.ts +214 -0
- package/src/plugins/composio/index.ts +15 -0
- package/src/plugins/domain-indexer/domain-indexer-agent.ts +57 -0
- package/src/plugins/domain-indexer/domain-indexer-tools.ts +253 -0
- package/src/plugins/domain-indexer/domain-indexer.plugin.ts +100 -0
- package/src/plugins/domain-indexer/index.ts +3 -0
- package/src/plugins/editor/blocknote-bridge.ts +120 -0
- package/src/plugins/editor/content-session.ts +331 -0
- package/src/plugins/editor/content-tools.ts +686 -0
- package/src/plugins/editor/document-model.ts +0 -0
- package/src/plugins/editor/editor-access-denied-tool.ts +58 -0
- package/src/plugins/editor/editor-agent.ts +34 -0
- package/src/plugins/editor/editor-config.ts +64 -0
- package/src/plugins/editor/editor-mx.ts +128 -0
- package/src/plugins/editor/editor.plugin.ts +143 -0
- package/src/plugins/editor/failures.ts +154 -0
- package/src/plugins/editor/index.ts +31 -0
- package/src/plugins/editor/jsdom-shim.ts +55 -0
- package/src/plugins/editor/prompts.ts +59 -0
- package/src/plugins/editor/prop-policy.ts +147 -0
- package/src/plugins/editor/provider.ts +386 -0
- package/src/plugins/editor/room-membership.ts +95 -0
- package/src/plugins/editor/standalone-editor-tool.ts +200 -0
- package/src/plugins/firecrawl/firecrawl-agent.ts +93 -0
- package/src/plugins/firecrawl/firecrawl-tools.ts +220 -0
- package/src/plugins/firecrawl/firecrawl.plugin.ts +105 -0
- package/src/plugins/firecrawl/index.ts +15 -0
- package/src/plugins/flows/action-metadata.ts +3505 -0
- package/src/plugins/flows/actions.ts +190 -0
- package/src/plugins/flows/edit.ts +314 -0
- package/src/plugins/flows/errors.ts +38 -0
- package/src/plugins/flows/explain.ts +43 -0
- package/src/plugins/flows/flow-doc.ts +111 -0
- package/src/plugins/flows/flows.plugin.ts +129 -0
- package/src/plugins/flows/forms.ts +330 -0
- package/src/plugins/flows/index.ts +2 -0
- package/src/plugins/flows/linkage.ts +147 -0
- package/src/plugins/flows/port-types.ts +53 -0
- package/src/plugins/flows/prompts.ts +65 -0
- package/src/plugins/flows/read.ts +392 -0
- package/src/plugins/flows/references.ts +66 -0
- package/src/plugins/flows/test-support.ts +165 -0
- package/src/plugins/flows/tools/authoring.ts +516 -0
- package/src/plugins/flows/tools/discovery.ts +119 -0
- package/src/plugins/flows/tools/forms.ts +142 -0
- package/src/plugins/flows/tools/inspect.ts +165 -0
- package/src/plugins/flows/tools/linkage.ts +117 -0
- package/src/plugins/flows/tools/settings.ts +184 -0
- package/src/plugins/flows/translator.ts +328 -0
- package/src/plugins/flows/types.ts +210 -0
- package/src/plugins/flows/ydoc-helpers.ts +543 -0
- package/src/plugins/index.ts +70 -0
- package/src/plugins/mcp-call-timeout.ts +38 -0
- package/src/plugins/mcp-tool-adapter.ts +87 -0
- package/src/plugins/memory/index.ts +17 -0
- package/src/plugins/memory/memory-tools.ts +463 -0
- package/src/plugins/memory/memory-ucan.ts +51 -0
- package/src/plugins/memory/memory.plugin.ts +122 -0
- package/src/plugins/memory/types.ts +147 -0
- package/src/plugins/portal/action-log.ts +34 -0
- package/src/plugins/portal/index.ts +2 -0
- package/src/plugins/portal/portal.plugin.ts +203 -0
- package/src/plugins/sandbox/index.ts +34 -0
- package/src/plugins/sandbox/sandbox-bridge.ts +295 -0
- package/src/plugins/sandbox/sandbox-mcp.ts +127 -0
- package/src/plugins/sandbox/sandbox-write-blob.ts +103 -0
- package/src/plugins/sandbox/sandbox.plugin.ts +599 -0
- package/src/plugins/tasks/index.ts +17 -0
- package/src/plugins/tasks/manifest.ts +81 -0
- package/src/plugins/tasks/middleware.ts +219 -0
- package/src/plugins/tasks/tasks-tools.ts +414 -0
- package/src/plugins/tasks/tasks.plugin.ts +74 -0
- package/src/plugins/ucan-failure.ts +133 -0
- package/src/plugins/user-preferences/index.ts +22 -0
- package/src/plugins/user-preferences/schema.ts +31 -0
- package/src/plugins/user-preferences/user-preferences-store.ts +172 -0
- package/src/plugins/user-preferences/user-preferences-tool.ts +131 -0
- package/src/plugins/user-preferences/user-preferences.plugin.ts +137 -0
- package/src/plugins/vfs/index.ts +40 -0
- package/src/plugins/vfs/vfs-auth.ts +56 -0
- package/src/plugins/vfs/vfs-client.ts +655 -0
- package/src/plugins/vfs/vfs-content.ts +181 -0
- package/src/plugins/vfs/vfs-errors.ts +163 -0
- package/src/plugins/vfs/vfs-sandbox-tools.ts +272 -0
- package/src/plugins/vfs/vfs-tools.ts +635 -0
- package/src/plugins/vfs/vfs.plugin.ts +201 -0
- package/src/realtime/frontend-call-registry.ts +163 -0
- package/src/realtime/realtime-endpoint.ts +619 -0
- package/src/realtime/session-socket-hub.ts +249 -0
- package/src/realtime/socket-io-codec.ts +187 -0
- package/src/secrets/jwe.ts +74 -0
- package/src/secrets/pin-cipher.ts +43 -0
- package/src/secrets/secrets-service.ts +319 -0
- package/src/shell/app.ts +564 -0
- package/src/shell/auth.ts +290 -0
- package/src/sqlite/blob-codec.ts +100 -0
- package/src/sqlite/blob-compactor.ts +146 -0
- package/src/sqlite/cache-config.ts +45 -0
- package/src/sqlite/database.ts +643 -0
- package/src/sqlite/do-vfs.ts +1485 -0
- package/src/sqlite/index.ts +61 -0
- package/src/sqlite/serialization.ts +370 -0
- package/src/sqlite/sessions-store.ts +311 -0
- package/src/sqlite/sqlite-saver.ts +919 -0
- package/src/sqlite/vacuum-policy.ts +77 -0
- package/src/sqlite/wa-sqlite-loader.ts +123 -0
- package/src/sqlite/wasm.d.ts +10 -0
- package/src/tasks/schedule.ts +174 -0
- package/src/tasks/scheduler.ts +753 -0
- package/src/tasks/spec.ts +125 -0
- package/src/tasks/store.ts +351 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Low-level Y.Doc block helpers for the flows plugin.
|
|
3
|
+
*
|
|
4
|
+
* These moved here from the editor plugin when it became a content assistant:
|
|
5
|
+
* they read and write flow **run state** (`Y.Map('runtime')`) and the compiled
|
|
6
|
+
* action blocks' `attrs` wrapper, which the content assistant must never touch.
|
|
7
|
+
* The flows plugin owns run state, so it owns these.
|
|
8
|
+
*
|
|
9
|
+
* Everything here operates on an already-synced `Y.Doc` — no Matrix I/O.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as Y from 'yjs';
|
|
13
|
+
|
|
14
|
+
const DEFAULT_FRAGMENT_NAME = 'document';
|
|
15
|
+
const DEFAULT_BLOCK_TYPE = 'paragraph';
|
|
16
|
+
const MUTATION_ORIGIN = 'ixo-oracle-flows';
|
|
17
|
+
|
|
18
|
+
// ── Attribute access ─────────────────────────────────────────────────
|
|
19
|
+
// BlockNote stores a block's canonical props under an `attrs` attribute whose
|
|
20
|
+
// value is an object, while yjs types XmlElement attributes as strings. The
|
|
21
|
+
// readers below narrow at runtime instead of asserting; the writer takes the
|
|
22
|
+
// element through a structural interface whose method signature is bivariant,
|
|
23
|
+
// so an object value is accepted without widening yjs's own types.
|
|
24
|
+
|
|
25
|
+
const isPlainObject = (value: unknown): value is Record<string, unknown> =>
|
|
26
|
+
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
27
|
+
|
|
28
|
+
interface AttributeWritable {
|
|
29
|
+
setAttribute(attributeName: string, attributeValue: unknown): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const setAnyAttribute = (
|
|
33
|
+
element: Y.XmlElement,
|
|
34
|
+
key: string,
|
|
35
|
+
value: unknown,
|
|
36
|
+
): void => {
|
|
37
|
+
const target: AttributeWritable = element;
|
|
38
|
+
target.setAttribute(key, value);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** A block's `id`, whether stored on the element or inside `attrs`. */
|
|
42
|
+
function readIdAttribute(element: Y.XmlElement): string | undefined {
|
|
43
|
+
const attrs = readAttrsObject(element);
|
|
44
|
+
const fromAttrs = attrs?.id;
|
|
45
|
+
if (typeof fromAttrs === 'string') return fromAttrs;
|
|
46
|
+
const direct = element.getAttribute('id');
|
|
47
|
+
return typeof direct === 'string' ? direct : undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The `attrs` object BlockNote keeps a block's canonical props in. */
|
|
51
|
+
function readAttrsObject(
|
|
52
|
+
element: Y.XmlElement,
|
|
53
|
+
): Record<string, unknown> | undefined {
|
|
54
|
+
const attributes: Record<string, unknown> = element.getAttributes();
|
|
55
|
+
const attrs = attributes.attrs;
|
|
56
|
+
return isPlainObject(attrs) ? attrs : undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function safeParseJson(value: string): unknown {
|
|
60
|
+
try {
|
|
61
|
+
return JSON.parse(value);
|
|
62
|
+
} catch {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── Traversal ────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
export interface BlockDetail {
|
|
70
|
+
id: string;
|
|
71
|
+
blockType: string;
|
|
72
|
+
nodeName: string;
|
|
73
|
+
attributes: Record<string, unknown>;
|
|
74
|
+
text?: string;
|
|
75
|
+
children?: BlockDetail[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface BlockSnapshot {
|
|
79
|
+
id: string;
|
|
80
|
+
type: string;
|
|
81
|
+
text: string;
|
|
82
|
+
attributes: Record<string, unknown>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface ParentResult {
|
|
86
|
+
parent: Y.XmlElement | Y.XmlFragment;
|
|
87
|
+
index: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function findBlockById(
|
|
91
|
+
container: Y.XmlElement | Y.XmlFragment,
|
|
92
|
+
blockId: string,
|
|
93
|
+
): Y.XmlElement | null {
|
|
94
|
+
for (const node of container.toArray()) {
|
|
95
|
+
if (!(node instanceof Y.XmlElement)) continue;
|
|
96
|
+
if (readIdAttribute(node) === blockId) return node;
|
|
97
|
+
const nested = findBlockById(node, blockId);
|
|
98
|
+
if (nested) return nested;
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Walk the tree to find a block's parent container and its index within it. */
|
|
104
|
+
export const findParentOf = (
|
|
105
|
+
container: Y.XmlElement | Y.XmlFragment,
|
|
106
|
+
blockId: string,
|
|
107
|
+
): ParentResult | null => {
|
|
108
|
+
const nodes = container.toArray();
|
|
109
|
+
for (let i = 0; i < nodes.length; i += 1) {
|
|
110
|
+
const node = nodes[i];
|
|
111
|
+
if (!(node instanceof Y.XmlElement)) continue;
|
|
112
|
+
if (readIdAttribute(node) === blockId)
|
|
113
|
+
return { parent: container, index: i };
|
|
114
|
+
const nested = findParentOf(node, blockId);
|
|
115
|
+
if (nested) return nested;
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
function extractText(element: Y.XmlElement): string {
|
|
121
|
+
const parts: string[] = [];
|
|
122
|
+
const visit = (node: Y.XmlElement | Y.XmlText): void => {
|
|
123
|
+
if (node instanceof Y.XmlText) {
|
|
124
|
+
parts.push(node.toString());
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
for (const child of node.toArray()) {
|
|
128
|
+
if (child instanceof Y.XmlElement || child instanceof Y.XmlText) {
|
|
129
|
+
visit(child);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
visit(element);
|
|
134
|
+
return parts.join('');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function extractBlockDetail(
|
|
138
|
+
element: Y.XmlElement | Y.XmlText,
|
|
139
|
+
): BlockDetail | null {
|
|
140
|
+
if (element instanceof Y.XmlText) {
|
|
141
|
+
return {
|
|
142
|
+
id: '',
|
|
143
|
+
nodeName: '#text',
|
|
144
|
+
blockType: '',
|
|
145
|
+
attributes: {},
|
|
146
|
+
children: [],
|
|
147
|
+
text: element.toString(),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const detail: BlockDetail = {
|
|
152
|
+
id: element.getAttribute('id') || '',
|
|
153
|
+
nodeName: element.nodeName,
|
|
154
|
+
blockType: '',
|
|
155
|
+
attributes: {},
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
for (const [key, value] of Object.entries(element.getAttributes())) {
|
|
159
|
+
detail.attributes[key] = value;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const attrsValue = readAttrsObject(element);
|
|
163
|
+
if (attrsValue) {
|
|
164
|
+
detail.attributes.attrs = attrsValue;
|
|
165
|
+
const attrsType = attrsValue.type;
|
|
166
|
+
if (typeof attrsType === 'string') detail.blockType = attrsType;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
detail.children = [];
|
|
170
|
+
for (const child of element.toArray()) {
|
|
171
|
+
if (child instanceof Y.XmlElement || child instanceof Y.XmlText) {
|
|
172
|
+
const childDetail = extractBlockDetail(child);
|
|
173
|
+
if (childDetail) detail.children.push(childDetail);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const textContent = extractText(element);
|
|
178
|
+
if (textContent.length > 0) detail.text = textContent;
|
|
179
|
+
|
|
180
|
+
return detail;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Every `blockContainer` in the document, depth-first. */
|
|
184
|
+
export function collectAllBlocks(fragment: Y.XmlFragment): BlockDetail[] {
|
|
185
|
+
const results: BlockDetail[] = [];
|
|
186
|
+
|
|
187
|
+
const walk = (container: Y.XmlElement | Y.XmlFragment): void => {
|
|
188
|
+
for (const node of container.toArray()) {
|
|
189
|
+
if (!(node instanceof Y.XmlElement)) continue;
|
|
190
|
+
if (node.nodeName === 'blockContainer') {
|
|
191
|
+
const detail = extractBlockDetail(node);
|
|
192
|
+
if (detail) results.push(detail);
|
|
193
|
+
}
|
|
194
|
+
walk(node);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
walk(fragment);
|
|
199
|
+
return results;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** One block's detail, or `null` when the id is not in the document. */
|
|
203
|
+
export function getBlockDetail(
|
|
204
|
+
doc: Y.Doc,
|
|
205
|
+
blockId: string,
|
|
206
|
+
): BlockDetail | null {
|
|
207
|
+
const container = findBlockById(
|
|
208
|
+
doc.getXmlFragment(DEFAULT_FRAGMENT_NAME),
|
|
209
|
+
blockId,
|
|
210
|
+
);
|
|
211
|
+
return container ? extractBlockDetail(container) : null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Flatten a block's props out of the CRDT structure:
|
|
216
|
+
* 1. every entry from `attrs.props` (canonical)
|
|
217
|
+
* 2. gaps filled from the content element's direct attributes
|
|
218
|
+
* 3. JSON-string props (`surveySchema`, `answers`, `inputs`, `links`) parsed
|
|
219
|
+
*
|
|
220
|
+
* Generic — no per-block-type field names.
|
|
221
|
+
*/
|
|
222
|
+
export function extractBlockProperties(
|
|
223
|
+
detail: BlockDetail,
|
|
224
|
+
): Record<string, unknown> {
|
|
225
|
+
const merged: Record<string, unknown> = {};
|
|
226
|
+
|
|
227
|
+
const directAttrs = detail.attributes || {};
|
|
228
|
+
const attrsObj = isPlainObject(directAttrs.attrs) ? directAttrs.attrs : {};
|
|
229
|
+
const attrsProps = isPlainObject(attrsObj.props) ? attrsObj.props : {};
|
|
230
|
+
for (const [key, value] of Object.entries(attrsProps)) {
|
|
231
|
+
merged[key] = value;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const contentChild = detail.children?.find(
|
|
235
|
+
(child) =>
|
|
236
|
+
child.nodeName &&
|
|
237
|
+
child.nodeName !== '#text' &&
|
|
238
|
+
child.nodeName !== 'blockGroup' &&
|
|
239
|
+
child.nodeName !== 'blockContainer',
|
|
240
|
+
);
|
|
241
|
+
if (contentChild) {
|
|
242
|
+
for (const [key, value] of Object.entries(contentChild.attributes || {})) {
|
|
243
|
+
if (key === 'id' || key === 'attrs') continue;
|
|
244
|
+
if (!(key in merged)) merged[key] = value;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
for (const key of ['surveySchema', 'answers', 'inputs'] as const) {
|
|
249
|
+
const raw = merged[key];
|
|
250
|
+
if (typeof raw !== 'string') continue;
|
|
251
|
+
const parsed = safeParseJson(raw);
|
|
252
|
+
if (isPlainObject(parsed)) merged[key] = parsed;
|
|
253
|
+
}
|
|
254
|
+
if (typeof merged.links === 'string') {
|
|
255
|
+
const parsed = safeParseJson(merged.links);
|
|
256
|
+
if (Array.isArray(parsed)) merged.links = parsed;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return merged;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ── Run state ────────────────────────────────────────────────────────
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Read per-block run state from `Y.Map('runtime')`. Scoped to one block when
|
|
266
|
+
* `nodeId` is given.
|
|
267
|
+
*/
|
|
268
|
+
export function readRuntimeState(
|
|
269
|
+
doc: Y.Doc,
|
|
270
|
+
nodeId?: string,
|
|
271
|
+
): Record<string, Record<string, unknown>> {
|
|
272
|
+
const runtimeMap = doc.getMap('runtime');
|
|
273
|
+
if (nodeId) {
|
|
274
|
+
const state = runtimeMap.get(nodeId);
|
|
275
|
+
return isPlainObject(state) ? { [nodeId]: state } : {};
|
|
276
|
+
}
|
|
277
|
+
const result: Record<string, Record<string, unknown>> = {};
|
|
278
|
+
runtimeMap.forEach((value, key) => {
|
|
279
|
+
if (isPlainObject(value)) result[key] = value;
|
|
280
|
+
});
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Merge updates into a block's run state. Must be called inside a
|
|
286
|
+
* `doc.transact()` by the caller.
|
|
287
|
+
*/
|
|
288
|
+
export function updateRuntimeState(
|
|
289
|
+
doc: Y.Doc,
|
|
290
|
+
blockId: string,
|
|
291
|
+
updates: Record<string, unknown>,
|
|
292
|
+
): Record<string, unknown> {
|
|
293
|
+
const runtimeMap = doc.getMap('runtime');
|
|
294
|
+
const existing = runtimeMap.get(blockId);
|
|
295
|
+
const merged = { ...(isPlainObject(existing) ? existing : {}), ...updates };
|
|
296
|
+
runtimeMap.set(blockId, merged);
|
|
297
|
+
return merged;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ── Mutations ────────────────────────────────────────────────────────
|
|
301
|
+
|
|
302
|
+
function snapshotBlock(element: Y.XmlElement): BlockSnapshot {
|
|
303
|
+
const attrs = readAttrsObject(element) ?? {};
|
|
304
|
+
const blockId = readIdAttribute(element);
|
|
305
|
+
if (!blockId) throw new Error('Unable to derive block id from element');
|
|
306
|
+
|
|
307
|
+
const blockType =
|
|
308
|
+
(typeof attrs.type === 'string' ? attrs.type : undefined) ??
|
|
309
|
+
element.nodeName;
|
|
310
|
+
const textNode = element
|
|
311
|
+
.toArray()
|
|
312
|
+
.find(
|
|
313
|
+
(node): node is Y.XmlElement =>
|
|
314
|
+
node instanceof Y.XmlElement && node.nodeName !== 'blockGroup',
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
id: blockId,
|
|
319
|
+
type: blockType,
|
|
320
|
+
text: textNode ? extractText(textNode) : '',
|
|
321
|
+
attributes: attrs,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function applyAttributeUpdates(
|
|
326
|
+
element: Y.XmlElement,
|
|
327
|
+
updates: Record<string, unknown> = {},
|
|
328
|
+
removals: string[] = [],
|
|
329
|
+
): void {
|
|
330
|
+
const existing = readAttrsObject(element) ?? {};
|
|
331
|
+
const { props: propsUpdates, ...rest } = updates;
|
|
332
|
+
const next: Record<string, unknown> = { ...existing, ...rest };
|
|
333
|
+
|
|
334
|
+
if (isPlainObject(propsUpdates)) {
|
|
335
|
+
const existingProps = isPlainObject(existing.props) ? existing.props : {};
|
|
336
|
+
const mergedProps: Record<string, unknown> = { ...existingProps };
|
|
337
|
+
|
|
338
|
+
for (const [key, value] of Object.entries(propsUpdates)) {
|
|
339
|
+
// Auto-serialize: when the stored value is a JSON string and the new
|
|
340
|
+
// value is structured, merge (objects) or replace (arrays) and write the
|
|
341
|
+
// JSON string back.
|
|
342
|
+
if (
|
|
343
|
+
typeof existingProps[key] === 'string' &&
|
|
344
|
+
value !== null &&
|
|
345
|
+
typeof value === 'object'
|
|
346
|
+
) {
|
|
347
|
+
if (Array.isArray(value)) {
|
|
348
|
+
mergedProps[key] = JSON.stringify(value);
|
|
349
|
+
} else {
|
|
350
|
+
const parsed = safeParseJson(String(existingProps[key]));
|
|
351
|
+
const existingObj = isPlainObject(parsed) ? parsed : {};
|
|
352
|
+
mergedProps[key] = JSON.stringify({ ...existingObj, ...value });
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
mergedProps[key] = value;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
next.props = mergedProps;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
for (const key of removals) {
|
|
363
|
+
if (key.startsWith('props.')) {
|
|
364
|
+
const propKey = key.slice('props.'.length);
|
|
365
|
+
if (isPlainObject(next.props)) delete next.props[propKey];
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
delete next[key];
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
setAnyAttribute(element, 'attrs', next);
|
|
372
|
+
|
|
373
|
+
const nextId = next.id;
|
|
374
|
+
if (typeof nextId === 'string') element.setAttribute('id', nextId);
|
|
375
|
+
|
|
376
|
+
const blockContent = element
|
|
377
|
+
.toArray()
|
|
378
|
+
.find(
|
|
379
|
+
(node): node is Y.XmlElement =>
|
|
380
|
+
node instanceof Y.XmlElement && node.nodeName !== 'blockGroup',
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
// Mirror the post-merge props onto the content element so JSON-string props
|
|
384
|
+
// carry their serialized value.
|
|
385
|
+
if (
|
|
386
|
+
blockContent &&
|
|
387
|
+
isPlainObject(next.props) &&
|
|
388
|
+
isPlainObject(propsUpdates)
|
|
389
|
+
) {
|
|
390
|
+
const finalProps = next.props;
|
|
391
|
+
for (const key of Object.keys(propsUpdates)) {
|
|
392
|
+
const value = finalProps[key];
|
|
393
|
+
blockContent.setAttribute(
|
|
394
|
+
key,
|
|
395
|
+
typeof value === 'string' ? value : JSON.stringify(value),
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (blockContent) {
|
|
401
|
+
for (const key of removals) {
|
|
402
|
+
if (!key.startsWith('props.')) continue;
|
|
403
|
+
blockContent.removeAttribute(key.slice('props.'.length));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function applyTextUpdate(
|
|
409
|
+
element: Y.XmlElement,
|
|
410
|
+
text: string | null | undefined,
|
|
411
|
+
): void {
|
|
412
|
+
if (typeof text === 'undefined') return;
|
|
413
|
+
|
|
414
|
+
const blockContent = element
|
|
415
|
+
.toArray()
|
|
416
|
+
.find(
|
|
417
|
+
(node): node is Y.XmlElement =>
|
|
418
|
+
node instanceof Y.XmlElement && node.nodeName !== 'blockGroup',
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
if (!blockContent) {
|
|
422
|
+
if (text === null || text === '') return;
|
|
423
|
+
const contentNode = new Y.XmlElement(DEFAULT_BLOCK_TYPE);
|
|
424
|
+
const inlineText = new Y.XmlText();
|
|
425
|
+
inlineText.insert(0, text);
|
|
426
|
+
contentNode.push([inlineText]);
|
|
427
|
+
element.push([contentNode]);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const textNode = blockContent
|
|
432
|
+
.toArray()
|
|
433
|
+
.find((child): child is Y.XmlText => child instanceof Y.XmlText);
|
|
434
|
+
|
|
435
|
+
if (!textNode) {
|
|
436
|
+
if (text === null || text === '') return;
|
|
437
|
+
const inlineText = new Y.XmlText();
|
|
438
|
+
inlineText.insert(0, text);
|
|
439
|
+
blockContent.push([inlineText]);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
textNode.delete(0, textNode.length);
|
|
444
|
+
if (text && text.length > 0) textNode.insert(0, text);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export interface EditBlockOptions {
|
|
448
|
+
blockId: string;
|
|
449
|
+
attributes?: Record<string, unknown>;
|
|
450
|
+
removeAttributes?: string[];
|
|
451
|
+
text?: string | null;
|
|
452
|
+
/** Fragment to edit; defaults to the document fragment. */
|
|
453
|
+
docName?: string;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/** Update a block's `attrs`/props and optionally its plain text. */
|
|
457
|
+
export const editBlock = (
|
|
458
|
+
doc: Y.Doc,
|
|
459
|
+
options: EditBlockOptions,
|
|
460
|
+
): BlockSnapshot => {
|
|
461
|
+
const {
|
|
462
|
+
blockId,
|
|
463
|
+
attributes = {},
|
|
464
|
+
removeAttributes = [],
|
|
465
|
+
text,
|
|
466
|
+
docName = DEFAULT_FRAGMENT_NAME,
|
|
467
|
+
} = options;
|
|
468
|
+
|
|
469
|
+
let snapshot: BlockSnapshot | undefined;
|
|
470
|
+
|
|
471
|
+
doc.transact(() => {
|
|
472
|
+
const fragment = doc.getXmlFragment(docName);
|
|
473
|
+
const target = findBlockById(fragment, blockId);
|
|
474
|
+
if (!target) throw new Error(`Block with id ${blockId} not found`);
|
|
475
|
+
|
|
476
|
+
applyAttributeUpdates(target, attributes, removeAttributes);
|
|
477
|
+
applyTextUpdate(target, text);
|
|
478
|
+
snapshot = snapshotBlock(target);
|
|
479
|
+
}, MUTATION_ORIGIN);
|
|
480
|
+
|
|
481
|
+
if (!snapshot) throw new Error('Failed to update block snapshot');
|
|
482
|
+
return snapshot;
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
export interface DeleteBlockOptions {
|
|
486
|
+
blockId: string;
|
|
487
|
+
/** Fragment to delete from; defaults to the document fragment. */
|
|
488
|
+
docName?: string;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/** Remove a block by id. Returns `false` when the id is not in the document. */
|
|
492
|
+
export const deleteBlock = (
|
|
493
|
+
doc: Y.Doc,
|
|
494
|
+
options: DeleteBlockOptions,
|
|
495
|
+
): boolean => {
|
|
496
|
+
const { blockId, docName = DEFAULT_FRAGMENT_NAME } = options;
|
|
497
|
+
let deleted = false;
|
|
498
|
+
|
|
499
|
+
const deleteFromContainer = (
|
|
500
|
+
container: Y.XmlElement | Y.XmlFragment,
|
|
501
|
+
): boolean => {
|
|
502
|
+
const nodes = container.toArray();
|
|
503
|
+
for (let i = 0; i < nodes.length; i += 1) {
|
|
504
|
+
const node = nodes[i];
|
|
505
|
+
if (!(node instanceof Y.XmlElement)) continue;
|
|
506
|
+
if (readIdAttribute(node) === blockId) {
|
|
507
|
+
container.delete(i, 1);
|
|
508
|
+
return true;
|
|
509
|
+
}
|
|
510
|
+
if (deleteFromContainer(node)) return true;
|
|
511
|
+
}
|
|
512
|
+
return false;
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
doc.transact(() => {
|
|
516
|
+
deleted = deleteFromContainer(doc.getXmlFragment(docName));
|
|
517
|
+
}, MUTATION_ORIGIN);
|
|
518
|
+
|
|
519
|
+
return deleted;
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
// ── Survey shapes ────────────────────────────────────────────────────
|
|
523
|
+
|
|
524
|
+
/** One question in a form step's SurveyJS schema. */
|
|
525
|
+
export interface SurveyElement {
|
|
526
|
+
type: string;
|
|
527
|
+
name: string;
|
|
528
|
+
title?: string;
|
|
529
|
+
description?: string;
|
|
530
|
+
isRequired?: boolean;
|
|
531
|
+
visibleIf?: string;
|
|
532
|
+
defaultValue?: unknown;
|
|
533
|
+
defaultValueExpression?: string;
|
|
534
|
+
inputType?: string;
|
|
535
|
+
choices?: Array<{ value: string; text: string }>;
|
|
536
|
+
choicesByUrl?: {
|
|
537
|
+
url: string;
|
|
538
|
+
valueName: string;
|
|
539
|
+
titleName: string;
|
|
540
|
+
};
|
|
541
|
+
templateElements?: SurveyElement[];
|
|
542
|
+
elements?: SurveyElement[];
|
|
543
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundled Workers-runtime plugins — ports of the Node runtime's MCP / fetch
|
|
3
|
+
* plugins. Each is gated the same way as on Node: `autoDetect` on its env
|
|
4
|
+
* key where one exists (memory / sandbox / firecrawl / composio), always-on
|
|
5
|
+
* otherwise (domain-indexer; vfs self-gates on `ctx.ucan.hasSigningKey()`).
|
|
6
|
+
*
|
|
7
|
+
* A host opts in by spreading `BUNDLED_WORKERS_PLUGINS` into
|
|
8
|
+
* `createOracleWorker({ plugins })` — plugins whose env keys are absent are
|
|
9
|
+
* skipped by their `autoDetect`.
|
|
10
|
+
*/
|
|
11
|
+
import type { OraclePlugin } from '../plugin-api/oracle-plugin';
|
|
12
|
+
import { ComposioPlugin } from './composio';
|
|
13
|
+
import { EditorPlugin } from './editor';
|
|
14
|
+
import { TasksPlugin } from './tasks'; // pre-constructed object (defineOraclePlugin)
|
|
15
|
+
import { DomainIndexerPlugin } from './domain-indexer';
|
|
16
|
+
import { FirecrawlPlugin } from './firecrawl';
|
|
17
|
+
import { MemoryPlugin } from './memory';
|
|
18
|
+
import { SandboxPlugin } from './sandbox';
|
|
19
|
+
import { VfsPlugin } from './vfs';
|
|
20
|
+
import { UserPreferencesPlugin } from './user-preferences';
|
|
21
|
+
import { PortalPlugin } from './portal';
|
|
22
|
+
import { AGUIPlugin } from './agui';
|
|
23
|
+
import { AttachmentsPlugin } from './attachments';
|
|
24
|
+
|
|
25
|
+
export * from './composio';
|
|
26
|
+
export * from './editor';
|
|
27
|
+
export * from './flows';
|
|
28
|
+
export * from './tasks';
|
|
29
|
+
export * from './domain-indexer';
|
|
30
|
+
export * from './firecrawl';
|
|
31
|
+
export * from './memory';
|
|
32
|
+
export * from './sandbox';
|
|
33
|
+
export * from './vfs';
|
|
34
|
+
export * from './user-preferences';
|
|
35
|
+
export * from './portal';
|
|
36
|
+
export * from './agui';
|
|
37
|
+
|
|
38
|
+
export const memoryPlugin = new MemoryPlugin();
|
|
39
|
+
export const sandboxPlugin = new SandboxPlugin();
|
|
40
|
+
export const firecrawlPlugin = new FirecrawlPlugin();
|
|
41
|
+
export const domainIndexerPlugin = new DomainIndexerPlugin();
|
|
42
|
+
export const composioPlugin = new ComposioPlugin();
|
|
43
|
+
export const vfsPlugin = new VfsPlugin();
|
|
44
|
+
export const editorPlugin = new EditorPlugin();
|
|
45
|
+
export const userPreferencesPlugin = new UserPreferencesPlugin();
|
|
46
|
+
export const portalPlugin = new PortalPlugin();
|
|
47
|
+
export const aguiPlugin = new AGUIPlugin();
|
|
48
|
+
export const attachmentsPlugin = new AttachmentsPlugin();
|
|
49
|
+
/** TasksPlugin is already an object (defineOraclePlugin), not a class. */
|
|
50
|
+
export const tasksPlugin = TasksPlugin;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The bundled plugin set ported to the Workers runtime, in dependency-free
|
|
54
|
+
* registration order. Forks that need custom options construct their own
|
|
55
|
+
* instances instead of using these singletons.
|
|
56
|
+
*/
|
|
57
|
+
export const BUNDLED_WORKERS_PLUGINS = [
|
|
58
|
+
memoryPlugin,
|
|
59
|
+
sandboxPlugin,
|
|
60
|
+
firecrawlPlugin,
|
|
61
|
+
domainIndexerPlugin,
|
|
62
|
+
composioPlugin,
|
|
63
|
+
vfsPlugin,
|
|
64
|
+
tasksPlugin,
|
|
65
|
+
editorPlugin,
|
|
66
|
+
userPreferencesPlugin,
|
|
67
|
+
portalPlugin,
|
|
68
|
+
aguiPlugin,
|
|
69
|
+
attachmentsPlugin,
|
|
70
|
+
] as const satisfies readonly OraclePlugin[];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-call timeout for upstream MCP tool invocations.
|
|
3
|
+
*
|
|
4
|
+
* Never hand the MCP SDK a tool timeout (`defaultToolTimeout` / `timeout`):
|
|
5
|
+
* on workerd a client created with one leaves the user's Durable Object
|
|
6
|
+
* resident after `close()` for the whole timeout window (measured: 47 s+ on
|
|
7
|
+
* a minimal object; the oracle's 420 s memory timeout pinned it for 7 min
|
|
8
|
+
* after every memory call), which blocks WebSocket hibernation and bills
|
|
9
|
+
* duration. A plain race against a timer that is ALWAYS cleared has no such
|
|
10
|
+
* effect. `onTimeout` lets the caller tear the client down so the abandoned
|
|
11
|
+
* request cannot keep the object busy either.
|
|
12
|
+
*/
|
|
13
|
+
export class McpCallTimeoutError extends Error {
|
|
14
|
+
constructor(label: string, ms: number) {
|
|
15
|
+
super(`${label} did not complete within ${Math.round(ms / 1000)} s`);
|
|
16
|
+
this.name = 'McpCallTimeoutError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function withCallTimeout<T>(
|
|
21
|
+
run: () => Promise<T>,
|
|
22
|
+
ms: number,
|
|
23
|
+
label: string,
|
|
24
|
+
onTimeout?: () => void | Promise<void>,
|
|
25
|
+
): Promise<T> {
|
|
26
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
27
|
+
const timeout = new Promise<never>((_, reject) => {
|
|
28
|
+
timer = setTimeout(() => {
|
|
29
|
+
void Promise.resolve(onTimeout?.()).catch(() => undefined);
|
|
30
|
+
reject(new McpCallTimeoutError(label, ms));
|
|
31
|
+
}, ms);
|
|
32
|
+
});
|
|
33
|
+
try {
|
|
34
|
+
return await Promise.race([run(), timeout]);
|
|
35
|
+
} finally {
|
|
36
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
37
|
+
}
|
|
38
|
+
}
|