@ixo/editor 5.10.0 → 5.11.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/dist/chunk-7ZKV7F6Z.mjs +824 -0
- package/dist/chunk-7ZKV7F6Z.mjs.map +1 -0
- package/dist/{chunk-MD3CXTGZ.mjs → chunk-GCR6VY7O.mjs} +27 -6
- package/dist/chunk-GCR6VY7O.mjs.map +1 -0
- package/dist/{chunk-OSIGG4JK.mjs → chunk-RAEW6M7A.mjs} +237 -161
- package/dist/{chunk-OSIGG4JK.mjs.map → chunk-RAEW6M7A.mjs.map} +1 -1
- package/dist/core/index.d.ts +18 -6
- package/dist/core/index.mjs +57 -3
- package/dist/core/index.mjs.map +1 -1
- package/dist/{graphql-client-BWqRGWY6.d.ts → graphql-client-DP4rq5no.d.ts} +1 -1
- package/dist/{index-DKKQa1q-.d.ts → index-C0ikjICf.d.ts} +11 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +39 -4
- package/dist/index.mjs.map +1 -1
- package/dist/mantine/index.d.ts +3 -3
- package/dist/mantine/index.mjs +2 -2
- package/dist/{decompile-BKY8P5Q_.d.ts → store-BT916StR.d.ts} +228 -3
- package/package.json +1 -1
- package/dist/chunk-MD3CXTGZ.mjs.map +0 -1
- package/dist/chunk-UREJIK4X.mjs +0 -63
- package/dist/chunk-UREJIK4X.mjs.map +0 -1
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isRuntimeRef,
|
|
3
|
+
readPendingInvocations
|
|
4
|
+
} from "./chunk-GCR6VY7O.mjs";
|
|
5
|
+
|
|
6
|
+
// src/core/lib/flowCompiler/resolveRefs.ts
|
|
7
|
+
function resolveRuntimeRefs(nb, getNodeOutput, triggerContext) {
|
|
8
|
+
return resolveValue(nb, getNodeOutput, triggerContext);
|
|
9
|
+
}
|
|
10
|
+
function resolveValue(value, getNodeOutput, triggerContext) {
|
|
11
|
+
if (isRuntimeRef(value)) {
|
|
12
|
+
return resolveRef(value.$ref, getNodeOutput, triggerContext);
|
|
13
|
+
}
|
|
14
|
+
if (Array.isArray(value)) {
|
|
15
|
+
return value.map((item) => resolveValue(item, getNodeOutput, triggerContext));
|
|
16
|
+
}
|
|
17
|
+
if (typeof value === "object" && value !== null) {
|
|
18
|
+
const result = {};
|
|
19
|
+
for (const [key, val] of Object.entries(value)) {
|
|
20
|
+
result[key] = resolveValue(val, getNodeOutput, triggerContext);
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
function resolveRef(ref, getNodeOutput, triggerContext) {
|
|
27
|
+
if (ref.startsWith("trigger.payload.")) {
|
|
28
|
+
if (!triggerContext) {
|
|
29
|
+
throw new Error(`Trigger ref "${ref}" used outside of a listener invocation context. trigger.payload.* refs are only valid on block.event-triggered blocks.`);
|
|
30
|
+
}
|
|
31
|
+
const fieldPath2 = ref.slice("trigger.payload.".length);
|
|
32
|
+
return getNestedValue(triggerContext.payload, fieldPath2);
|
|
33
|
+
}
|
|
34
|
+
if (triggerContext && Object.prototype.hasOwnProperty.call(triggerContext.refSnapshots, ref)) {
|
|
35
|
+
return triggerContext.refSnapshots[ref];
|
|
36
|
+
}
|
|
37
|
+
const outputIndex = ref.indexOf(".output.");
|
|
38
|
+
if (outputIndex === -1) {
|
|
39
|
+
throw new Error(`Invalid runtime reference "${ref}". Expected format: "nodeId.output.fieldPath" or "trigger.payload.fieldPath"`);
|
|
40
|
+
}
|
|
41
|
+
const nodeId = ref.slice(0, outputIndex);
|
|
42
|
+
const fieldPath = ref.slice(outputIndex + ".output.".length);
|
|
43
|
+
const output = getNodeOutput(nodeId);
|
|
44
|
+
if (!output) {
|
|
45
|
+
return void 0;
|
|
46
|
+
}
|
|
47
|
+
return getNestedValue(output, fieldPath);
|
|
48
|
+
}
|
|
49
|
+
function getNestedValue(obj, path) {
|
|
50
|
+
const parts = path.split(".");
|
|
51
|
+
let current = obj;
|
|
52
|
+
for (const part of parts) {
|
|
53
|
+
if (current == null || typeof current !== "object") {
|
|
54
|
+
return void 0;
|
|
55
|
+
}
|
|
56
|
+
current = current[part];
|
|
57
|
+
}
|
|
58
|
+
return current;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/core/lib/flowAgent/store.ts
|
|
62
|
+
import * as Y from "yjs";
|
|
63
|
+
|
|
64
|
+
// src/core/lib/flowAgent/utils.ts
|
|
65
|
+
function stableStringify(value) {
|
|
66
|
+
if (value === void 0) return "null";
|
|
67
|
+
if (value === null || typeof value !== "object") {
|
|
68
|
+
return JSON.stringify(value);
|
|
69
|
+
}
|
|
70
|
+
if (Array.isArray(value)) {
|
|
71
|
+
return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
72
|
+
}
|
|
73
|
+
const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
|
|
74
|
+
return `{${entries.map(([key, val]) => `${JSON.stringify(key)}:${stableStringify(val)}`).join(",")}}`;
|
|
75
|
+
}
|
|
76
|
+
function fnv1a32(input) {
|
|
77
|
+
let hash = 2166136261;
|
|
78
|
+
for (let i = 0; i < input.length; i++) {
|
|
79
|
+
hash ^= input.charCodeAt(i);
|
|
80
|
+
hash = Math.imul(hash, 16777619);
|
|
81
|
+
}
|
|
82
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
83
|
+
}
|
|
84
|
+
function createIntentHash(intent) {
|
|
85
|
+
return fnv1a32(stableStringify(intent));
|
|
86
|
+
}
|
|
87
|
+
function parseJsonProp(value, fallback) {
|
|
88
|
+
if (value == null || value === "") return fallback;
|
|
89
|
+
if (typeof value === "object") return value;
|
|
90
|
+
if (typeof value !== "string") return fallback;
|
|
91
|
+
try {
|
|
92
|
+
const parsed = JSON.parse(value);
|
|
93
|
+
return parsed;
|
|
94
|
+
} catch {
|
|
95
|
+
return fallback;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function getBlockActionType(block) {
|
|
99
|
+
const props = block?.props;
|
|
100
|
+
const actionType = props?.actionType;
|
|
101
|
+
return typeof actionType === "string" && actionType.length > 0 ? actionType : void 0;
|
|
102
|
+
}
|
|
103
|
+
function getBlockTitle(block) {
|
|
104
|
+
const props = block?.props;
|
|
105
|
+
const title = props?.title;
|
|
106
|
+
return typeof title === "string" && title.length > 0 ? title : void 0;
|
|
107
|
+
}
|
|
108
|
+
function getBlockType(block) {
|
|
109
|
+
const type = block?.type;
|
|
110
|
+
return typeof type === "string" && type.length > 0 ? type : "block";
|
|
111
|
+
}
|
|
112
|
+
function getBlockProps(block) {
|
|
113
|
+
return block?.props || {};
|
|
114
|
+
}
|
|
115
|
+
function getBlockId(block) {
|
|
116
|
+
const id = block?.id;
|
|
117
|
+
return typeof id === "string" && id.length > 0 ? id : void 0;
|
|
118
|
+
}
|
|
119
|
+
function getAssigneeDid(block) {
|
|
120
|
+
const props = getBlockProps(block);
|
|
121
|
+
const assignment = parseJsonProp(props.assignment, {});
|
|
122
|
+
const did = assignment?.assignedActor?.did;
|
|
123
|
+
return typeof did === "string" && did.length > 0 ? did : void 0;
|
|
124
|
+
}
|
|
125
|
+
function getDueAt(block) {
|
|
126
|
+
const props = getBlockProps(block);
|
|
127
|
+
const ttlAbsoluteDueDate = props.ttlAbsoluteDueDate;
|
|
128
|
+
if (typeof ttlAbsoluteDueDate !== "string" || ttlAbsoluteDueDate.length === 0) return void 0;
|
|
129
|
+
const parsed = new Date(ttlAbsoluteDueDate).getTime();
|
|
130
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
131
|
+
}
|
|
132
|
+
function capabilityForCommand(can, flowUri, nodeId) {
|
|
133
|
+
return {
|
|
134
|
+
can,
|
|
135
|
+
with: `${flowUri}:${nodeId}`
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/core/lib/flowAgent/store.ts
|
|
140
|
+
var FLOW_AGENT_OUTBOX_KEY = "agentOutbox";
|
|
141
|
+
var FLOW_AGENT_LEASES_KEY = "agentLeases";
|
|
142
|
+
var FLOW_AGENT_AUDIT_SCOPE = "__flow_agent__";
|
|
143
|
+
function getFlowAgentMaps(yDoc) {
|
|
144
|
+
return {
|
|
145
|
+
outbox: yDoc.getMap(FLOW_AGENT_OUTBOX_KEY),
|
|
146
|
+
leases: yDoc.getMap(FLOW_AGENT_LEASES_KEY)
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function computeAgentCommandId(params) {
|
|
150
|
+
const intentHash = createIntentHash(params);
|
|
151
|
+
return `${params.flowId}:${params.nodeId}:${intentHash}`;
|
|
152
|
+
}
|
|
153
|
+
function queueAgentCommand(yDoc, command) {
|
|
154
|
+
const { outbox } = getFlowAgentMaps(yDoc);
|
|
155
|
+
const existing = outbox.get(command.id);
|
|
156
|
+
if (existing) return { command: existing, created: false };
|
|
157
|
+
outbox.set(command.id, command);
|
|
158
|
+
appendAgentLedgerEvent(yDoc, {
|
|
159
|
+
type: "agent.command",
|
|
160
|
+
flowId: command.flowId,
|
|
161
|
+
nodeId: command.nodeId,
|
|
162
|
+
commandId: command.id,
|
|
163
|
+
actorDid: command.actorDid,
|
|
164
|
+
timestamp: command.createdAt,
|
|
165
|
+
details: {
|
|
166
|
+
commandType: command.type,
|
|
167
|
+
status: command.status,
|
|
168
|
+
idempotencyKey: command.idempotencyKey,
|
|
169
|
+
reason: command.reason
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
return { command, created: true };
|
|
173
|
+
}
|
|
174
|
+
function readQueuedAgentCommands(yDoc) {
|
|
175
|
+
const { outbox } = getFlowAgentMaps(yDoc);
|
|
176
|
+
const commands = [];
|
|
177
|
+
outbox.forEach((command) => {
|
|
178
|
+
if (command.status === "queued" || command.status === "leased" || command.status === "running") {
|
|
179
|
+
commands.push(command);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
commands.sort((a, b) => a.createdAt - b.createdAt || a.id.localeCompare(b.id));
|
|
183
|
+
return commands;
|
|
184
|
+
}
|
|
185
|
+
function updateAgentCommand(yDoc, commandId, patch) {
|
|
186
|
+
const { outbox } = getFlowAgentMaps(yDoc);
|
|
187
|
+
const current = outbox.get(commandId);
|
|
188
|
+
if (!current) return null;
|
|
189
|
+
const next = { ...current, ...patch };
|
|
190
|
+
outbox.set(commandId, next);
|
|
191
|
+
return next;
|
|
192
|
+
}
|
|
193
|
+
function appendAgentLedgerEvent(yDoc, event) {
|
|
194
|
+
const auditMap = yDoc.getMap("auditTrail");
|
|
195
|
+
let arr = auditMap.get(FLOW_AGENT_AUDIT_SCOPE);
|
|
196
|
+
if (!arr) {
|
|
197
|
+
arr = new Y.Array();
|
|
198
|
+
auditMap.set(FLOW_AGENT_AUDIT_SCOPE, arr);
|
|
199
|
+
}
|
|
200
|
+
const fullEvent = {
|
|
201
|
+
id: event.id || `${event.type}:${event.commandId || event.nodeId || "flow"}:${createIntentHash(event.details)}:${event.timestamp}`,
|
|
202
|
+
type: event.type,
|
|
203
|
+
flowId: event.flowId,
|
|
204
|
+
actorDid: event.actorDid,
|
|
205
|
+
timestamp: event.timestamp,
|
|
206
|
+
details: JSON.parse(stableStringify(event.details))
|
|
207
|
+
};
|
|
208
|
+
if (event.nodeId) fullEvent.nodeId = event.nodeId;
|
|
209
|
+
if (event.commandId) fullEvent.commandId = event.commandId;
|
|
210
|
+
arr.push([
|
|
211
|
+
{
|
|
212
|
+
id: fullEvent.id,
|
|
213
|
+
type: fullEvent.type,
|
|
214
|
+
details: fullEvent,
|
|
215
|
+
meta: {
|
|
216
|
+
timestamp: new Date(fullEvent.timestamp).toISOString(),
|
|
217
|
+
userId: fullEvent.actorDid,
|
|
218
|
+
editable: false
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
]);
|
|
222
|
+
return fullEvent;
|
|
223
|
+
}
|
|
224
|
+
function readAgentLedgerEvents(yDoc, eventType) {
|
|
225
|
+
const auditMap = yDoc.getMap("auditTrail");
|
|
226
|
+
const arr = auditMap.get(FLOW_AGENT_AUDIT_SCOPE);
|
|
227
|
+
if (!arr) return [];
|
|
228
|
+
const events = [];
|
|
229
|
+
arr.forEach((value) => {
|
|
230
|
+
if (!value || typeof value !== "object") return;
|
|
231
|
+
const entry = value;
|
|
232
|
+
if (!entry.details) return;
|
|
233
|
+
if (eventType && entry.details.type !== eventType) return;
|
|
234
|
+
events.push(entry.details);
|
|
235
|
+
});
|
|
236
|
+
events.sort((a, b) => a.timestamp - b.timestamp || a.id.localeCompare(b.id));
|
|
237
|
+
return events;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// src/core/lib/flowAgent/policy.ts
|
|
241
|
+
var COMMAND_CAPABILITIES = {
|
|
242
|
+
diagnose_blocker: "flow/observe",
|
|
243
|
+
assign_actor: "flow/node/assign",
|
|
244
|
+
notify_actor: "flow/notify",
|
|
245
|
+
execute_action: "flow/node/execute",
|
|
246
|
+
validate_external_state: "flow/mutation/execute",
|
|
247
|
+
submit_claim: "flow/claim/submit",
|
|
248
|
+
watch_udid: "flow/observe",
|
|
249
|
+
archive_flow: "flow/archive",
|
|
250
|
+
propose_config_change: "flow/config/propose"
|
|
251
|
+
};
|
|
252
|
+
function requiredCapabilityForCommand(type, flowUri, nodeId) {
|
|
253
|
+
return capabilityForCommand(COMMAND_CAPABILITIES[type], flowUri, nodeId);
|
|
254
|
+
}
|
|
255
|
+
function isCapabilityMatch(granted, required) {
|
|
256
|
+
return canMatches(granted.can, required.can) && resourceMatches(granted.with, required.with);
|
|
257
|
+
}
|
|
258
|
+
function canMatches(granted, required) {
|
|
259
|
+
if (granted === "*" || granted === required) return true;
|
|
260
|
+
if (granted.endsWith("/*")) {
|
|
261
|
+
const prefix = granted.slice(0, -1);
|
|
262
|
+
return required.startsWith(prefix);
|
|
263
|
+
}
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
function resourceMatches(granted, required) {
|
|
267
|
+
if (granted === "*" || granted === required) return true;
|
|
268
|
+
if (granted.endsWith("*")) {
|
|
269
|
+
const prefix = granted.slice(0, -1);
|
|
270
|
+
return required.startsWith(prefix);
|
|
271
|
+
}
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
function evaluateFlowAgentPolicy({
|
|
275
|
+
actorDid,
|
|
276
|
+
commandType,
|
|
277
|
+
flowUri,
|
|
278
|
+
nodeId,
|
|
279
|
+
delegationStore,
|
|
280
|
+
delegations,
|
|
281
|
+
now = Date.now()
|
|
282
|
+
}) {
|
|
283
|
+
const required = requiredCapabilityForCommand(commandType, flowUri, nodeId);
|
|
284
|
+
const candidates = delegations || delegationStore?.getByAudience(actorDid) || [];
|
|
285
|
+
for (const delegation of candidates) {
|
|
286
|
+
if (delegation.audienceDid !== actorDid) continue;
|
|
287
|
+
if (delegation.expiration != null && delegation.expiration <= now) continue;
|
|
288
|
+
if (!delegation.capabilities.some((capability) => isCapabilityMatch(capability, required))) continue;
|
|
289
|
+
return {
|
|
290
|
+
allowed: true,
|
|
291
|
+
reason: "Allowed by UCAN delegation",
|
|
292
|
+
capability: required,
|
|
293
|
+
proofCids: [delegation.cid, ...delegation.proofCids]
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
allowed: false,
|
|
298
|
+
reason: `Actor ${actorDid} lacks ${required.can} on ${required.with}`,
|
|
299
|
+
capability: required,
|
|
300
|
+
proofCids: []
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/core/lib/flowAgent/commands.ts
|
|
305
|
+
function createAgentCommand({ type, flowId, flowUri, nodeId, actor, reason, payload = {}, now = Date.now() }) {
|
|
306
|
+
const capability = requiredCapabilityForCommand(type, flowUri, nodeId);
|
|
307
|
+
const id = computeAgentCommandId({ flowId, nodeId, type, payload });
|
|
308
|
+
return {
|
|
309
|
+
id,
|
|
310
|
+
type,
|
|
311
|
+
flowId,
|
|
312
|
+
flowUri,
|
|
313
|
+
nodeId,
|
|
314
|
+
actorDid: actor.did,
|
|
315
|
+
status: "queued",
|
|
316
|
+
capability,
|
|
317
|
+
idempotencyKey: `${id}:${createIntentHash({ capability, payload })}`,
|
|
318
|
+
createdAt: now,
|
|
319
|
+
updatedAt: now,
|
|
320
|
+
reason,
|
|
321
|
+
payload
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
function validateAgentCommand(command) {
|
|
325
|
+
if (!command.id) return { valid: false, error: "Command id is required" };
|
|
326
|
+
if (!command.flowId) return { valid: false, error: "flowId is required" };
|
|
327
|
+
if (!command.flowUri) return { valid: false, error: "flowUri is required" };
|
|
328
|
+
if (!command.nodeId) return { valid: false, error: "nodeId is required" };
|
|
329
|
+
if (!command.actorDid) return { valid: false, error: "actorDid is required" };
|
|
330
|
+
if (!command.capability?.can || !command.capability?.with) return { valid: false, error: "capability is required" };
|
|
331
|
+
if (!command.idempotencyKey) return { valid: false, error: "idempotencyKey is required" };
|
|
332
|
+
if (isExternalMutation(command.type) && !command.payload.validator) {
|
|
333
|
+
return {
|
|
334
|
+
valid: false,
|
|
335
|
+
error: `${command.type} requires payload.validator for post-action read-back validation`
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
if (command.type === "propose_config_change" && !command.payload.proposal) {
|
|
339
|
+
return { valid: false, error: "Config changes must be expressed as payload.proposal" };
|
|
340
|
+
}
|
|
341
|
+
return { valid: true };
|
|
342
|
+
}
|
|
343
|
+
function isExternalMutation(type) {
|
|
344
|
+
return type === "validate_external_state";
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// src/core/lib/flowAgent/context.ts
|
|
348
|
+
function buildFlowAgentContext({ yDoc, flowId, flowUri = `ixo:flow:${flowId}`, actor, blocks, editor, now }) {
|
|
349
|
+
return {
|
|
350
|
+
yDoc,
|
|
351
|
+
flowId,
|
|
352
|
+
flowUri,
|
|
353
|
+
actor,
|
|
354
|
+
...blocks ? { blocks } : {},
|
|
355
|
+
...editor ? { editor } : {},
|
|
356
|
+
...now ? { now } : {}
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// src/core/lib/flowAgent/lease.ts
|
|
361
|
+
var DEFAULT_FLOW_AGENT_LEASE_TTL_MS = 6e4;
|
|
362
|
+
function acquireFlowAgentLease({
|
|
363
|
+
leases,
|
|
364
|
+
commandId,
|
|
365
|
+
nodeId,
|
|
366
|
+
actorDid,
|
|
367
|
+
now = Date.now(),
|
|
368
|
+
ttlMs = DEFAULT_FLOW_AGENT_LEASE_TTL_MS
|
|
369
|
+
}) {
|
|
370
|
+
const current = leases.get(commandId);
|
|
371
|
+
if (current && current.expiresAt > now && current.actorDid !== actorDid) {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
const nextEpoch = current ? current.epoch + 1 : 1;
|
|
375
|
+
const lease = {
|
|
376
|
+
id: `${commandId}:lease:${nextEpoch}`,
|
|
377
|
+
commandId,
|
|
378
|
+
nodeId,
|
|
379
|
+
actorDid,
|
|
380
|
+
acquiredAt: now,
|
|
381
|
+
expiresAt: now + ttlMs,
|
|
382
|
+
epoch: nextEpoch
|
|
383
|
+
};
|
|
384
|
+
leases.set(commandId, lease);
|
|
385
|
+
return lease;
|
|
386
|
+
}
|
|
387
|
+
function validateFlowAgentLease(leases, lease, now = Date.now()) {
|
|
388
|
+
const current = leases.get(lease.commandId);
|
|
389
|
+
return !!current && current.actorDid === lease.actorDid && current.epoch === lease.epoch && current.expiresAt > now;
|
|
390
|
+
}
|
|
391
|
+
function releaseFlowAgentLease(leases, lease) {
|
|
392
|
+
const current = leases.get(lease.commandId);
|
|
393
|
+
if (!current || current.actorDid !== lease.actorDid || current.epoch !== lease.epoch) return false;
|
|
394
|
+
leases.delete(lease.commandId);
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
function cleanupExpiredFlowAgentLeases(leases, now = Date.now()) {
|
|
398
|
+
const expired = [];
|
|
399
|
+
leases.forEach((lease, commandId) => {
|
|
400
|
+
if (lease.expiresAt <= now) {
|
|
401
|
+
expired.push(lease);
|
|
402
|
+
leases.delete(commandId);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
return expired;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/core/lib/flowAgent/state.ts
|
|
409
|
+
function classifyBlockerCause(block, runtime) {
|
|
410
|
+
const props = getBlockProps(block);
|
|
411
|
+
if (runtime.error?.code === "missing_ucan") return "missing_ucan";
|
|
412
|
+
if (runtime.error?.code === "validation_mismatch") return "validation_mismatch";
|
|
413
|
+
if (runtime.error?.code === "external_confirmation_pending") return "external_confirmation_pending";
|
|
414
|
+
if (runtime.error) return "service_error";
|
|
415
|
+
const conditions = parseJsonProp(props.conditions, {});
|
|
416
|
+
if (conditions.enabled && Array.isArray(conditions.conditions) && conditions.conditions.length > 0) {
|
|
417
|
+
return "failed_upstream";
|
|
418
|
+
}
|
|
419
|
+
const inputs = parseJsonProp(props.inputs, {});
|
|
420
|
+
const requiredInputs = parseJsonProp(props.requiredInputs, []);
|
|
421
|
+
if (requiredInputs.some((key) => inputs[key] == null || inputs[key] === "")) {
|
|
422
|
+
return "missing_input";
|
|
423
|
+
}
|
|
424
|
+
return void 0;
|
|
425
|
+
}
|
|
426
|
+
function classifyNodeState({ block, runtime, now, pendingInvocationCount = 0 }) {
|
|
427
|
+
if (runtime.state === "completed" || runtime.state === "cancelled") return "Done";
|
|
428
|
+
if (runtime.state === "failed" || runtime.error) return "Blocked";
|
|
429
|
+
const dueAt = getDueAt(block);
|
|
430
|
+
if (dueAt != null && dueAt <= now) return "Overdue";
|
|
431
|
+
if (pendingInvocationCount > 0) return "Pending";
|
|
432
|
+
return "Pending";
|
|
433
|
+
}
|
|
434
|
+
function snapshotNode(block, runtime, now, pendingInvocationCount = 0) {
|
|
435
|
+
const nodeId = getBlockId(block);
|
|
436
|
+
if (!nodeId) {
|
|
437
|
+
throw new Error("Cannot snapshot a block without an id");
|
|
438
|
+
}
|
|
439
|
+
const publicState = classifyNodeState({ block, runtime, now, pendingInvocationCount });
|
|
440
|
+
const dueAt = getDueAt(block);
|
|
441
|
+
const assigneeDid = getAssigneeDid(block);
|
|
442
|
+
const snapshot = {
|
|
443
|
+
nodeId,
|
|
444
|
+
blockType: getBlockType(block),
|
|
445
|
+
runtime,
|
|
446
|
+
publicState,
|
|
447
|
+
pendingInvocationCount
|
|
448
|
+
};
|
|
449
|
+
const actionType = getBlockActionType(block);
|
|
450
|
+
if (actionType) snapshot.actionType = actionType;
|
|
451
|
+
const title = getBlockTitle(block);
|
|
452
|
+
if (title) snapshot.title = title;
|
|
453
|
+
if (publicState === "Blocked") {
|
|
454
|
+
snapshot.blockerCause = classifyBlockerCause(block, runtime) || "unknown";
|
|
455
|
+
}
|
|
456
|
+
if (assigneeDid) snapshot.assigneeDid = assigneeDid;
|
|
457
|
+
if (dueAt != null) snapshot.dueAt = dueAt;
|
|
458
|
+
return snapshot;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// src/core/lib/flowAgent/orchestrator.ts
|
|
462
|
+
function getBlocks(context) {
|
|
463
|
+
return context.blocks || context.editor?.document || [];
|
|
464
|
+
}
|
|
465
|
+
function getRuntime(yDoc, nodeId) {
|
|
466
|
+
const runtime = yDoc.getMap("runtime");
|
|
467
|
+
const value = runtime.get(nodeId);
|
|
468
|
+
return value && typeof value === "object" ? value : {};
|
|
469
|
+
}
|
|
470
|
+
function validateInputs(block) {
|
|
471
|
+
const props = getBlockProps(block);
|
|
472
|
+
const inputs = parseJsonProp(props.inputs, {});
|
|
473
|
+
const requiredInputs = parseJsonProp(props.requiredInputs, []);
|
|
474
|
+
const missing = requiredInputs.filter((key) => inputs[key] == null || inputs[key] === "");
|
|
475
|
+
if (missing.length > 0) {
|
|
476
|
+
return { valid: false, reason: `Missing required inputs: ${missing.join(", ")}` };
|
|
477
|
+
}
|
|
478
|
+
return { valid: true };
|
|
479
|
+
}
|
|
480
|
+
function chooseActor(block, options) {
|
|
481
|
+
const props = getBlockProps(block);
|
|
482
|
+
const actionType = getBlockActionType(block);
|
|
483
|
+
const requiredSkill = typeof props.requiredSkill === "string" ? props.requiredSkill : actionType;
|
|
484
|
+
if (!options.candidateActors || options.candidateActors.length === 0) return void 0;
|
|
485
|
+
if (!requiredSkill) return options.candidateActors[0];
|
|
486
|
+
return options.candidateActors.find((actor) => actor.skills?.includes(requiredSkill)) || options.candidateActors[0];
|
|
487
|
+
}
|
|
488
|
+
function canQueueCommand(type, nodeId, context, options) {
|
|
489
|
+
return evaluateFlowAgentPolicy({
|
|
490
|
+
actorDid: context.actor.did,
|
|
491
|
+
commandType: type,
|
|
492
|
+
flowUri: context.flowUri,
|
|
493
|
+
nodeId,
|
|
494
|
+
delegationStore: options.delegationStore,
|
|
495
|
+
delegations: options.delegations,
|
|
496
|
+
now: context.now?.() || Date.now()
|
|
497
|
+
}).allowed;
|
|
498
|
+
}
|
|
499
|
+
function queueIfAuthorized(context, options, type, nodeId, reason, payload) {
|
|
500
|
+
const now = context.now?.() || Date.now();
|
|
501
|
+
const policy = evaluateFlowAgentPolicy({
|
|
502
|
+
actorDid: context.actor.did,
|
|
503
|
+
commandType: type,
|
|
504
|
+
flowUri: context.flowUri,
|
|
505
|
+
nodeId,
|
|
506
|
+
delegationStore: options.delegationStore,
|
|
507
|
+
delegations: options.delegations,
|
|
508
|
+
now
|
|
509
|
+
});
|
|
510
|
+
appendAgentLedgerEvent(context.yDoc, {
|
|
511
|
+
type: "agent.decision",
|
|
512
|
+
flowId: context.flowId,
|
|
513
|
+
nodeId,
|
|
514
|
+
actorDid: context.actor.did,
|
|
515
|
+
timestamp: now,
|
|
516
|
+
details: {
|
|
517
|
+
commandType: type,
|
|
518
|
+
allowed: policy.allowed,
|
|
519
|
+
reason: policy.reason,
|
|
520
|
+
capability: policy.capability
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
if (!policy.allowed) return null;
|
|
524
|
+
const command = createAgentCommand({
|
|
525
|
+
type,
|
|
526
|
+
flowId: context.flowId,
|
|
527
|
+
flowUri: context.flowUri,
|
|
528
|
+
nodeId,
|
|
529
|
+
actor: context.actor,
|
|
530
|
+
reason,
|
|
531
|
+
payload,
|
|
532
|
+
now
|
|
533
|
+
});
|
|
534
|
+
const validation = validateAgentCommand(command);
|
|
535
|
+
if (!validation.valid) {
|
|
536
|
+
appendAgentLedgerEvent(context.yDoc, {
|
|
537
|
+
type: "agent.validation",
|
|
538
|
+
flowId: context.flowId,
|
|
539
|
+
nodeId,
|
|
540
|
+
commandId: command.id,
|
|
541
|
+
actorDid: context.actor.did,
|
|
542
|
+
timestamp: now,
|
|
543
|
+
details: { valid: false, error: validation.error }
|
|
544
|
+
});
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
return queueAgentCommand(context.yDoc, command).created ? command : null;
|
|
548
|
+
}
|
|
549
|
+
function planForSnapshot(context, options, block, snapshot) {
|
|
550
|
+
const queued = [];
|
|
551
|
+
const actionType = getBlockActionType(block);
|
|
552
|
+
if (snapshot.publicState === "Done") {
|
|
553
|
+
const runtime = snapshot.runtime;
|
|
554
|
+
if (actionType === "qi/claim.submit" && runtime.output?.claimId && !runtime.output?.udid) {
|
|
555
|
+
const command = queueIfAuthorized(context, options, "watch_udid", snapshot.nodeId, "Claim submitted but UDID is not yet observed", {
|
|
556
|
+
claimId: runtime.output.claimId,
|
|
557
|
+
timeoutMs: 864e5
|
|
558
|
+
});
|
|
559
|
+
if (command) queued.push(command);
|
|
560
|
+
}
|
|
561
|
+
return queued;
|
|
562
|
+
}
|
|
563
|
+
if (snapshot.publicState === "Overdue") {
|
|
564
|
+
const command = queueIfAuthorized(context, options, "notify_actor", snapshot.nodeId, "Action node is overdue", {
|
|
565
|
+
assigneeDid: snapshot.assigneeDid,
|
|
566
|
+
dueAt: snapshot.dueAt,
|
|
567
|
+
severity: "overdue"
|
|
568
|
+
});
|
|
569
|
+
if (command) queued.push(command);
|
|
570
|
+
return queued;
|
|
571
|
+
}
|
|
572
|
+
if (snapshot.publicState === "Blocked") {
|
|
573
|
+
const command = queueIfAuthorized(context, options, "diagnose_blocker", snapshot.nodeId, "Action node is blocked and requires diagnosis", {
|
|
574
|
+
cause: snapshot.blockerCause || "unknown",
|
|
575
|
+
error: snapshot.runtime.error
|
|
576
|
+
});
|
|
577
|
+
if (command) queued.push(command);
|
|
578
|
+
return queued;
|
|
579
|
+
}
|
|
580
|
+
const assigneeDid = getAssigneeDid(block);
|
|
581
|
+
if (!assigneeDid) {
|
|
582
|
+
const actor = chooseActor(block, options);
|
|
583
|
+
const command = queueIfAuthorized(context, options, "assign_actor", snapshot.nodeId, "Action node has no assigned actor", {
|
|
584
|
+
targetActorDid: actor?.did,
|
|
585
|
+
targetMatrixUserId: actor?.matrixUserId,
|
|
586
|
+
requiredActionType: actionType
|
|
587
|
+
});
|
|
588
|
+
if (command) queued.push(command);
|
|
589
|
+
return queued;
|
|
590
|
+
}
|
|
591
|
+
const inputValidation = validateInputs(block);
|
|
592
|
+
if (!inputValidation.valid) {
|
|
593
|
+
const command = queueIfAuthorized(context, options, "diagnose_blocker", snapshot.nodeId, inputValidation.reason || "Action node inputs are not ready", {
|
|
594
|
+
cause: "missing_input"
|
|
595
|
+
});
|
|
596
|
+
if (command) queued.push(command);
|
|
597
|
+
return queued;
|
|
598
|
+
}
|
|
599
|
+
if (actionType === "qi/claim.submit") {
|
|
600
|
+
const command = queueIfAuthorized(context, options, "submit_claim", snapshot.nodeId, "Claim action is pending and agent is authorized to submit", {
|
|
601
|
+
actionType
|
|
602
|
+
});
|
|
603
|
+
if (command) queued.push(command);
|
|
604
|
+
return queued;
|
|
605
|
+
}
|
|
606
|
+
if (actionType && canQueueCommand("execute_action", snapshot.nodeId, context, options)) {
|
|
607
|
+
const command = queueIfAuthorized(context, options, "execute_action", snapshot.nodeId, "Action node is pending and executable", {
|
|
608
|
+
actionType
|
|
609
|
+
});
|
|
610
|
+
if (command) queued.push(command);
|
|
611
|
+
return queued;
|
|
612
|
+
}
|
|
613
|
+
const notifyCommand = queueIfAuthorized(context, options, "notify_actor", snapshot.nodeId, "Action node is pending but the Flow Agent cannot execute it", {
|
|
614
|
+
assigneeDid,
|
|
615
|
+
requiredActionType: actionType,
|
|
616
|
+
severity: "attention"
|
|
617
|
+
});
|
|
618
|
+
if (notifyCommand) queued.push(notifyCommand);
|
|
619
|
+
return queued;
|
|
620
|
+
}
|
|
621
|
+
function planRalphLoopCommands(context, options = {}) {
|
|
622
|
+
const now = context.now?.() || Date.now();
|
|
623
|
+
const blocks = getBlocks(context);
|
|
624
|
+
const snapshots = [];
|
|
625
|
+
const queuedCommands = [];
|
|
626
|
+
for (const block of blocks) {
|
|
627
|
+
const nodeId = getBlockId(block);
|
|
628
|
+
if (!nodeId) continue;
|
|
629
|
+
const pendingInvocationCount = readPendingInvocations(context.yDoc, nodeId).length;
|
|
630
|
+
const snapshot = snapshotNode(block, getRuntime(context.yDoc, nodeId), now, pendingInvocationCount);
|
|
631
|
+
snapshots.push(snapshot);
|
|
632
|
+
queuedCommands.push(...planForSnapshot(context, options, block, snapshot));
|
|
633
|
+
}
|
|
634
|
+
const flowDone = snapshots.length > 0 && snapshots.every((snapshot) => snapshot.publicState === "Done");
|
|
635
|
+
if (flowDone && options.archiveWhenDone !== false) {
|
|
636
|
+
const archiveCommand = queueIfAuthorized(context, options, "archive_flow", context.flowId, "All Ralph loop action nodes are done", {
|
|
637
|
+
restartIfRepeats: true
|
|
638
|
+
});
|
|
639
|
+
if (archiveCommand) queuedCommands.push(archiveCommand);
|
|
640
|
+
}
|
|
641
|
+
return { snapshots, queuedCommands, flowDone };
|
|
642
|
+
}
|
|
643
|
+
async function callExecutor(command, context, executor) {
|
|
644
|
+
switch (command.type) {
|
|
645
|
+
case "execute_action":
|
|
646
|
+
return executor.executeAction ? executor.executeAction(command, context) : { commandId: command.id, success: false, error: "No executeAction handler configured" };
|
|
647
|
+
case "assign_actor":
|
|
648
|
+
return executor.assignActor ? executor.assignActor(command, context) : { commandId: command.id, success: false, error: "No assignActor handler configured" };
|
|
649
|
+
case "notify_actor":
|
|
650
|
+
return executor.notifyActor ? executor.notifyActor(command, context) : { commandId: command.id, success: false, error: "No notifyActor handler configured" };
|
|
651
|
+
case "validate_external_state":
|
|
652
|
+
return executor.validateExternalState ? executor.validateExternalState(command, context) : { commandId: command.id, success: false, error: "No validateExternalState handler configured" };
|
|
653
|
+
case "submit_claim":
|
|
654
|
+
return executor.submitClaim ? executor.submitClaim(command, context) : { commandId: command.id, success: false, error: "No submitClaim handler configured" };
|
|
655
|
+
case "watch_udid":
|
|
656
|
+
return executor.watchUdid ? executor.watchUdid(command, context) : { commandId: command.id, success: false, error: "No watchUdid handler configured" };
|
|
657
|
+
case "archive_flow":
|
|
658
|
+
return executor.archiveFlow ? executor.archiveFlow(command, context) : { commandId: command.id, success: false, error: "No archiveFlow handler configured" };
|
|
659
|
+
case "propose_config_change":
|
|
660
|
+
return executor.proposeConfigChange ? executor.proposeConfigChange(command, context) : { commandId: command.id, success: false, error: "No proposeConfigChange handler configured" };
|
|
661
|
+
case "diagnose_blocker":
|
|
662
|
+
return executor.diagnoseBlocker ? executor.diagnoseBlocker(command, context) : { commandId: command.id, success: false, error: "No diagnoseBlocker handler configured" };
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
async function executeQueuedAgentCommands(context, options = {}) {
|
|
666
|
+
const executor = options.executor;
|
|
667
|
+
if (!executor) return [];
|
|
668
|
+
const now = context.now?.() || Date.now();
|
|
669
|
+
const { leases } = getFlowAgentMaps(context.yDoc);
|
|
670
|
+
const results = [];
|
|
671
|
+
for (const command of readQueuedAgentCommands(context.yDoc)) {
|
|
672
|
+
const validation = validateAgentCommand(command);
|
|
673
|
+
if (!validation.valid) {
|
|
674
|
+
updateAgentCommand(context.yDoc, command.id, { status: "failed", error: validation.error, updatedAt: now });
|
|
675
|
+
results.push({ commandId: command.id, success: false, error: validation.error });
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
const policy = evaluateFlowAgentPolicy({
|
|
679
|
+
actorDid: context.actor.did,
|
|
680
|
+
commandType: command.type,
|
|
681
|
+
flowUri: context.flowUri,
|
|
682
|
+
nodeId: command.nodeId,
|
|
683
|
+
delegationStore: options.delegationStore,
|
|
684
|
+
delegations: options.delegations,
|
|
685
|
+
now
|
|
686
|
+
});
|
|
687
|
+
if (!policy.allowed) {
|
|
688
|
+
updateAgentCommand(context.yDoc, command.id, { status: "skipped", error: policy.reason, updatedAt: now });
|
|
689
|
+
results.push({ commandId: command.id, success: false, error: policy.reason });
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
const lease = acquireFlowAgentLease({
|
|
693
|
+
leases,
|
|
694
|
+
commandId: command.id,
|
|
695
|
+
nodeId: command.nodeId,
|
|
696
|
+
actorDid: context.actor.did,
|
|
697
|
+
now,
|
|
698
|
+
ttlMs: options.leaseTtlMs
|
|
699
|
+
});
|
|
700
|
+
if (!lease) {
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
updateAgentCommand(context.yDoc, command.id, { status: "running", lease, updatedAt: now });
|
|
704
|
+
try {
|
|
705
|
+
const result = await callExecutor({ ...command, lease, status: "running" }, context, executor);
|
|
706
|
+
if (!validateFlowAgentLease(leases, lease, context.now?.() || Date.now())) {
|
|
707
|
+
const leaseError = "Lease expired or was superseded before command commit";
|
|
708
|
+
updateAgentCommand(context.yDoc, command.id, { status: "failed", error: leaseError, updatedAt: context.now?.() || Date.now() });
|
|
709
|
+
results.push({ commandId: command.id, success: false, error: leaseError });
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
updateAgentCommand(context.yDoc, command.id, {
|
|
713
|
+
status: result.success ? "confirmed" : "failed",
|
|
714
|
+
error: result.error,
|
|
715
|
+
updatedAt: context.now?.() || Date.now()
|
|
716
|
+
});
|
|
717
|
+
appendAgentLedgerEvent(context.yDoc, {
|
|
718
|
+
type: result.success ? "agent.validation" : "agent.escalation",
|
|
719
|
+
flowId: context.flowId,
|
|
720
|
+
nodeId: command.nodeId,
|
|
721
|
+
commandId: command.id,
|
|
722
|
+
actorDid: context.actor.did,
|
|
723
|
+
timestamp: context.now?.() || Date.now(),
|
|
724
|
+
details: {
|
|
725
|
+
success: result.success,
|
|
726
|
+
confirmed: result.confirmed,
|
|
727
|
+
output: result.output,
|
|
728
|
+
error: result.error
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
results.push(result);
|
|
732
|
+
} finally {
|
|
733
|
+
releaseFlowAgentLease(leases, lease);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
return results;
|
|
737
|
+
}
|
|
738
|
+
async function tickFlowAgent(context, options = {}) {
|
|
739
|
+
const plan = planRalphLoopCommands(context, options);
|
|
740
|
+
const executedCommands = await executeQueuedAgentCommands(context, options);
|
|
741
|
+
return {
|
|
742
|
+
flowDone: plan.flowDone,
|
|
743
|
+
snapshots: plan.snapshots,
|
|
744
|
+
queuedCommands: plan.queuedCommands,
|
|
745
|
+
executedCommands
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// src/core/lib/flowAgent/service.ts
|
|
750
|
+
var FlowAgentService = class {
|
|
751
|
+
constructor(context, options = {}) {
|
|
752
|
+
this.context = context;
|
|
753
|
+
this.options = options;
|
|
754
|
+
this.timer = null;
|
|
755
|
+
this.running = false;
|
|
756
|
+
}
|
|
757
|
+
async tick() {
|
|
758
|
+
if (this.running) {
|
|
759
|
+
return {
|
|
760
|
+
flowDone: false,
|
|
761
|
+
snapshots: [],
|
|
762
|
+
queuedCommands: [],
|
|
763
|
+
executedCommands: []
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
this.running = true;
|
|
767
|
+
try {
|
|
768
|
+
const result = await tickFlowAgent(this.context, this.options);
|
|
769
|
+
await this.options.onTick?.(result);
|
|
770
|
+
return result;
|
|
771
|
+
} catch (error) {
|
|
772
|
+
this.options.onError?.(error);
|
|
773
|
+
throw error;
|
|
774
|
+
} finally {
|
|
775
|
+
this.running = false;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
start() {
|
|
779
|
+
if (this.timer) return;
|
|
780
|
+
const intervalMs = this.options.intervalMs || 3e4;
|
|
781
|
+
this.timer = setInterval(() => {
|
|
782
|
+
this.tick().catch((error) => {
|
|
783
|
+
this.options.onError?.(error);
|
|
784
|
+
});
|
|
785
|
+
}, intervalMs);
|
|
786
|
+
}
|
|
787
|
+
stop() {
|
|
788
|
+
if (!this.timer) return;
|
|
789
|
+
clearInterval(this.timer);
|
|
790
|
+
this.timer = null;
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
export {
|
|
795
|
+
resolveRuntimeRefs,
|
|
796
|
+
getFlowAgentMaps,
|
|
797
|
+
computeAgentCommandId,
|
|
798
|
+
queueAgentCommand,
|
|
799
|
+
readQueuedAgentCommands,
|
|
800
|
+
updateAgentCommand,
|
|
801
|
+
appendAgentLedgerEvent,
|
|
802
|
+
readAgentLedgerEvents,
|
|
803
|
+
requiredCapabilityForCommand,
|
|
804
|
+
isCapabilityMatch,
|
|
805
|
+
canMatches,
|
|
806
|
+
resourceMatches,
|
|
807
|
+
evaluateFlowAgentPolicy,
|
|
808
|
+
createAgentCommand,
|
|
809
|
+
validateAgentCommand,
|
|
810
|
+
isExternalMutation,
|
|
811
|
+
buildFlowAgentContext,
|
|
812
|
+
acquireFlowAgentLease,
|
|
813
|
+
validateFlowAgentLease,
|
|
814
|
+
releaseFlowAgentLease,
|
|
815
|
+
cleanupExpiredFlowAgentLeases,
|
|
816
|
+
classifyBlockerCause,
|
|
817
|
+
classifyNodeState,
|
|
818
|
+
snapshotNode,
|
|
819
|
+
planRalphLoopCommands,
|
|
820
|
+
executeQueuedAgentCommands,
|
|
821
|
+
tickFlowAgent,
|
|
822
|
+
FlowAgentService
|
|
823
|
+
};
|
|
824
|
+
//# sourceMappingURL=chunk-7ZKV7F6Z.mjs.map
|