@ixo/editor 6.13.0 → 6.15.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.
@@ -549,6 +549,10 @@ function getAction(type) {
549
549
  function getAllActions() {
550
550
  return Array.from(actions.values());
551
551
  }
552
+ function isRepeatableAction(type) {
553
+ if (!type) return false;
554
+ return getAction(type)?.cardinality === "many";
555
+ }
552
556
  function getAliasEntries() {
553
557
  return Array.from(aliases.entries());
554
558
  }
@@ -645,7 +649,8 @@ function generateActionManifest() {
645
649
  proof: serializeProof(action),
646
650
  hasDynamicEvents: !!action.getDynamicEvents,
647
651
  hasDynamicOutputSchema: !!action.getDynamicOutputSchema,
648
- eligibleForEventTrigger: !!action.eligibleForEventTrigger
652
+ eligibleForEventTrigger: !!action.eligibleForEventTrigger,
653
+ hasCustomInputValidation: !!action.getMissingInputs
649
654
  };
650
655
  if (action.can) entry.can = action.can;
651
656
  if (action.requiredCapability) entry.requiredCapability = action.requiredCapability;
@@ -664,6 +669,49 @@ function generateActionManifest() {
664
669
  return { manifestVersion: "1", actions: actions2 };
665
670
  }
666
671
 
672
+ // src/core/lib/actionRegistry/inputRequirements.ts
673
+ function isBlankInputValue(value) {
674
+ if (value == null) return true;
675
+ if (typeof value === "string") return value.trim().length === 0;
676
+ if (Array.isArray(value)) return value.length === 0;
677
+ return false;
678
+ }
679
+ function parseMaybeJsonObject(value) {
680
+ if (value && typeof value === "object" && !Array.isArray(value)) return value;
681
+ if (typeof value === "string" && value.trim()) {
682
+ try {
683
+ const parsed = JSON.parse(value);
684
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
685
+ } catch {
686
+ return void 0;
687
+ }
688
+ }
689
+ return void 0;
690
+ }
691
+ function dedupe(values) {
692
+ return Array.from(new Set(values.filter((value) => typeof value === "string" && value.length > 0)));
693
+ }
694
+ function getMissingActionInputs(actionType, inputs) {
695
+ if (!actionType) return [];
696
+ const action = getAction(actionType);
697
+ if (!action) return [];
698
+ const merged = inputs ?? {};
699
+ if (action.getMissingInputs) {
700
+ try {
701
+ return dedupe(action.getMissingInputs(merged) || []);
702
+ } catch (error) {
703
+ warnOnce(
704
+ `missing-inputs:${action.type}`,
705
+ `[flow-config] action ${action.type}: getMissingInputs threw (${error instanceof Error ? error.message : String(error)}); reporting no missing inputs`
706
+ );
707
+ return [];
708
+ }
709
+ }
710
+ const schema = action.inputSchema;
711
+ const required = Array.isArray(schema?.required) ? schema.required.filter((name) => typeof name === "string") : [];
712
+ return dedupe(required.filter((name) => isBlankInputValue(merged[name])));
713
+ }
714
+
667
715
  // src/core/lib/actionRegistry/canMapping.ts
668
716
  var CAN_TO_TYPE = {
669
717
  "bid/submit": "qi/bid.submit",
@@ -1110,6 +1158,9 @@ registerAction({
1110
1158
  { path: "purposeDescription", displayName: "Purpose Description", type: "string", description: "The user-provided purpose text" },
1111
1159
  { path: "blueprintCandidates", displayName: "Blueprint Candidates", type: "array", description: "Ranked array of matching blueprint DIDs and metadata" }
1112
1160
  ],
1161
+ // Mirrors run(): either userMessage or purposeDescription satisfies the
1162
+ // purpose-text requirement, reported under the primary name.
1163
+ getMissingInputs: (inputs) => String(inputs.userMessage || inputs.purposeDescription || "").trim() ? [] : ["userMessage"],
1113
1164
  run: async (inputs) => {
1114
1165
  const purposeDescription = String(inputs.userMessage || inputs.purposeDescription || "").trim();
1115
1166
  if (!purposeDescription) throw new Error("userMessage is required");
@@ -1223,6 +1274,8 @@ registerAction({
1223
1274
  pendingDisplayFields: ["selectedEntityName", "selectedEntityDid"]
1224
1275
  }
1225
1276
  ],
1277
+ // Mirrors run(): a truthy `skipped` waives the selection entirely.
1278
+ getMissingInputs: (inputs) => inputs.skipped ? [] : isBlankInputValue(inputs.selectedEntityDid) ? ["selectedEntityDid"] : [],
1226
1279
  run: async (inputs) => {
1227
1280
  const skipped = !!inputs.skipped;
1228
1281
  if (skipped) {
@@ -1285,6 +1338,30 @@ registerAction({
1285
1338
  pendingDisplayFields: ["memberConfig.memberCount"]
1286
1339
  }
1287
1340
  ],
1341
+ // Mirrors run(), including its default: an absent groupType is treated as
1342
+ // 'categorical', not as a missing input.
1343
+ getMissingInputs: (inputs) => {
1344
+ const groupType = String(inputs.groupType || "").trim() || "categorical";
1345
+ if (groupType === "nftStaking") {
1346
+ return String(inputs.nftContractAddress || "").trim() ? [] : ["nftContractAddress"];
1347
+ }
1348
+ if (groupType === "tokenStaking") {
1349
+ const tokenConfig = inputs.tokenConfig;
1350
+ if (!tokenConfig || typeof tokenConfig !== "object") return ["tokenConfig"];
1351
+ if (tokenConfig.isExistingToken) {
1352
+ return String(tokenConfig.tokenAddress || "").trim() ? [] : ["tokenConfig.tokenAddress"];
1353
+ }
1354
+ const missing2 = [];
1355
+ if (!String(tokenConfig.tokenName || "").trim()) missing2.push("tokenConfig.tokenName");
1356
+ if (!String(tokenConfig.tokenSymbol || "").trim()) missing2.push("tokenConfig.tokenSymbol");
1357
+ if (isBlankInputValue(tokenConfig.tokenSupply)) missing2.push("tokenConfig.tokenSupply");
1358
+ return missing2;
1359
+ }
1360
+ const missing = [];
1361
+ if (!Array.isArray(inputs.members) || inputs.members.length === 0) missing.push("members");
1362
+ if (groupType === "multisig" && isBlankInputValue(inputs.multisigThreshold)) missing.push("multisigThreshold");
1363
+ return missing;
1364
+ },
1288
1365
  run: async (inputs) => {
1289
1366
  const groupType = inputs.groupType || "categorical";
1290
1367
  if (groupType === "nftStaking") {
@@ -1373,6 +1450,29 @@ registerAction({
1373
1450
  pendingDisplayFields: ["governanceConfig.groupName", "governanceConfig.groupType"]
1374
1451
  }
1375
1452
  ],
1453
+ // Mirrors run()'s presence preamble: base fields always, then the decision
1454
+ // policy fields the selected groupType demands. Value validity (quorum and
1455
+ // threshold ranges, veto sum) stays in run().
1456
+ getMissingInputs: (inputs) => {
1457
+ const missing = [];
1458
+ if (!String(inputs.groupName || "").trim()) missing.push("groupName");
1459
+ const groupType = String(inputs.groupType || "").trim();
1460
+ if (!groupType) missing.push("groupType");
1461
+ const governance = inputs.governance;
1462
+ if (!governance || typeof governance !== "object") {
1463
+ missing.push("governance", "governance.votingPeriod");
1464
+ return missing;
1465
+ }
1466
+ if (!governance.votingPeriod) missing.push("governance.votingPeriod");
1467
+ if (!groupType) return missing;
1468
+ if (groupType === "multisig") {
1469
+ if (!String(governance.threshold ?? "").trim()) missing.push("governance.threshold");
1470
+ } else {
1471
+ if (governance.quorum == null || String(governance.quorum).trim() === "") missing.push("governance.quorum");
1472
+ if (governance.threshold == null || String(governance.threshold).trim() === "") missing.push("governance.threshold");
1473
+ }
1474
+ return missing;
1475
+ },
1376
1476
  run: async (inputs) => {
1377
1477
  const groupName = String(inputs.groupName || "").trim();
1378
1478
  if (!groupName) throw new Error("groupName is required");
@@ -5557,6 +5657,14 @@ registerAction({
5557
5657
  pendingDisplayFields: ["entityDid", "governanceGroupCoreAddress"]
5558
5658
  }
5559
5659
  ],
5660
+ // Mirrors run(): the card envelope (object or JSON string) must carry
5661
+ // credentialSubject.name before signing can start.
5662
+ getMissingInputs: (inputs) => {
5663
+ const card = parseMaybeJsonObject(inputs.domainCardData);
5664
+ if (!card) return ["domainCardData"];
5665
+ const subject = card.credentialSubject;
5666
+ return subject && String(subject.name || "").trim() ? [] : ["domainCardData.credentialSubject.name"];
5667
+ },
5560
5668
  run: async (inputs, ctx) => {
5561
5669
  const handlers = ctx.handlers || {};
5562
5670
  let domainCardData;
@@ -5910,6 +6018,14 @@ registerAction({
5910
6018
  pendingDisplayFields: ["approvedAt"]
5911
6019
  }
5912
6020
  ],
6021
+ // Mirrors run(): the card envelope (object or JSON string) must carry
6022
+ // credentialSubject.name before a preview can render.
6023
+ getMissingInputs: (inputs) => {
6024
+ const card = parseMaybeJsonObject(inputs.domainCardData);
6025
+ if (!card) return ["domainCardData"];
6026
+ const subject = card.credentialSubject;
6027
+ return subject && String(subject.name || "").trim() ? [] : ["domainCardData.credentialSubject.name"];
6028
+ },
5913
6029
  run: async (inputs) => {
5914
6030
  let domainCardData = inputs.domainCardData;
5915
6031
  if (typeof domainCardData === "string") {
@@ -12309,6 +12425,44 @@ async function executeActionBlock(params) {
12309
12425
  };
12310
12426
  }
12311
12427
 
12428
+ // src/core/lib/flowEngine/blockPhase.ts
12429
+ function deriveBlockPhase(params) {
12430
+ const { runtime, actionType, completionVerification, pendingInvocationCount = 0, runCount } = params;
12431
+ const repeatable = isRepeatableAction(actionType);
12432
+ const state = runtime?.state ?? "idle";
12433
+ const base = { repeatable, ...repeatable && runCount !== void 0 ? { runCount } : {} };
12434
+ if (state === "completed" && completionVerification?.status === "unverified") {
12435
+ return { ...base, phase: "failed", detail: "unverified", safeToRetry: false };
12436
+ }
12437
+ switch (state) {
12438
+ case "idle":
12439
+ if (pendingInvocationCount > 0) {
12440
+ return { ...base, phase: "active", detail: "queued", safeToRetry: true };
12441
+ }
12442
+ return { ...base, phase: "pending", detail: "idle", safeToRetry: true };
12443
+ case "running":
12444
+ return { ...base, phase: "active", detail: "running", safeToRetry: false };
12445
+ case "awaiting_readback":
12446
+ return { ...base, phase: "active", detail: "awaiting_readback", safeToRetry: false };
12447
+ case "completed":
12448
+ if (repeatable) {
12449
+ return { ...base, phase: "active", detail: "standing", safeToRetry: true };
12450
+ }
12451
+ return { ...base, phase: "complete", detail: "completed", safeToRetry: false };
12452
+ case "cancelled":
12453
+ return { ...base, phase: "complete", detail: "cancelled", safeToRetry: false };
12454
+ case "failed":
12455
+ return { ...base, phase: "failed", detail: "failed", safeToRetry: true };
12456
+ case "needs_verification":
12457
+ return { ...base, phase: "failed", detail: "needs_verification", safeToRetry: false };
12458
+ default:
12459
+ return { ...base, phase: "failed", detail: "needs_verification", safeToRetry: false };
12460
+ }
12461
+ }
12462
+ function countSuccessfulRuns(yDoc, blockId) {
12463
+ return readRunRecords(yDoc, blockId).filter((record) => !record.error).length;
12464
+ }
12465
+
12312
12466
  // src/core/lib/flowEngine/validateBlockConfig.ts
12313
12467
  function validateBlockConfig(block) {
12314
12468
  const issues = [];
@@ -13921,13 +14075,18 @@ function classifyBlockerCause(block, runtime) {
13921
14075
  return void 0;
13922
14076
  }
13923
14077
  function classifyNodeState({ block, runtime, now, pendingInvocationCount = 0, completionVerification }) {
13924
- if (runtime.state === "completed" && completionVerification?.status === "unverified") return "Blocked";
13925
- if (runtime.state === "completed" || runtime.state === "cancelled") return "Done";
13926
- if (runtime.state === "needs_verification") return "Blocked";
13927
- if (runtime.state === "failed" || runtime.error) return "Blocked";
14078
+ const derived = deriveBlockPhase({
14079
+ runtime,
14080
+ actionType: getBlockActionType(block),
14081
+ completionVerification,
14082
+ pendingInvocationCount
14083
+ });
14084
+ if (derived.phase === "failed") return "Blocked";
14085
+ if (derived.phase === "complete") return "Done";
14086
+ if (derived.detail === "standing" && pendingInvocationCount === 0) return "Done";
14087
+ if (runtime.error) return "Blocked";
13928
14088
  const dueAt = getDueAt(block);
13929
14089
  if (dueAt != null && dueAt <= now) return "Overdue";
13930
- if (pendingInvocationCount > 0) return "Pending";
13931
14090
  return "Pending";
13932
14091
  }
13933
14092
  function snapshotNode(block, runtime, now, pendingInvocationCount = 0, completionVerification) {
@@ -14835,6 +14994,8 @@ export {
14835
14994
  getEventsForBlock,
14836
14995
  getOutputSchemaForBlock,
14837
14996
  generateActionManifest,
14997
+ isBlankInputValue,
14998
+ getMissingActionInputs,
14838
14999
  canToType,
14839
15000
  typeToCan,
14840
15001
  getAllCanMappings,
@@ -14928,6 +15089,8 @@ export {
14928
15089
  executeActionBlock,
14929
15090
  verifyCompletion,
14930
15091
  isVerifiedCompletion,
15092
+ deriveBlockPhase,
15093
+ countSuccessfulRuns,
14931
15094
  validateBlockConfig,
14932
15095
  createUcanDelegationStore,
14933
15096
  createMemoryUcanDelegationStore,
@@ -14987,4 +15150,4 @@ export {
14987
15150
  executeQueuedFlowAgentCoreCommands,
14988
15151
  FlowAgentService
14989
15152
  };
14990
- //# sourceMappingURL=chunk-ESPPJONO.js.map
15153
+ //# sourceMappingURL=chunk-WSRVRHND.js.map