@aifabrix/builder 2.53.0 → 2.54.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/aifabrix.js +2 -2
- package/lib/api/assistants.api.js +8 -5
- package/lib/api/channel-identity.api.js +41 -0
- package/lib/api/evidence-authoring.api.js +5 -1
- package/lib/api/evidence.api.js +2 -0
- package/lib/api/types/evidence.types.js +2 -0
- package/lib/api/work-search.api.js +136 -0
- package/lib/channels/add-channel.js +163 -126
- package/lib/channels/approval-guides/chatgpt.js +14 -18
- package/lib/channels/approval-guides/claude.js +29 -0
- package/lib/channels/approval-guides/index.js +88 -22
- package/lib/channels/approval-guides/mcp-client.js +27 -0
- package/lib/channels/approval-guides/microsoft-copilot.js +52 -3
- package/lib/channels/approval-guides/slack.js +10 -10
- package/lib/channels/approval-guides/teams-bot.js +8 -12
- package/lib/channels/channel-add-continuations.js +91 -0
- package/lib/channels/channel-add-ensure.js +124 -27
- package/lib/channels/channel-add-existing.js +160 -28
- package/lib/channels/channel-add-failure.js +60 -0
- package/lib/channels/channel-add-remaining.js +486 -0
- package/lib/channels/channel-add-tty.js +190 -0
- package/lib/channels/channel-add-wizard-config.js +138 -0
- package/lib/channels/channel-add-wizard.js +250 -0
- package/lib/channels/channel-artifact-generator.js +27 -13
- package/lib/channels/channel-cardinality.js +83 -0
- package/lib/channels/channel-catalog-icon.js +45 -0
- package/lib/channels/channel-cli-instructions.js +75 -0
- package/lib/channels/channel-configuration-map.js +213 -0
- package/lib/channels/channel-credential.js +11 -14
- package/lib/channels/channel-definition-project.js +101 -0
- package/lib/channels/channel-definition-registry.js +154 -0
- package/lib/channels/channel-description.js +59 -2
- package/lib/channels/channel-discovery.js +16 -2
- package/lib/channels/channel-external-system.js +86 -9
- package/lib/channels/channel-install-envelope-actions.js +88 -0
- package/lib/channels/channel-install-envelope-microsoft.js +155 -0
- package/lib/channels/channel-install-envelope.js +356 -0
- package/lib/channels/channel-install-help-md.js +172 -0
- package/lib/channels/channel-install-http-payload.js +62 -0
- package/lib/channels/channel-install-inventory.js +115 -0
- package/lib/channels/channel-install-profiles.js +90 -0
- package/lib/channels/channel-instance-collision.js +118 -0
- package/lib/channels/channel-instance-key.js +100 -0
- package/lib/channels/channel-instance-materialize.js +240 -0
- package/lib/channels/channel-integration-generator.js +16 -9
- package/lib/channels/channel-integration-template.js +101 -22
- package/lib/channels/channel-inventory-fetch.js +79 -0
- package/lib/channels/channel-oauth-preflight.js +104 -0
- package/lib/channels/channel-package-version.js +76 -0
- package/lib/channels/channel-predefined-client.js +154 -0
- package/lib/channels/channel-probe.js +7 -2
- package/lib/channels/channel-runtime-resolver.js +180 -72
- package/lib/channels/channel-status-display.js +86 -16
- package/lib/channels/channel-targets.js +106 -92
- package/lib/channels/chatgpt-callback.js +318 -0
- package/lib/channels/chatgpt-channel-add.js +229 -0
- package/lib/channels/chatgpt-preflight.js +396 -0
- package/lib/channels/chatgpt-runtime-contract.js +17 -0
- package/lib/channels/chatgpt-setup-values.js +124 -0
- package/lib/channels/claude-callback.js +67 -0
- package/lib/channels/claude-channel-add.js +254 -0
- package/lib/channels/claude-runtime-contract.js +40 -0
- package/lib/channels/claude-runtime.js +58 -0
- package/lib/channels/claude-setup-values.js +89 -0
- package/lib/channels/mcp-client/config-merge.js +148 -0
- package/lib/channels/mcp-client/ensure-client.js +231 -0
- package/lib/channels/mcp-client/paths.js +53 -0
- package/lib/channels/mcp-client/profiles.js +122 -0
- package/lib/channels/mcp-client/redirect-uris.js +99 -0
- package/lib/channels/mcp-client/runtime.js +59 -0
- package/lib/channels/mcp-client-channel-add.js +186 -0
- package/lib/channels/microsoft-copilot-channel-add.js +117 -7
- package/lib/channels/microsoft-copilot-entra-identifier-uris.js +187 -0
- package/lib/channels/microsoft-copilot-entra-sso-form.js +106 -18
- package/lib/channels/microsoft-copilot-entra-sso.js +31 -2
- package/lib/channels/microsoft-copilot-package-prior.js +107 -0
- package/lib/channels/microsoft-copilot-package.js +29 -28
- package/lib/channels/microsoft-provisioning/a365.js +80 -0
- package/lib/channels/microsoft-provisioning/auth-config.js +240 -0
- package/lib/channels/microsoft-provisioning/auth.js +179 -0
- package/lib/channels/microsoft-provisioning/cli.js +71 -0
- package/lib/channels/microsoft-provisioning/constants.js +75 -0
- package/lib/channels/microsoft-provisioning/errors.js +117 -0
- package/lib/channels/microsoft-provisioning/index.js +55 -0
- package/lib/channels/microsoft-provisioning/install-atk.js +158 -0
- package/lib/channels/microsoft-provisioning/install-teams.js +115 -0
- package/lib/channels/microsoft-provisioning/login-portal-device.js +236 -0
- package/lib/channels/microsoft-provisioning/login-portal-parse.js +71 -0
- package/lib/channels/microsoft-provisioning/login-portal-teams.js +193 -0
- package/lib/channels/microsoft-provisioning/login-portal.js +230 -0
- package/lib/channels/microsoft-provisioning/persist.js +110 -0
- package/lib/channels/microsoft-provisioning/teams-bot-args.js +119 -0
- package/lib/channels/microsoft-provisioning/teams-bot-controller.js +123 -0
- package/lib/channels/microsoft-provisioning/teams-bot-parse.js +134 -0
- package/lib/channels/microsoft-provisioning/teams-bot-persist.js +113 -0
- package/lib/channels/microsoft-provisioning/teams-bot-provision.js +234 -0
- package/lib/channels/microsoft-provisioning/teams-bot-secrets.js +45 -0
- package/lib/channels/microsoft-provisioning/teams-cli.js +82 -0
- package/lib/channels/runtime-tool-catalog.js +5 -2
- package/lib/channels/slack-channel-add-portal.js +130 -0
- package/lib/channels/slack-channel-add-tty-auth.js +95 -0
- package/lib/channels/slack-channel-add.js +258 -0
- package/lib/channels/slack-provisioning/apps.js +181 -0
- package/lib/channels/slack-provisioning/auth-store.js +110 -0
- package/lib/channels/slack-provisioning/auth-ticket.js +185 -0
- package/lib/channels/slack-provisioning/auth.js +115 -0
- package/lib/channels/slack-provisioning/callbacks.js +41 -0
- package/lib/channels/slack-provisioning/cli.js +226 -0
- package/lib/channels/slack-provisioning/constants.js +37 -0
- package/lib/channels/slack-provisioning/credentials.js +169 -0
- package/lib/channels/slack-provisioning/curl-shim.js +132 -0
- package/lib/channels/slack-provisioning/developer-install.js +118 -0
- package/lib/channels/slack-provisioning/env-file.js +120 -0
- package/lib/channels/slack-provisioning/errors.js +47 -0
- package/lib/channels/slack-provisioning/go-ca-bundle.js +137 -0
- package/lib/channels/slack-provisioning/index.js +250 -0
- package/lib/channels/slack-provisioning/install-cli.js +323 -0
- package/lib/channels/slack-provisioning/manifest.js +89 -0
- package/lib/channels/slack-provisioning/oauth-lock.js +33 -0
- package/lib/channels/slack-provisioning/offer-cli.js +113 -0
- package/lib/channels/slack-provisioning/onboard-members.js +122 -0
- package/lib/channels/slack-provisioning/onboard.js +89 -0
- package/lib/channels/slack-provisioning/persist.js +95 -0
- package/lib/channels/slack-provisioning/remaining-urls.js +158 -0
- package/lib/channels/slack-provisioning/with-curl-on-path.js +48 -0
- package/lib/channels/teams-bot-channel-add.js +94 -0
- package/lib/channels/vendor-dataplane-url.js +249 -0
- package/lib/cli/setup-certification.help.js +2 -0
- package/lib/cli/setup-external-system-help.js +2 -0
- package/lib/cli/setup-external-system.js +1 -1
- package/lib/commands/channel-add-help.js +366 -0
- package/lib/commands/channel-add-setup.js +162 -80
- package/lib/commands/channel-configure.js +118 -0
- package/lib/commands/channel.js +10 -1
- package/lib/commands/governance-upload-external.js +6 -3
- package/lib/commands/lifecycle-command-action.js +2 -5
- package/lib/commands/lifecycle-external.js +93 -21
- package/lib/commands/repair-datasource-test-payload.js +4 -2
- package/lib/commands/repair-datasource-webhooks-sync-bridge.js +23 -2
- package/lib/commands/repair-datasource.js +3 -0
- package/lib/commands/repair-external-run.js +59 -25
- package/lib/commands/repair-openapi-rest-runtime.js +127 -0
- package/lib/commands/repair-orchestration-tag-merge.js +339 -0
- package/lib/commands/repair-rbac-catalog-defaults.js +96 -0
- package/lib/commands/repair-rbac-platform-preset.js +26 -15
- package/lib/commands/repair-rbac-reader-roles.js +8 -21
- package/lib/commands/repair-rbac.js +10 -34
- package/lib/commands/repair-system-permissions.js +24 -22
- package/lib/commands/role-assistant-test-output.js +14 -0
- package/lib/commands/role-assistant.js +59 -21
- package/lib/commands/test-e2e-external.js +148 -21
- package/lib/commands/upload-credential-push-log.js +2 -1
- package/lib/commands/upload-publish-context.js +1 -1
- package/lib/commands/upload-role-assistant-shell.js +124 -0
- package/lib/commands/upload-sidecar-followups.js +6 -3
- package/lib/commands/upload.js +18 -3
- package/lib/core/secrets-docker-env.js +4 -1
- package/lib/core/secrets-env-content.js +17 -3
- package/lib/datasource/capability/capability-manifest.js +0 -3
- package/lib/datasource/capability/copy-test-payload.js +3 -0
- package/lib/external-system/deploy-helpers.js +26 -1
- package/lib/external-system/deploy.js +10 -6
- package/lib/external-system/download-helpers.js +29 -2
- package/lib/external-system/download-role-assistant.js +83 -4
- package/lib/external-system/download.js +40 -30
- package/lib/external-system/sync-deploy-manifest.js +3 -2
- package/lib/external-system/upload-identity.js +42 -0
- package/lib/generator/index.js +19 -15
- package/lib/generator/split.js +13 -2
- package/lib/lifecycle/role-assistant-governance.js +146 -0
- package/lib/lifecycle/scenario-upload.js +181 -30
- package/lib/programmatic/builder-api-cors.js +143 -0
- package/lib/programmatic/builder-api-request.js +6 -0
- package/lib/programmatic/builder-api-route-dispatch.js +56 -42
- package/lib/programmatic/builder-api-server.js +8 -1
- package/lib/programmatic/capability-registry.js +54 -0
- package/lib/programmatic/channel-install-help-resolve.js +359 -0
- package/lib/programmatic/channel-install-session.js +218 -0
- package/lib/programmatic/help-content/channel-onboarding.md +2 -0
- package/lib/programmatic/help-content/cip-overview.md +3 -1
- package/lib/programmatic/http-raw-response.js +62 -0
- package/lib/programmatic/openapi-channel-install-bindings.js +179 -0
- package/lib/programmatic/openapi-channel-install-schemas.js +286 -0
- package/lib/programmatic/openapi-component-schemas.js +18 -1
- package/lib/programmatic/openapi-description-enricher.js +3 -1
- package/lib/programmatic/openapi-descriptions.js +74 -3
- package/lib/programmatic/openapi-envelope-examples.js +117 -0
- package/lib/programmatic/openapi-operation-bindings.js +4 -1
- package/lib/programmatic/openapi-schema-descriptions-channel.js +316 -0
- package/lib/programmatic/openapi-spec.js +12 -8
- package/lib/programmatic/platform-rbac-catalog.js +36 -18
- package/lib/programmatic/route-handler-map.js +51 -1
- package/lib/programmatic/route-handlers-context.js +2 -1
- package/lib/programmatic/run-auth-permissions.js +54 -0
- package/lib/programmatic/run-channel-catalog.js +196 -0
- package/lib/programmatic/run-channel-install-artifacts.js +137 -0
- package/lib/programmatic/run-channel-install-copilot.js +353 -0
- package/lib/programmatic/run-channel-install-options.js +232 -0
- package/lib/programmatic/run-channel-install-teams-bot.js +204 -0
- package/lib/programmatic/run-channel-install.js +346 -0
- package/lib/programmatic/run-channel-package.js +87 -7
- package/lib/programmatic/workspace-context.js +2 -0
- package/lib/role-assistant/evidence-sync.js +4 -2
- package/lib/role-assistant/evidence-validation-issues.js +51 -0
- package/lib/role-assistant/knowledge-sync.js +10 -4
- package/lib/role-assistant/settings.js +49 -2
- package/lib/role-assistant/test-cases-evaluate.js +30 -2
- package/lib/role-assistant/test-cases-evidence.js +175 -0
- package/lib/role-assistant/test-cases-search.js +229 -0
- package/lib/role-assistant/test-cases.js +37 -4
- package/lib/role-assistant/test-runner-search.js +149 -0
- package/lib/role-assistant/test-runner-workhub-helpers.js +40 -2
- package/lib/role-assistant/test-runner-workhub-soft.js +9 -3
- package/lib/role-assistant/test-runner-workhub.js +29 -11
- package/lib/role-assistant/test-runner.js +16 -1
- package/lib/schema/external-datasource.schema.json +119 -16
- package/lib/schema/external-system.schema.json +159 -2
- package/lib/schema/mcp-client-profile.schema.json +103 -0
- package/lib/utils/app-path-resolve.js +4 -0
- package/lib/utils/authentication-env-template.js +44 -6
- package/lib/utils/credential-secrets-env.js +6 -1
- package/lib/utils/env-copy.js +19 -7
- package/lib/utils/external-env-template.js +20 -2
- package/lib/utils/paths.js +18 -1
- package/lib/utils/platform-shell-package.js +69 -0
- package/lib/utils/port-resolver.js +50 -1
- package/lib/utils/rewrite-name-flag.js +20 -0
- package/lib/utils/role-assistant-paths.js +13 -10
- package/lib/utils/schema-resolver.js +11 -1
- package/lib/utils/secrets-helpers.js +12 -21
- package/lib/utils/token-manager.js +10 -5
- package/lib/validation/validate.js +40 -0
- package/package.json +1 -1
- package/templates/README.md +1 -1
- package/templates/applications/builder-api/Dockerfile +1 -1
- package/templates/applications/builder-api/env.template +7 -0
- package/templates/applications/dataplane/application.yaml +6 -5
- package/templates/applications/dataplane/env.template +2 -2
- package/templates/applications/dataplane/rbac.yaml +7 -2
- package/templates/applications/keycloak/Dockerfile +30 -4
- package/templates/applications/keycloak/application.yaml +2 -2
- package/templates/applications/keycloak/cimd-lenient-parse/AbstractClientIdMetadataDocumentExecutor.java +896 -0
- package/templates/applications/keycloak/cimd-lenient-parse/SOURCE.md +19 -0
- package/templates/applications/keycloak/env.template +3 -0
- package/templates/applications/miso-controller/application.yaml +1 -2
- package/templates/applications/miso-controller/env.template +2 -0
- package/templates/channels/README.md +5 -5
- package/templates/channels/chatgpt/TROUBLESHOOTING.md +51 -0
- package/templates/channels/chatgpt/channel.json +59 -0
- package/templates/channels/chatgpt/icon.svg +1 -0
- package/templates/channels/chatgpt/install.template.md.hbs +20 -95
- package/templates/channels/chatgpt/integration/README.md +1 -1
- package/templates/channels/chatgpt/integration/application.json +2 -2
- package/templates/channels/chatgpt/integration/chatgpt-system.json +49 -4
- package/templates/channels/chatgpt/integration/env.template +2 -4
- package/templates/channels/chatgpt/integration/rbac.json +13 -0
- package/templates/channels/claude/TROUBLESHOOTING.md +41 -0
- package/templates/channels/claude/channel.json +59 -0
- package/templates/channels/claude/icon.svg +1 -0
- package/templates/channels/claude/install.template.md.hbs +21 -0
- package/templates/channels/claude/integration/README.md +14 -0
- package/templates/channels/claude/integration/application.json +15 -0
- package/templates/channels/claude/integration/claude-system.json +59 -0
- package/templates/channels/claude/integration/env.template +2 -0
- package/templates/channels/claude/integration/rbac.json +13 -0
- package/templates/channels/claude/manifest.template.json.hbs +40 -0
- package/templates/channels/mcp-client/channel.json +72 -0
- package/templates/channels/mcp-client/icon.svg +1 -0
- package/templates/channels/mcp-client/install.template.md.hbs +24 -0
- package/templates/channels/mcp-client/integration/README.md +13 -0
- package/templates/channels/mcp-client/integration/application.json +15 -0
- package/templates/channels/mcp-client/integration/env.template +2 -0
- package/templates/channels/mcp-client/integration/mcp-client-system.json +72 -0
- package/templates/channels/mcp-client/integration/rbac.json +13 -0
- package/templates/channels/mcp-client/manifest.template.json.hbs +41 -0
- package/templates/channels/mcp-client/profiles/cursor.json +26 -0
- package/templates/channels/mcp-client/profiles/github-copilot.json +29 -0
- package/templates/channels/mcp-client/profiles/vscode.json +29 -0
- package/templates/channels/microsoft-copilot/channel.json +73 -0
- package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +8 -4
- package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +65 -5
- package/templates/channels/microsoft-copilot/icon.svg +1 -0
- package/templates/channels/microsoft-copilot/install.template.md.hbs +9 -9
- package/templates/channels/microsoft-copilot/integration/README.md +1 -1
- package/templates/channels/microsoft-copilot/integration/application.json +2 -2
- package/templates/channels/microsoft-copilot/integration/env.template +3 -6
- package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +63 -4
- package/templates/channels/microsoft-copilot/integration/rbac.json +13 -0
- package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +5 -5
- package/templates/channels/slack/channel.json +223 -0
- package/templates/channels/slack/icon.svg +1 -0
- package/templates/channels/slack/install.template.md.hbs +36 -28
- package/templates/channels/slack/integration/README.md +245 -22
- package/templates/channels/slack/integration/application.json +44 -7
- package/templates/channels/slack/integration/env.template +15 -6
- package/templates/channels/slack/integration/openapi/openapi.json +126 -0
- package/templates/channels/slack/integration/rbac.json +84 -0
- package/templates/channels/slack/integration/slack-datasource-commands.json +154 -0
- package/templates/channels/slack/integration/slack-datasource-conversations.json +28 -3
- package/templates/channels/slack/integration/slack-datasource-events.json +154 -0
- package/templates/channels/slack/integration/slack-datasource-interactive.json +154 -0
- package/templates/channels/slack/integration/slack-datasource-messages.json +40 -2
- package/templates/channels/slack/integration/slack-system.json +155 -77
- package/templates/channels/slack/manifest.template.json.hbs +5 -2
- package/templates/channels/teams-bot/channel.json +161 -0
- package/templates/channels/teams-bot/icon.svg +1 -0
- package/templates/channels/teams-bot/install.template.md.hbs +12 -13
- package/templates/channels/teams-bot/integration/README.md +5 -4
- package/templates/channels/teams-bot/integration/application.json +6 -4
- package/templates/channels/teams-bot/integration/env.template +5 -4
- package/templates/channels/teams-bot/integration/openapi/openapi.json +68 -0
- package/templates/channels/teams-bot/integration/rbac.json +21 -0
- package/templates/channels/teams-bot/integration/teams-bot-datasource-activities.json +154 -0
- package/templates/channels/teams-bot/integration/teams-bot-system.json +128 -23
- package/templates/channels/web-embed/channel.json +60 -0
- package/templates/channels/web-embed/install.template.md.hbs +1 -1
- package/templates/channels/web-embed/integration/README.md +2 -2
- package/templates/channels/web-embed/integration/application.json +2 -2
- package/templates/channels/web-embed/integration/env.template +1 -1
- package/templates/channels/web-embed/integration/rbac.json +13 -0
- package/templates/channels/web-embed/integration/web-embed-system.json +50 -4
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assertions for work.skills / work.search role-assistant cases.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Evaluate expected.search and expected.skills against Layer B results
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const { operationKeys } = require('./test-runner-search');
|
|
12
|
+
|
|
13
|
+
function asObject(value) {
|
|
14
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function searchRows(payload) {
|
|
18
|
+
const body = asObject(payload);
|
|
19
|
+
if (Array.isArray(body.data)) {
|
|
20
|
+
return body.data;
|
|
21
|
+
}
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function searchMeta(payload) {
|
|
26
|
+
const body = asObject(payload);
|
|
27
|
+
return asObject(body.meta);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function errorHaystack(searchObs) {
|
|
31
|
+
const payload = asObject(searchObs.payload);
|
|
32
|
+
return [
|
|
33
|
+
searchObs.errorText,
|
|
34
|
+
payload.error,
|
|
35
|
+
payload.detail,
|
|
36
|
+
payload.title,
|
|
37
|
+
payload.message
|
|
38
|
+
]
|
|
39
|
+
.filter((row) => typeof row === 'string')
|
|
40
|
+
.join(' ');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function evaluateStatusCode(expectedCode, actualCode) {
|
|
44
|
+
if (!Number.isFinite(Number(expectedCode))) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const expected = Number(expectedCode);
|
|
48
|
+
const actual = Number(actualCode);
|
|
49
|
+
const passed = actual === expected;
|
|
50
|
+
return {
|
|
51
|
+
id: 'expected.search.statusCode',
|
|
52
|
+
passed,
|
|
53
|
+
message: passed
|
|
54
|
+
? `Search HTTP status matched ${expected}.`
|
|
55
|
+
: `Search HTTP status expected ${expected} but got ${actual}.`
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function evaluateErrorContains(needle, haystack) {
|
|
60
|
+
if (typeof needle !== 'string' || !needle.trim()) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const passed = String(haystack || '').toLowerCase().includes(needle.trim().toLowerCase());
|
|
64
|
+
return {
|
|
65
|
+
id: 'expected.search.errorContains',
|
|
66
|
+
passed,
|
|
67
|
+
message: passed
|
|
68
|
+
? `Search error contained "${needle.trim()}".`
|
|
69
|
+
: `Search error did not contain "${needle.trim()}".`
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function evaluateSearchShape(expectedSearch, searchObs) {
|
|
74
|
+
const assertions = [];
|
|
75
|
+
const meta = searchMeta(searchObs.payload);
|
|
76
|
+
const rows = searchRows(searchObs.payload);
|
|
77
|
+
if (expectedSearch.resultMode) {
|
|
78
|
+
const actual = String(meta.resultMode || '');
|
|
79
|
+
const passed = actual === String(expectedSearch.resultMode);
|
|
80
|
+
assertions.push({
|
|
81
|
+
id: 'expected.search.resultMode',
|
|
82
|
+
passed,
|
|
83
|
+
message: passed
|
|
84
|
+
? `Search resultMode matched "${expectedSearch.resultMode}".`
|
|
85
|
+
: `Search resultMode expected "${expectedSearch.resultMode}" but got "${actual}".`
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
if (expectedSearch.operation) {
|
|
89
|
+
const actual = String(meta.operation || searchObs.arguments?.operation || '');
|
|
90
|
+
const passed = actual === String(expectedSearch.operation);
|
|
91
|
+
assertions.push({
|
|
92
|
+
id: 'expected.search.operation',
|
|
93
|
+
passed,
|
|
94
|
+
message: passed
|
|
95
|
+
? `Search operation matched "${expectedSearch.operation}".`
|
|
96
|
+
: `Search operation expected "${expectedSearch.operation}" but got "${actual}".`
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
if (Number.isFinite(Number(expectedSearch.minRows))) {
|
|
100
|
+
const minRows = Number(expectedSearch.minRows);
|
|
101
|
+
const passed = rows.length >= minRows;
|
|
102
|
+
assertions.push({
|
|
103
|
+
id: 'expected.search.minRows',
|
|
104
|
+
passed,
|
|
105
|
+
message: passed
|
|
106
|
+
? `Search returned at least ${minRows} row(s).`
|
|
107
|
+
: `Search returned ${rows.length} row(s); expected at least ${minRows}.`
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return assertions;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function evaluateSearchExpectations(expectedSearch, observation) {
|
|
114
|
+
if (!expectedSearch || typeof expectedSearch !== 'object' || Array.isArray(expectedSearch)) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
const searchObs = asObject(observation.search);
|
|
118
|
+
const assertions = [];
|
|
119
|
+
const statusAssertion = evaluateStatusCode(expectedSearch.statusCode, searchObs.status);
|
|
120
|
+
if (statusAssertion) {
|
|
121
|
+
assertions.push(statusAssertion);
|
|
122
|
+
}
|
|
123
|
+
const errorAssertion = evaluateErrorContains(
|
|
124
|
+
expectedSearch.errorContains,
|
|
125
|
+
errorHaystack(searchObs)
|
|
126
|
+
);
|
|
127
|
+
if (errorAssertion) {
|
|
128
|
+
assertions.push(errorAssertion);
|
|
129
|
+
}
|
|
130
|
+
assertions.push(...evaluateSearchShape(expectedSearch, searchObs));
|
|
131
|
+
return assertions;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function skillsResourceEntry(skillsPayload, resourceType) {
|
|
135
|
+
const rts = asObject(asObject(skillsPayload).search).resourceTypes;
|
|
136
|
+
return asObject(asObject(rts)[resourceType]);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function evaluateSkillsOperations(expectedOps, spec) {
|
|
140
|
+
if (!Array.isArray(expectedOps) || expectedOps.length === 0) {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
const observed = new Set(operationKeys(spec));
|
|
144
|
+
return expectedOps.map((operation) => {
|
|
145
|
+
const key = String(operation || '').trim();
|
|
146
|
+
const passed = observed.has(key);
|
|
147
|
+
return {
|
|
148
|
+
id: `expected.skills.operations:${key}`,
|
|
149
|
+
passed,
|
|
150
|
+
message: passed
|
|
151
|
+
? `Search grammar published operation "${key}".`
|
|
152
|
+
: `Search grammar did not publish operation "${key}".`
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function publishedFilterNames(spec) {
|
|
158
|
+
const names = new Set(Object.keys(asObject(spec.fields)));
|
|
159
|
+
const operations = Array.isArray(spec.operations) ? spec.operations : [];
|
|
160
|
+
for (const item of operations) {
|
|
161
|
+
const filters = item && typeof item === 'object' ? item.filters : null;
|
|
162
|
+
if (!Array.isArray(filters)) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
for (const name of filters) {
|
|
166
|
+
if (typeof name === 'string' && name.trim()) {
|
|
167
|
+
names.add(name.trim());
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return names;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function evaluateSkillsFilterFields(expectedFields, spec) {
|
|
175
|
+
if (!Array.isArray(expectedFields) || expectedFields.length === 0) {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
const published = publishedFilterNames(spec);
|
|
179
|
+
return expectedFields.map((name) => {
|
|
180
|
+
const key = String(name || '').trim();
|
|
181
|
+
const passed = Boolean(key && published.has(key));
|
|
182
|
+
return {
|
|
183
|
+
id: `expected.skills.filterFields:${key}`,
|
|
184
|
+
passed,
|
|
185
|
+
message: passed
|
|
186
|
+
? `Search grammar published filter field "${key}".`
|
|
187
|
+
: `Search grammar did not publish filter field "${key}".`
|
|
188
|
+
};
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function evaluateSkillsExpectations(expectedSkills, observation) {
|
|
193
|
+
if (!expectedSkills || typeof expectedSkills !== 'object' || Array.isArray(expectedSkills)) {
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
196
|
+
const skillsObs = asObject(observation.skills);
|
|
197
|
+
const payload = skillsObs.payload;
|
|
198
|
+
const resourceType = String(expectedSkills.resourceType || '').trim();
|
|
199
|
+
const spec = resourceType ? skillsResourceEntry(payload, resourceType) : {};
|
|
200
|
+
const assertions = [];
|
|
201
|
+
if (resourceType) {
|
|
202
|
+
const passed = Object.keys(spec).length > 0;
|
|
203
|
+
assertions.push({
|
|
204
|
+
id: 'expected.skills.resourceType',
|
|
205
|
+
passed,
|
|
206
|
+
message: passed
|
|
207
|
+
? `work.skills published resourceType "${resourceType}".`
|
|
208
|
+
: `work.skills did not publish resourceType "${resourceType}".`
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
if (expectedSkills.operationRequired === true) {
|
|
212
|
+
const passed = spec.operationRequired === true;
|
|
213
|
+
assertions.push({
|
|
214
|
+
id: 'expected.skills.operationRequired',
|
|
215
|
+
passed,
|
|
216
|
+
message: passed
|
|
217
|
+
? 'work.skills marked operationRequired for this resourceType.'
|
|
218
|
+
: 'work.skills did not mark operationRequired for this resourceType.'
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
assertions.push(...evaluateSkillsOperations(expectedSkills.operations, spec));
|
|
222
|
+
assertions.push(...evaluateSkillsFilterFields(expectedSkills.filterFields, spec));
|
|
223
|
+
return assertions;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
module.exports = {
|
|
227
|
+
evaluateSearchExpectations,
|
|
228
|
+
evaluateSkillsExpectations
|
|
229
|
+
};
|
|
@@ -98,21 +98,54 @@ function normalizeCaseFromFile(filePath, parsed) {
|
|
|
98
98
|
}
|
|
99
99
|
return {
|
|
100
100
|
caseId,
|
|
101
|
+
executionPath: parsed.executionPath,
|
|
102
|
+
roleRef: parsed.roleRef,
|
|
103
|
+
businessRoleRef: parsed.businessRoleRef,
|
|
101
104
|
input: parsed.input || {},
|
|
102
105
|
answers: Array.isArray(parsed.answers) ? parsed.answers : [],
|
|
103
106
|
approvals: Array.isArray(parsed.approvals) ? parsed.approvals : [],
|
|
104
107
|
expected,
|
|
108
|
+
preflight: parsed.preflight && typeof parsed.preflight === 'object' ? parsed.preflight : {},
|
|
105
109
|
sourceFile: filePath
|
|
106
110
|
};
|
|
107
111
|
}
|
|
108
112
|
|
|
109
|
-
function
|
|
110
|
-
if (!safeExistsSync(
|
|
113
|
+
function listYamlFileNames(dirPath) {
|
|
114
|
+
if (!safeExistsSync(dirPath)) {
|
|
111
115
|
return [];
|
|
112
116
|
}
|
|
113
|
-
return safeReaddirSync(
|
|
117
|
+
return safeReaddirSync(dirPath)
|
|
114
118
|
.filter((entry) => /\.(ya?ml)$/i.test(entry))
|
|
115
|
-
.map((entry) => path.join(
|
|
119
|
+
.map((entry) => path.join(dirPath, entry));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function safeIsDirectory(dirPath) {
|
|
123
|
+
try {
|
|
124
|
+
if (nodeFs.existsSync(dirPath)) {
|
|
125
|
+
return nodeFs.statSync(dirPath).isDirectory();
|
|
126
|
+
}
|
|
127
|
+
return fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory();
|
|
128
|
+
} catch {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function listTestCaseFiles(testsDir) {
|
|
134
|
+
const files = listYamlFileNames(testsDir);
|
|
135
|
+
if (!safeExistsSync(testsDir)) {
|
|
136
|
+
return files;
|
|
137
|
+
}
|
|
138
|
+
for (const entry of safeReaddirSync(testsDir)) {
|
|
139
|
+
if (String(entry).startsWith('.')) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const child = path.join(testsDir, entry);
|
|
143
|
+
if (!safeIsDirectory(child)) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
files.push(...listYamlFileNames(child));
|
|
147
|
+
}
|
|
148
|
+
return files;
|
|
116
149
|
}
|
|
117
150
|
|
|
118
151
|
function looksLikePathSelector(selector) {
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Role-assistant typed search cases (work.skills / work.search).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Execute viewpoint and table search cases without Work Hub
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const { getWorkSkills, searchWork } = require('../api/work-search.api');
|
|
12
|
+
const { roleAssistantSystemKey } = require('../utils/role-assistant-paths');
|
|
13
|
+
const {
|
|
14
|
+
resolveInput,
|
|
15
|
+
wantsSearchPath,
|
|
16
|
+
wantsSkillsOnlyPath
|
|
17
|
+
} = require('./test-runner-workhub-helpers');
|
|
18
|
+
|
|
19
|
+
function operationKeys(spec) {
|
|
20
|
+
const raw = Array.isArray(spec && spec.operations) ? spec.operations : [];
|
|
21
|
+
const keys = [];
|
|
22
|
+
for (const item of raw) {
|
|
23
|
+
if (typeof item === 'string' && item.trim()) {
|
|
24
|
+
keys.push(item.trim());
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const key = item && typeof item === 'object' ? String(item.key || '').trim() : '';
|
|
28
|
+
if (key) {
|
|
29
|
+
keys.push(key);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return keys;
|
|
33
|
+
}
|
|
34
|
+
|
|
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
|
+
function pickResourceType(skillsPayload, operation) {
|
|
41
|
+
const rts = resourceTypesMap(skillsPayload);
|
|
42
|
+
const keys = Object.keys(rts);
|
|
43
|
+
const wanted = String(operation || '').trim();
|
|
44
|
+
if (wanted) {
|
|
45
|
+
const hits = keys.filter((resourceType) => operationKeys(rts[resourceType]).includes(wanted));
|
|
46
|
+
if (hits.length === 1) {
|
|
47
|
+
return hits[0];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return keys.length === 1 ? keys[0] : '';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function resolveRoleRef(roleKey, testCase) {
|
|
54
|
+
const input = resolveInput(testCase);
|
|
55
|
+
const candidates = [
|
|
56
|
+
input.roleRef,
|
|
57
|
+
input.businessRoleRef,
|
|
58
|
+
testCase && testCase.roleRef,
|
|
59
|
+
testCase && testCase.businessRoleRef,
|
|
60
|
+
roleAssistantSystemKey(roleKey)
|
|
61
|
+
];
|
|
62
|
+
for (const row of candidates) {
|
|
63
|
+
if (typeof row === 'string' && row.trim()) {
|
|
64
|
+
return row.trim();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return roleAssistantSystemKey(roleKey);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function assignOptionalNumber(args, input, key) {
|
|
71
|
+
if (Number.isFinite(input[key])) {
|
|
72
|
+
args[key] = input[key];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function buildSearchArguments(roleRef, input, skillsPayload) {
|
|
77
|
+
const args = { roleRef };
|
|
78
|
+
const resourceType =
|
|
79
|
+
String(input.resourceType || '').trim() || pickResourceType(skillsPayload, input.operation);
|
|
80
|
+
if (resourceType) {
|
|
81
|
+
args.resourceType = resourceType;
|
|
82
|
+
}
|
|
83
|
+
if (typeof input.operation === 'string' && input.operation.trim()) {
|
|
84
|
+
args.operation = input.operation.trim();
|
|
85
|
+
}
|
|
86
|
+
if (input.filters && typeof input.filters === 'object' && !Array.isArray(input.filters)) {
|
|
87
|
+
args.filters = input.filters;
|
|
88
|
+
}
|
|
89
|
+
assignOptionalNumber(args, input, 'page');
|
|
90
|
+
assignOptionalNumber(args, input, 'pageSize');
|
|
91
|
+
if (Array.isArray(input.fields) && input.fields.length) {
|
|
92
|
+
args.fields = input.fields;
|
|
93
|
+
}
|
|
94
|
+
return args;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function statusToken(statusCode) {
|
|
98
|
+
return Number(statusCode) === 200 ? 'completed' : 'stopped';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function searchLifecycleResult(roleKey, testCase, roleRef, skillsRes, searchRes, args) {
|
|
102
|
+
return {
|
|
103
|
+
kind: searchRes ? 'search' : 'skills',
|
|
104
|
+
roleKey,
|
|
105
|
+
caseId: testCase.caseId,
|
|
106
|
+
roleRef,
|
|
107
|
+
processExecutionId: null,
|
|
108
|
+
skills: skillsRes,
|
|
109
|
+
search: searchRes ? { ...searchRes, arguments: args } : null,
|
|
110
|
+
finalStatus: {
|
|
111
|
+
status: statusToken((searchRes || skillsRes).status)
|
|
112
|
+
},
|
|
113
|
+
timeline: [],
|
|
114
|
+
interactions: [],
|
|
115
|
+
workResult: null,
|
|
116
|
+
workResultError: null
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function runSearchCaseLifecycle(roleKey, testCase, runtimeContext) {
|
|
121
|
+
const dataplaneUrl = runtimeContext && runtimeContext.dataplaneUrl;
|
|
122
|
+
const authConfig = runtimeContext && runtimeContext.authConfig;
|
|
123
|
+
if (!dataplaneUrl || !authConfig) {
|
|
124
|
+
throw new Error('Role assistant search case requires dataplane auth context.');
|
|
125
|
+
}
|
|
126
|
+
const roleRef = resolveRoleRef(roleKey, testCase);
|
|
127
|
+
const skillsRes = await getWorkSkills(dataplaneUrl, authConfig, roleRef);
|
|
128
|
+
if (wantsSkillsOnlyPath(testCase)) {
|
|
129
|
+
return searchLifecycleResult(roleKey, testCase, roleRef, skillsRes, null, null);
|
|
130
|
+
}
|
|
131
|
+
const args = buildSearchArguments(roleRef, resolveInput(testCase), skillsRes.payload);
|
|
132
|
+
if (!args.resourceType) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
'Search case requires input.resourceType (or exactly one matching type from work.skills).'
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
const searchRes = await searchWork(dataplaneUrl, authConfig, args);
|
|
138
|
+
return searchLifecycleResult(roleKey, testCase, roleRef, skillsRes, searchRes, args);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = {
|
|
142
|
+
wantsSearchPath,
|
|
143
|
+
wantsSkillsOnlyPath,
|
|
144
|
+
operationKeys,
|
|
145
|
+
pickResourceType,
|
|
146
|
+
resolveRoleRef,
|
|
147
|
+
buildSearchArguments,
|
|
148
|
+
runSearchCaseLifecycle
|
|
149
|
+
};
|
|
@@ -34,9 +34,45 @@ function resolveObjective(testCase) {
|
|
|
34
34
|
return '';
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
function
|
|
37
|
+
function firstNonEmptyString(values) {
|
|
38
|
+
for (const value of values) {
|
|
39
|
+
if (typeof value === 'string' && value.trim()) {
|
|
40
|
+
return value.trim();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return '';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function searchPathToken(testCase) {
|
|
38
47
|
const input = resolveInput(testCase);
|
|
39
|
-
|
|
48
|
+
return firstNonEmptyString([
|
|
49
|
+
input.executionPath,
|
|
50
|
+
input.execution_path,
|
|
51
|
+
testCase && testCase.executionPath,
|
|
52
|
+
input.mode
|
|
53
|
+
]).toLowerCase();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function wantsSearchPath(testCase) {
|
|
57
|
+
const pathToken = searchPathToken(testCase);
|
|
58
|
+
return (
|
|
59
|
+
pathToken === 'search' ||
|
|
60
|
+
pathToken === 'work.search' ||
|
|
61
|
+
pathToken === 'skills' ||
|
|
62
|
+
pathToken === 'work.skills'
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function wantsSkillsOnlyPath(testCase) {
|
|
67
|
+
const pathToken = searchPathToken(testCase);
|
|
68
|
+
return pathToken === 'skills' || pathToken === 'work.skills';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function wantsWorkHubPath(testCase) {
|
|
72
|
+
if (wantsSearchPath(testCase)) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
const pathToken = searchPathToken(testCase);
|
|
40
76
|
if (pathToken === 'workhub' || pathToken === 'work-hub' || pathToken === 'plan') {
|
|
41
77
|
return true;
|
|
42
78
|
}
|
|
@@ -91,6 +127,8 @@ module.exports = {
|
|
|
91
127
|
delayMs,
|
|
92
128
|
resolveInput,
|
|
93
129
|
resolveObjective,
|
|
130
|
+
wantsSearchPath,
|
|
131
|
+
wantsSkillsOnlyPath,
|
|
94
132
|
wantsWorkHubPath,
|
|
95
133
|
unwrapId,
|
|
96
134
|
extractPeFromTask,
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Soft Work Hub outcomes for insufficient-info / clarify-first Intent.
|
|
3
|
-
* Soft cases may stop before accept when Intent is not Act-ready
|
|
4
|
-
*
|
|
3
|
+
* Soft cases may stop before accept when Intent is not Act-ready or cannot be
|
|
4
|
+
* classified (unavailable without a next question) — that is a governed
|
|
5
|
+
* fail-closed outcome, not a harness failure.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
'use strict';
|
|
8
9
|
|
|
9
10
|
function isIntentNotActReadyError(error) {
|
|
10
11
|
const msg = String((error && error.message) || error || '');
|
|
11
|
-
|
|
12
|
+
const type = String((error && (error.errorType || error.type)) || '');
|
|
13
|
+
return (
|
|
14
|
+
/not Act-ready|intentNotActReady/i.test(msg) ||
|
|
15
|
+
/Intent is not available|runtimeIntentUnavailable/i.test(msg) ||
|
|
16
|
+
/runtimeIntentUnavailable/i.test(type)
|
|
17
|
+
);
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
function attachPlanIdToError(error, planId) {
|
|
@@ -302,21 +302,36 @@ function assertTerminalWorkHubTask(last, taskId, timeoutMs, options = {}) {
|
|
|
302
302
|
throw new Error(`Work Hub task ${taskId} produced no status rows.`);
|
|
303
303
|
}
|
|
304
304
|
const finalStatusToken = String(last?.status || last?.taskStatus || '').trim().toLowerCase();
|
|
305
|
-
if (!TERMINAL_TASK.has(finalStatusToken)) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
305
|
+
if (!TERMINAL_TASK.has(finalStatusToken) && options.recordTimeout === true) {
|
|
306
|
+
last = {
|
|
307
|
+
...last,
|
|
308
|
+
pollTimedOut: true,
|
|
309
|
+
lastStatus: finalStatusToken || 'unknown',
|
|
310
|
+
pollTimeoutMs: timeoutMs
|
|
311
|
+
};
|
|
312
312
|
}
|
|
313
313
|
return last;
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
+
function shouldStopPendingApprovalPoll(status, ctx) {
|
|
317
|
+
if (status !== 'pendingapproval' && status !== 'pending_approval') {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
return ctx.approveStopped === true && (Number(ctx.pendingApprovalStreak) || 0) >= 3;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function applyWorkHubPollStatus(ctx, last, status) {
|
|
324
|
+
ctx.status = status;
|
|
325
|
+
ctx.taskRow = last;
|
|
326
|
+
ctx.pendingApprovalStreak =
|
|
327
|
+
status === 'pendingapproval' || status === 'pending_approval'
|
|
328
|
+
? (Number(ctx.pendingApprovalStreak) || 0) + 1
|
|
329
|
+
: 0;
|
|
330
|
+
}
|
|
331
|
+
|
|
316
332
|
async function pollWorkHubTask(dataplaneUrl, authConfig, taskId, testCase, options) {
|
|
317
333
|
const timeoutMs = Number(options.pollTimeoutMs) || 180000;
|
|
318
334
|
const intervalMs = Number(options.pollIntervalMs) || 3000;
|
|
319
|
-
const soft = testCase?.expected?.soft === true;
|
|
320
335
|
const started = Date.now();
|
|
321
336
|
let last = null;
|
|
322
337
|
const timeline = [];
|
|
@@ -324,6 +339,7 @@ async function pollWorkHubTask(dataplaneUrl, authConfig, taskId, testCase, optio
|
|
|
324
339
|
status: '',
|
|
325
340
|
approveAttempts: 0,
|
|
326
341
|
approveStopped: false,
|
|
342
|
+
pendingApprovalStreak: 0,
|
|
327
343
|
interactions: [],
|
|
328
344
|
taskRow: null
|
|
329
345
|
};
|
|
@@ -335,15 +351,17 @@ async function pollWorkHubTask(dataplaneUrl, authConfig, taskId, testCase, optio
|
|
|
335
351
|
if (TERMINAL_TASK.has(status)) {
|
|
336
352
|
break;
|
|
337
353
|
}
|
|
338
|
-
ctx
|
|
339
|
-
ctx.taskRow = last;
|
|
354
|
+
applyWorkHubPollStatus(ctx, last, status);
|
|
340
355
|
await maybeApproveTask(dataplaneUrl, authConfig, taskId, ctx);
|
|
341
356
|
await maybeRespondWaiting(dataplaneUrl, authConfig, last, testCase, ctx.interactions);
|
|
357
|
+
if (shouldStopPendingApprovalPoll(status, ctx)) {
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
342
360
|
await delayMs(intervalMs);
|
|
343
361
|
}
|
|
344
362
|
|
|
345
363
|
return {
|
|
346
|
-
finalTask: assertTerminalWorkHubTask(last, taskId, timeoutMs, {
|
|
364
|
+
finalTask: assertTerminalWorkHubTask(last, taskId, timeoutMs, { recordTimeout: true }),
|
|
347
365
|
timeline,
|
|
348
366
|
interactions: ctx.interactions
|
|
349
367
|
};
|
|
@@ -18,6 +18,7 @@ const {
|
|
|
18
18
|
getWorkExecutionResult
|
|
19
19
|
} = require('../api/work.api');
|
|
20
20
|
const { wantsWorkHubPath, runWorkHubCaseLifecycle } = require('./test-runner-workhub');
|
|
21
|
+
const { wantsSearchPath, runSearchCaseLifecycle } = require('./test-runner-search');
|
|
21
22
|
const {
|
|
22
23
|
buildStartIntentPayload,
|
|
23
24
|
resolveInputObject,
|
|
@@ -58,6 +59,9 @@ function applyOptionalStringField(payload, value, key) {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
function isExecutableRoleAssistantCase(testCase) {
|
|
62
|
+
if (wantsSearchPath(testCase)) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
61
65
|
const input = resolveInputObject(testCase);
|
|
62
66
|
if (typeof input.content !== 'undefined') {
|
|
63
67
|
return true;
|
|
@@ -370,6 +374,9 @@ async function runRoleAssistantCaseLifecycle(roleKey, testCase, runtimeContext,
|
|
|
370
374
|
throw new Error('Role assistant case runtime requires dataplane auth context.');
|
|
371
375
|
}
|
|
372
376
|
const runnerOptions = buildRunnerOptions(options);
|
|
377
|
+
if (wantsSearchPath(testCase)) {
|
|
378
|
+
return runSearchCaseLifecycle(roleKey, testCase, runtimeContext, runnerOptions);
|
|
379
|
+
}
|
|
373
380
|
if (wantsWorkHubPath(testCase)) {
|
|
374
381
|
return runWorkHubCaseLifecycle(roleKey, testCase, runtimeContext, runnerOptions);
|
|
375
382
|
}
|
|
@@ -418,11 +425,19 @@ function mergeCaseExecutionIntoLifecycleResult(lifecycleResult, executionResult)
|
|
|
418
425
|
task: executionResult?.workResult?.task || null,
|
|
419
426
|
steps: Array.isArray(executionResult?.workResult?.steps) ? executionResult.workResult.steps : [],
|
|
420
427
|
workTimeline: executionResult?.timeline || [],
|
|
421
|
-
workInteractions: executionResult?.interactions || []
|
|
428
|
+
workInteractions: executionResult?.interactions || [],
|
|
429
|
+
search: executionResult?.search || null,
|
|
430
|
+
skills: executionResult?.skills || null,
|
|
431
|
+
roleRef: executionResult?.roleRef || null
|
|
422
432
|
};
|
|
423
433
|
}
|
|
424
434
|
|
|
425
435
|
function lifecycleVerdictFromCaseExecution(executionResult, fallbackVerdict) {
|
|
436
|
+
if (executionResult?.kind === 'search' || executionResult?.kind === 'skills') {
|
|
437
|
+
return Number.isFinite(Number(executionResult.search?.status || executionResult.skills?.status))
|
|
438
|
+
? VERDICT.VERIFIED
|
|
439
|
+
: VERDICT.FAILED;
|
|
440
|
+
}
|
|
426
441
|
const statusToken = normalizeToken(executionResult?.finalStatus?.status);
|
|
427
442
|
if (statusToken === 'completed') {
|
|
428
443
|
return VERDICT.VERIFIED;
|