@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,516 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authoring tools. `validate_flow` is a pure compile-without-write.
|
|
3
|
+
* `create_template`/`add_step` go through the editor's compiler (`setupFlowFromBaseUcan`,
|
|
4
|
+
* which manages its own doc and syncs to the room). The remaining mutators are
|
|
5
|
+
* thin wrappers over the tested per-block edit functions (edit.ts).
|
|
6
|
+
*
|
|
7
|
+
* Conditions are applied as a post-pass via the direct `props.conditions` write
|
|
8
|
+
* (setStepConditions) — never through the compiler's `cap.condition`, which
|
|
9
|
+
* never evaluates (translator.ts).
|
|
10
|
+
*/
|
|
11
|
+
import {
|
|
12
|
+
compileBaseUcanFlow,
|
|
13
|
+
getActionByCan,
|
|
14
|
+
setupFlowFromBaseUcan,
|
|
15
|
+
} from '@ixo/editor/core';
|
|
16
|
+
import type { MatrixClient } from 'matrix-js-sdk';
|
|
17
|
+
import type { Doc as YDoc } from 'yjs';
|
|
18
|
+
import { z } from 'zod';
|
|
19
|
+
import { tool } from '../../../plugin-api/tool-helper';
|
|
20
|
+
import type { PluginTool, RuntimeContext } from '../../../plugin-api/types';
|
|
21
|
+
import { getActionDef, isEventCapable } from '../actions';
|
|
22
|
+
import {
|
|
23
|
+
removeStep,
|
|
24
|
+
reorderStep,
|
|
25
|
+
setStepAssignment,
|
|
26
|
+
setStepConditions,
|
|
27
|
+
setStepConfirmation,
|
|
28
|
+
setStepEventTrigger,
|
|
29
|
+
setStepExecution,
|
|
30
|
+
setStepInputs,
|
|
31
|
+
setStepPhase,
|
|
32
|
+
setStepSchedule,
|
|
33
|
+
setStepSkills,
|
|
34
|
+
setStepTrigger,
|
|
35
|
+
updateFlowMeta,
|
|
36
|
+
} from '../edit';
|
|
37
|
+
import { FlowError, toToolError } from '../errors';
|
|
38
|
+
import {
|
|
39
|
+
requireRoomMembership,
|
|
40
|
+
resolveFlowRef,
|
|
41
|
+
resolveFlowsMatrixClient,
|
|
42
|
+
withFlowDoc,
|
|
43
|
+
} from '../flow-doc';
|
|
44
|
+
import { readStep } from '../read';
|
|
45
|
+
import { flowSpecToBaseUcan } from '../translator';
|
|
46
|
+
import {
|
|
47
|
+
flowSpecSchema,
|
|
48
|
+
flowStepSchema,
|
|
49
|
+
type Condition,
|
|
50
|
+
type FlowSpecInput,
|
|
51
|
+
type FlowStep,
|
|
52
|
+
} from './../types';
|
|
53
|
+
|
|
54
|
+
const base = {
|
|
55
|
+
flowRef: z
|
|
56
|
+
.string()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe('Which flow. Omit to use the flow that is currently open.'),
|
|
59
|
+
};
|
|
60
|
+
const ok = { ok: true } as const;
|
|
61
|
+
|
|
62
|
+
/** All conditions declared on a step (runWhen folded with conditions[]). */
|
|
63
|
+
function conditionsOf(step: FlowStep): Condition[] {
|
|
64
|
+
return [...(step.runWhen ? [step.runWhen] : []), ...(step.conditions ?? [])];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** onEvent must point at an event-capable upstream action. Returns error strings. */
|
|
68
|
+
function eventCapabilityErrors(flow: FlowSpecInput): string[] {
|
|
69
|
+
const errors: string[] = [];
|
|
70
|
+
for (const step of flow.steps) {
|
|
71
|
+
if (!step.onEvent) continue;
|
|
72
|
+
const source = flow.steps.find((s) => s.id === step.onEvent?.fromStep);
|
|
73
|
+
if (!source) {
|
|
74
|
+
errors.push(
|
|
75
|
+
`Step "${step.id}" triggers on an unknown step "${step.onEvent.fromStep}".`,
|
|
76
|
+
);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const def = getActionDef(source.action);
|
|
80
|
+
if (def && !isEventCapable(def)) {
|
|
81
|
+
errors.push(
|
|
82
|
+
`Step "${step.id}" auto-triggers on "${source.id}", but its action "${source.action}" cannot emit events. ` +
|
|
83
|
+
'Use ordering (after) + an input reference instead.',
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return errors;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const validateSchema = z.object({ flow: flowSpecSchema });
|
|
91
|
+
|
|
92
|
+
function buildValidateFlowTool(): PluginTool {
|
|
93
|
+
return tool(
|
|
94
|
+
async (args) => {
|
|
95
|
+
try {
|
|
96
|
+
const { flow } = validateSchema.parse(args);
|
|
97
|
+
const errors = eventCapabilityErrors(flow);
|
|
98
|
+
if (errors.length === 0) {
|
|
99
|
+
// Surfaces the compiler's exact messages (duplicate id, unknown action, trigger cycle, ...).
|
|
100
|
+
try {
|
|
101
|
+
compileBaseUcanFlow(
|
|
102
|
+
flowSpecToBaseUcan(flow, { flowId: flow.ref ?? 'validate' }),
|
|
103
|
+
{ getActionByCan },
|
|
104
|
+
);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
errors.push(err instanceof Error ? err.message : String(err));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return { ok: errors.length === 0, errors, warnings: [] };
|
|
110
|
+
} catch (err) {
|
|
111
|
+
return toToolError(err);
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'validate_flow',
|
|
116
|
+
description:
|
|
117
|
+
'Check whether a flow is valid without saving it. Returns the exact problems (unknown action, duplicate step id, ' +
|
|
118
|
+
'a non-event-capable auto-trigger, a trigger cycle, etc.) so they can be fixed before creating the flow.',
|
|
119
|
+
schema: validateSchema,
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Apply each step's conditions to an already-authored flow room (post-pass). */
|
|
125
|
+
async function applyConditions(
|
|
126
|
+
ctx: RuntimeContext,
|
|
127
|
+
flowRef: string,
|
|
128
|
+
matrixClient: MatrixClient | undefined,
|
|
129
|
+
steps: FlowStep[],
|
|
130
|
+
): Promise<void> {
|
|
131
|
+
const withConditions = steps.filter((s) => conditionsOf(s).length > 0);
|
|
132
|
+
if (withConditions.length === 0) return;
|
|
133
|
+
await withFlowDoc(ctx, flowRef, matrixClient, async (doc) => {
|
|
134
|
+
for (const step of withConditions)
|
|
135
|
+
setStepConditions(doc, step.id, conditionsOf(step));
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Apply each step's assignee to an already-authored flow room (post-pass).
|
|
141
|
+
* The compiler only writes the authorization whitelist; the assignee the portal
|
|
142
|
+
* shows + nudges lives in `props.assignment`, written by setStepAssignment.
|
|
143
|
+
*/
|
|
144
|
+
async function applyAssignments(
|
|
145
|
+
ctx: RuntimeContext,
|
|
146
|
+
flowRef: string,
|
|
147
|
+
matrixClient: MatrixClient | undefined,
|
|
148
|
+
steps: FlowStep[],
|
|
149
|
+
): Promise<void> {
|
|
150
|
+
const withAssignee = steps.filter((s) => s.assignTo);
|
|
151
|
+
if (withAssignee.length === 0) return;
|
|
152
|
+
await withFlowDoc(ctx, flowRef, matrixClient, async (doc) => {
|
|
153
|
+
for (const step of withAssignee)
|
|
154
|
+
setStepAssignment(doc, step.id, step.assignTo);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Apply Flow Agent execution boundaries and governed skill matching metadata. */
|
|
159
|
+
async function applyExecutionPolicy(
|
|
160
|
+
ctx: RuntimeContext,
|
|
161
|
+
flowRef: string,
|
|
162
|
+
matrixClient: MatrixClient | undefined,
|
|
163
|
+
steps: FlowStep[],
|
|
164
|
+
): Promise<void> {
|
|
165
|
+
const configured = steps.filter(
|
|
166
|
+
(step) => step.execution !== undefined || step.skills !== undefined,
|
|
167
|
+
);
|
|
168
|
+
if (configured.length === 0) return;
|
|
169
|
+
await withFlowDoc(ctx, flowRef, matrixClient, async (doc) => {
|
|
170
|
+
for (const step of configured) {
|
|
171
|
+
if (step.execution) setStepExecution(doc, step.id, step.execution);
|
|
172
|
+
if (step.skills) setStepSkills(doc, step.id, step.skills);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Allocate the room to author the template into.
|
|
179
|
+
*
|
|
180
|
+
* The Node runtime first asks the user's front-end (over its WS browser-tool
|
|
181
|
+
* channel) to create a dedicated `#template-*` room and invite the oracle.
|
|
182
|
+
* The Workers runtime has no request/response browser-tool channel — its
|
|
183
|
+
* `emit.browserToolCall` is a one-way SSE event — so that path does not exist
|
|
184
|
+
* here: the template is authored into the room the portal already opened
|
|
185
|
+
* (`state.editorRoomId`, or an explicit `flow.ref`). When a round-trip
|
|
186
|
+
* browser-tool channel lands on Workers, restore the Node runtime's
|
|
187
|
+
* `create_template_room` request here.
|
|
188
|
+
*/
|
|
189
|
+
function allocateTemplateRoom(
|
|
190
|
+
ctx: RuntimeContext,
|
|
191
|
+
flow: FlowSpecInput,
|
|
192
|
+
): string {
|
|
193
|
+
return resolveFlowRef(ctx, flow.ref);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function buildCreateTemplateTool(
|
|
197
|
+
matrixClient: MatrixClient | undefined,
|
|
198
|
+
): PluginTool {
|
|
199
|
+
const createTemplateSchema = z.object({
|
|
200
|
+
flow: flowSpecSchema,
|
|
201
|
+
|
|
202
|
+
personalSpace: z
|
|
203
|
+
.boolean()
|
|
204
|
+
.optional()
|
|
205
|
+
.describe(
|
|
206
|
+
'Where to put the template. Default false = the domain the user is currently viewing. ' +
|
|
207
|
+
'Set true only when the user explicitly asks for their personal/private flows space.',
|
|
208
|
+
),
|
|
209
|
+
});
|
|
210
|
+
return tool(
|
|
211
|
+
async (args, ctx: RuntimeContext) => {
|
|
212
|
+
try {
|
|
213
|
+
const { flow } = createTemplateSchema.parse(args);
|
|
214
|
+
const errors = eventCapabilityErrors(flow);
|
|
215
|
+
if (errors.length > 0)
|
|
216
|
+
return {
|
|
217
|
+
ok: false,
|
|
218
|
+
error: { code: 'validation_failed', message: errors.join(' ') },
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const client = await resolveFlowsMatrixClient(ctx, matrixClient);
|
|
222
|
+
const roomId = allocateTemplateRoom(ctx, flow);
|
|
223
|
+
await requireRoomMembership(ctx, roomId);
|
|
224
|
+
|
|
225
|
+
const flowId = `flow-${crypto.randomUUID().slice(0, 8)}`;
|
|
226
|
+
const plan = flowSpecToBaseUcan(flow, {
|
|
227
|
+
flowId,
|
|
228
|
+
ownerDid: ctx.user.did,
|
|
229
|
+
});
|
|
230
|
+
await setupFlowFromBaseUcan({
|
|
231
|
+
plan,
|
|
232
|
+
roomId,
|
|
233
|
+
matrixClient: client,
|
|
234
|
+
creatorDid: ctx.user.did,
|
|
235
|
+
});
|
|
236
|
+
await applyConditions(ctx, roomId, matrixClient, flow.steps);
|
|
237
|
+
await applyAssignments(ctx, roomId, matrixClient, flow.steps);
|
|
238
|
+
await applyExecutionPolicy(ctx, roomId, matrixClient, flow.steps);
|
|
239
|
+
|
|
240
|
+
return { ok: true, flowRef: roomId };
|
|
241
|
+
} catch (err) {
|
|
242
|
+
return toToolError(err);
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
name: 'create_template',
|
|
247
|
+
description:
|
|
248
|
+
'Create a BRAND-NEW flow template — a reusable blueprint the user instantiates and runs in ' +
|
|
249
|
+
'the portal. Do NOT use this to change a template that already exists or is currently open — use the editing tools ' +
|
|
250
|
+
'(add_step, update_step, set_step_*, remove_step, reorder_step, connect_steps), which modify the open template in ' +
|
|
251
|
+
'place. Validate first with validate_flow.',
|
|
252
|
+
schema: createTemplateSchema,
|
|
253
|
+
},
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const addStepSchema = z.object({
|
|
258
|
+
...base,
|
|
259
|
+
step: flowStepSchema,
|
|
260
|
+
position: z
|
|
261
|
+
.union([
|
|
262
|
+
z.number().int().nonnegative(),
|
|
263
|
+
z.object({ after: z.string() }),
|
|
264
|
+
z.object({ before: z.string() }),
|
|
265
|
+
])
|
|
266
|
+
.optional()
|
|
267
|
+
.describe(
|
|
268
|
+
'Where to place the step: a 0-based index, {after: id}, or {before: id}. Omitted = append.',
|
|
269
|
+
),
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
function buildAddStepTool(matrixClient: MatrixClient | undefined): PluginTool {
|
|
273
|
+
return tool(
|
|
274
|
+
async (args, ctx: RuntimeContext) => {
|
|
275
|
+
try {
|
|
276
|
+
const { flowRef, step, position } = addStepSchema.parse(args);
|
|
277
|
+
const roomId = resolveFlowRef(ctx, flowRef);
|
|
278
|
+
await requireRoomMembership(ctx, roomId);
|
|
279
|
+
const client = await resolveFlowsMatrixClient(ctx, matrixClient);
|
|
280
|
+
|
|
281
|
+
// Read the existing flow id so the merge targets the same document.
|
|
282
|
+
const flowId = await withFlowDoc(
|
|
283
|
+
ctx,
|
|
284
|
+
flowRef,
|
|
285
|
+
matrixClient,
|
|
286
|
+
async (doc) => {
|
|
287
|
+
const value = doc.getMap('qi.flow.meta').get('flowId');
|
|
288
|
+
return typeof value === 'string' && value.length > 0
|
|
289
|
+
? value
|
|
290
|
+
: `flow-${crypto.randomUUID().slice(0, 8)}`;
|
|
291
|
+
},
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
const plan = flowSpecToBaseUcan(
|
|
295
|
+
{ title: '', steps: [step] },
|
|
296
|
+
{ flowId, ownerDid: ctx.user.did },
|
|
297
|
+
);
|
|
298
|
+
await setupFlowFromBaseUcan({
|
|
299
|
+
plan,
|
|
300
|
+
roomId,
|
|
301
|
+
matrixClient: client,
|
|
302
|
+
creatorDid: ctx.user.did,
|
|
303
|
+
strategy: 'merge',
|
|
304
|
+
});
|
|
305
|
+
await applyConditions(ctx, roomId, matrixClient, [step]);
|
|
306
|
+
await applyAssignments(ctx, roomId, matrixClient, [step]);
|
|
307
|
+
await applyExecutionPolicy(ctx, roomId, matrixClient, [step]);
|
|
308
|
+
|
|
309
|
+
if (typeof position === 'number') {
|
|
310
|
+
await withFlowDoc(ctx, flowRef, matrixClient, async (doc) =>
|
|
311
|
+
reorderStep(doc, step.id, position),
|
|
312
|
+
);
|
|
313
|
+
} else if (position) {
|
|
314
|
+
await withFlowDoc(ctx, flowRef, matrixClient, async (doc) => {
|
|
315
|
+
const order = doc
|
|
316
|
+
.getArray('qi.flow.order')
|
|
317
|
+
.toArray()
|
|
318
|
+
.filter((v): v is string => typeof v === 'string');
|
|
319
|
+
const anchor =
|
|
320
|
+
'after' in position ? position.after : position.before;
|
|
321
|
+
const idx = order.indexOf(anchor);
|
|
322
|
+
if (idx >= 0)
|
|
323
|
+
reorderStep(doc, step.id, 'after' in position ? idx + 1 : idx);
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
return ok;
|
|
327
|
+
} catch (err) {
|
|
328
|
+
return toToolError(err);
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
name: 'add_step',
|
|
333
|
+
description:
|
|
334
|
+
'Add one step to an existing flow without disturbing the others. Optionally place it at a position.',
|
|
335
|
+
schema: addStepSchema,
|
|
336
|
+
},
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const removeSchema = z.object({ ...base, stepId: z.string().min(1) });
|
|
341
|
+
const reorderSchema = z.object({
|
|
342
|
+
...base,
|
|
343
|
+
stepId: z.string().min(1),
|
|
344
|
+
toIndex: z.number().int().nonnegative(),
|
|
345
|
+
});
|
|
346
|
+
const metaSchema = z.object({
|
|
347
|
+
...base,
|
|
348
|
+
title: z.string().optional(),
|
|
349
|
+
goal: z.string().optional(),
|
|
350
|
+
});
|
|
351
|
+
const connectSchema = z.object({
|
|
352
|
+
...base,
|
|
353
|
+
fromStep: z.string().min(1),
|
|
354
|
+
field: z.string().min(1),
|
|
355
|
+
toStep: z.string().min(1),
|
|
356
|
+
input: z.string().min(1),
|
|
357
|
+
});
|
|
358
|
+
const stepPatchSchema = flowStepSchema.partial().extend({
|
|
359
|
+
phase: z
|
|
360
|
+
.string()
|
|
361
|
+
.min(1)
|
|
362
|
+
.nullable()
|
|
363
|
+
.optional()
|
|
364
|
+
.describe('Set the step phase, or use null to clear an existing phase.'),
|
|
365
|
+
});
|
|
366
|
+
const updateStepSchema = z.object({
|
|
367
|
+
...base,
|
|
368
|
+
stepId: z.string().min(1),
|
|
369
|
+
patch: stepPatchSchema,
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
/** Route an update_step patch to the focused per-block edits. */
|
|
373
|
+
export function applyStepPatch(
|
|
374
|
+
doc: YDoc,
|
|
375
|
+
stepId: string,
|
|
376
|
+
patch: z.infer<typeof stepPatchSchema>,
|
|
377
|
+
): void {
|
|
378
|
+
if (patch.inputs) setStepInputs(doc, stepId, patch.inputs);
|
|
379
|
+
if (patch.runWhen !== undefined || patch.conditions !== undefined) {
|
|
380
|
+
setStepConditions(doc, stepId, [
|
|
381
|
+
...(patch.runWhen ? [patch.runWhen] : []),
|
|
382
|
+
...(patch.conditions ?? []),
|
|
383
|
+
]);
|
|
384
|
+
}
|
|
385
|
+
if (patch.due !== undefined || patch.commitTo !== undefined)
|
|
386
|
+
setStepSchedule(doc, stepId, patch.due, patch.commitTo);
|
|
387
|
+
if (patch.assignTo !== undefined)
|
|
388
|
+
setStepAssignment(doc, stepId, patch.assignTo);
|
|
389
|
+
if (patch.requireConfirmation !== undefined)
|
|
390
|
+
setStepConfirmation(doc, stepId, patch.requireConfirmation);
|
|
391
|
+
if (patch.phase !== undefined)
|
|
392
|
+
setStepPhase(doc, stepId, patch.phase ?? undefined);
|
|
393
|
+
if (patch.execution !== undefined)
|
|
394
|
+
setStepExecution(doc, stepId, patch.execution);
|
|
395
|
+
if (patch.skills !== undefined) setStepSkills(doc, stepId, patch.skills);
|
|
396
|
+
// Both write the same trigger props; onEvent is the more specific intent.
|
|
397
|
+
if (patch.onEvent !== undefined)
|
|
398
|
+
setStepEventTrigger(doc, stepId, patch.onEvent);
|
|
399
|
+
else if (patch.trigger !== undefined)
|
|
400
|
+
setStepTrigger(doc, stepId, patch.trigger);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export function buildAuthoringTools(
|
|
404
|
+
matrixClient: MatrixClient | undefined,
|
|
405
|
+
): PluginTool[] {
|
|
406
|
+
return [
|
|
407
|
+
buildValidateFlowTool(),
|
|
408
|
+
buildCreateTemplateTool(matrixClient),
|
|
409
|
+
buildAddStepTool(matrixClient),
|
|
410
|
+
tool(
|
|
411
|
+
async (args, ctx: RuntimeContext) => {
|
|
412
|
+
try {
|
|
413
|
+
const { flowRef, stepId } = removeSchema.parse(args);
|
|
414
|
+
await withFlowDoc(ctx, flowRef, matrixClient, async (doc, roomId) =>
|
|
415
|
+
removeStep(doc, roomId, stepId),
|
|
416
|
+
);
|
|
417
|
+
return ok;
|
|
418
|
+
} catch (err) {
|
|
419
|
+
return toToolError(err);
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
name: 'remove_step',
|
|
424
|
+
description:
|
|
425
|
+
'Remove a step from a flow. Rejected if another step still references it (the referrers are named).',
|
|
426
|
+
schema: removeSchema,
|
|
427
|
+
},
|
|
428
|
+
),
|
|
429
|
+
tool(
|
|
430
|
+
async (args, ctx: RuntimeContext) => {
|
|
431
|
+
try {
|
|
432
|
+
const { flowRef, stepId, toIndex } = reorderSchema.parse(args);
|
|
433
|
+
await withFlowDoc(ctx, flowRef, matrixClient, async (doc) =>
|
|
434
|
+
reorderStep(doc, stepId, toIndex),
|
|
435
|
+
);
|
|
436
|
+
return ok;
|
|
437
|
+
} catch (err) {
|
|
438
|
+
return toToolError(err);
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
name: 'reorder_step',
|
|
443
|
+
description: 'Move a step to a new 0-based position in the flow.',
|
|
444
|
+
schema: reorderSchema,
|
|
445
|
+
},
|
|
446
|
+
),
|
|
447
|
+
tool(
|
|
448
|
+
async (args, ctx: RuntimeContext) => {
|
|
449
|
+
try {
|
|
450
|
+
const { flowRef, title, goal } = metaSchema.parse(args);
|
|
451
|
+
await withFlowDoc(ctx, flowRef, matrixClient, async (doc) =>
|
|
452
|
+
updateFlowMeta(doc, { title, goal }),
|
|
453
|
+
);
|
|
454
|
+
return ok;
|
|
455
|
+
} catch (err) {
|
|
456
|
+
return toToolError(err);
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
name: 'update_flow_meta',
|
|
461
|
+
description: "Update a flow's title and/or goal.",
|
|
462
|
+
schema: metaSchema,
|
|
463
|
+
},
|
|
464
|
+
),
|
|
465
|
+
tool(
|
|
466
|
+
async (args, ctx: RuntimeContext) => {
|
|
467
|
+
try {
|
|
468
|
+
const { flowRef, fromStep, field, toStep, input } =
|
|
469
|
+
connectSchema.parse(args);
|
|
470
|
+
await withFlowDoc(ctx, flowRef, matrixClient, async (doc, roomId) => {
|
|
471
|
+
const current = readStep(doc, roomId, toStep);
|
|
472
|
+
if (!current)
|
|
473
|
+
throw new FlowError(
|
|
474
|
+
'step_not_found',
|
|
475
|
+
`No step "${toStep}" in this flow.`,
|
|
476
|
+
);
|
|
477
|
+
setStepInputs(doc, toStep, {
|
|
478
|
+
...(current.inputs ?? {}),
|
|
479
|
+
[input]: `{{${fromStep}.output.${field}}}`,
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
return ok;
|
|
483
|
+
} catch (err) {
|
|
484
|
+
return toToolError(err);
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
name: 'connect_steps',
|
|
489
|
+
description:
|
|
490
|
+
"Wire one step's output into another step's input (a data reference).",
|
|
491
|
+
schema: connectSchema,
|
|
492
|
+
},
|
|
493
|
+
),
|
|
494
|
+
tool(
|
|
495
|
+
async (args, ctx: RuntimeContext) => {
|
|
496
|
+
try {
|
|
497
|
+
const { flowRef, stepId, patch } = updateStepSchema.parse(args);
|
|
498
|
+
await withFlowDoc(ctx, flowRef, matrixClient, async (doc) =>
|
|
499
|
+
applyStepPatch(doc, stepId, patch),
|
|
500
|
+
);
|
|
501
|
+
return ok;
|
|
502
|
+
} catch (err) {
|
|
503
|
+
return toToolError(err);
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
name: 'update_step',
|
|
508
|
+
description:
|
|
509
|
+
"Update any subset of a step's settings in one call (phase, execution boundary, required skills, inputs, conditions, schedule, assignee, confirmation, " +
|
|
510
|
+
'trigger, onEvent). onEvent takes precedence over trigger when both are given; set trigger to "manual" to ' +
|
|
511
|
+
'clear an onEvent auto-trigger. Set phase to null to clear an existing phase.',
|
|
512
|
+
schema: updateStepSchema,
|
|
513
|
+
},
|
|
514
|
+
),
|
|
515
|
+
];
|
|
516
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discovery tools (spec §3.1) — enumerate and describe the available actions.
|
|
3
|
+
* These read only the action catalog, so they never connect to a flow room.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import type { MatrixClient } from 'matrix-js-sdk';
|
|
7
|
+
import { tool } from '../../../plugin-api/tool-helper';
|
|
8
|
+
import type { PluginTool, RuntimeContext } from '../../../plugin-api/types';
|
|
9
|
+
import { describeAction, listActions } from '../actions';
|
|
10
|
+
import { toToolError } from '../errors';
|
|
11
|
+
import { withFlowDoc } from '../flow-doc';
|
|
12
|
+
import { listReferenceableFields } from '../references';
|
|
13
|
+
|
|
14
|
+
const listActionsSchema = z.object({
|
|
15
|
+
category: z
|
|
16
|
+
.string()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe('Filter to actions in this category.'),
|
|
19
|
+
tag: z.string().optional().describe('Filter to actions carrying this tag.'),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const describeActionSchema = z.object({
|
|
23
|
+
action: z.string().min(1).describe('Action name (from list_actions).'),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const referenceableSchema = z.object({
|
|
27
|
+
flowRef: z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe('Which flow. Omit to use the flow that is currently open.'),
|
|
31
|
+
stepId: z
|
|
32
|
+
.string()
|
|
33
|
+
.min(1)
|
|
34
|
+
.describe(
|
|
35
|
+
'The step you are wiring data INTO (the downstream/consumer step) — NOT the source. ' +
|
|
36
|
+
'Returns the outputs of the steps that run before it. Passing the first step returns nothing (nothing runs before it).',
|
|
37
|
+
),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export function buildListActionsTool(): PluginTool {
|
|
41
|
+
return tool(
|
|
42
|
+
async (args) => {
|
|
43
|
+
try {
|
|
44
|
+
const { category, tag } = listActionsSchema.parse(args);
|
|
45
|
+
return { actions: listActions({ category, tag }) };
|
|
46
|
+
} catch (err) {
|
|
47
|
+
return toToolError(err);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'list_actions',
|
|
52
|
+
description:
|
|
53
|
+
'List the actions available to use as steps in a flow. Optionally filter by category or tag. ' +
|
|
54
|
+
'Returns each action with a short summary and when to use it.',
|
|
55
|
+
schema: listActionsSchema,
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function buildDescribeActionTool(): PluginTool {
|
|
61
|
+
return tool(
|
|
62
|
+
async (args) => {
|
|
63
|
+
try {
|
|
64
|
+
const { action } = describeActionSchema.parse(args);
|
|
65
|
+
const description = describeAction(action);
|
|
66
|
+
if (!description) {
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
error: {
|
|
70
|
+
code: 'unknown_action',
|
|
71
|
+
message: `Unknown action "${action}".`,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return description;
|
|
76
|
+
} catch (err) {
|
|
77
|
+
return toToolError(err);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'describe_action',
|
|
82
|
+
description:
|
|
83
|
+
'Get the full friendly spec of one action: its summary, inputs, outputs, the events it can emit, ' +
|
|
84
|
+
'the lifecycle hooks it supports, and whether it is a form. Use before adding a step to know how to configure it.',
|
|
85
|
+
schema: describeActionSchema,
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function buildListReferenceableFieldsTool(
|
|
91
|
+
matrixClient: MatrixClient | undefined,
|
|
92
|
+
): PluginTool {
|
|
93
|
+
return tool(
|
|
94
|
+
async (args, ctx: RuntimeContext) => {
|
|
95
|
+
try {
|
|
96
|
+
const { flowRef, stepId } = referenceableSchema.parse(args);
|
|
97
|
+
return await withFlowDoc(
|
|
98
|
+
ctx,
|
|
99
|
+
flowRef,
|
|
100
|
+
matrixClient,
|
|
101
|
+
async (doc, roomId) => ({
|
|
102
|
+
fields: listReferenceableFields(doc, roomId, stepId),
|
|
103
|
+
}),
|
|
104
|
+
);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
return toToolError(err);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: 'list_referenceable_fields',
|
|
111
|
+
description:
|
|
112
|
+
'List the outputs a step can pull data FROM. Pass the step that NEEDS the data (the downstream/consumer ' +
|
|
113
|
+
"step) — it returns the output fields of every step before it (e.g. a form's answers like answers.did), so you " +
|
|
114
|
+
'wire the right "{{source.output.field}}" or call connect_steps. Passing the source step returns nothing, since ' +
|
|
115
|
+
'nothing runs before it.',
|
|
116
|
+
schema: referenceableSchema,
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
}
|