@ixo/editor 5.12.0 → 5.14.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.
@@ -543,7 +543,14 @@ registerAction({
543
543
  // events-and-triggers plan.
544
544
  eligibleForEventTrigger: true,
545
545
  run: async (inputs, ctx) => {
546
- const { url, method = "GET", headers = {}, body } = inputs;
546
+ const endpoint = inputs.endpoint ?? inputs.url;
547
+ const url = typeof endpoint === "string" ? endpoint : "";
548
+ if (!url) {
549
+ throw new Error("HTTP request action requires an endpoint or url input");
550
+ }
551
+ const method = typeof inputs.method === "string" ? inputs.method : "GET";
552
+ const headers = inputs.headers && typeof inputs.headers === "object" && !Array.isArray(inputs.headers) ? inputs.headers : {};
553
+ const body = inputs.body;
547
554
  const requestFn = ctx.services.http?.request;
548
555
  if (requestFn) {
549
556
  const result = await requestFn({ url, method, headers, body });
@@ -2572,14 +2579,14 @@ var tempDomainCreatorSurvey = {
2572
2579
  function resolveEntityTypeFromSchema(schemaType) {
2573
2580
  const normalized = schemaType.replace(/^schema:/i, "").toLowerCase();
2574
2581
  const schemaToEntity = {
2575
- organization: "dao",
2576
- nonprofit: "dao",
2577
- corporation: "dao",
2578
- governmentorganization: "dao"
2582
+ organization: "dao/pod",
2583
+ nonprofit: "dao/pod",
2584
+ corporation: "dao/pod",
2585
+ governmentorganization: "dao/pod"
2579
2586
  };
2580
2587
  if (schemaToEntity[normalized]) return schemaToEntity[normalized];
2581
- const allowed = /* @__PURE__ */ new Set(["dao", "protocol", "oracle", "investment", "project", "asset"]);
2582
- return allowed.has(normalized) ? normalized : "dao";
2588
+ const allowed = /* @__PURE__ */ new Set(["dao/pod", "protocol", "oracle", "investment", "project", "asset"]);
2589
+ return allowed.has(normalized) ? normalized : "dao/pod";
2583
2590
  }
2584
2591
  function parseJsonInput(value) {
2585
2592
  if (!value) return null;
@@ -2718,7 +2725,7 @@ registerAction({
2718
2725
  if (!domainCardData?.credentialSubject?.name) {
2719
2726
  throw new Error("domainCardData is missing or invalid (credentialSubject.name required)");
2720
2727
  }
2721
- const entityType = String(inputs.entityType || "").trim() || (domainCardData.credentialSubject?.type?.[0] ? resolveEntityTypeFromSchema(domainCardData.credentialSubject.type[0]) : "dao");
2728
+ const entityType = String(inputs.entityType || "").trim() || (domainCardData.credentialSubject?.type?.[0] ? resolveEntityTypeFromSchema(domainCardData.credentialSubject.type[0]) : "dao/pod");
2722
2729
  const issuerDid = handlers.getEntityDid?.() || handlers.getCurrentUser?.()?.address;
2723
2730
  if (!issuerDid) throw new Error("Unable to determine issuer DID");
2724
2731
  const entityDidPlaceholder = "did:ixo:entity:pending";
@@ -5143,6 +5150,22 @@ function toSupportedDID(did) {
5143
5150
  }
5144
5151
  return void 0;
5145
5152
  }
5153
+ function normalizeDid(did) {
5154
+ if (did.startsWith("did:")) return did;
5155
+ if (!did.startsWith("@did-")) return did;
5156
+ const [localpart] = did.split(":");
5157
+ const parts = localpart.split("-");
5158
+ if (parts.length < 3 || parts[0] !== "@did") return did;
5159
+ return `did:${parts[1]}:${parts.slice(2).join("-")}`;
5160
+ }
5161
+ function toInvocationAudienceDID(did) {
5162
+ const normalized = normalizeDid(did);
5163
+ const supported = toSupportedDID(normalized);
5164
+ if (!supported) {
5165
+ throw new Error(`Invalid DID "${did}", must start with 'did:'`);
5166
+ }
5167
+ return supported;
5168
+ }
5146
5169
  function toUcantoCapabilities(capabilities) {
5147
5170
  return capabilities.map((cap) => ({
5148
5171
  can: cap.can,
@@ -5151,13 +5174,14 @@ function toUcantoCapabilities(capabilities) {
5151
5174
  }));
5152
5175
  }
5153
5176
  async function getSigner(handlers, did, didType, entityRoomId, pin) {
5154
- const supportedDid = toSupportedDID(did);
5177
+ const normalizedDid = normalizeDid(did);
5178
+ const supportedDid = toSupportedDID(normalizedDid);
5155
5179
  if (handlers.getPrivateKey) {
5156
- const privateKey = await handlers.getPrivateKey({ did, didType, entityRoomId, pin });
5180
+ const privateKey = await handlers.getPrivateKey({ did: normalizedDid, didType, entityRoomId, pin });
5157
5181
  return parseSigner(privateKey, supportedDid);
5158
5182
  }
5159
5183
  if (handlers.getMnemonic) {
5160
- const mnemonic = await handlers.getMnemonic({ did, didType, entityRoomId, pin });
5184
+ const mnemonic = await handlers.getMnemonic({ did: normalizedDid, didType, entityRoomId, pin });
5161
5185
  const result = await signerFromMnemonic(mnemonic, supportedDid);
5162
5186
  return result.signer;
5163
5187
  }
@@ -5168,6 +5192,7 @@ function getCidFromDelegation(delegation) {
5168
5192
  }
5169
5193
  var createUcanService = (config) => {
5170
5194
  const { delegationStore, invocationStore, handlers, flowOwnerDid } = config;
5195
+ const normalizedFlowOwnerDid = normalizeDid(flowOwnerDid);
5171
5196
  const delegationCache = /* @__PURE__ */ new Map();
5172
5197
  const isConfigured = () => {
5173
5198
  const hasSessionHandlers = !!(handlers.createSignerSession && handlers.signWithSession);
@@ -5202,11 +5227,12 @@ var createUcanService = (config) => {
5202
5227
  };
5203
5228
  const createRootDelegation = async (params) => {
5204
5229
  const { flowOwnerDid: ownerDid, issuerType, entityRoomId, flowUri: uri, pin } = params;
5205
- const signer = await getSigner(handlers, ownerDid, issuerType, entityRoomId, pin);
5230
+ const normalizedOwnerDid = normalizeDid(ownerDid);
5231
+ const signer = await getSigner(handlers, normalizedOwnerDid, issuerType, entityRoomId, pin);
5206
5232
  const capabilities = [{ can: "flow/*", with: uri }];
5207
5233
  const delegation = await ucanCreateDelegation({
5208
5234
  issuer: signer,
5209
- audience: ownerDid,
5235
+ audience: toInvocationAudienceDID(normalizedOwnerDid),
5210
5236
  capabilities,
5211
5237
  proofs: []
5212
5238
  });
@@ -5215,8 +5241,8 @@ var createUcanService = (config) => {
5215
5241
  const storedDelegation = {
5216
5242
  cid,
5217
5243
  delegation: serialized,
5218
- issuerDid: ownerDid,
5219
- audienceDid: ownerDid,
5244
+ issuerDid: normalizedOwnerDid,
5245
+ audienceDid: normalizedOwnerDid,
5220
5246
  capabilities,
5221
5247
  createdAt: Date.now(),
5222
5248
  format: "car",
@@ -5229,12 +5255,14 @@ var createUcanService = (config) => {
5229
5255
  };
5230
5256
  const createDelegation = async (params) => {
5231
5257
  const { issuerDid, issuerType, entityRoomId, audience, capabilities, proofs = [], expiration, pin } = params;
5232
- const signer = await getSigner(handlers, issuerDid, issuerType, entityRoomId, pin);
5258
+ const normalizedIssuerDid = normalizeDid(issuerDid);
5259
+ const normalizedAudienceDid = normalizeDid(audience);
5260
+ const signer = await getSigner(handlers, normalizedIssuerDid, issuerType, entityRoomId, pin);
5233
5261
  const proofCids = proofs.length > 0 ? proofs : [delegationStore.getRootCid()].filter(Boolean);
5234
5262
  const proofDelegations = await getProofDelegations(proofCids);
5235
5263
  const delegation = await ucanCreateDelegation({
5236
5264
  issuer: signer,
5237
- audience,
5265
+ audience: toInvocationAudienceDID(normalizedAudienceDid),
5238
5266
  capabilities: toUcantoCapabilities(capabilities),
5239
5267
  proofs: proofDelegations,
5240
5268
  expiration: expiration ? Math.floor(expiration / 1e3) : void 0
@@ -5244,8 +5272,8 @@ var createUcanService = (config) => {
5244
5272
  const storedDelegation = {
5245
5273
  cid,
5246
5274
  delegation: serialized,
5247
- issuerDid,
5248
- audienceDid: audience,
5275
+ issuerDid: normalizedIssuerDid,
5276
+ audienceDid: normalizedAudienceDid,
5249
5277
  capabilities,
5250
5278
  expiration,
5251
5279
  createdAt: Date.now(),
@@ -5319,7 +5347,7 @@ var createUcanService = (config) => {
5319
5347
  for (let i = 0; i < proofChain.length - 1; i++) {
5320
5348
  const current = proofChain[i];
5321
5349
  const parent = proofChain[i + 1];
5322
- if (parent.audienceDid !== current.issuerDid) {
5350
+ if (normalizeDid(parent.audienceDid) !== normalizeDid(current.issuerDid)) {
5323
5351
  return {
5324
5352
  valid: false,
5325
5353
  error: `Chain broken: ${parent.cid} audience (${parent.audienceDid}) != ${current.cid} issuer (${current.issuerDid})`
@@ -5327,7 +5355,7 @@ var createUcanService = (config) => {
5327
5355
  }
5328
5356
  }
5329
5357
  const root = proofChain[proofChain.length - 1];
5330
- if (root.issuerDid !== flowOwnerDid) {
5358
+ if (normalizeDid(root.issuerDid) !== normalizedFlowOwnerDid) {
5331
5359
  return { valid: false, error: `Root issuer ${root.issuerDid} is not flow owner ${flowOwnerDid}` };
5332
5360
  }
5333
5361
  return { valid: true, proofChain };
@@ -5352,7 +5380,7 @@ var createUcanService = (config) => {
5352
5380
  };
5353
5381
  const invocation = await ucanCreateInvocation({
5354
5382
  issuer: signer,
5355
- audience: flowOwnerDid,
5383
+ audience: toInvocationAudienceDID(normalizedFlowOwnerDid),
5356
5384
  capability: ucantoCapability,
5357
5385
  proofs: proofDelegations
5358
5386
  });
@@ -5868,13 +5896,12 @@ var VERSION_MANIFEST = {
5868
5896
  "1.0.0": {
5869
5897
  version: "1.0.0",
5870
5898
  label: "UCAN Required",
5871
- // TODO: flip to true when ready to enforce UCAN (end of week)
5872
- ucanRequired: false,
5873
- delegationRootRequired: false,
5874
- whitelistOnlyAllowed: true,
5875
- unrestrictedAllowed: true,
5876
- executionPath: "legacy",
5877
- authorizationFn: "v1",
5899
+ ucanRequired: true,
5900
+ delegationRootRequired: true,
5901
+ whitelistOnlyAllowed: false,
5902
+ unrestrictedAllowed: false,
5903
+ executionPath: "invocation",
5904
+ authorizationFn: "v2",
5878
5905
  allowedAuthModes: ["capability"],
5879
5906
  ui: {
5880
5907
  showDelegationPanel: true,
@@ -7571,6 +7598,1218 @@ function setRootMetadata(yDoc, root, plan, creatorDid, docId) {
7571
7598
  });
7572
7599
  }
7573
7600
 
7601
+ // src/core/lib/flowAgent/store.ts
7602
+ import * as Y6 from "yjs";
7603
+
7604
+ // src/core/lib/flowAgent/utils.ts
7605
+ function stableStringify(value) {
7606
+ if (value === void 0) return "null";
7607
+ if (value === null || typeof value !== "object") {
7608
+ return JSON.stringify(value);
7609
+ }
7610
+ if (Array.isArray(value)) {
7611
+ return `[${value.map((item) => stableStringify(item)).join(",")}]`;
7612
+ }
7613
+ const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
7614
+ return `{${entries.map(([key, val]) => `${JSON.stringify(key)}:${stableStringify(val)}`).join(",")}}`;
7615
+ }
7616
+ function fnv1a322(input) {
7617
+ let hash = 2166136261;
7618
+ for (let i = 0; i < input.length; i++) {
7619
+ hash ^= input.charCodeAt(i);
7620
+ hash = Math.imul(hash, 16777619);
7621
+ }
7622
+ return (hash >>> 0).toString(16).padStart(8, "0");
7623
+ }
7624
+ function createIntentHash(intent) {
7625
+ return fnv1a322(stableStringify(intent));
7626
+ }
7627
+ function parseJsonProp(value, fallback) {
7628
+ if (value == null || value === "") return fallback;
7629
+ if (typeof value === "object") return value;
7630
+ if (typeof value !== "string") return fallback;
7631
+ try {
7632
+ const parsed = JSON.parse(value);
7633
+ return parsed;
7634
+ } catch {
7635
+ return fallback;
7636
+ }
7637
+ }
7638
+ function getBlockActionType(block) {
7639
+ const props = block?.props;
7640
+ const actionType = props?.actionType;
7641
+ return typeof actionType === "string" && actionType.length > 0 ? actionType : void 0;
7642
+ }
7643
+ function getBlockTitle(block) {
7644
+ const props = block?.props;
7645
+ const title = props?.title;
7646
+ return typeof title === "string" && title.length > 0 ? title : void 0;
7647
+ }
7648
+ function getBlockType(block) {
7649
+ const type = block?.type;
7650
+ return typeof type === "string" && type.length > 0 ? type : "block";
7651
+ }
7652
+ function getBlockProps(block) {
7653
+ return block?.props || {};
7654
+ }
7655
+ function getBlockId(block) {
7656
+ const id = block?.id;
7657
+ return typeof id === "string" && id.length > 0 ? id : void 0;
7658
+ }
7659
+ function getAssigneeDid(block) {
7660
+ const props = getBlockProps(block);
7661
+ const assignment = parseJsonProp(props.assignment, {});
7662
+ const did = assignment?.assignedActor?.did;
7663
+ return typeof did === "string" && did.length > 0 ? did : void 0;
7664
+ }
7665
+ function getDueAt(block) {
7666
+ const props = getBlockProps(block);
7667
+ const ttlAbsoluteDueDate = props.ttlAbsoluteDueDate;
7668
+ if (typeof ttlAbsoluteDueDate !== "string" || ttlAbsoluteDueDate.length === 0) return void 0;
7669
+ const parsed = new Date(ttlAbsoluteDueDate).getTime();
7670
+ return Number.isFinite(parsed) ? parsed : void 0;
7671
+ }
7672
+ function capabilityForCommand(can, flowUri, nodeId) {
7673
+ return {
7674
+ can,
7675
+ with: `${flowUri}:${nodeId}`
7676
+ };
7677
+ }
7678
+
7679
+ // src/core/lib/flowAgent/store.ts
7680
+ var FLOW_AGENT_OUTBOX_KEY = "agentOutbox";
7681
+ var FLOW_AGENT_LEASES_KEY = "agentLeases";
7682
+ var FLOW_AGENT_AUDIT_SCOPE = "__flow_agent__";
7683
+ function getFlowAgentMaps(yDoc) {
7684
+ return {
7685
+ outbox: yDoc.getMap(FLOW_AGENT_OUTBOX_KEY),
7686
+ leases: yDoc.getMap(FLOW_AGENT_LEASES_KEY)
7687
+ };
7688
+ }
7689
+ function computeAgentCommandId(params) {
7690
+ const intentHash = createIntentHash(params);
7691
+ return `${params.flowId}:${params.nodeId}:${intentHash}`;
7692
+ }
7693
+ function queueAgentCommand(yDoc, command) {
7694
+ const { outbox } = getFlowAgentMaps(yDoc);
7695
+ const existing = outbox.get(command.id);
7696
+ if (existing) {
7697
+ if (existing.type === "assign_actor" && existing.status === "failed") {
7698
+ const retried = {
7699
+ ...existing,
7700
+ status: "queued",
7701
+ error: void 0,
7702
+ lease: void 0,
7703
+ reason: command.reason,
7704
+ payload: command.payload,
7705
+ capability: command.capability,
7706
+ actorDid: command.actorDid,
7707
+ updatedAt: command.updatedAt
7708
+ };
7709
+ outbox.set(command.id, retried);
7710
+ try {
7711
+ appendAgentLedgerEvent(yDoc, {
7712
+ type: "agent.command",
7713
+ flowId: retried.flowId,
7714
+ nodeId: retried.nodeId,
7715
+ commandId: retried.id,
7716
+ actorDid: retried.actorDid,
7717
+ timestamp: retried.updatedAt,
7718
+ details: {
7719
+ commandType: retried.type,
7720
+ status: retried.status,
7721
+ idempotencyKey: retried.idempotencyKey,
7722
+ reason: retried.reason,
7723
+ retry: true
7724
+ }
7725
+ });
7726
+ } catch (error) {
7727
+ const message = error instanceof Error ? error.message : "Failed to append flow-agent ledger event";
7728
+ if (!message.includes("Unexpected content type") && !message.includes("already been defined with a different constructor")) {
7729
+ throw error;
7730
+ }
7731
+ }
7732
+ return { command: retried, created: true };
7733
+ }
7734
+ return { command: existing, created: false };
7735
+ }
7736
+ outbox.set(command.id, command);
7737
+ try {
7738
+ appendAgentLedgerEvent(yDoc, {
7739
+ type: "agent.command",
7740
+ flowId: command.flowId,
7741
+ nodeId: command.nodeId,
7742
+ commandId: command.id,
7743
+ actorDid: command.actorDid,
7744
+ timestamp: command.createdAt,
7745
+ details: {
7746
+ commandType: command.type,
7747
+ status: command.status,
7748
+ idempotencyKey: command.idempotencyKey,
7749
+ reason: command.reason
7750
+ }
7751
+ });
7752
+ } catch (error) {
7753
+ const message = error instanceof Error ? error.message : "Failed to append flow-agent ledger event";
7754
+ if (!message.includes("Unexpected content type") && !message.includes("already been defined with a different constructor")) {
7755
+ throw error;
7756
+ }
7757
+ }
7758
+ return { command, created: true };
7759
+ }
7760
+ function readQueuedAgentCommands(yDoc) {
7761
+ const { outbox } = getFlowAgentMaps(yDoc);
7762
+ const commands = [];
7763
+ outbox.forEach((command) => {
7764
+ if (command.status === "queued" || command.status === "leased" || command.status === "running") {
7765
+ commands.push(command);
7766
+ }
7767
+ });
7768
+ commands.sort((a, b) => a.createdAt - b.createdAt || a.id.localeCompare(b.id));
7769
+ return commands;
7770
+ }
7771
+ function updateAgentCommand(yDoc, commandId, patch) {
7772
+ const { outbox } = getFlowAgentMaps(yDoc);
7773
+ const current = outbox.get(commandId);
7774
+ if (!current) return null;
7775
+ const next = { ...current, ...patch };
7776
+ outbox.set(commandId, next);
7777
+ return next;
7778
+ }
7779
+ function appendAgentLedgerEvent(yDoc, event) {
7780
+ const auditMap = yDoc.getMap("auditTrail");
7781
+ let arr = auditMap.get(FLOW_AGENT_AUDIT_SCOPE);
7782
+ if (!arr) {
7783
+ arr = new Y6.Array();
7784
+ auditMap.set(FLOW_AGENT_AUDIT_SCOPE, arr);
7785
+ }
7786
+ const fullEvent = {
7787
+ id: event.id || `${event.type}:${event.commandId || event.nodeId || "flow"}:${createIntentHash(event.details)}:${event.timestamp}`,
7788
+ type: event.type,
7789
+ flowId: event.flowId,
7790
+ actorDid: event.actorDid,
7791
+ timestamp: event.timestamp,
7792
+ details: JSON.parse(stableStringify(event.details))
7793
+ };
7794
+ if (event.nodeId) fullEvent.nodeId = event.nodeId;
7795
+ if (event.commandId) fullEvent.commandId = event.commandId;
7796
+ arr.push([
7797
+ {
7798
+ id: fullEvent.id,
7799
+ type: fullEvent.type,
7800
+ details: fullEvent,
7801
+ meta: {
7802
+ timestamp: new Date(fullEvent.timestamp).toISOString(),
7803
+ userId: fullEvent.actorDid,
7804
+ editable: false
7805
+ }
7806
+ }
7807
+ ]);
7808
+ return fullEvent;
7809
+ }
7810
+ function readAgentLedgerEvents(yDoc, eventType) {
7811
+ const auditMap = yDoc.getMap("auditTrail");
7812
+ const arr = auditMap.get(FLOW_AGENT_AUDIT_SCOPE);
7813
+ if (!arr) return [];
7814
+ const events = [];
7815
+ arr.forEach((value) => {
7816
+ if (!value || typeof value !== "object") return;
7817
+ const entry = value;
7818
+ if (!entry.details) return;
7819
+ if (eventType && entry.details.type !== eventType) return;
7820
+ events.push(entry.details);
7821
+ });
7822
+ events.sort((a, b) => a.timestamp - b.timestamp || a.id.localeCompare(b.id));
7823
+ return events;
7824
+ }
7825
+
7826
+ // src/core/lib/flowAgent/policy.ts
7827
+ var COMMAND_CAPABILITIES = {
7828
+ diagnose_blocker: "flow/observe",
7829
+ assign_actor: "flow/node/assign",
7830
+ notify_actor: "flow/notify",
7831
+ execute_action: "flow/node/execute",
7832
+ validate_external_state: "flow/mutation/execute",
7833
+ submit_claim: "flow/claim/submit",
7834
+ watch_udid: "flow/observe",
7835
+ archive_flow: "flow/archive",
7836
+ propose_config_change: "flow/config/propose"
7837
+ };
7838
+ function requiredCapabilityForCommand(type, flowUri, nodeId) {
7839
+ return capabilityForCommand(COMMAND_CAPABILITIES[type], flowUri, nodeId);
7840
+ }
7841
+ function isCapabilityMatch(granted, required) {
7842
+ return canMatches(granted.can, required.can) && resourceMatches(granted.with, required.with);
7843
+ }
7844
+ function canMatches(granted, required) {
7845
+ if (granted === "*" || granted === required) return true;
7846
+ if (granted.endsWith("/*")) {
7847
+ const prefix = granted.slice(0, -1);
7848
+ return required.startsWith(prefix);
7849
+ }
7850
+ return false;
7851
+ }
7852
+ function resourceMatches(granted, required) {
7853
+ if (granted === "*" || granted === required) return true;
7854
+ if (granted.endsWith("*")) {
7855
+ const prefix = granted.slice(0, -1);
7856
+ return required.startsWith(prefix);
7857
+ }
7858
+ return false;
7859
+ }
7860
+ function evaluateFlowAgentPolicy({
7861
+ actorDid,
7862
+ commandType,
7863
+ flowUri,
7864
+ nodeId,
7865
+ delegationStore,
7866
+ delegations,
7867
+ now = Date.now()
7868
+ }) {
7869
+ const required = requiredCapabilityForCommand(commandType, flowUri, nodeId);
7870
+ const candidates = delegations || delegationStore?.getByAudience(actorDid) || [];
7871
+ for (const delegation of candidates) {
7872
+ if (delegation.audienceDid !== actorDid) continue;
7873
+ if (delegation.expiration != null && delegation.expiration <= now) continue;
7874
+ if (!delegation.capabilities.some((capability) => isCapabilityMatch(capability, required))) continue;
7875
+ return {
7876
+ allowed: true,
7877
+ reason: "Allowed by UCAN delegation",
7878
+ capability: required,
7879
+ proofCids: [delegation.cid, ...delegation.proofCids]
7880
+ };
7881
+ }
7882
+ return {
7883
+ allowed: false,
7884
+ reason: `Actor ${actorDid} lacks ${required.can} on ${required.with}`,
7885
+ capability: required,
7886
+ proofCids: []
7887
+ };
7888
+ }
7889
+
7890
+ // src/core/lib/flowAgent/commands.ts
7891
+ function createAgentCommand({ type, flowId, flowUri, nodeId, actor, reason, payload = {}, now = Date.now() }) {
7892
+ const capability = requiredCapabilityForCommand(type, flowUri, nodeId);
7893
+ const id = computeAgentCommandId({ flowId, nodeId, type, payload });
7894
+ return {
7895
+ id,
7896
+ type,
7897
+ flowId,
7898
+ flowUri,
7899
+ nodeId,
7900
+ actorDid: actor.did,
7901
+ status: "queued",
7902
+ capability,
7903
+ idempotencyKey: `${id}:${createIntentHash({ capability, payload })}`,
7904
+ createdAt: now,
7905
+ updatedAt: now,
7906
+ reason,
7907
+ payload
7908
+ };
7909
+ }
7910
+ function validateAgentCommand(command) {
7911
+ if (!command.id) return { valid: false, error: "Command id is required" };
7912
+ if (!command.flowId) return { valid: false, error: "flowId is required" };
7913
+ if (!command.flowUri) return { valid: false, error: "flowUri is required" };
7914
+ if (!command.nodeId) return { valid: false, error: "nodeId is required" };
7915
+ if (!command.actorDid) return { valid: false, error: "actorDid is required" };
7916
+ if (!command.capability?.can || !command.capability?.with) return { valid: false, error: "capability is required" };
7917
+ if (!command.idempotencyKey) return { valid: false, error: "idempotencyKey is required" };
7918
+ if (isExternalMutation(command.type) && !command.payload.validator) {
7919
+ return {
7920
+ valid: false,
7921
+ error: `${command.type} requires payload.validator for post-action read-back validation`
7922
+ };
7923
+ }
7924
+ if (command.type === "propose_config_change" && !command.payload.proposal) {
7925
+ return { valid: false, error: "Config changes must be expressed as payload.proposal" };
7926
+ }
7927
+ return { valid: true };
7928
+ }
7929
+ function isExternalMutation(type) {
7930
+ return type === "validate_external_state";
7931
+ }
7932
+
7933
+ // src/core/lib/flowAgent/context.ts
7934
+ function buildFlowAgentContext({ yDoc, flowId, flowUri = `ixo:flow:${flowId}`, actor, blocks, editor, now }) {
7935
+ return {
7936
+ yDoc,
7937
+ flowId,
7938
+ flowUri,
7939
+ actor,
7940
+ ...blocks ? { blocks } : {},
7941
+ ...editor ? { editor } : {},
7942
+ ...now ? { now } : {}
7943
+ };
7944
+ }
7945
+
7946
+ // src/core/lib/flowAgent/lease.ts
7947
+ var DEFAULT_FLOW_AGENT_LEASE_TTL_MS = 6e4;
7948
+ function acquireFlowAgentLease({
7949
+ leases,
7950
+ commandId,
7951
+ nodeId,
7952
+ actorDid,
7953
+ now = Date.now(),
7954
+ ttlMs = DEFAULT_FLOW_AGENT_LEASE_TTL_MS
7955
+ }) {
7956
+ const current = leases.get(commandId);
7957
+ if (current && current.expiresAt > now && current.actorDid !== actorDid) {
7958
+ return null;
7959
+ }
7960
+ const nextEpoch = current ? current.epoch + 1 : 1;
7961
+ const lease = {
7962
+ id: `${commandId}:lease:${nextEpoch}`,
7963
+ commandId,
7964
+ nodeId,
7965
+ actorDid,
7966
+ acquiredAt: now,
7967
+ expiresAt: now + ttlMs,
7968
+ epoch: nextEpoch
7969
+ };
7970
+ leases.set(commandId, lease);
7971
+ return lease;
7972
+ }
7973
+ function validateFlowAgentLease(leases, lease, now = Date.now()) {
7974
+ const current = leases.get(lease.commandId);
7975
+ return !!current && current.actorDid === lease.actorDid && current.epoch === lease.epoch && current.expiresAt > now;
7976
+ }
7977
+ function releaseFlowAgentLease(leases, lease) {
7978
+ const current = leases.get(lease.commandId);
7979
+ if (!current || current.actorDid !== lease.actorDid || current.epoch !== lease.epoch) return false;
7980
+ leases.delete(lease.commandId);
7981
+ return true;
7982
+ }
7983
+ function cleanupExpiredFlowAgentLeases(leases, now = Date.now()) {
7984
+ const expired = [];
7985
+ leases.forEach((lease, commandId) => {
7986
+ if (lease.expiresAt <= now) {
7987
+ expired.push(lease);
7988
+ leases.delete(commandId);
7989
+ }
7990
+ });
7991
+ return expired;
7992
+ }
7993
+
7994
+ // src/core/lib/flowAgent/state.ts
7995
+ function classifyBlockerCause(block, runtime) {
7996
+ const props = getBlockProps(block);
7997
+ if (runtime.error?.code === "missing_ucan") return "missing_ucan";
7998
+ if (runtime.error?.code === "validation_mismatch") return "validation_mismatch";
7999
+ if (runtime.error?.code === "external_confirmation_pending") return "external_confirmation_pending";
8000
+ if (runtime.error) return "service_error";
8001
+ const conditions = parseJsonProp(props.conditions, {});
8002
+ if (conditions.enabled && Array.isArray(conditions.conditions) && conditions.conditions.length > 0) {
8003
+ return "failed_upstream";
8004
+ }
8005
+ const inputs = parseJsonProp(props.inputs, {});
8006
+ const requiredInputs = parseJsonProp(props.requiredInputs, []);
8007
+ if (requiredInputs.some((key) => inputs[key] == null || inputs[key] === "")) {
8008
+ return "missing_input";
8009
+ }
8010
+ return void 0;
8011
+ }
8012
+ function classifyNodeState({ block, runtime, now, pendingInvocationCount = 0 }) {
8013
+ if (runtime.state === "completed" || runtime.state === "cancelled") return "Done";
8014
+ if (runtime.state === "failed" || runtime.error) return "Blocked";
8015
+ const dueAt = getDueAt(block);
8016
+ if (dueAt != null && dueAt <= now) return "Overdue";
8017
+ if (pendingInvocationCount > 0) return "Pending";
8018
+ return "Pending";
8019
+ }
8020
+ function snapshotNode(block, runtime, now, pendingInvocationCount = 0) {
8021
+ const nodeId = getBlockId(block);
8022
+ if (!nodeId) {
8023
+ throw new Error("Cannot snapshot a block without an id");
8024
+ }
8025
+ const publicState = classifyNodeState({ block, runtime, now, pendingInvocationCount });
8026
+ const dueAt = getDueAt(block);
8027
+ const assigneeDid = getAssigneeDid(block);
8028
+ const snapshot = {
8029
+ nodeId,
8030
+ blockType: getBlockType(block),
8031
+ runtime,
8032
+ publicState,
8033
+ pendingInvocationCount
8034
+ };
8035
+ const actionType = getBlockActionType(block);
8036
+ if (actionType) snapshot.actionType = actionType;
8037
+ const title = getBlockTitle(block);
8038
+ if (title) snapshot.title = title;
8039
+ if (publicState === "Blocked") {
8040
+ snapshot.blockerCause = classifyBlockerCause(block, runtime) || "unknown";
8041
+ }
8042
+ if (assigneeDid) snapshot.assigneeDid = assigneeDid;
8043
+ if (dueAt != null) snapshot.dueAt = dueAt;
8044
+ return snapshot;
8045
+ }
8046
+
8047
+ // src/core/lib/flowAgent/orchestrator.ts
8048
+ var BLOCKER_DIAGNOSIS_VERSION = 2;
8049
+ function getBlocks(context) {
8050
+ return context.blocks || context.editor?.document || [];
8051
+ }
8052
+ function getRuntime(yDoc, nodeId) {
8053
+ const runtime = yDoc.getMap("runtime");
8054
+ const value = runtime.get(nodeId);
8055
+ return value && typeof value === "object" ? value : {};
8056
+ }
8057
+ function validateInputs(block) {
8058
+ const props = getBlockProps(block);
8059
+ const inputs = parseJsonProp(props.inputs, {});
8060
+ const requiredInputs = parseJsonProp(props.requiredInputs, []);
8061
+ const missing = requiredInputs.filter((key) => inputs[key] == null || inputs[key] === "");
8062
+ if (missing.length > 0) {
8063
+ return { valid: false, reason: `Missing required inputs: ${missing.join(", ")}` };
8064
+ }
8065
+ return { valid: true };
8066
+ }
8067
+ function chooseActor(block, options) {
8068
+ const props = getBlockProps(block);
8069
+ const actionType = getBlockActionType(block);
8070
+ const requiredSkill = typeof props.requiredSkill === "string" ? props.requiredSkill : actionType;
8071
+ if (!options.candidateActors || options.candidateActors.length === 0) return void 0;
8072
+ if (!requiredSkill) return options.candidateActors[0];
8073
+ return options.candidateActors.find((actor) => actor.skills?.includes(requiredSkill)) || options.candidateActors[0];
8074
+ }
8075
+ function canQueueCommand(type, nodeId, context, options) {
8076
+ return evaluateFlowAgentPolicy({
8077
+ actorDid: context.actor.did,
8078
+ commandType: type,
8079
+ flowUri: context.flowUri,
8080
+ nodeId,
8081
+ delegationStore: options.delegationStore,
8082
+ delegations: options.delegations,
8083
+ now: context.now?.() || Date.now()
8084
+ }).allowed;
8085
+ }
8086
+ function safeAppendAgentLedgerEvent(yDoc, event) {
8087
+ try {
8088
+ appendAgentLedgerEvent(yDoc, event);
8089
+ } catch (error) {
8090
+ const message = error instanceof Error ? error.message : "Failed to append flow-agent ledger event";
8091
+ if (message.includes("Unexpected content type") || message.includes("already been defined with a different constructor")) {
8092
+ return;
8093
+ }
8094
+ throw error;
8095
+ }
8096
+ }
8097
+ function queueIfAuthorized(context, options, type, nodeId, reason, payload) {
8098
+ const now = context.now?.() || Date.now();
8099
+ const policy = evaluateFlowAgentPolicy({
8100
+ actorDid: context.actor.did,
8101
+ commandType: type,
8102
+ flowUri: context.flowUri,
8103
+ nodeId,
8104
+ delegationStore: options.delegationStore,
8105
+ delegations: options.delegations,
8106
+ now
8107
+ });
8108
+ safeAppendAgentLedgerEvent(context.yDoc, {
8109
+ type: "agent.decision",
8110
+ flowId: context.flowId,
8111
+ nodeId,
8112
+ actorDid: context.actor.did,
8113
+ timestamp: now,
8114
+ details: {
8115
+ commandType: type,
8116
+ allowed: policy.allowed,
8117
+ reason: policy.reason,
8118
+ capability: policy.capability
8119
+ }
8120
+ });
8121
+ if (!policy.allowed) return null;
8122
+ const command = createAgentCommand({
8123
+ type,
8124
+ flowId: context.flowId,
8125
+ flowUri: context.flowUri,
8126
+ nodeId,
8127
+ actor: context.actor,
8128
+ reason,
8129
+ payload,
8130
+ now
8131
+ });
8132
+ const validation = validateAgentCommand(command);
8133
+ if (!validation.valid) {
8134
+ safeAppendAgentLedgerEvent(context.yDoc, {
8135
+ type: "agent.validation",
8136
+ flowId: context.flowId,
8137
+ nodeId,
8138
+ commandId: command.id,
8139
+ actorDid: context.actor.did,
8140
+ timestamp: now,
8141
+ details: { valid: false, error: validation.error }
8142
+ });
8143
+ return null;
8144
+ }
8145
+ return queueAgentCommand(context.yDoc, command).created ? command : null;
8146
+ }
8147
+ function planForSnapshot(context, options, block, snapshot) {
8148
+ const queued = [];
8149
+ const actionType = getBlockActionType(block);
8150
+ if (snapshot.publicState === "Done") {
8151
+ const runtime = snapshot.runtime;
8152
+ if (actionType === "qi/claim.submit" && runtime.output?.claimId && !runtime.output?.udid) {
8153
+ const command = queueIfAuthorized(context, options, "watch_udid", snapshot.nodeId, "Claim submitted but UDID is not yet observed", {
8154
+ claimId: runtime.output.claimId,
8155
+ timeoutMs: 864e5
8156
+ });
8157
+ if (command) queued.push(command);
8158
+ }
8159
+ return queued;
8160
+ }
8161
+ if (snapshot.publicState === "Overdue") {
8162
+ const command = queueIfAuthorized(context, options, "notify_actor", snapshot.nodeId, "Action node is overdue", {
8163
+ assigneeDid: snapshot.assigneeDid,
8164
+ dueAt: snapshot.dueAt,
8165
+ severity: "overdue"
8166
+ });
8167
+ if (command) queued.push(command);
8168
+ return queued;
8169
+ }
8170
+ if (snapshot.publicState === "Blocked") {
8171
+ const command = queueIfAuthorized(context, options, "diagnose_blocker", snapshot.nodeId, "Action node is blocked and requires diagnosis", {
8172
+ cause: snapshot.blockerCause || "unknown",
8173
+ error: snapshot.runtime.error,
8174
+ diagnosisVersion: BLOCKER_DIAGNOSIS_VERSION
8175
+ });
8176
+ if (command) queued.push(command);
8177
+ return queued;
8178
+ }
8179
+ const assigneeDid = getAssigneeDid(block);
8180
+ if (!assigneeDid) {
8181
+ const actor = chooseActor(block, options);
8182
+ const command = queueIfAuthorized(context, options, "assign_actor", snapshot.nodeId, "Action node has no assigned actor", {
8183
+ targetActorDid: actor?.did,
8184
+ targetMatrixUserId: actor?.matrixUserId,
8185
+ requiredActionType: actionType
8186
+ });
8187
+ if (command) queued.push(command);
8188
+ return queued;
8189
+ }
8190
+ const inputValidation = validateInputs(block);
8191
+ if (!inputValidation.valid) {
8192
+ const command = queueIfAuthorized(context, options, "diagnose_blocker", snapshot.nodeId, inputValidation.reason || "Action node inputs are not ready", {
8193
+ cause: "missing_input"
8194
+ });
8195
+ if (command) queued.push(command);
8196
+ return queued;
8197
+ }
8198
+ if (actionType === "qi/claim.submit") {
8199
+ const command = queueIfAuthorized(context, options, "submit_claim", snapshot.nodeId, "Claim action is pending and agent is authorized to submit", {
8200
+ actionType
8201
+ });
8202
+ if (command) queued.push(command);
8203
+ return queued;
8204
+ }
8205
+ if (actionType && canQueueCommand("execute_action", snapshot.nodeId, context, options)) {
8206
+ const command = queueIfAuthorized(context, options, "execute_action", snapshot.nodeId, "Action node is pending and executable", {
8207
+ actionType
8208
+ });
8209
+ if (command) queued.push(command);
8210
+ return queued;
8211
+ }
8212
+ const notifyCommand = queueIfAuthorized(context, options, "notify_actor", snapshot.nodeId, "Action node is pending but the Flow Agent cannot execute it", {
8213
+ assigneeDid,
8214
+ requiredActionType: actionType,
8215
+ severity: "attention"
8216
+ });
8217
+ if (notifyCommand) queued.push(notifyCommand);
8218
+ return queued;
8219
+ }
8220
+ function planRalphLoopCommands(context, options = {}) {
8221
+ const now = context.now?.() || Date.now();
8222
+ const blocks = getBlocks(context);
8223
+ const snapshots = [];
8224
+ const queuedCommands = [];
8225
+ for (const block of blocks) {
8226
+ const nodeId = getBlockId(block);
8227
+ if (!nodeId) continue;
8228
+ const pendingInvocationCount = readPendingInvocations(context.yDoc, nodeId).length;
8229
+ const snapshot = snapshotNode(block, getRuntime(context.yDoc, nodeId), now, pendingInvocationCount);
8230
+ snapshots.push(snapshot);
8231
+ queuedCommands.push(...planForSnapshot(context, options, block, snapshot));
8232
+ }
8233
+ const flowDone = snapshots.length > 0 && snapshots.every((snapshot) => snapshot.publicState === "Done");
8234
+ if (flowDone && options.archiveWhenDone !== false) {
8235
+ const archiveCommand = queueIfAuthorized(context, options, "archive_flow", context.flowId, "All Ralph loop action nodes are done", {
8236
+ restartIfRepeats: true
8237
+ });
8238
+ if (archiveCommand) queuedCommands.push(archiveCommand);
8239
+ }
8240
+ return { snapshots, queuedCommands, flowDone };
8241
+ }
8242
+ async function callExecutor(command, context, executor) {
8243
+ switch (command.type) {
8244
+ case "execute_action":
8245
+ return executor.executeAction ? executor.executeAction(command, context) : { commandId: command.id, success: false, error: "No executeAction handler configured" };
8246
+ case "assign_actor":
8247
+ return executor.assignActor ? executor.assignActor(command, context) : { commandId: command.id, success: false, error: "No assignActor handler configured" };
8248
+ case "notify_actor":
8249
+ return executor.notifyActor ? executor.notifyActor(command, context) : { commandId: command.id, success: false, error: "No notifyActor handler configured" };
8250
+ case "validate_external_state":
8251
+ return executor.validateExternalState ? executor.validateExternalState(command, context) : { commandId: command.id, success: false, error: "No validateExternalState handler configured" };
8252
+ case "submit_claim":
8253
+ return executor.submitClaim ? executor.submitClaim(command, context) : { commandId: command.id, success: false, error: "No submitClaim handler configured" };
8254
+ case "watch_udid":
8255
+ return executor.watchUdid ? executor.watchUdid(command, context) : { commandId: command.id, success: false, error: "No watchUdid handler configured" };
8256
+ case "archive_flow":
8257
+ return executor.archiveFlow ? executor.archiveFlow(command, context) : { commandId: command.id, success: false, error: "No archiveFlow handler configured" };
8258
+ case "propose_config_change":
8259
+ return executor.proposeConfigChange ? executor.proposeConfigChange(command, context) : { commandId: command.id, success: false, error: "No proposeConfigChange handler configured" };
8260
+ case "diagnose_blocker":
8261
+ return executor.diagnoseBlocker ? executor.diagnoseBlocker(command, context) : { commandId: command.id, success: false, error: "No diagnoseBlocker handler configured" };
8262
+ }
8263
+ }
8264
+ async function executeQueuedAgentCommands(context, options = {}) {
8265
+ const executor = options.executor;
8266
+ if (!executor) return [];
8267
+ const now = context.now?.() || Date.now();
8268
+ const { leases } = getFlowAgentMaps(context.yDoc);
8269
+ const results = [];
8270
+ for (const command of readQueuedAgentCommands(context.yDoc)) {
8271
+ const validation = validateAgentCommand(command);
8272
+ if (!validation.valid) {
8273
+ updateAgentCommand(context.yDoc, command.id, { status: "failed", error: validation.error, updatedAt: now });
8274
+ results.push({ commandId: command.id, success: false, error: validation.error });
8275
+ continue;
8276
+ }
8277
+ const policy = evaluateFlowAgentPolicy({
8278
+ actorDid: context.actor.did,
8279
+ commandType: command.type,
8280
+ flowUri: context.flowUri,
8281
+ nodeId: command.nodeId,
8282
+ delegationStore: options.delegationStore,
8283
+ delegations: options.delegations,
8284
+ now
8285
+ });
8286
+ if (!policy.allowed) {
8287
+ updateAgentCommand(context.yDoc, command.id, { status: "skipped", error: policy.reason, updatedAt: now });
8288
+ results.push({ commandId: command.id, success: false, error: policy.reason });
8289
+ continue;
8290
+ }
8291
+ const lease = acquireFlowAgentLease({
8292
+ leases,
8293
+ commandId: command.id,
8294
+ nodeId: command.nodeId,
8295
+ actorDid: context.actor.did,
8296
+ now,
8297
+ ttlMs: options.leaseTtlMs
8298
+ });
8299
+ if (!lease) {
8300
+ continue;
8301
+ }
8302
+ updateAgentCommand(context.yDoc, command.id, { status: "running", lease, updatedAt: now });
8303
+ try {
8304
+ const result = await callExecutor({ ...command, lease, status: "running" }, context, executor);
8305
+ if (!validateFlowAgentLease(leases, lease, context.now?.() || Date.now())) {
8306
+ const leaseError = "Lease expired or was superseded before command commit";
8307
+ updateAgentCommand(context.yDoc, command.id, { status: "failed", error: leaseError, updatedAt: context.now?.() || Date.now() });
8308
+ results.push({ commandId: command.id, success: false, error: leaseError });
8309
+ continue;
8310
+ }
8311
+ updateAgentCommand(context.yDoc, command.id, {
8312
+ status: result.success ? "confirmed" : "failed",
8313
+ error: result.error,
8314
+ updatedAt: context.now?.() || Date.now()
8315
+ });
8316
+ safeAppendAgentLedgerEvent(context.yDoc, {
8317
+ type: result.success ? "agent.validation" : "agent.escalation",
8318
+ flowId: context.flowId,
8319
+ nodeId: command.nodeId,
8320
+ commandId: command.id,
8321
+ actorDid: context.actor.did,
8322
+ timestamp: context.now?.() || Date.now(),
8323
+ details: {
8324
+ success: result.success,
8325
+ confirmed: result.confirmed,
8326
+ output: result.output,
8327
+ error: result.error
8328
+ }
8329
+ });
8330
+ results.push(result);
8331
+ } finally {
8332
+ releaseFlowAgentLease(leases, lease);
8333
+ }
8334
+ }
8335
+ return results;
8336
+ }
8337
+ async function tickFlowAgent(context, options = {}) {
8338
+ const plan = planRalphLoopCommands(context, options);
8339
+ const executedCommands = await executeQueuedAgentCommands(context, options);
8340
+ return {
8341
+ flowDone: plan.flowDone,
8342
+ snapshots: plan.snapshots,
8343
+ queuedCommands: plan.queuedCommands,
8344
+ executedCommands
8345
+ };
8346
+ }
8347
+
8348
+ // src/core/lib/flowAgent/execution.ts
8349
+ var DEFAULT_SCHEMA_VERSION = "1.0.0";
8350
+ function safeAppendAgentLedgerEvent2(yDoc, event) {
8351
+ try {
8352
+ appendAgentLedgerEvent(yDoc, event);
8353
+ } catch (error) {
8354
+ const message = error instanceof Error ? error.message : "Failed to append flow-agent ledger event";
8355
+ if (message.includes("Unexpected content type") || message.includes("already been defined with a different constructor")) {
8356
+ return;
8357
+ }
8358
+ throw error;
8359
+ }
8360
+ }
8361
+ function createYDocRuntimeManager(yDoc) {
8362
+ const runtime = yDoc.getMap("runtime");
8363
+ return {
8364
+ get: (nodeId) => {
8365
+ const value = runtime.get(nodeId);
8366
+ return value && typeof value === "object" ? { ...value } : {};
8367
+ },
8368
+ update: (nodeId, updates) => {
8369
+ const current = runtime.get(nodeId);
8370
+ runtime.set(nodeId, {
8371
+ ...current && typeof current === "object" ? current : {},
8372
+ ...updates
8373
+ });
8374
+ }
8375
+ };
8376
+ }
8377
+ function getRuntimeRecord(yDoc, nodeId) {
8378
+ const runtime = yDoc.getMap("runtime");
8379
+ const value = runtime.get(nodeId);
8380
+ return value && typeof value === "object" ? value : {};
8381
+ }
8382
+ function clearRuntimeBlocker(yDoc, nodeId, updates) {
8383
+ const runtime = yDoc.getMap("runtime");
8384
+ const current = getRuntimeRecord(yDoc, nodeId);
8385
+ const { state: _state, error: _error, ...rest } = current;
8386
+ runtime.set(nodeId, {
8387
+ ...rest,
8388
+ ...updates
8389
+ });
8390
+ }
8391
+ function findContextBlock(context, nodeId) {
8392
+ const block = (context.blocks || []).find((entry) => getBlockId(entry) === nodeId);
8393
+ return block && typeof block === "object" ? block : null;
8394
+ }
8395
+ function parseInputs2(value) {
8396
+ return parseJsonProp(value, {});
8397
+ }
8398
+ function getXmlAttribute(element, key) {
8399
+ return element.getAttribute(key);
8400
+ }
8401
+ function setXmlAttribute(element, key, value) {
8402
+ element.setAttribute(key, value);
8403
+ }
8404
+ function isRecord(value) {
8405
+ return value != null && typeof value === "object" && !Array.isArray(value);
8406
+ }
8407
+ function isYXmlContainerLike(value) {
8408
+ return value != null && typeof value === "object" && typeof value.toArray === "function";
8409
+ }
8410
+ function isYXmlElementLike(value) {
8411
+ return isYXmlContainerLike(value) && typeof value.nodeName === "string" && typeof value.getAttribute === "function" && typeof value.setAttribute === "function";
8412
+ }
8413
+ function getElementBlockId(element) {
8414
+ const directId = getXmlAttribute(element, "id");
8415
+ if (typeof directId === "string" && directId.length > 0) return directId;
8416
+ const attrs = getXmlAttribute(element, "attrs");
8417
+ const attrsId = isRecord(attrs) ? attrs.id : void 0;
8418
+ return typeof attrsId === "string" && attrsId.length > 0 ? attrsId : void 0;
8419
+ }
8420
+ function updateXmlElementProps(element, blockId, propsPatch) {
8421
+ const attrs = getXmlAttribute(element, "attrs");
8422
+ if (isRecord(attrs)) {
8423
+ const existingProps = isRecord(attrs.props) ? attrs.props : {};
8424
+ setXmlAttribute(element, "attrs", {
8425
+ ...attrs,
8426
+ id: typeof attrs.id === "string" ? attrs.id : blockId,
8427
+ props: {
8428
+ ...existingProps,
8429
+ ...propsPatch
8430
+ }
8431
+ });
8432
+ }
8433
+ for (const [key, value] of Object.entries(propsPatch)) {
8434
+ element.setAttribute(key, typeof value === "string" ? value : JSON.stringify(value));
8435
+ }
8436
+ }
8437
+ function findXmlElementById(container, blockId) {
8438
+ for (const node of container.toArray()) {
8439
+ if (!isYXmlElementLike(node)) continue;
8440
+ const id = getElementBlockId(node);
8441
+ if (id === blockId) return node;
8442
+ const nested = findXmlElementById(node, blockId);
8443
+ if (nested) return nested;
8444
+ }
8445
+ return null;
8446
+ }
8447
+ function updateDocumentBlockProps(yDoc, blockId, propsPatch) {
8448
+ const target = findXmlElementById(yDoc.getXmlFragment("document"), blockId);
8449
+ if (!target) return false;
8450
+ updateXmlElementProps(target, blockId, propsPatch);
8451
+ const content = target.toArray().find((node) => isYXmlElementLike(node) && node.nodeName !== "blockGroup");
8452
+ if (content) {
8453
+ updateXmlElementProps(content, blockId, propsPatch);
8454
+ }
8455
+ return true;
8456
+ }
8457
+ function assignActorInYDoc(yDoc, command, context) {
8458
+ const targetActorDid = typeof command.payload.targetActorDid === "string" && command.payload.targetActorDid.length > 0 ? command.payload.targetActorDid : context.actor.did;
8459
+ const targetMatrixUserId = typeof command.payload.targetMatrixUserId === "string" ? command.payload.targetMatrixUserId : void 0;
8460
+ const assignment = {
8461
+ assignedActor: {
8462
+ did: targetActorDid,
8463
+ avatar: "",
8464
+ displayName: targetMatrixUserId || targetActorDid
8465
+ },
8466
+ assignedBy: {
8467
+ did: context.actor.did,
8468
+ avatar: "",
8469
+ displayName: context.actor.displayName || context.actor.matrixUserId || ""
8470
+ },
8471
+ assignedTimestamp: new Date(context.now?.() || Date.now()).toISOString()
8472
+ };
8473
+ const assignmentJson = JSON.stringify(assignment);
8474
+ const updatedDocument = updateDocumentBlockProps(yDoc, command.nodeId, {
8475
+ assignment: assignmentJson
8476
+ });
8477
+ if (!updatedDocument) {
8478
+ throw new Error(`Block ${command.nodeId} was not found for assignment`);
8479
+ }
8480
+ const runtime = createYDocRuntimeManager(yDoc);
8481
+ runtime.update(command.nodeId, {
8482
+ assignedActorDid: targetActorDid,
8483
+ assignedByDid: context.actor.did,
8484
+ assignedAt: assignment.assignedTimestamp,
8485
+ lastCommandId: command.id
8486
+ });
8487
+ return assignment;
8488
+ }
8489
+ async function executeAssignActorCommand(command, context, options) {
8490
+ const capability = { can: "flow/node/assign", with: `${context.flowUri}:${command.nodeId}` };
8491
+ const proofs = await options.ucanService.findValidProofs(context.actor.did, capability);
8492
+ if (!proofs.found || !proofs.proofCids) {
8493
+ return {
8494
+ commandId: command.id,
8495
+ success: false,
8496
+ error: proofs.error || "No valid delegation found for assignment"
8497
+ };
8498
+ }
8499
+ const result = await options.ucanService.executeWithInvocation(
8500
+ {
8501
+ invokerDid: context.actor.did,
8502
+ invokerType: options.actorType,
8503
+ entityRoomId: options.entityRoomId,
8504
+ capability,
8505
+ proofs: proofs.proofCids,
8506
+ pin: options.pin
8507
+ },
8508
+ async () => assignActorInYDoc(context.yDoc, command, context),
8509
+ context.flowId,
8510
+ command.nodeId
8511
+ );
8512
+ return {
8513
+ commandId: command.id,
8514
+ success: result.success,
8515
+ confirmed: result.success,
8516
+ output: result.actionResult && typeof result.actionResult === "object" ? { assignment: result.actionResult, invocationCid: result.invocationCid } : { invocationCid: result.invocationCid },
8517
+ error: result.error
8518
+ };
8519
+ }
8520
+ async function executeActionCommand(command, context, options) {
8521
+ const actionType = typeof command.payload.actionType === "string" ? command.payload.actionType : void 0;
8522
+ if (!actionType) {
8523
+ return { commandId: command.id, success: false, error: "execute_action command is missing payload.actionType" };
8524
+ }
8525
+ const action = getAction(actionType);
8526
+ if (!action) {
8527
+ return { commandId: command.id, success: false, error: `No registered action found for ${actionType}` };
8528
+ }
8529
+ const block = findContextBlock(context, command.nodeId);
8530
+ if (!block) {
8531
+ return { commandId: command.id, success: false, error: `Block ${command.nodeId} was not found in runtime context` };
8532
+ }
8533
+ const props = getBlockProps(block);
8534
+ const inputs = parseInputs2(props.inputs);
8535
+ const runtime = createYDocRuntimeManager(context.yDoc);
8536
+ const flowNode = buildFlowNodeFromBlock(block);
8537
+ const outcome = await executeNode({
8538
+ node: flowNode,
8539
+ actorDid: context.actor.did,
8540
+ actorType: options.actorType,
8541
+ entityRoomId: options.entityRoomId,
8542
+ context: {
8543
+ runtime,
8544
+ ucanService: options.ucanService,
8545
+ invocationStore: options.invocationStore,
8546
+ flowUri: context.flowUri,
8547
+ flowId: context.flowId,
8548
+ flowOwnerDid: "",
8549
+ schemaVersion: options.schemaVersion || DEFAULT_SCHEMA_VERSION,
8550
+ now: options.now || context.now
8551
+ },
8552
+ action: async () => {
8553
+ const result = await action.run(inputs, {
8554
+ actorDid: context.actor.did,
8555
+ flowId: context.flowId,
8556
+ flowUri: context.flowUri,
8557
+ nodeId: command.nodeId,
8558
+ flowNode,
8559
+ runtime,
8560
+ services: options.actionServices || {}
8561
+ });
8562
+ return { payload: result.output };
8563
+ },
8564
+ pin: options.pin
8565
+ });
8566
+ if (outcome.success && outcome.result) {
8567
+ runtime.update(command.nodeId, {
8568
+ state: "completed",
8569
+ output: outcome.result.payload || {},
8570
+ executedByDid: context.actor.did,
8571
+ executedAt: options.now?.() || context.now?.() || Date.now(),
8572
+ lastInvocationCid: outcome.invocationCid || outcome.capabilityId,
8573
+ lastCommandId: command.id
8574
+ });
8575
+ } else if (!outcome.success) {
8576
+ runtime.update(command.nodeId, {
8577
+ state: "failed",
8578
+ error: {
8579
+ message: outcome.error || "Unknown execution error",
8580
+ at: options.now?.() || context.now?.() || Date.now(),
8581
+ commandId: command.id
8582
+ },
8583
+ lastCommandId: command.id
8584
+ });
8585
+ }
8586
+ return {
8587
+ commandId: command.id,
8588
+ success: outcome.success,
8589
+ confirmed: outcome.success,
8590
+ output: {
8591
+ ...outcome.result?.payload && typeof outcome.result.payload === "object" ? outcome.result.payload : {},
8592
+ ...outcome.invocationCid ? { invocationCid: outcome.invocationCid } : {}
8593
+ },
8594
+ error: outcome.error
8595
+ };
8596
+ }
8597
+ function getRecoverableBlockerReason(runtime) {
8598
+ const error = runtime.error;
8599
+ const message = error && typeof error === "object" && typeof error.message === "string" ? error.message : typeof error === "string" ? error : void 0;
8600
+ if (!message) return null;
8601
+ if (message.includes("Invalid DID") && message.includes("must start with 'did:'")) {
8602
+ return "matrix_did_normalization";
8603
+ }
8604
+ if (message.includes("Failed to parse URL from undefined")) {
8605
+ return "http_endpoint_input_normalization";
8606
+ }
8607
+ if (message.includes("Unexpected content type") || message.includes("already been defined with a different constructor")) {
8608
+ return "audit_trail_shape";
8609
+ }
8610
+ return null;
8611
+ }
8612
+ function resetFailedActionCommands(context, nodeId, now) {
8613
+ const { outbox } = getFlowAgentMaps(context.yDoc);
8614
+ const resetIds = [];
8615
+ outbox.forEach((command) => {
8616
+ if (command.nodeId !== nodeId || command.type !== "execute_action" || command.status !== "failed") {
8617
+ return;
8618
+ }
8619
+ outbox.set(command.id, {
8620
+ ...command,
8621
+ status: "queued",
8622
+ error: void 0,
8623
+ lease: void 0,
8624
+ updatedAt: now,
8625
+ reason: `Retry after blocker diagnosis: ${command.reason}`
8626
+ });
8627
+ resetIds.push(command.id);
8628
+ });
8629
+ return resetIds;
8630
+ }
8631
+ async function executeDiagnoseBlockerCommand(command, context, options) {
8632
+ const now = options.now?.() || context.now?.() || Date.now();
8633
+ const runtime = getRuntimeRecord(context.yDoc, command.nodeId);
8634
+ const recovery = getRecoverableBlockerReason(runtime);
8635
+ if (!recovery) {
8636
+ return {
8637
+ commandId: command.id,
8638
+ success: true,
8639
+ confirmed: true,
8640
+ output: {
8641
+ blockerCause: command.payload.cause || "unknown",
8642
+ recovery: "manual_intervention_required"
8643
+ }
8644
+ };
8645
+ }
8646
+ clearRuntimeBlocker(context.yDoc, command.nodeId, {
8647
+ diagnosedAt: now,
8648
+ diagnosedByDid: context.actor.did,
8649
+ lastDiagnosisCommandId: command.id,
8650
+ lastRecovery: recovery
8651
+ });
8652
+ const resetCommandIds = resetFailedActionCommands(context, command.nodeId, now);
8653
+ return {
8654
+ commandId: command.id,
8655
+ success: true,
8656
+ confirmed: true,
8657
+ output: {
8658
+ blockerCause: command.payload.cause || "unknown",
8659
+ recovery,
8660
+ resetCommandIds
8661
+ }
8662
+ };
8663
+ }
8664
+ async function executeFlowAgentCoreCommand(command, context, options) {
8665
+ switch (command.type) {
8666
+ case "assign_actor":
8667
+ return executeAssignActorCommand(command, context, options);
8668
+ case "execute_action":
8669
+ return executeActionCommand(command, context, options);
8670
+ case "diagnose_blocker":
8671
+ return executeDiagnoseBlockerCommand(command, context, options);
8672
+ default:
8673
+ return { commandId: command.id, success: false, error: `No editor-core executor for ${command.type}` };
8674
+ }
8675
+ }
8676
+ async function callConfiguredExecutor(command, context, options) {
8677
+ if (!options.executor) return void 0;
8678
+ switch (command.type) {
8679
+ case "assign_actor":
8680
+ return options.executor.assignActor?.(command, context);
8681
+ case "execute_action":
8682
+ return options.executor.executeAction?.(command, context);
8683
+ case "diagnose_blocker":
8684
+ return options.executor.diagnoseBlocker?.(command, context);
8685
+ default:
8686
+ return void 0;
8687
+ }
8688
+ }
8689
+ async function executeQueuedFlowAgentCoreCommands(context, options) {
8690
+ const now = options.now?.() || context.now?.() || Date.now();
8691
+ const { leases } = getFlowAgentMaps(context.yDoc);
8692
+ const results = [];
8693
+ for (const command of readQueuedAgentCommands(context.yDoc)) {
8694
+ if (options.commandTypes && !options.commandTypes.includes(command.type)) {
8695
+ continue;
8696
+ }
8697
+ const validation = validateAgentCommand(command);
8698
+ if (!validation.valid) {
8699
+ updateAgentCommand(context.yDoc, command.id, { status: "failed", error: validation.error, updatedAt: now });
8700
+ results.push({ commandId: command.id, success: false, error: validation.error });
8701
+ continue;
8702
+ }
8703
+ const policy = evaluateFlowAgentPolicy({
8704
+ actorDid: context.actor.did,
8705
+ commandType: command.type,
8706
+ flowUri: context.flowUri,
8707
+ nodeId: command.nodeId,
8708
+ delegationStore: options.delegationStore,
8709
+ delegations: options.delegations,
8710
+ now
8711
+ });
8712
+ if (!policy.allowed) {
8713
+ updateAgentCommand(context.yDoc, command.id, { status: "skipped", error: policy.reason, updatedAt: now });
8714
+ results.push({ commandId: command.id, success: false, error: policy.reason });
8715
+ continue;
8716
+ }
8717
+ const lease = acquireFlowAgentLease({
8718
+ leases,
8719
+ commandId: command.id,
8720
+ nodeId: command.nodeId,
8721
+ actorDid: context.actor.did,
8722
+ now,
8723
+ ttlMs: options.leaseTtlMs
8724
+ });
8725
+ if (!lease) continue;
8726
+ updateAgentCommand(context.yDoc, command.id, { status: "running", lease, updatedAt: now });
8727
+ try {
8728
+ const runningCommand = { ...command, lease, status: "running" };
8729
+ const result = await Promise.resolve(callConfiguredExecutor(runningCommand, context, options)) || await executeFlowAgentCoreCommand(runningCommand, context, options);
8730
+ const normalized = result || {
8731
+ commandId: command.id,
8732
+ success: false,
8733
+ error: `No executor configured for ${command.type}`
8734
+ };
8735
+ if (!validateFlowAgentLease(leases, lease, options.now?.() || context.now?.() || Date.now())) {
8736
+ const error = "Lease expired or was superseded before command commit";
8737
+ updateAgentCommand(context.yDoc, command.id, { status: "failed", error, updatedAt: options.now?.() || context.now?.() || Date.now() });
8738
+ results.push({ commandId: command.id, success: false, error });
8739
+ continue;
8740
+ }
8741
+ updateAgentCommand(context.yDoc, command.id, {
8742
+ status: normalized.success ? "confirmed" : "failed",
8743
+ error: normalized.error,
8744
+ updatedAt: options.now?.() || context.now?.() || Date.now()
8745
+ });
8746
+ safeAppendAgentLedgerEvent2(context.yDoc, {
8747
+ type: normalized.success ? "agent.validation" : "agent.escalation",
8748
+ flowId: context.flowId,
8749
+ nodeId: command.nodeId,
8750
+ commandId: command.id,
8751
+ actorDid: context.actor.did,
8752
+ timestamp: options.now?.() || context.now?.() || Date.now(),
8753
+ details: {
8754
+ success: normalized.success,
8755
+ confirmed: normalized.confirmed,
8756
+ output: normalized.output,
8757
+ error: normalized.error
8758
+ }
8759
+ });
8760
+ results.push(normalized);
8761
+ } finally {
8762
+ releaseFlowAgentLease(leases, lease);
8763
+ }
8764
+ }
8765
+ return results;
8766
+ }
8767
+
8768
+ // src/core/lib/flowAgent/service.ts
8769
+ var FlowAgentService = class {
8770
+ constructor(context, options = {}) {
8771
+ this.context = context;
8772
+ this.options = options;
8773
+ this.timer = null;
8774
+ this.running = false;
8775
+ }
8776
+ async tick() {
8777
+ if (this.running) {
8778
+ return {
8779
+ flowDone: false,
8780
+ snapshots: [],
8781
+ queuedCommands: [],
8782
+ executedCommands: []
8783
+ };
8784
+ }
8785
+ this.running = true;
8786
+ try {
8787
+ const result = await tickFlowAgent(this.context, this.options);
8788
+ await this.options.onTick?.(result);
8789
+ return result;
8790
+ } catch (error) {
8791
+ this.options.onError?.(error);
8792
+ throw error;
8793
+ } finally {
8794
+ this.running = false;
8795
+ }
8796
+ }
8797
+ start() {
8798
+ if (this.timer) return;
8799
+ const intervalMs = this.options.intervalMs || 3e4;
8800
+ this.timer = setInterval(() => {
8801
+ this.tick().catch((error) => {
8802
+ this.options.onError?.(error);
8803
+ });
8804
+ }, intervalMs);
8805
+ }
8806
+ stop() {
8807
+ if (!this.timer) return;
8808
+ clearInterval(this.timer);
8809
+ this.timer = null;
8810
+ }
8811
+ };
8812
+
7574
8813
  export {
7575
8814
  resolveActionType,
7576
8815
  registerAction,
@@ -7644,6 +8883,35 @@ export {
7644
8883
  setActiveEditor,
7645
8884
  getActiveEditor,
7646
8885
  readFlow,
7647
- setupFlowFromBaseUcan
8886
+ setupFlowFromBaseUcan,
8887
+ getFlowAgentMaps,
8888
+ computeAgentCommandId,
8889
+ queueAgentCommand,
8890
+ readQueuedAgentCommands,
8891
+ updateAgentCommand,
8892
+ appendAgentLedgerEvent,
8893
+ readAgentLedgerEvents,
8894
+ requiredCapabilityForCommand,
8895
+ isCapabilityMatch,
8896
+ canMatches,
8897
+ resourceMatches,
8898
+ evaluateFlowAgentPolicy,
8899
+ createAgentCommand,
8900
+ validateAgentCommand,
8901
+ isExternalMutation,
8902
+ buildFlowAgentContext,
8903
+ acquireFlowAgentLease,
8904
+ validateFlowAgentLease,
8905
+ releaseFlowAgentLease,
8906
+ cleanupExpiredFlowAgentLeases,
8907
+ classifyBlockerCause,
8908
+ classifyNodeState,
8909
+ snapshotNode,
8910
+ planRalphLoopCommands,
8911
+ executeQueuedAgentCommands,
8912
+ tickFlowAgent,
8913
+ executeFlowAgentCoreCommand,
8914
+ executeQueuedFlowAgentCoreCommands,
8915
+ FlowAgentService
7648
8916
  };
7649
- //# sourceMappingURL=chunk-GCR6VY7O.mjs.map
8917
+ //# sourceMappingURL=chunk-JAB2IBVH.mjs.map