@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,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The FlowSpec <-> BaseUcanFlow translator — the one module where the editor's
|
|
3
|
+
* internals could leak, so it is small and exhaustively tested. It maps the
|
|
4
|
+
* friendly FlowSpec (types.ts) onto the verified `BaseUcanFlow`/`FlowCapability`
|
|
5
|
+
* shape and back.
|
|
6
|
+
*
|
|
7
|
+
* Two deliberate decisions, both load-bearing (see spec §2.3, §7.1):
|
|
8
|
+
* - Sequencing is `capabilities[]` ORDER + data references. `after` is ordering
|
|
9
|
+
* only; `onEvent` is the advanced event-trigger path.
|
|
10
|
+
* - Conditions are NEVER routed through `capability.condition`. The compiler
|
|
11
|
+
* writes that operator verbatim and the FE evaluator can't read it, so a
|
|
12
|
+
* condition authored that way never fires. Instead we build the compiled
|
|
13
|
+
* `props.conditions` JSON directly, in the evaluator's operator vocabulary,
|
|
14
|
+
* and the edit layer writes it onto the block. `buildConditionsProp` /
|
|
15
|
+
* `parseConditionsProp` own that mapping.
|
|
16
|
+
*/
|
|
17
|
+
import type {
|
|
18
|
+
BaseUcanFlow,
|
|
19
|
+
FlowCapability,
|
|
20
|
+
TriggerSpec,
|
|
21
|
+
} from '@ixo/editor/core';
|
|
22
|
+
import { canToType, getActionByCan, typeToCan } from '@ixo/editor/core';
|
|
23
|
+
import { FlowError } from './errors';
|
|
24
|
+
import type { Condition, FlowSpecInput, FlowStep } from './types';
|
|
25
|
+
|
|
26
|
+
/** The compiler's block-id convention (verified: `generateBlockId`). */
|
|
27
|
+
export const BLOCK_ID_PREFIX = 'flow_block_';
|
|
28
|
+
export const stepIdToBlockId = (stepId: string): string =>
|
|
29
|
+
`${BLOCK_ID_PREFIX}${stepId}`;
|
|
30
|
+
export const blockIdToStepId = (blockId: string): string =>
|
|
31
|
+
blockId.startsWith(BLOCK_ID_PREFIX)
|
|
32
|
+
? blockId.slice(BLOCK_ID_PREFIX.length)
|
|
33
|
+
: blockId;
|
|
34
|
+
|
|
35
|
+
/** Friendly condition operator -> the FE evaluator's exact operator string. */
|
|
36
|
+
const OP_TO_EVALUATOR: Record<Condition['is'], string> = {
|
|
37
|
+
equals: 'equals',
|
|
38
|
+
notEquals: 'not_equals',
|
|
39
|
+
greaterThan: 'greater_than',
|
|
40
|
+
lessThan: 'less_than',
|
|
41
|
+
contains: 'contains',
|
|
42
|
+
isEmpty: 'is_empty',
|
|
43
|
+
isNotEmpty: 'is_not_empty',
|
|
44
|
+
};
|
|
45
|
+
const EVALUATOR_TO_OP: Record<string, Condition['is']> = Object.fromEntries(
|
|
46
|
+
Object.entries(OP_TO_EVALUATOR).map(([friendly, evaluator]) => [
|
|
47
|
+
evaluator,
|
|
48
|
+
friendly as Condition['is'],
|
|
49
|
+
]),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const REF_PATTERN = /^\{\{\s*(.+?)\s*\}\}$/;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Data refs cross an id boundary. The agent works in STEP ids
|
|
56
|
+
* ("fill-form.output.answers.did"), but the flow engine resolves a `$ref` by
|
|
57
|
+
* calling `runtime.get(<id>)`, and the runtime is keyed by the BLOCK id
|
|
58
|
+
* (`flow_block_<stepId>`). So the stored `$ref` must carry the block id, or the
|
|
59
|
+
* engine reads `runtime.get("fill-form")` (empty) and the input resolves to
|
|
60
|
+
* nothing. These translate the id half of an "<id>.output.<path>" ref; non-output
|
|
61
|
+
* refs (e.g. `trigger.payload.*`) pass through untouched.
|
|
62
|
+
*/
|
|
63
|
+
function stepRefToBlockRef(ref: string): string {
|
|
64
|
+
const i = ref.indexOf('.output.');
|
|
65
|
+
if (i <= 0) return ref;
|
|
66
|
+
return `${stepIdToBlockId(ref.slice(0, i))}${ref.slice(i)}`;
|
|
67
|
+
}
|
|
68
|
+
function blockRefToStepRef(ref: string): string {
|
|
69
|
+
const i = ref.indexOf('.output.');
|
|
70
|
+
if (i <= 0) return ref;
|
|
71
|
+
const id = ref.slice(0, i);
|
|
72
|
+
return id.startsWith(BLOCK_ID_PREFIX)
|
|
73
|
+
? `${id.slice(BLOCK_ID_PREFIX.length)}${ref.slice(i)}`
|
|
74
|
+
: ref;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Rewrite every embedded "{{ <id>.output.<path> }}" placeholder inside a template
|
|
79
|
+
* string, mapping the id half with `mapRef` (`stepRefToBlockRef` outbound,
|
|
80
|
+
* `blockRefToStepRef` inbound). A *standalone* ref becomes a `{ $ref }` (handled
|
|
81
|
+
* above) so the engine resolves it as a typed value; an *embedded* ref lives in a
|
|
82
|
+
* larger string — e.g. an oracle `prompt` ("Employee {{x.output.answers.name}}
|
|
83
|
+
* submitted ..."). The engine only resolves `$ref` objects, so these placeholders
|
|
84
|
+
* are filled downstream against the same block-id-keyed runtime state; they must
|
|
85
|
+
* therefore carry the block id, or every "{{...}}" resolves to an empty string.
|
|
86
|
+
* Non-output tokens (handlebars helpers like `{{#if}}`, `trigger.payload.*`)
|
|
87
|
+
* contain no ".output." and pass through untouched.
|
|
88
|
+
*/
|
|
89
|
+
function rewriteEmbeddedRefs(
|
|
90
|
+
text: string,
|
|
91
|
+
mapRef: (ref: string) => string,
|
|
92
|
+
): string {
|
|
93
|
+
return text.replace(
|
|
94
|
+
/(\{\{\s*)([^{}]+?)(\s*\}\})/g,
|
|
95
|
+
(_full, open: string, ref: string, close: string) =>
|
|
96
|
+
`${open}${mapRef(ref)}${close}`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Resolve a friendly action name (registry `type`) to its `can` ability string. */
|
|
101
|
+
export function actionToCan(action: string): string {
|
|
102
|
+
const can = typeToCan(action);
|
|
103
|
+
if (can) return can;
|
|
104
|
+
// Tolerate an action that is already a `can` string.
|
|
105
|
+
if (getActionByCan(action)) return action;
|
|
106
|
+
throw new FlowError('unknown_action', `Unknown action "${action}".`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Resolve a `can` ability string back to the friendly action name (registry `type`). */
|
|
110
|
+
export function canToAction(can: string): string {
|
|
111
|
+
return canToType(can) ?? can;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** A `{ $ref: string }` runtime reference — the only shape the engine resolves. */
|
|
115
|
+
function isRuntimeRef(value: unknown): value is { $ref: string } {
|
|
116
|
+
return (
|
|
117
|
+
!!value &&
|
|
118
|
+
typeof value === 'object' &&
|
|
119
|
+
'$ref' in value &&
|
|
120
|
+
typeof value.$ref === 'string'
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Convert a single friendly value into its `nb` form, recursing through objects
|
|
126
|
+
* and arrays. A "{{step-id.output.field}}" string becomes a `{ $ref }`; every
|
|
127
|
+
* other value passes through. Recursion matters because the engine's
|
|
128
|
+
* `resolveRuntimeRefs` resolves `$ref` at any depth — a nested mustache string
|
|
129
|
+
* (e.g. inside an email `variables` map) would otherwise ship verbatim.
|
|
130
|
+
*/
|
|
131
|
+
function friendlyValueToNb(value: unknown): unknown {
|
|
132
|
+
if (typeof value === 'string') {
|
|
133
|
+
const match = REF_PATTERN.exec(value);
|
|
134
|
+
if (match?.[1]) return { $ref: stepRefToBlockRef(match[1]) };
|
|
135
|
+
return value.includes('{{')
|
|
136
|
+
? rewriteEmbeddedRefs(value, stepRefToBlockRef)
|
|
137
|
+
: value;
|
|
138
|
+
}
|
|
139
|
+
if (Array.isArray(value)) return value.map(friendlyValueToNb);
|
|
140
|
+
if (value && typeof value === 'object') {
|
|
141
|
+
const out: Record<string, unknown> = {};
|
|
142
|
+
for (const [key, val] of Object.entries(value)) {
|
|
143
|
+
out[key] = friendlyValueToNb(val);
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Convert a single `nb` value back to its friendly form (reverse + recursive). */
|
|
151
|
+
function nbValueToFriendly(value: unknown): unknown {
|
|
152
|
+
if (isRuntimeRef(value)) return `{{${blockRefToStepRef(value.$ref)}}}`;
|
|
153
|
+
if (typeof value === 'string')
|
|
154
|
+
return value.includes('{{')
|
|
155
|
+
? rewriteEmbeddedRefs(value, blockRefToStepRef)
|
|
156
|
+
: value;
|
|
157
|
+
if (Array.isArray(value)) return value.map(nbValueToFriendly);
|
|
158
|
+
if (value && typeof value === 'object') {
|
|
159
|
+
const out: Record<string, unknown> = {};
|
|
160
|
+
for (const [key, val] of Object.entries(value)) {
|
|
161
|
+
out[key] = nbValueToFriendly(val);
|
|
162
|
+
}
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
return value;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Convert friendly inputs into capability `nb`. A value written as
|
|
170
|
+
* "{{step-id.output.field}}" becomes a `{ $ref }` (the BaseUcanFlow ref form);
|
|
171
|
+
* everything else passes through as a literal. Nested objects/arrays are walked
|
|
172
|
+
* so refs inside maps (e.g. email `variables`) resolve at execution too.
|
|
173
|
+
*/
|
|
174
|
+
export function friendlyInputsToNb(
|
|
175
|
+
inputs: Record<string, unknown> | undefined,
|
|
176
|
+
): Record<string, unknown> | undefined {
|
|
177
|
+
if (!inputs) return undefined;
|
|
178
|
+
const nb: Record<string, unknown> = {};
|
|
179
|
+
for (const [key, value] of Object.entries(inputs)) {
|
|
180
|
+
nb[key] = friendlyValueToNb(value);
|
|
181
|
+
}
|
|
182
|
+
return nb;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Reverse of {@link friendlyInputsToNb}: `{ $ref }` -> "{{...}}", recursively. */
|
|
186
|
+
export function nbToFriendlyInputs(
|
|
187
|
+
nb: Record<string, unknown> | undefined,
|
|
188
|
+
): Record<string, unknown> | undefined {
|
|
189
|
+
if (!nb || Object.keys(nb).length === 0) return undefined;
|
|
190
|
+
const inputs: Record<string, unknown> = {};
|
|
191
|
+
for (const [key, value] of Object.entries(nb)) {
|
|
192
|
+
inputs[key] = nbValueToFriendly(value);
|
|
193
|
+
}
|
|
194
|
+
return inputs;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Build the compiled `props.conditions` JSON string in the FE evaluator's vocabulary. */
|
|
198
|
+
export function buildConditionsProp(conditions: Condition[]): string {
|
|
199
|
+
return JSON.stringify({
|
|
200
|
+
enabled: true,
|
|
201
|
+
mode: 'all_must_pass',
|
|
202
|
+
conditions: conditions.map((c) => ({
|
|
203
|
+
id: `cond_${c.fromStep}_${c.field}`,
|
|
204
|
+
name: `Condition from ${c.fromStep}`,
|
|
205
|
+
sourceBlockId: stepIdToBlockId(c.fromStep),
|
|
206
|
+
sourceBlockType: 'action',
|
|
207
|
+
rule: {
|
|
208
|
+
type: 'property_value',
|
|
209
|
+
property: c.field,
|
|
210
|
+
operator: OP_TO_EVALUATOR[c.is],
|
|
211
|
+
value: c.value,
|
|
212
|
+
},
|
|
213
|
+
effect: { action: 'enable' },
|
|
214
|
+
})),
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Parse a compiled `props.conditions` JSON string back into friendly
|
|
220
|
+
* Conditions. Defensive: returns [] on anything unparseable. `blockToStep`
|
|
221
|
+
* maps a source block id back to a step id (falls back to stripping the
|
|
222
|
+
* `flow_block_` prefix).
|
|
223
|
+
*/
|
|
224
|
+
export function parseConditionsProp(
|
|
225
|
+
raw: unknown,
|
|
226
|
+
blockToStep?: (blockId: string) => string,
|
|
227
|
+
): Condition[] {
|
|
228
|
+
if (typeof raw !== 'string' || raw.length === 0) return [];
|
|
229
|
+
let parsed: unknown;
|
|
230
|
+
try {
|
|
231
|
+
parsed = JSON.parse(raw);
|
|
232
|
+
} catch {
|
|
233
|
+
return [];
|
|
234
|
+
}
|
|
235
|
+
const list = (parsed as { conditions?: unknown }).conditions;
|
|
236
|
+
if (!Array.isArray(list)) return [];
|
|
237
|
+
const resolveStep = blockToStep ?? blockIdToStepId;
|
|
238
|
+
const out: Condition[] = [];
|
|
239
|
+
for (const entry of list) {
|
|
240
|
+
const rule = (entry as { rule?: Record<string, unknown> }).rule;
|
|
241
|
+
const sourceBlockId = (entry as { sourceBlockId?: unknown }).sourceBlockId;
|
|
242
|
+
if (!rule || typeof sourceBlockId !== 'string') continue;
|
|
243
|
+
const friendlyOp = EVALUATOR_TO_OP[String(rule.operator)];
|
|
244
|
+
if (!friendlyOp) continue;
|
|
245
|
+
out.push({
|
|
246
|
+
fromStep: resolveStep(sourceBlockId),
|
|
247
|
+
field: String(rule.property),
|
|
248
|
+
is: friendlyOp,
|
|
249
|
+
value: rule.value,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
return out;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function triggerForStep(step: FlowStep): TriggerSpec | undefined {
|
|
256
|
+
if (step.onEvent) {
|
|
257
|
+
return {
|
|
258
|
+
type: 'block.event',
|
|
259
|
+
// Plan-level triggers carry the STEP id; the editor compiler remaps it to
|
|
260
|
+
// the block id (flow_block_<stepId>) when writing the compiled `props.trigger`.
|
|
261
|
+
sourceBlockId: step.onEvent.fromStep,
|
|
262
|
+
eventName: step.onEvent.event,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
if (step.trigger === 'flow-start') return { type: 'flow.start' };
|
|
266
|
+
if (step.trigger === 'manual') return { type: 'manual' };
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function ttlForStep(step: FlowStep): FlowCapability['ttl'] | undefined {
|
|
271
|
+
if (!step.due) return undefined;
|
|
272
|
+
const ttl: NonNullable<FlowCapability['ttl']> = {};
|
|
273
|
+
if (step.due.at) ttl.absoluteDueDate = step.due.at;
|
|
274
|
+
if (step.due.within) ttl.fromEnablement = step.due.within;
|
|
275
|
+
if (step.due.afterCommitment) ttl.fromCommitment = step.due.afterCommitment;
|
|
276
|
+
if (step.commitTo) ttl.fromCommitment = step.commitTo;
|
|
277
|
+
return Object.keys(ttl).length > 0 ? ttl : undefined;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function actorForStep(step: FlowStep): FlowCapability['actor'] | undefined {
|
|
281
|
+
if (!step.assignTo) return undefined;
|
|
282
|
+
return { authorisedActors: [step.assignTo] };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Translate one step into a FlowCapability (without conditions — see module note). */
|
|
286
|
+
export function stepToCapability(
|
|
287
|
+
step: FlowStep,
|
|
288
|
+
flowId: string,
|
|
289
|
+
): FlowCapability {
|
|
290
|
+
const cap: FlowCapability = {
|
|
291
|
+
id: step.id,
|
|
292
|
+
can: actionToCan(step.action),
|
|
293
|
+
with: `ixo:flow:${flowId}:${step.id}`,
|
|
294
|
+
};
|
|
295
|
+
const nb = friendlyInputsToNb(step.inputs);
|
|
296
|
+
if (nb) cap.nb = nb;
|
|
297
|
+
if (step.title) cap.title = step.title;
|
|
298
|
+
if (step.description) cap.description = step.description;
|
|
299
|
+
if (step.phase) cap.phase = step.phase;
|
|
300
|
+
const trigger = triggerForStep(step);
|
|
301
|
+
if (trigger) cap.trigger = trigger;
|
|
302
|
+
const ttl = ttlForStep(step);
|
|
303
|
+
if (ttl) cap.ttl = ttl;
|
|
304
|
+
const actor = actorForStep(step);
|
|
305
|
+
if (actor) cap.actor = actor;
|
|
306
|
+
return cap;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Translate a FlowSpec into a BaseUcanFlow. Capability order follows step
|
|
311
|
+
* order (the engine's sequencing primitive). Conditions are NOT emitted here;
|
|
312
|
+
* the caller writes `props.conditions` directly via the edit layer.
|
|
313
|
+
*/
|
|
314
|
+
export function flowSpecToBaseUcan(
|
|
315
|
+
spec: FlowSpecInput,
|
|
316
|
+
opts: { flowId: string; ownerDid?: string },
|
|
317
|
+
): BaseUcanFlow {
|
|
318
|
+
const plan: BaseUcanFlow = {
|
|
319
|
+
kind: 'qi.flow.base-ucan',
|
|
320
|
+
version: '1.0',
|
|
321
|
+
flowId: opts.flowId,
|
|
322
|
+
title: spec.title,
|
|
323
|
+
capabilities: spec.steps.map((step) => stepToCapability(step, opts.flowId)),
|
|
324
|
+
};
|
|
325
|
+
if (spec.goal) plan.goal = spec.goal;
|
|
326
|
+
if (opts.ownerDid) plan.meta = { rootIssuer: opts.ownerDid };
|
|
327
|
+
return plan;
|
|
328
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The FlowSpec model — the friendly, leak-proof projection of the editor's
|
|
3
|
+
* `BaseUcanFlow` that the agent operates on. No tool I/O ever mentions a
|
|
4
|
+
* block, Y.Doc, `can`/`with`, CAR/CID, delegation, or `nb`; the agent thinks
|
|
5
|
+
* in steps / actions / inputs / conditions / schedules / forms.
|
|
6
|
+
*
|
|
7
|
+
* `translator.ts` owns the FlowSpec <-> BaseUcanFlow projection. These zod
|
|
8
|
+
* schemas are the contract for every tool's input; the read path returns the
|
|
9
|
+
* same shape augmented with a read-only `status`.
|
|
10
|
+
*/
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Condition operators, aligned 1:1 with the FE condition evaluator's
|
|
15
|
+
* vocabulary (see translator's CONDITION_OP_TO_EVALUATOR). We deliberately do
|
|
16
|
+
* NOT expose the compiler's `eq`/`neq`/... vocabulary — those never evaluate
|
|
17
|
+
* because no normalizer maps them to the evaluator's strings.
|
|
18
|
+
*/
|
|
19
|
+
export const CONDITION_OPERATORS = [
|
|
20
|
+
'equals',
|
|
21
|
+
'notEquals',
|
|
22
|
+
'greaterThan',
|
|
23
|
+
'lessThan',
|
|
24
|
+
'contains',
|
|
25
|
+
'isEmpty',
|
|
26
|
+
'isNotEmpty',
|
|
27
|
+
] as const;
|
|
28
|
+
|
|
29
|
+
export const conditionSchema = z.object({
|
|
30
|
+
fromStep: z
|
|
31
|
+
.string()
|
|
32
|
+
.describe('Id of the upstream step whose value is checked.'),
|
|
33
|
+
field: z.string().describe('Field on the upstream step to inspect.'),
|
|
34
|
+
is: z.enum(CONDITION_OPERATORS).describe('Comparison operator.'),
|
|
35
|
+
value: z
|
|
36
|
+
.unknown()
|
|
37
|
+
.optional()
|
|
38
|
+
.describe('Value to compare against (omit for isEmpty/isNotEmpty).'),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const hookSchema = z.object({
|
|
42
|
+
type: z.enum(['sendEmail', 'addLinkedEntity', 'sendMatrixDM']),
|
|
43
|
+
config: z.record(z.string(), z.unknown()),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export const dueSchema = z.object({
|
|
47
|
+
at: z.string().optional().describe('Absolute due date (ISO 8601).'),
|
|
48
|
+
within: z
|
|
49
|
+
.string()
|
|
50
|
+
.optional()
|
|
51
|
+
.describe(
|
|
52
|
+
'Duration from when the step becomes active (ISO 8601 duration).',
|
|
53
|
+
),
|
|
54
|
+
afterCommitment: z
|
|
55
|
+
.string()
|
|
56
|
+
.optional()
|
|
57
|
+
.describe('Duration from commitment (ISO 8601 duration).'),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export const onEventSchema = z.object({
|
|
61
|
+
fromStep: z.string().describe('Upstream step that emits the event.'),
|
|
62
|
+
event: z.string().describe('Event name the upstream step emits.'),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A single step the agent authors. Mirrors FlowStep in the spec (§2.1). The
|
|
67
|
+
* read-only `status` is intentionally absent here — agents never write it; it
|
|
68
|
+
* is only attached on read (see FlowStepRead).
|
|
69
|
+
*/
|
|
70
|
+
export const flowStepSchema = z.object({
|
|
71
|
+
id: z
|
|
72
|
+
.string()
|
|
73
|
+
.min(1)
|
|
74
|
+
.describe('Stable, human-readable step id, e.g. "load-batches".'),
|
|
75
|
+
action: z
|
|
76
|
+
.string()
|
|
77
|
+
.min(1)
|
|
78
|
+
.describe('Action name from list_actions (e.g. "qi/email.send").'),
|
|
79
|
+
title: z.string().optional(),
|
|
80
|
+
description: z.string().optional(),
|
|
81
|
+
phase: z
|
|
82
|
+
.string()
|
|
83
|
+
.min(1)
|
|
84
|
+
.optional()
|
|
85
|
+
.describe(
|
|
86
|
+
'Stable semantic group for this step, such as "discovery" or "deployment".',
|
|
87
|
+
),
|
|
88
|
+
execution: z
|
|
89
|
+
.enum(['human-only', 'agent-capable'])
|
|
90
|
+
.optional()
|
|
91
|
+
.describe(
|
|
92
|
+
'Whether only a person may run this step or a suitably skilled, authorised Flow Agent may run it.',
|
|
93
|
+
),
|
|
94
|
+
|
|
95
|
+
inputs: z
|
|
96
|
+
.record(z.string(), z.unknown())
|
|
97
|
+
.optional()
|
|
98
|
+
.describe(
|
|
99
|
+
'Inputs for the action. A value may be a reference to an upstream output, written as "{{step-id.output.field}}".',
|
|
100
|
+
),
|
|
101
|
+
form: z
|
|
102
|
+
.record(z.string(), z.unknown())
|
|
103
|
+
.optional()
|
|
104
|
+
.describe(
|
|
105
|
+
'For human form steps: pre-filled answers keyed by question name. Pre-fill only — the user submits in the portal.',
|
|
106
|
+
),
|
|
107
|
+
|
|
108
|
+
after: z
|
|
109
|
+
.array(z.string())
|
|
110
|
+
.optional()
|
|
111
|
+
.describe(
|
|
112
|
+
'Order this step after the named steps. Pair with an input reference for a real data dependency. This is ordering only, not an auto-trigger.',
|
|
113
|
+
),
|
|
114
|
+
runWhen: conditionSchema
|
|
115
|
+
.optional()
|
|
116
|
+
.describe(
|
|
117
|
+
'Gate this step on an upstream value (static configuration only).',
|
|
118
|
+
),
|
|
119
|
+
conditions: z
|
|
120
|
+
.array(conditionSchema)
|
|
121
|
+
.optional()
|
|
122
|
+
.describe('Multiple activation gates (all must pass).'),
|
|
123
|
+
onEvent: onEventSchema
|
|
124
|
+
.optional()
|
|
125
|
+
.describe(
|
|
126
|
+
'Advanced: auto-trigger this step when an upstream step emits an event. Only valid for event-capable upstream actions; validate_flow enforces this.',
|
|
127
|
+
),
|
|
128
|
+
|
|
129
|
+
trigger: z
|
|
130
|
+
.enum(['manual', 'flow-start'])
|
|
131
|
+
.optional()
|
|
132
|
+
.describe('When the step runs. Default "manual".'),
|
|
133
|
+
due: dueSchema.optional(),
|
|
134
|
+
|
|
135
|
+
assignTo: z
|
|
136
|
+
.string()
|
|
137
|
+
.optional()
|
|
138
|
+
.describe('Assignee (a DID or known alias) — metadata only.'),
|
|
139
|
+
commitTo: z
|
|
140
|
+
.string()
|
|
141
|
+
.optional()
|
|
142
|
+
.describe('Commitment window (ISO 8601 duration).'),
|
|
143
|
+
|
|
144
|
+
on: z
|
|
145
|
+
.record(z.string(), z.array(hookSchema))
|
|
146
|
+
.optional()
|
|
147
|
+
.describe('Lifecycle hooks: event name -> hooks.'),
|
|
148
|
+
skills: z
|
|
149
|
+
.array(z.string())
|
|
150
|
+
.optional()
|
|
151
|
+
.describe(
|
|
152
|
+
'Governed skill ids required for agent-capable execution. The first is used for Flow Agent matching.',
|
|
153
|
+
),
|
|
154
|
+
requireConfirmation: z
|
|
155
|
+
.boolean()
|
|
156
|
+
.optional()
|
|
157
|
+
.describe('Hint the portal to force a confirmation before this step runs.'),
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* A whole flow as the agent authors it. `ref` is the opaque flow handle; it is
|
|
162
|
+
* omitted on create and assigned by the plugin.
|
|
163
|
+
*/
|
|
164
|
+
export const flowSpecSchema = z.object({
|
|
165
|
+
ref: z
|
|
166
|
+
.string()
|
|
167
|
+
.optional()
|
|
168
|
+
.describe('Opaque flow handle. Omit when creating; the plugin assigns it.'),
|
|
169
|
+
title: z.string().min(1),
|
|
170
|
+
goal: z.string().optional(),
|
|
171
|
+
steps: z.array(flowStepSchema),
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
export type Condition = z.infer<typeof conditionSchema>;
|
|
175
|
+
export type FlowStep = z.infer<typeof flowStepSchema>;
|
|
176
|
+
export type FlowSpecInput = z.infer<typeof flowSpecSchema>;
|
|
177
|
+
|
|
178
|
+
/** Lifecycle state of a step, read from the runtime map. */
|
|
179
|
+
export type StepState =
|
|
180
|
+
| 'idle'
|
|
181
|
+
| 'running'
|
|
182
|
+
| 'completed'
|
|
183
|
+
| 'failed'
|
|
184
|
+
| 'cancelled'
|
|
185
|
+
| 'awaiting_readback';
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Read-only status derived from the runtime map (§2.6). `state`/`error`/
|
|
189
|
+
* `lastRunAt` are stored; `blockedBy`/`stale` are computed on read.
|
|
190
|
+
*/
|
|
191
|
+
export interface StepStatus {
|
|
192
|
+
state: StepState;
|
|
193
|
+
error?: { message: string; code?: string; at?: number };
|
|
194
|
+
lastRunAt?: number;
|
|
195
|
+
/** Upstream step ids that are failed or whose output this step still needs. */
|
|
196
|
+
blockedBy?: string[];
|
|
197
|
+
/** Completed but missing its expected proof (e.g. a transaction hash / claim id). */
|
|
198
|
+
stale?: boolean;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** A step as returned by read tools: the authored shape plus its read-only status. */
|
|
202
|
+
export type FlowStepRead = FlowStep & { status?: StepStatus };
|
|
203
|
+
|
|
204
|
+
/** A flow as returned by read tools. */
|
|
205
|
+
export interface FlowSpecRead {
|
|
206
|
+
ref: string;
|
|
207
|
+
title: string;
|
|
208
|
+
goal?: string;
|
|
209
|
+
steps: FlowStepRead[];
|
|
210
|
+
}
|