@aifabrix/builder 2.54.1 → 2.55.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.
Files changed (210) hide show
  1. package/README.md +8 -0
  2. package/lib/agent-kit/apply-ops.js +82 -0
  3. package/lib/agent-kit/checksums.js +28 -0
  4. package/lib/agent-kit/classify.js +113 -0
  5. package/lib/agent-kit/claude-shim.js +61 -0
  6. package/lib/agent-kit/constants.js +55 -0
  7. package/lib/agent-kit/file-list.js +103 -0
  8. package/lib/agent-kit/host-approve.js +130 -0
  9. package/lib/agent-kit/hosting/github.js +63 -0
  10. package/lib/agent-kit/hosts.js +71 -0
  11. package/lib/agent-kit/init.js +319 -0
  12. package/lib/agent-kit/kit-root.js +17 -0
  13. package/lib/agent-kit/managed-section.js +79 -0
  14. package/lib/agent-kit/package-load.js +45 -0
  15. package/lib/agent-kit/plan-ops.js +69 -0
  16. package/lib/agent-kit/remote-prompts.js +239 -0
  17. package/lib/agent-kit/render-host-markdown.js +40 -0
  18. package/lib/agent-kit/repo-guard.js +47 -0
  19. package/lib/agent-kit/report.js +107 -0
  20. package/lib/agent-kit/run.js +82 -0
  21. package/lib/agent-kit/scm/git.js +122 -0
  22. package/lib/agent-kit/state-file.js +83 -0
  23. package/lib/agent-kit/status.js +50 -0
  24. package/lib/agent-kit/v1-detect.js +81 -0
  25. package/lib/agent-kit/validate-kit.js +87 -0
  26. package/lib/agent-kit/workspace-name.js +40 -0
  27. package/lib/agent-kit/workspace-skeleton.js +103 -0
  28. package/lib/api/role-assistant-test-pack.api.js +86 -0
  29. package/lib/api/work-search.api.js +31 -4
  30. package/lib/api/work.api.js +19 -12
  31. package/lib/app/list-external-systems.js +2 -1
  32. package/lib/app/show-display.js +33 -6
  33. package/lib/app/show.js +25 -2
  34. package/lib/channels/add-channel.js +1 -0
  35. package/lib/channels/approval-guides/claude.js +2 -2
  36. package/lib/channels/channel-add-failure.js +1 -0
  37. package/lib/channels/channel-add-remaining.js +15 -9
  38. package/lib/channels/channel-definition-status.js +115 -0
  39. package/lib/channels/channel-description.js +2 -1
  40. package/lib/channels/channel-install-envelope.js +4 -2
  41. package/lib/channels/channel-integration-generator.js +2 -0
  42. package/lib/channels/channel-runtime-resolver.js +14 -0
  43. package/lib/channels/channel-targets.js +31 -5
  44. package/lib/channels/claude-setup-values.js +4 -1
  45. package/lib/channels/slack-channel-add.js +59 -6
  46. package/lib/channels/slack-mcp-callback.js +51 -0
  47. package/lib/channels/slack-mcp-runtime.js +156 -0
  48. package/lib/channels/slack-provisioning/install-cli.js +14 -2
  49. package/lib/channels/slack-provisioning/with-curl-on-path.js +6 -1
  50. package/lib/cli/index.js +4 -0
  51. package/lib/cli/setup-certification-commands.js +2 -2
  52. package/lib/cli/setup-certification.help.js +3 -1
  53. package/lib/commands/agent-kit-help.js +79 -0
  54. package/lib/commands/agent-kit.js +346 -0
  55. package/lib/commands/channel-add-help.js +5 -5
  56. package/lib/commands/datasource-capability-relate-cli.js +5 -4
  57. package/lib/commands/repair-auth-apply.js +1 -1
  58. package/lib/commands/repair-datasource-key-skip.js +39 -0
  59. package/lib/commands/repair-datasource-keys.js +24 -12
  60. package/lib/commands/repair-rbac.js +16 -10
  61. package/lib/commands/role-assistant-copy-output.js +61 -0
  62. package/lib/commands/role-assistant-copy.js +155 -0
  63. package/lib/commands/role-assistant-test-output.js +8 -9
  64. package/lib/commands/role-assistant-test-result-rows.js +42 -0
  65. package/lib/commands/role-assistant.js +117 -48
  66. package/lib/commands/search.js +143 -0
  67. package/lib/commands/test-trust-external.js +7 -5
  68. package/lib/commands/upload-sidecar-followups.js +4 -1
  69. package/lib/commands/verify-operations-skip-e2e.js +131 -0
  70. package/lib/commands/verify-operations-step-plan.js +6 -33
  71. package/lib/commands/verify-operations-steps.js +3 -1
  72. package/lib/datasource/capability/relate-operations.js +28 -10
  73. package/lib/datasource/capability/run-capability-relate.js +48 -25
  74. package/lib/datasource/datasource-identifier.js +13 -5
  75. package/lib/deployment/deployer-poll-run.js +117 -35
  76. package/lib/external-system/deploy-dataplane-published.js +70 -0
  77. package/lib/external-system/deploy.js +12 -2
  78. package/lib/external-system/download-role-assistant.js +41 -15
  79. package/lib/external-system/download.js +7 -1
  80. package/lib/generator/wizard-datasource-key.js +54 -0
  81. package/lib/generator/wizard.js +55 -54
  82. package/lib/integration-definition/apply-datasource-keys.js +75 -0
  83. package/lib/integration-definition/apply-relationships.js +195 -0
  84. package/lib/integration-definition/apply-static-enterprise.js +229 -0
  85. package/lib/integration-definition/apply-system-file.js +71 -0
  86. package/lib/integration-definition/apply-test-endpoint.js +89 -0
  87. package/lib/integration-definition/apply-viewpoint-defaults.js +157 -0
  88. package/lib/integration-definition/apply.js +78 -59
  89. package/lib/integration-definition/enrich-openapi-operations.js +36 -10
  90. package/lib/integration-definition/enrich-openapi-pick.js +87 -0
  91. package/lib/lifecycle/role-assistant-governance.js +56 -21
  92. package/lib/programmatic/capability-registry.js +1 -0
  93. package/lib/programmatic/index.js +2 -0
  94. package/lib/programmatic/openapi-apply-result-schemas.js +8 -0
  95. package/lib/programmatic/openapi-channel-install-schemas.js +4 -1
  96. package/lib/programmatic/openapi-component-schemas.js +1 -0
  97. package/lib/programmatic/openapi-descriptions.js +9 -3
  98. package/lib/programmatic/openapi-envelope-examples.js +7 -0
  99. package/lib/programmatic/openapi-operation-bindings.js +5 -0
  100. package/lib/programmatic/openapi-request-schemas.js +5 -0
  101. package/lib/programmatic/openapi-schema-descriptions-channel.js +8 -5
  102. package/lib/programmatic/openapi-schema-descriptions-operations.js +19 -0
  103. package/lib/programmatic/openapi-spec.js +1 -0
  104. package/lib/programmatic/request-timeout.js +3 -0
  105. package/lib/programmatic/route-handler-map.js +7 -0
  106. package/lib/programmatic/run-channel-catalog.js +50 -8
  107. package/lib/programmatic/run-channel-install-options.js +5 -1
  108. package/lib/programmatic/run-download.js +41 -0
  109. package/lib/protection/auth-context.js +3 -1
  110. package/lib/protection/protection-display.js +3 -2
  111. package/lib/protection/report-exit.js +50 -0
  112. package/lib/protection/run-commands.js +2 -0
  113. package/lib/role-assistant/package-validate.js +175 -0
  114. package/lib/role-assistant/test-case-assertion-row.js +36 -0
  115. package/lib/role-assistant/test-case-copy-bind.js +77 -0
  116. package/lib/role-assistant/test-case-copy-expected.js +127 -0
  117. package/lib/role-assistant/test-case-copy-from-work.js +90 -0
  118. package/lib/role-assistant/test-case-copy-interactions.js +67 -0
  119. package/lib/role-assistant/test-case-copy.js +119 -0
  120. package/lib/role-assistant/test-case-expected-catalog.js +135 -0
  121. package/lib/role-assistant/test-case-validate.js +88 -0
  122. package/lib/role-assistant/test-cases-bid.js +117 -4
  123. package/lib/role-assistant/test-cases-evaluate.js +137 -30
  124. package/lib/role-assistant/test-cases-honesty.js +155 -6
  125. package/lib/role-assistant/test-cases-lifecycle.js +291 -0
  126. package/lib/role-assistant/test-cases-search-graph.js +122 -0
  127. package/lib/role-assistant/test-cases-search.js +20 -2
  128. package/lib/role-assistant/test-cases.js +34 -168
  129. package/lib/role-assistant/test-pack-map.js +180 -0
  130. package/lib/role-assistant/test-pack-sync.js +187 -0
  131. package/lib/role-assistant/test-runner-debug.js +54 -0
  132. package/lib/role-assistant/test-runner-respond-body.js +36 -0
  133. package/lib/role-assistant/test-runner-search.js +20 -5
  134. package/lib/role-assistant/test-runner-start-payload.js +63 -10
  135. package/lib/role-assistant/test-runner-workhub-answers.js +45 -16
  136. package/lib/role-assistant/test-runner-workhub.js +45 -21
  137. package/lib/role-assistant/test-runner.js +50 -10
  138. package/lib/schema/external-datasource.schema.json +36 -5
  139. package/lib/schema/external-system.schema.json +20 -2
  140. package/lib/schema/role-assistant-test-case.schema.json +408 -0
  141. package/lib/search/dataplane-context.js +23 -0
  142. package/lib/search/search-args.js +211 -0
  143. package/lib/search/search-display.js +238 -0
  144. package/lib/search/search-errors.js +50 -0
  145. package/lib/search/search-prompts.js +119 -0
  146. package/lib/search/search-roles.js +85 -0
  147. package/lib/search/search-run.js +241 -0
  148. package/lib/search/search-skills.js +198 -0
  149. package/lib/utils/authentication-slot-show.js +153 -0
  150. package/lib/utils/credential-display.js +43 -1
  151. package/lib/utils/external-system-readiness-compact-blocks.js +5 -1
  152. package/lib/utils/external-system-readiness-compact-display.js +30 -1
  153. package/lib/utils/external-system-validators.js +2 -1
  154. package/lib/utils/help-builder.js +3 -1
  155. package/lib/utils/schema-loader.js +37 -0
  156. package/lib/validation/validate-external-file.js +5 -1
  157. package/lib/validation/validate.js +3 -2
  158. package/package.json +1 -1
  159. package/templates/agent-kit/adapters/claude/nested.CLAUDE.md +1 -0
  160. package/templates/agent-kit/adapters/claude/root.CLAUDE.md +5 -0
  161. package/templates/agent-kit/adapters/cursor/commands/cust-0-plan.md +5 -0
  162. package/templates/agent-kit/adapters/cursor/commands/cust-1-validate.md +5 -0
  163. package/templates/agent-kit/adapters/cursor/commands/cust-2-build.md +5 -0
  164. package/templates/agent-kit/adapters/cursor/commands/cust-3-push.md +5 -0
  165. package/templates/agent-kit/adapters/cursor/commands/cust-biz-1-validate.md +5 -0
  166. package/templates/agent-kit/adapters/cursor/commands/cust-biz-2-build.md +5 -0
  167. package/templates/agent-kit/adapters/cursor/commands/cust-biz-3-prove.md +5 -0
  168. package/templates/agent-kit/agent-kit.yaml +45 -0
  169. package/templates/agent-kit/instructions/AGENTKIT.md +83 -0
  170. package/templates/agent-kit/instructions/integration.AGENTS.md +25 -0
  171. package/templates/agent-kit/instructions/role-assistant.AGENTS.md +22 -0
  172. package/templates/agent-kit/instructions/root.AGENTS.md +37 -0
  173. package/templates/agent-kit/parity-inventory.yaml +188 -0
  174. package/templates/agent-kit/skills/aifabrix-connected-system/SKILL.md +58 -0
  175. package/templates/agent-kit/skills/aifabrix-connected-system/references/contract-checklist.md +40 -0
  176. package/templates/agent-kit/skills/aifabrix-connected-system/references/delivery-gates.md +54 -0
  177. package/templates/agent-kit/skills/aifabrix-connected-system/references/production-safety.md +35 -0
  178. package/templates/agent-kit/skills/aifabrix-connected-system/scripts/reserved-system-key.js +19 -0
  179. package/templates/agent-kit/skills/aifabrix-plan/SKILL.md +51 -0
  180. package/templates/agent-kit/skills/aifabrix-plan/references/challenge-checklist.md +108 -0
  181. package/templates/agent-kit/skills/aifabrix-plan/references/interaction.md +34 -0
  182. package/templates/agent-kit/skills/aifabrix-plan/references/plan-template.md +182 -0
  183. package/templates/agent-kit/skills/aifabrix-prove/SKILL.md +50 -0
  184. package/templates/agent-kit/skills/aifabrix-prove/references/demo-template.md +65 -0
  185. package/templates/agent-kit/skills/aifabrix-prove/references/evidence-lifecycle.md +68 -0
  186. package/templates/agent-kit/skills/aifabrix-prove/references/learning-template.md +66 -0
  187. package/templates/agent-kit/skills/aifabrix-role-assistant/SKILL.md +42 -0
  188. package/templates/agent-kit/skills/aifabrix-role-assistant/references/package-boundary.md +21 -0
  189. package/templates/agent-kit/skills/shared/hosts.md +78 -0
  190. package/templates/agent-kit/workspace/.cursor/plans/README.md +22 -0
  191. package/templates/agent-kit/workspace/LICENSE.mit +21 -0
  192. package/templates/agent-kit/workspace/README.md +89 -0
  193. package/templates/applications/builder-api/Dockerfile +3 -1
  194. package/templates/applications/dataplane/application.yaml +2 -2
  195. package/templates/applications/dataplane/env.template +1 -1
  196. package/templates/applications/dataplane/rbac.yaml +28 -0
  197. package/templates/applications/keycloak/env.template +3 -3
  198. package/templates/applications/miso-controller/application.yaml +1 -1
  199. package/templates/applications/miso-controller/env.template +8 -9
  200. package/templates/channels/claude/TROUBLESHOOTING.md +3 -0
  201. package/templates/channels/claude/channel.json +1 -1
  202. package/templates/channels/claude/install.template.md.hbs +1 -0
  203. package/templates/channels/claude/integration/claude-system.json +1 -1
  204. package/templates/channels/slack/channel.json +11 -0
  205. package/templates/channels/slack/install.template.md.hbs +9 -0
  206. package/templates/channels/slack/integration/slack-system.json +11 -0
  207. package/templates/channels/slack/manifest.template.json.hbs +6 -3
  208. package/templates/channels/teams-bot/channel.json +1 -0
  209. package/templates/python/docker-compose.hbs +14 -0
  210. package/templates/typescript/docker-compose.hbs +14 -0
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Keep human.respond bodies on the OpenAPI request shape.
3
+ * WaitPayload.prompt is server-owned and must not be echoed.
4
+ * @fileoverview Strip extra wait fields from RA respond payloads
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const HUMAN_RESPOND_KEYS = [
10
+ 'respondIdempotencyKey',
11
+ 'answerType',
12
+ 'value',
13
+ 'evidenceRef',
14
+ 'declined',
15
+ 'selectedOptionId',
16
+ 'approvalDecision',
17
+ 'proposedPatch',
18
+ 'requestVersion',
19
+ 'stateVersionRef',
20
+ 'requestFingerprint'
21
+ ];
22
+
23
+ function pickHumanRespondBody(payload) {
24
+ const out = {};
25
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
26
+ return out;
27
+ }
28
+ for (const key of HUMAN_RESPOND_KEYS) {
29
+ if (payload[key] !== undefined) {
30
+ out[key] = payload[key];
31
+ }
32
+ }
33
+ return out;
34
+ }
35
+
36
+ module.exports = { pickHumanRespondBody };
@@ -9,6 +9,7 @@
9
9
  'use strict';
10
10
 
11
11
  const { getWorkSkills, searchWork } = require('../api/work-search.api');
12
+ const { resourceTypesMap } = require('../search/search-skills');
12
13
  const { roleAssistantSystemKey } = require('../utils/role-assistant-paths');
13
14
  const {
14
15
  resolveInput,
@@ -32,11 +33,6 @@ function operationKeys(spec) {
32
33
  return keys;
33
34
  }
34
35
 
35
- function resourceTypesMap(skillsPayload) {
36
- const rts = skillsPayload && skillsPayload.search && skillsPayload.search.resourceTypes;
37
- return rts && typeof rts === 'object' && !Array.isArray(rts) ? rts : {};
38
- }
39
-
40
36
  function pickResourceType(skillsPayload, operation) {
41
37
  const rts = resourceTypesMap(skillsPayload);
42
38
  const keys = Object.keys(rts);
@@ -73,6 +69,24 @@ function assignOptionalNumber(args, input, key) {
73
69
  }
74
70
  }
75
71
 
72
+ function assignGraphFields(args, input) {
73
+ if (input.graph === true) {
74
+ args.graph = { depth: 1 };
75
+ } else if (input.graph && typeof input.graph === 'object' && !Array.isArray(input.graph)) {
76
+ args.graph = { depth: 1 };
77
+ }
78
+ if (typeof input.query === 'string' && input.query.trim()) {
79
+ args.query = input.query.trim();
80
+ }
81
+ if (!Array.isArray(input.knowledgeKeys) || !input.knowledgeKeys.length) {
82
+ return;
83
+ }
84
+ const keys = input.knowledgeKeys.map((item) => String(item || '').trim()).filter(Boolean);
85
+ if (keys.length) {
86
+ args.knowledgeKeys = keys;
87
+ }
88
+ }
89
+
76
90
  function buildSearchArguments(roleRef, input, skillsPayload) {
77
91
  const args = { roleRef };
78
92
  const resourceType =
@@ -91,6 +105,7 @@ function buildSearchArguments(roleRef, input, skillsPayload) {
91
105
  if (Array.isArray(input.fields) && input.fields.length) {
92
106
  args.fields = input.fields;
93
107
  }
108
+ assignGraphFields(args, input);
94
109
  return args;
95
110
  }
96
111
 
@@ -37,6 +37,16 @@ function resolveInputMessage(testCase) {
37
37
  return '';
38
38
  }
39
39
 
40
+ function applyObjectiveField(payload, message) {
41
+ if (message) {
42
+ payload.objective = message;
43
+ return;
44
+ }
45
+ if (typeof payload.content === 'string' && payload.content.trim()) {
46
+ payload.objective = payload.content.trim();
47
+ }
48
+ }
49
+
40
50
  function applyStartPayloadOptionalFields(payload, input) {
41
51
  const mappedFields = [
42
52
  ['activationId', 'activationId'],
@@ -58,34 +68,77 @@ function buildStartPayloadSourceMetadata(roleKey, testCase, input) {
58
68
  };
59
69
  }
60
70
 
61
- function buildStartIntentPayload(roleKey, testCase) {
62
- const input = resolveInputObject(testCase);
63
- const message = resolveInputMessage(testCase);
64
- const hasContent = typeof input.content !== 'undefined';
71
+ function resolveCaseMetadata(testCase) {
72
+ if (testCase && typeof testCase.metadata === 'object' && testCase.metadata) {
73
+ return testCase.metadata;
74
+ }
75
+ return {};
76
+ }
77
+
78
+ function resolveLearningLevel(metadata) {
79
+ if (typeof metadata.learningLevel === 'string' && metadata.learningLevel.trim()) {
80
+ return metadata.learningLevel.trim();
81
+ }
82
+ return null;
83
+ }
84
+
85
+ function assertHasStartContent(testCase, hasContent, message) {
65
86
  if (!hasContent && !message) {
66
87
  throw new Error(
67
88
  `Role assistant test case "${testCase.caseId}" is missing input.message or input.content for runtime execution.`
68
89
  );
69
90
  }
70
- const payload = {
91
+ }
92
+
93
+ function resolveComVocabulary(input) {
94
+ if (Array.isArray(input.comVocabulary) && input.comVocabulary.length > 0) {
95
+ return input.comVocabulary.map((row) => String(row));
96
+ }
97
+ return ['objective', 'entity', 'action'];
98
+ }
99
+
100
+ function buildStartIntentBasePayload(roleKey, testCase, input, message, hasContent) {
101
+ return {
71
102
  submissionId: String(input.submissionId || buildRuntimeId(`ra-${roleKey}`)),
72
103
  content: hasContent ? input.content : message,
73
104
  comModelRef: String(input.comModelRef || 'aifabrix.com/intent/default'),
74
105
  comModelVersion: String(input.comModelVersion || '1.0'),
75
- comVocabulary:
76
- Array.isArray(input.comVocabulary) && input.comVocabulary.length > 0
77
- ? input.comVocabulary.map((row) => String(row))
78
- : ['objective', 'entity', 'action'],
106
+ comVocabulary: resolveComVocabulary(input),
79
107
  sourceType: String(input.sourceType || 'api'),
80
108
  sourceLanguage: String(input.sourceLanguage || 'en'),
81
109
  responseLanguage: String(input.responseLanguage || input.sourceLanguage || 'en'),
82
110
  sourceMetadata: buildStartPayloadSourceMetadata(roleKey, testCase, input)
83
111
  };
84
- applyStartPayloadOptionalFields(payload, input);
112
+ }
113
+
114
+ function applyLearningLevel(payload, learningLevel) {
115
+ if (learningLevel === null) {
116
+ return;
117
+ }
118
+ payload.learningLevel = learningLevel;
119
+ payload.sourceMetadata = {
120
+ ...(payload.sourceMetadata || {}),
121
+ learningLevel
122
+ };
123
+ }
124
+
125
+ function applyStartAttachments(payload, testCase) {
85
126
  const attachments = buildStartAttachments(testCase);
86
127
  if (attachments && attachments.length > 0) {
87
128
  payload.attachments = attachments;
88
129
  }
130
+ }
131
+
132
+ function buildStartIntentPayload(roleKey, testCase) {
133
+ const input = resolveInputObject(testCase);
134
+ const message = resolveInputMessage(testCase);
135
+ const hasContent = typeof input.content !== 'undefined';
136
+ assertHasStartContent(testCase, hasContent, message);
137
+ const payload = buildStartIntentBasePayload(roleKey, testCase, input, message, hasContent);
138
+ applyObjectiveField(payload, message);
139
+ applyStartPayloadOptionalFields(payload, input);
140
+ applyLearningLevel(payload, resolveLearningLevel(resolveCaseMetadata(testCase)));
141
+ applyStartAttachments(payload, testCase);
89
142
  return payload;
90
143
  }
91
144
 
@@ -11,6 +11,7 @@ const {
11
11
  resolveSuiteRunId,
12
12
  loadCaseBidDocument
13
13
  } = require('./test-runner-bid-document');
14
+ const { pickHumanRespondBody } = require('./test-runner-respond-body');
14
15
 
15
16
  function resolveWorkHubAnswerValue(answer) {
16
17
  if (answer === null || answer === undefined) {
@@ -31,24 +32,41 @@ function resolveWorkHubAnswerValue(answer) {
31
32
  return undefined;
32
33
  }
33
34
 
35
+ function asPlainObject(value) {
36
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
37
+ return {};
38
+ }
39
+ return value;
40
+ }
41
+
42
+ function firstNormalizedKey(candidates) {
43
+ for (const part of candidates) {
44
+ const text = String(part || '').trim().toLowerCase();
45
+ if (text) {
46
+ return text;
47
+ }
48
+ }
49
+ return '';
50
+ }
51
+
52
+ function waitQuestionKey(wait) {
53
+ const row = asPlainObject(wait);
54
+ const objectRef = asPlainObject(row.objectRef);
55
+ return firstNormalizedKey([row.questionKey, row.requestKey, objectRef.capabilityKey]);
56
+ }
57
+
58
+ function answerWhenKey(entry) {
59
+ const when = asPlainObject(asPlainObject(entry).when);
60
+ return firstNormalizedKey([when.questionKey, when.requestKey, when.key]);
61
+ }
62
+
34
63
  function pickAnswerForWait(answers, wait) {
35
- const questionKey = String(wait.questionKey || wait.requestKey || '').trim().toLowerCase();
64
+ const questionKey = waitQuestionKey(wait);
36
65
  if (!questionKey) {
37
66
  return null;
38
67
  }
39
- for (const entry of answers) {
40
- if (!entry || typeof entry !== 'object') {
41
- continue;
42
- }
43
- const when = entry.when && typeof entry.when === 'object' ? entry.when : {};
44
- const matchKey = String(when.questionKey || when.requestKey || when.key || '')
45
- .trim()
46
- .toLowerCase();
47
- if (matchKey && matchKey === questionKey) {
48
- return entry;
49
- }
50
- }
51
- return null;
68
+ const list = Array.isArray(answers) ? answers : [];
69
+ return list.find((entry) => answerWhenKey(entry) === questionKey) || null;
52
70
  }
53
71
 
54
72
  function parseFieldsFromPrompt(prompt) {
@@ -423,14 +441,25 @@ function buildRespondPayload(peId, wait, testCase, statusRow, interactions, obje
423
441
  };
424
442
  if (answerType === 'approval') {
425
443
  payload.approvalDecision = 'approved';
426
- return payload;
444
+ return pickHumanRespondBody(payload);
427
445
  }
428
446
  const value = resolveRespondValue(testCase, wait, statusRow, objective);
429
447
  if (value === undefined) {
430
448
  return null;
431
449
  }
450
+ const choiceIds = Array.isArray(wait.allowedChoiceIds) ? wait.allowedChoiceIds : [];
451
+ const isChoice =
452
+ String(wait.interactionKind || '').toLowerCase() === 'choice' || choiceIds.length > 0;
453
+ if (isChoice) {
454
+ const selected = typeof value === 'string' ? value.trim() : '';
455
+ if (!selected) {
456
+ return null;
457
+ }
458
+ payload.selectedOptionId = selected;
459
+ return pickHumanRespondBody(payload);
460
+ }
432
461
  payload.value = value;
433
- return payload;
462
+ return pickHumanRespondBody(payload);
434
463
  }
435
464
 
436
465
  module.exports = {
@@ -14,8 +14,7 @@ const {
14
14
  approveWorkTask,
15
15
  getWorkExecutionStatus,
16
16
  respondWorkExecution,
17
- continueWorkExecution,
18
- getWorkExecutionResult
17
+ continueWorkExecution
19
18
  } = require('../api/work.api');
20
19
  const {
21
20
  synthesizeWaitAnswer,
@@ -367,16 +366,9 @@ async function pollWorkHubTask(dataplaneUrl, authConfig, taskId, testCase, optio
367
366
  };
368
367
  }
369
368
 
370
- async function fetchBusinessResult(dataplaneUrl, authConfig, processExecutionId) {
371
- try {
372
- const workResult = await getWorkExecutionResult(dataplaneUrl, authConfig, processExecutionId, {
373
- requestedDepth: 'business',
374
- format: 'json'
375
- });
376
- return { workResult, workResultError: null };
377
- } catch (error) {
378
- return { workResult: null, workResultError: error.message || String(error) };
379
- }
369
+ async function fetchBusinessResult(dataplaneUrl, authConfig, processExecutionId, options = {}) {
370
+ const { fetchWorkHubResult } = require('./test-runner-debug');
371
+ return fetchWorkHubResult(dataplaneUrl, authConfig, processExecutionId, options);
380
372
  }
381
373
 
382
374
  function buildWorkHubLifecycleResult(roleKey, testCase, started, pollState, peBundle) {
@@ -406,6 +398,38 @@ function buildWorkHubLifecycleResult(roleKey, testCase, started, pollState, peBu
406
398
  };
407
399
  }
408
400
 
401
+ async function finishWorkHubAfterPoll(args) {
402
+ const {
403
+ roleKey,
404
+ testCase,
405
+ started,
406
+ pollState,
407
+ dataplaneUrl,
408
+ authConfig,
409
+ options
410
+ } = args;
411
+ const processExecutionId = extractPeFromTask(pollState.finalTask);
412
+ if (!processExecutionId) {
413
+ throw new Error(`Work Hub task ${started.taskId} finished without processExecutionId.`);
414
+ }
415
+ const { persistRoleAssistantDebugLog } = require('./test-runner-debug');
416
+ const peBundle = {
417
+ processExecutionId,
418
+ ...(await fetchBusinessResult(dataplaneUrl, authConfig, processExecutionId, options))
419
+ };
420
+ await persistRoleAssistantDebugLog(
421
+ roleKey,
422
+ {
423
+ processExecutionId,
424
+ requestedDepth: options && options.debug === true ? 'technical' : 'business',
425
+ workResult: peBundle.workResult,
426
+ workResultError: peBundle.workResultError
427
+ },
428
+ options
429
+ );
430
+ return buildWorkHubLifecycleResult(roleKey, testCase, started, pollState, peBundle);
431
+ }
432
+
409
433
  async function runWorkHubCaseLifecycle(roleKey, testCase, runtimeContext, options = {}) {
410
434
  const { startWorkHubOrSoftClarify } = require('./test-runner-workhub-soft');
411
435
  const dataplaneUrl = runtimeContext?.dataplaneUrl;
@@ -440,15 +464,15 @@ async function runWorkHubCaseLifecycle(roleKey, testCase, runtimeContext, option
440
464
  testCase,
441
465
  options
442
466
  );
443
- const processExecutionId = extractPeFromTask(pollState.finalTask);
444
- if (!processExecutionId) {
445
- throw new Error(`Work Hub task ${started.taskId} finished without processExecutionId.`);
446
- }
447
- const peBundle = {
448
- processExecutionId,
449
- ...(await fetchBusinessResult(dataplaneUrl, authConfig, processExecutionId))
450
- };
451
- return buildWorkHubLifecycleResult(roleKey, testCase, started, pollState, peBundle);
467
+ return finishWorkHubAfterPoll({
468
+ roleKey,
469
+ testCase,
470
+ started,
471
+ pollState,
472
+ dataplaneUrl,
473
+ authConfig,
474
+ options
475
+ });
452
476
  }
453
477
 
454
478
  module.exports = {
@@ -24,6 +24,7 @@ const {
24
24
  resolveInputObject,
25
25
  resolveInputMessage
26
26
  } = require('./test-runner-start-payload');
27
+ const { pickHumanRespondBody } = require('./test-runner-respond-body');
27
28
  const TERMINAL_STATUSES = new Set(['completed', 'stopped']);
28
29
  const WAITING_STATUS = 'waiting';
29
30
 
@@ -74,6 +75,7 @@ function isExecutableRoleAssistantCase(testCase) {
74
75
 
75
76
  function extractProcessExecutionId(startResponse) {
76
77
  const candidates = [
78
+ startResponse?.work?.processExecutionId,
77
79
  startResponse?.intent?.processExecutionId,
78
80
  startResponse?.processExecutionId,
79
81
  startResponse?.status?.processExecutionId
@@ -111,6 +113,8 @@ function collectWaitMatchTokens(wait) {
111
113
  add(wait.objectRef.name);
112
114
  add(wait.objectRef.type);
113
115
  add(wait.objectRef.id);
116
+ add(wait.objectRef.capabilityKey);
117
+ add(wait.objectRef.resourceType);
114
118
  }
115
119
  if (wait.safeProjection && typeof wait.safeProjection === 'object') {
116
120
  add(wait.safeProjection.capabilityKey);
@@ -197,22 +201,40 @@ function applyWaitVersioningFields(payload, wait) {
197
201
  applyOptionalStringField(payload, wait.requestFingerprint, 'requestFingerprint');
198
202
  }
199
203
 
204
+ function waitIsChoice(wait) {
205
+ const ids = wait && Array.isArray(wait.allowedChoiceIds) ? wait.allowedChoiceIds : [];
206
+ return String((wait && wait.interactionKind) || '').toLowerCase() === 'choice' || ids.length > 0;
207
+ }
208
+
209
+ function choiceIdFromAnswer(answerEntry, respond) {
210
+ if (typeof respond.selectedOptionId === 'string' && respond.selectedOptionId.trim()) {
211
+ return respond.selectedOptionId.trim();
212
+ }
213
+ if (typeof answerEntry?.value === 'string' && answerEntry.value.trim()) {
214
+ return answerEntry.value.trim();
215
+ }
216
+ return '';
217
+ }
218
+
200
219
  function buildAskRespondPayload(answerEntry, wait) {
201
220
  const respond = answerEntry && typeof answerEntry.respond === 'object' && answerEntry.respond
202
221
  ? answerEntry.respond
203
222
  : {};
204
223
  const payload = {
205
224
  respondIdempotencyKey: buildRuntimeId('ra-answer'),
206
- answerType: 'ask',
207
- value: resolveAskRespondValue(respond)
225
+ answerType: 'ask'
208
226
  };
209
- applyOptionalStringField(payload, respond.selectedOptionId, 'selectedOptionId');
210
- applyOptionalStringField(payload, respond.evidenceRef, 'evidenceRef');
227
+ if (waitIsChoice(wait)) {
228
+ applyOptionalStringField(payload, choiceIdFromAnswer(answerEntry, respond), 'selectedOptionId');
229
+ } else {
230
+ payload.value = resolveAskRespondValue(Object.keys(respond).length ? respond : answerEntry);
231
+ applyOptionalStringField(payload, respond.evidenceRef, 'evidenceRef');
232
+ }
211
233
  if (respond.declined === true) {
212
234
  payload.declined = true;
213
235
  }
214
236
  applyWaitVersioningFields(payload, wait);
215
- return payload;
237
+ return pickHumanRespondBody(payload);
216
238
  }
217
239
 
218
240
  function buildApprovalRespondPayload(approvalEntry, wait) {
@@ -228,7 +250,7 @@ function buildApprovalRespondPayload(approvalEntry, wait) {
228
250
  approvalDecision: decision
229
251
  };
230
252
  applyWaitVersioningFields(payload, wait);
231
- return payload;
253
+ return pickHumanRespondBody(payload);
232
254
  }
233
255
 
234
256
  function buildStatusSnapshot(statusRow) {
@@ -247,7 +269,8 @@ function buildRunnerOptions(options = {}) {
247
269
  return {
248
270
  maxStatusPolls: Number.isFinite(maxStatusPolls) && maxStatusPolls > 0 ? maxStatusPolls : 20,
249
271
  statusPollIntervalMs:
250
- Number.isFinite(statusPollIntervalMs) && statusPollIntervalMs >= 0 ? statusPollIntervalMs : 750
272
+ Number.isFinite(statusPollIntervalMs) && statusPollIntervalMs >= 0 ? statusPollIntervalMs : 750,
273
+ debug: options.debug === true
251
274
  };
252
275
  }
253
276
 
@@ -270,6 +293,18 @@ function finalizeWaitingAdvanceStatus(respondResult, continueResult) {
270
293
  : afterRespondStatus;
271
294
  }
272
295
 
296
+ async function continueAfterRespond(dataplaneUrl, authConfig, processExecutionId) {
297
+ try {
298
+ return await continueWorkExecution(dataplaneUrl, authConfig, processExecutionId, {});
299
+ } catch (error) {
300
+ const text = String(error && error.message ? error.message : error);
301
+ if (/already terminal/i.test(text)) {
302
+ return getWorkExecutionStatus(dataplaneUrl, authConfig, processExecutionId);
303
+ }
304
+ throw error;
305
+ }
306
+ }
307
+
273
308
  async function advanceApprovalWaitingExecution(context, approvals, wait) {
274
309
  const { dataplaneUrl, authConfig, processExecutionId, usedApprovalIndexes } = context;
275
310
  const match = findMatchingEntry(approvals, usedApprovalIndexes, wait);
@@ -279,7 +314,7 @@ async function advanceApprovalWaitingExecution(context, approvals, wait) {
279
314
  usedApprovalIndexes.add(match.index);
280
315
  const payload = buildApprovalRespondPayload(match.entry, wait);
281
316
  const respondResult = await respondWorkExecution(dataplaneUrl, authConfig, processExecutionId, payload);
282
- const continueResult = await continueWorkExecution(dataplaneUrl, authConfig, processExecutionId, {});
317
+ const continueResult = await continueAfterRespond(dataplaneUrl, authConfig, processExecutionId);
283
318
  return {
284
319
  status: finalizeWaitingAdvanceStatus(respondResult, continueResult),
285
320
  matched: true,
@@ -297,7 +332,7 @@ async function advanceAskWaitingExecution(context, answers, wait) {
297
332
  usedAnswerIndexes.add(match.index);
298
333
  const payload = buildAskRespondPayload(match.entry, wait);
299
334
  const respondResult = await respondWorkExecution(dataplaneUrl, authConfig, processExecutionId, payload);
300
- const continueResult = await continueWorkExecution(dataplaneUrl, authConfig, processExecutionId, {});
335
+ const continueResult = await continueAfterRespond(dataplaneUrl, authConfig, processExecutionId);
301
336
  return {
302
337
  status: finalizeWaitingAdvanceStatus(respondResult, continueResult),
303
338
  matched: true,
@@ -344,7 +379,11 @@ async function pollCaseExecutionStatus(dataplaneUrl, authConfig, processExecutio
344
379
  type: advanced.interactionType,
345
380
  waitReason: wait.waitReason || wait.answerType || wait.interactionKind || null,
346
381
  matched: advanced.matched === true,
347
- questionKey: wait.questionKey || wait.requestKey || null
382
+ questionKey:
383
+ wait.questionKey ||
384
+ wait.requestKey ||
385
+ (wait.objectRef && wait.objectRef.capabilityKey) ||
386
+ null
348
387
  });
349
388
  if (!advanced.matched || !advanced.status) {
350
389
  break;
@@ -451,6 +490,7 @@ function lifecycleVerdictFromCaseExecution(executionResult, fallbackVerdict) {
451
490
  module.exports = {
452
491
  isExecutableRoleAssistantCase,
453
492
  buildStartIntentPayload,
493
+ buildRunnerOptions,
454
494
  runRoleAssistantCaseLifecycle,
455
495
  mergeCaseExecutionIntoLifecycleResult,
456
496
  lifecycleVerdictFromCaseExecution
@@ -7,12 +7,12 @@
7
7
  "key":"external-datasource-schema",
8
8
  "name":"External Data Source Configuration Schema",
9
9
  "description":"JSON schema for validating ExternalDataSource configuration files",
10
- "version":"2.9.9",
10
+ "version":"2.9.11",
11
11
  "type":"schema",
12
12
  "category":"integration",
13
13
  "author":"AI Fabrix Team",
14
14
  "createdAt":"2024-01-01T00:00:00Z",
15
- "updatedAt":"2026-08-27T00:00:00Z",
15
+ "updatedAt":"2026-08-30T00:00:00Z",
16
16
  "compatibility":{
17
17
  "minVersion":"1.0.0",
18
18
  "maxVersion":"3.0.0",
@@ -31,6 +31,22 @@
31
31
  "external-system.schema.json"
32
32
  ],
33
33
  "changelog":[
34
+ {
35
+ "version":"2.9.11",
36
+ "date":"2026-08-30T00:00:00Z",
37
+ "changes":[
38
+ "Documented fieldMappings.attributes.expression closed pipe-function allow-list (toLower, toUpper, trim, substring, replace, default, coalesce, join, split, normalizeWhitespace, index). CIP $defs unchanged (543 visual N/A)."
39
+ ],
40
+ "breaking":false
41
+ },
42
+ {
43
+ "version":"2.9.10",
44
+ "date":"2026-08-29T00:00:00Z",
45
+ "changes":[
46
+ "Added optional sync.incrementalKey (CIP list fetch.query filter already on the list operation) and optional sync.incrementalSchedule (only when incrementalKey is set). schedule is the full-set (sync all) cron. CIP $defs unchanged (543 visual N/A)."
47
+ ],
48
+ "breaking":false
49
+ },
34
50
  {
35
51
  "version":"2.9.9",
36
52
  "date":"2026-08-27T00:00:00Z",
@@ -623,7 +639,7 @@
623
639
  "properties":{
624
640
  "expression":{
625
641
  "type":"string",
626
- "description":"Pipe-based DSL expression. Allowed roots: raw.<path>, fk.<fk>.metadata.<field>, fk.<fk>.dimension.<dimension>[.label], dimension.<dimension>[.label]. FK traversal in DSL is one-hop only.",
642
+ "description":"Pipe-based DSL expression. Allowed roots: raw.<path>, fk.<fk>.metadata.<field>, fk.<fk>.dimension.<dimension>[.label], dimension.<dimension>[.label]. FK traversal in DSL is one-hop only. Closed pipe functions: toLower, toUpper, trim, substring, replace, default, coalesce, join, split, normalizeWhitespace, index.",
627
643
  "maxLength":512,
628
644
  "pattern":"^\\s*\\{\\{(raw\\.[a-zA-Z0-9_\\[\\].]+|fk\\.[a-z][a-zA-Z0-9]*\\.metadata\\.[a-z][a-zA-Z0-9]*|fk\\.[a-z][a-zA-Z0-9]*\\.dimension\\.[a-zA-Z0-9_]+(\\.label)?|dimension\\.[a-zA-Z0-9_]+(\\.label)?)\\}\\}(\\s*\\|\\s*[a-zA-Z0-9_]+(\\([^)]*\\))?)*\\s*$"
629
645
  },
@@ -857,7 +873,7 @@
857
873
  },
858
874
  "schedule":{
859
875
  "type":"string",
860
- "description":"Cron schedule for sync execution. Supports 5-field cron syntax or @hourly/@daily/@weekly/@monthly.",
876
+ "description":"Cron for full-set (sync all) jobs. Supports 5-field cron syntax or @hourly/@daily/@weekly/@monthly.",
861
877
  "pattern":"^(@hourly|@daily|@weekly|@monthly|([0-9*/,-]+\\s+){4}[0-9*/,-]+)$"
862
878
  },
863
879
  "batchSize":{
@@ -865,8 +881,23 @@
865
881
  "minimum":1,
866
882
  "maximum":10000,
867
883
  "default":500
884
+ },
885
+ "incrementalKey":{
886
+ "type":"string",
887
+ "description":"Name of the list CIP fetch.query filter already authored on the list operation. Must map to a metadataSchema field of type date, date-time, or integer (unix time). Incremental jobs bind lastSyncedAt into that key. Never inferred from field names.",
888
+ "pattern":"^[a-zA-Z_$][a-zA-Z0-9_$]*$"
889
+ },
890
+ "incrementalSchedule":{
891
+ "type":"string",
892
+ "description":"Optional cron for incremental jobs. Allowed only when incrementalKey is set. schedule remains the full-set (sync all) cron.",
893
+ "pattern":"^(@hourly|@daily|@weekly|@monthly|([0-9*/,-]+\\s+){4}[0-9*/,-]+)$"
868
894
  }
869
895
  },
896
+ "dependentRequired":{
897
+ "incrementalSchedule":[
898
+ "incrementalKey"
899
+ ]
900
+ },
870
901
  "additionalProperties":false
871
902
  },
872
903
  "openapi":{
@@ -3004,7 +3035,7 @@
3004
3035
  "description":"Optional inline mapping overrides { normalizedField -> expression }.",
3005
3036
  "additionalProperties":{
3006
3037
  "type":"string",
3007
- "description":"Pipe-based DSL expression overriding fieldMappings expression."
3038
+ "description":"Pipe-based DSL expression overriding fieldMappings expression. Same closed function allow-list as fieldMappings.attributes.expression."
3008
3039
  }
3009
3040
  }
3010
3041
  },
@@ -7,12 +7,12 @@
7
7
  "key": "external-system-schema",
8
8
  "name": "External System Configuration Schema",
9
9
  "description": "JSON schema for validating ExternalSystem configuration files",
10
- "version": "1.16.0",
10
+ "version": "1.17.0",
11
11
  "type": "schema",
12
12
  "category": "integration",
13
13
  "author": "AI Fabrix Team",
14
14
  "createdAt": "2024-01-01T00:00:00Z",
15
- "updatedAt": "2026-08-23T00:00:00Z",
15
+ "updatedAt": "2026-08-28T00:00:00Z",
16
16
  "compatibility": {
17
17
  "minVersion": "1.4.0",
18
18
  "maxVersion": "2.0.0",
@@ -27,6 +27,14 @@
27
27
  ],
28
28
  "dependencies": [],
29
29
  "changelog": [
30
+ {
31
+ "version": "1.17.0",
32
+ "date": "2026-08-28T00:00:00Z",
33
+ "changes": [
34
+ "Added optional channel.status (visible | hide | coming-soon) for Channel Definition catalog and install. Omit for visible. hide: omitted from catalog and refused for new install. coming-soon: catalog read-only, new install refused."
35
+ ],
36
+ "breaking": false
37
+ },
30
38
  {
31
39
  "version": "1.16.0",
32
40
  "date": "2026-08-23T00:00:00Z",
@@ -1789,6 +1797,16 @@
1789
1797
  "type": "boolean",
1790
1798
  "default": false
1791
1799
  },
1800
+ "status": {
1801
+ "type": "string",
1802
+ "enum": [
1803
+ "visible",
1804
+ "hide",
1805
+ "coming-soon"
1806
+ ],
1807
+ "default": "visible",
1808
+ "description": "Channel catalog and install availability. Omit or visible: listed and installable. hide: omitted from the catalog; new install is refused (CLI and API). coming-soon: listed read-only with a coming-soon indication; new install is refused (CLI and API). Does not disable existing installations. Distinct from experimental (opt-in CLI)."
1809
+ },
1792
1810
  "maxInstances": {
1793
1811
  "type": "integer",
1794
1812
  "minimum": 1,