@aifabrix/builder 2.51.0 → 2.51.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/jest.isolated-projects.js +77 -1
- package/jest.projects.js +5 -426
- package/lib/api/assistants.api.js +67 -0
- package/lib/api/datasources-extended.api.js +6 -6
- package/lib/api/evidence-authoring-helpers.js +267 -0
- package/lib/api/evidence-authoring.api.js +337 -0
- package/lib/api/evidence.api.js +57 -1
- package/lib/api/index.js +24 -7
- package/lib/api/pipeline.api.js +43 -3
- package/lib/api/records-bulk.api.js +28 -3
- package/lib/api/types/evidence.types.js +156 -8
- package/lib/api/work.api.js +283 -0
- package/lib/app/run-deploy-job.js +108 -0
- package/lib/app/run-helpers.js +10 -3
- package/lib/app/run.js +8 -0
- package/lib/channels/add-channel.js +54 -23
- package/lib/channels/approval-guides/chatgpt.js +8 -7
- package/lib/channels/approval-guides/microsoft-copilot.js +7 -8
- package/lib/channels/channel-artifact-generator.js +14 -2
- package/lib/channels/channel-credential.js +3 -1
- package/lib/channels/channel-probe-evidence.js +79 -0
- package/lib/channels/channel-probe.js +265 -13
- package/lib/channels/channel-runtime-resolver.js +120 -29
- package/lib/channels/channel-status-display.js +44 -0
- package/lib/channels/channel-targets.js +7 -0
- package/lib/channels/chatgpt-runtime-contract.js +29 -0
- package/lib/channels/microsoft-copilot-package.js +280 -0
- package/lib/channels/runtime-tool-catalog.js +165 -0
- package/lib/cli/index.js +2 -0
- package/lib/cli/setup-app.js +4 -0
- package/lib/cli/setup-external-system-help.js +8 -2
- package/lib/cli/setup-external-system.js +12 -0
- package/lib/commands/app-test.js +2 -14
- package/lib/commands/datasource-load-export-cli.js +6 -1
- package/lib/commands/deploy-gate-failure-details.js +19 -17
- package/lib/commands/dev-down.js +2 -6
- package/lib/commands/evidence-kind.js +65 -15
- package/lib/commands/governance-generate-external.js +56 -21
- package/lib/commands/lifecycle-external.js +10 -4
- package/lib/commands/repair-datasource-apply.js +10 -1
- package/lib/commands/repair-datasource-run.js +4 -2
- package/lib/commands/repair-datasource-test-payload.js +80 -19
- package/lib/commands/repair-datasource-webhooks-sync-bridge.js +42 -3
- package/lib/commands/repair-internal.js +9 -1
- package/lib/commands/repair-openapi-sync.js +34 -17
- package/lib/commands/role-assistant.js +313 -0
- package/lib/commands/upload-data-sync-finalize.js +5 -4
- package/lib/commands/upload-sidecar-followups.js +144 -47
- package/lib/commands/upload.js +43 -6
- package/lib/commands/verify-governance-combined-external.js +73 -9
- package/lib/commands/verify-governance-db.js +80 -25
- package/lib/commands/verify-governance-external.js +29 -5
- package/lib/commands/wizard-core-helpers.js +7 -3
- package/lib/commands/wizard-core.js +78 -9
- package/lib/datasource/bulk-loader-run-batches.js +4 -2
- package/lib/datasource/bulk-loader-service.js +5 -1
- package/lib/datasource/datasource-validate-summary.js +2 -2
- package/lib/datasource/validate.js +13 -6
- package/lib/external-system/deploy-helpers.js +35 -1
- package/lib/external-system/deploy.js +23 -10
- package/lib/external-system/download-helpers.js +111 -2
- package/lib/external-system/download-role-assistant.js +55 -0
- package/lib/external-system/download.js +45 -26
- package/lib/external-system/test-helpers.js +1 -1
- package/lib/external-system/test.js +39 -3
- package/lib/generator/builders.js +3 -0
- package/lib/generator/wizard.js +51 -4
- package/lib/lifecycle/access-control-abac.js +121 -0
- package/lib/lifecycle/lifecycle-governance-options.js +4 -1
- package/lib/lifecycle/product-model.js +3 -2
- package/lib/lifecycle/report-display.js +14 -2
- package/lib/programmatic/capability-registry.js +20 -1
- package/lib/programmatic/command-options.js +1 -0
- package/lib/programmatic/index.js +4 -0
- package/lib/programmatic/manifest-patch-section.js +48 -9
- package/lib/programmatic/manifest-section-registry-data.js +3 -2
- package/lib/programmatic/openapi-apply-result-schemas.js +44 -0
- package/lib/programmatic/openapi-component-schemas.js +16 -1
- package/lib/programmatic/openapi-description-enricher.js +3 -1
- package/lib/programmatic/openapi-descriptions.js +55 -5
- package/lib/programmatic/openapi-evidence-schemas.js +161 -0
- package/lib/programmatic/openapi-manifest-schemas.js +6 -2
- package/lib/programmatic/openapi-operation-bindings.js +99 -0
- package/lib/programmatic/openapi-request-schemas.js +36 -1
- package/lib/programmatic/openapi-result-schemas.js +1 -1
- package/lib/programmatic/openapi-schema-descriptions-certify.js +5 -1
- package/lib/programmatic/openapi-schema-descriptions-core.js +1 -1
- package/lib/programmatic/openapi-schema-descriptions-evidence.js +156 -0
- package/lib/programmatic/openapi-schema-descriptions-manifest.js +4 -3
- package/lib/programmatic/openapi-schema-descriptions-operations.js +70 -0
- package/lib/programmatic/openapi-spec.js +47 -28
- package/lib/programmatic/orchestration-openapi-meta.js +5 -0
- package/lib/programmatic/request-timeout.js +3 -0
- package/lib/programmatic/route-handler-map.js +89 -2
- package/lib/programmatic/run-certify.js +15 -2
- package/lib/programmatic/run-deploy.js +74 -0
- package/lib/programmatic/run-evidence-authoring.js +80 -0
- package/lib/programmatic/run-protection.js +124 -0
- package/lib/programmatic/run-template-workspace.js +28 -1
- package/lib/programmatic/workspace-context.js +6 -0
- package/lib/protection/auth-context.js +48 -6
- package/lib/protection/paths.js +22 -0
- package/lib/protection/run-protection-create-helpers.js +22 -1
- package/lib/protection/run-protection-create.js +1 -1
- package/lib/protection/sync-after-upload.js +1 -1
- package/lib/role-assistant/evidence-sync.js +450 -0
- package/lib/role-assistant/github-workflow.js +96 -0
- package/lib/role-assistant/knowledge-sync.js +465 -0
- package/lib/role-assistant/settings.js +126 -0
- package/lib/role-assistant/test-cases-evaluate.js +170 -0
- package/lib/role-assistant/test-cases-honesty.js +184 -0
- package/lib/role-assistant/test-cases.js +364 -0
- package/lib/role-assistant/test-runner-workhub-answers.js +203 -0
- package/lib/role-assistant/test-runner-workhub.js +385 -0
- package/lib/role-assistant/test-runner.js +500 -0
- package/lib/schema/application-schema.json +16 -2
- package/lib/schema/deployment-rules.yaml +1 -0
- package/lib/schema/external-datasource.schema.json +38 -4
- package/lib/schema/external-system.schema.json +4 -4
- package/lib/schema/infra.parameter.yaml +20 -0
- package/lib/schema/role-assistant-settings.schema.json +108 -0
- package/lib/utils/api.js +52 -27
- package/lib/utils/app-path-resolve.js +72 -24
- package/lib/utils/authentication-shape.js +3 -1
- package/lib/utils/deployment-validation.js +28 -3
- package/lib/utils/docker-run-ephemeral.js +55 -0
- package/lib/utils/enterprise-knowledge-paths.js +29 -12
- package/lib/utils/env-copy-keycloak.js +196 -0
- package/lib/utils/env-copy.js +34 -1
- package/lib/utils/external-system-readiness-compact-display.js +3 -0
- package/lib/utils/help-builder.js +1 -0
- package/lib/utils/paths.js +67 -23
- package/lib/utils/pipeline-upload-auth-retry.js +15 -3
- package/lib/utils/role-assistant-paths.js +98 -0
- package/lib/utils/schema-loader.js +41 -0
- package/lib/utils/schema-resolver.js +36 -4
- package/lib/utils/token-manager.js +38 -29
- package/lib/utils/url-declarative-resolve-load-doc.js +38 -1
- package/lib/validation/external-manifest-validator.js +31 -8
- package/lib/validation/validate-external-file.js +8 -1
- package/package.json +2 -1
- package/scripts/export-builder-api-evidence.js +1 -1
- package/templates/applications/dataplane/application.yaml +23 -15
- package/templates/applications/dataplane/env.template +1 -1
- package/templates/applications/dataplane/rbac.yaml +14 -0
- package/templates/applications/miso-controller/application.yaml +1 -1
- package/templates/applications/miso-controller/env.template +3 -1
- package/templates/channels/chatgpt/install.template.md.hbs +42 -24
- package/templates/channels/chatgpt/integration/README.md +10 -7
- package/templates/channels/chatgpt/integration/application.json +2 -2
- package/templates/channels/chatgpt/integration/chatgpt-system.json +3 -3
- package/templates/channels/chatgpt/integration/env.template +1 -1
- package/templates/channels/chatgpt/manifest.template.json.hbs +36 -5
- package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +28 -0
- package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +22 -0
- package/templates/channels/microsoft-copilot/install.template.md.hbs +32 -22
- package/templates/channels/microsoft-copilot/integration/README.md +9 -7
- package/templates/channels/microsoft-copilot/integration/application.json +2 -2
- package/templates/channels/microsoft-copilot/integration/env.template +1 -1
- package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +7 -6
- package/templates/channels/microsoft-copilot/manifest.template.json.hbs +34 -6
- /package/.cursor/plans/{157.1-security-quality-violations-by-type.csv → Done/157.1-security-quality-violations-by-type.csv} +0 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Microsoft Copilot package artifact generation (plan 164).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Build source files, package ZIP, and lifecycle summary for microsoft-copilot channel
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const crypto = require('crypto');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const AdmZip = require('adm-zip');
|
|
15
|
+
|
|
16
|
+
const PLACEHOLDER_ICON_BASE64 =
|
|
17
|
+
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO2s9WQAAAAASUVORK5CYII=';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} outputPath
|
|
21
|
+
* @param {Object} runtimeValues
|
|
22
|
+
* @returns {Object}
|
|
23
|
+
*/
|
|
24
|
+
function buildCopilotOutputPaths(outputPath, runtimeValues) {
|
|
25
|
+
const rootDir = path.dirname(outputPath);
|
|
26
|
+
const sourceDir = path.join(rootDir, 'source');
|
|
27
|
+
const buildDir = path.join(rootDir, 'build');
|
|
28
|
+
const installDir = path.join(rootDir, 'install');
|
|
29
|
+
const environment = String(runtimeValues.environment || 'dev').trim() || 'dev';
|
|
30
|
+
const zipFileName = `appPackage.${environment}.zip`;
|
|
31
|
+
return {
|
|
32
|
+
rootDir,
|
|
33
|
+
sourceDir,
|
|
34
|
+
buildDir,
|
|
35
|
+
installDir,
|
|
36
|
+
summaryPath: outputPath,
|
|
37
|
+
zipFileName,
|
|
38
|
+
zipPath: path.join(buildDir, zipFileName),
|
|
39
|
+
validationPath: path.join(installDir, 'validation.json'),
|
|
40
|
+
installPath: path.join(installDir, 'README.md')
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @returns {{ color: Buffer, outline: Buffer }}
|
|
46
|
+
*/
|
|
47
|
+
function getPlaceholderIcons() {
|
|
48
|
+
const icon = Buffer.from(PLACEHOLDER_ICON_BASE64, 'base64');
|
|
49
|
+
return { color: icon, outline: icon };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {Object} runtimeValues
|
|
54
|
+
* @param {(templatePath: string, context: Object) => string} renderTemplateFile
|
|
55
|
+
* @param {string} templatesDir
|
|
56
|
+
* @returns {{ sourceFiles: Record<string, string>, installText: string }}
|
|
57
|
+
*/
|
|
58
|
+
function renderCopilotTemplates(runtimeValues, renderTemplateFile, templatesDir) {
|
|
59
|
+
const sourceFiles = {
|
|
60
|
+
'manifest.json': renderTemplateFile(path.join(templatesDir, 'manifest.template.json.hbs'), runtimeValues),
|
|
61
|
+
'declarativeAgent.json': renderTemplateFile(
|
|
62
|
+
path.join(templatesDir, 'declarative-agent.template.json.hbs'),
|
|
63
|
+
runtimeValues
|
|
64
|
+
),
|
|
65
|
+
'ai-plugin.json': renderTemplateFile(
|
|
66
|
+
path.join(templatesDir, 'ai-plugin.template.json.hbs'),
|
|
67
|
+
runtimeValues
|
|
68
|
+
)
|
|
69
|
+
};
|
|
70
|
+
const installText = renderTemplateFile(path.join(templatesDir, 'install.template.md.hbs'), runtimeValues);
|
|
71
|
+
return { sourceFiles, installText };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @param {Record<string, string>} sourceFiles
|
|
76
|
+
* @param {(content: string) => void} assertNoSecretsInArtifact
|
|
77
|
+
*/
|
|
78
|
+
function assertNoSecretsInSourceFiles(sourceFiles, assertNoSecretsInArtifact) {
|
|
79
|
+
for (const content of Object.values(sourceFiles)) {
|
|
80
|
+
assertNoSecretsInArtifact(content);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {Record<string, string>} sourceFiles
|
|
86
|
+
* @param {{ color: Buffer, outline: Buffer }} icons
|
|
87
|
+
* @returns {Buffer}
|
|
88
|
+
*/
|
|
89
|
+
function buildCopilotZipBuffer(sourceFiles, icons) {
|
|
90
|
+
const zip = new AdmZip();
|
|
91
|
+
zip.addFile('manifest.json', Buffer.from(sourceFiles['manifest.json'], 'utf8'));
|
|
92
|
+
zip.addFile('declarativeAgent.json', Buffer.from(sourceFiles['declarativeAgent.json'], 'utf8'));
|
|
93
|
+
zip.addFile('ai-plugin.json', Buffer.from(sourceFiles['ai-plugin.json'], 'utf8'));
|
|
94
|
+
zip.addFile('color.png', icons.color);
|
|
95
|
+
zip.addFile('outline.png', icons.outline);
|
|
96
|
+
return zip.toBuffer();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @param {Object} runtimeValues
|
|
101
|
+
* @param {Object} paths
|
|
102
|
+
* @param {string[]} sourceFileNames
|
|
103
|
+
* @returns {Object}
|
|
104
|
+
*/
|
|
105
|
+
function buildCopilotValidation(runtimeValues, paths, sourceFileNames) {
|
|
106
|
+
return {
|
|
107
|
+
tier: 'builderValidated',
|
|
108
|
+
environment: runtimeValues.environment,
|
|
109
|
+
sourceFiles: sourceFileNames,
|
|
110
|
+
zipFile: paths.zipFileName,
|
|
111
|
+
runtimeSystemKey: runtimeValues.runtimeSystemKey,
|
|
112
|
+
runtimeMcpUrl: runtimeValues.mcpUrl,
|
|
113
|
+
runtimeTransport: runtimeValues.runtimeTransport,
|
|
114
|
+
expectedLayerBToolNames: runtimeValues.layerBToolNames || [],
|
|
115
|
+
boundaries: {
|
|
116
|
+
onePackagePerEnvironment: true,
|
|
117
|
+
topLevelVendorToolsExposed: false,
|
|
118
|
+
roleAssistantResolution: 'dynamic-runtime'
|
|
119
|
+
},
|
|
120
|
+
requiresMicrosoftAdmin: [
|
|
121
|
+
'upload-package',
|
|
122
|
+
'tenant-approval',
|
|
123
|
+
'assignment-and-consent',
|
|
124
|
+
'first-copilot-invoke'
|
|
125
|
+
]
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @param {Array<string|Buffer>} parts
|
|
131
|
+
* @returns {string}
|
|
132
|
+
*/
|
|
133
|
+
function computeCopilotArtifactHash(parts) {
|
|
134
|
+
const hash = crypto.createHash('sha256');
|
|
135
|
+
for (const part of parts) {
|
|
136
|
+
if (Buffer.isBuffer(part)) {
|
|
137
|
+
hash.update(part);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
hash.update(String(part), 'utf8');
|
|
141
|
+
}
|
|
142
|
+
return hash.digest('hex');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @param {Object} runtimeValues
|
|
147
|
+
* @param {Object} paths
|
|
148
|
+
* @param {Object} validation
|
|
149
|
+
* @param {string} artifactHash
|
|
150
|
+
* @returns {Object}
|
|
151
|
+
*/
|
|
152
|
+
function buildCopilotSummary(runtimeValues, paths, validation, artifactHash) {
|
|
153
|
+
return {
|
|
154
|
+
channelTarget: runtimeValues.channelTarget,
|
|
155
|
+
name: runtimeValues.appName,
|
|
156
|
+
baseName: runtimeValues.baseName,
|
|
157
|
+
description: runtimeValues.appDescription,
|
|
158
|
+
environment: runtimeValues.environment,
|
|
159
|
+
runtime: {
|
|
160
|
+
runtimeSystemKey: runtimeValues.runtimeSystemKey,
|
|
161
|
+
remoteMcpUrl: runtimeValues.mcpUrl,
|
|
162
|
+
transport: runtimeValues.runtimeTransport,
|
|
163
|
+
assistantCatalogUrl: runtimeValues.assistantCatalogUrl,
|
|
164
|
+
roleAssistantResolution: 'dynamic-runtime',
|
|
165
|
+
layerBToolNames: runtimeValues.layerBToolNames || []
|
|
166
|
+
},
|
|
167
|
+
authentication: {
|
|
168
|
+
mode: runtimeValues.runtimeAuthMode,
|
|
169
|
+
credentialKey: runtimeValues.credentialKey,
|
|
170
|
+
credentialPortalUrl: runtimeValues.credentialPortalUrl,
|
|
171
|
+
scopes: runtimeValues.scopes || []
|
|
172
|
+
},
|
|
173
|
+
package: {
|
|
174
|
+
appId: runtimeValues.packageAppId,
|
|
175
|
+
version: runtimeValues.packageVersion,
|
|
176
|
+
sourceFiles: ['manifest.json', 'declarativeAgent.json', 'ai-plugin.json', 'color.png', 'outline.png'],
|
|
177
|
+
zipFileName: paths.zipFileName,
|
|
178
|
+
installGuide: path.relative(paths.rootDir, paths.installPath),
|
|
179
|
+
validationFile: path.relative(paths.rootDir, paths.validationPath)
|
|
180
|
+
},
|
|
181
|
+
probeTiers: {
|
|
182
|
+
builderValidated: true,
|
|
183
|
+
runtimeValidated: false,
|
|
184
|
+
requiresMicrosoftAdmin: true
|
|
185
|
+
},
|
|
186
|
+
artifact: {
|
|
187
|
+
format: 'microsoft-365-copilot-package',
|
|
188
|
+
hash: artifactHash,
|
|
189
|
+
generatedAt: new Date().toISOString()
|
|
190
|
+
},
|
|
191
|
+
validation
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* @param {Object} params
|
|
197
|
+
*/
|
|
198
|
+
function writeCopilotArtifacts(params) {
|
|
199
|
+
const { paths, sourceFiles, icons, zipBuffer, installText, validationText, summaryText } = params;
|
|
200
|
+
fs.mkdirSync(paths.sourceDir, { recursive: true });
|
|
201
|
+
fs.mkdirSync(paths.buildDir, { recursive: true });
|
|
202
|
+
fs.mkdirSync(paths.installDir, { recursive: true });
|
|
203
|
+
fs.writeFileSync(path.join(paths.sourceDir, 'manifest.json'), sourceFiles['manifest.json'], 'utf8');
|
|
204
|
+
fs.writeFileSync(
|
|
205
|
+
path.join(paths.sourceDir, 'declarativeAgent.json'),
|
|
206
|
+
sourceFiles['declarativeAgent.json'],
|
|
207
|
+
'utf8'
|
|
208
|
+
);
|
|
209
|
+
fs.writeFileSync(path.join(paths.sourceDir, 'ai-plugin.json'), sourceFiles['ai-plugin.json'], 'utf8');
|
|
210
|
+
fs.writeFileSync(path.join(paths.sourceDir, 'color.png'), icons.color);
|
|
211
|
+
fs.writeFileSync(path.join(paths.sourceDir, 'outline.png'), icons.outline);
|
|
212
|
+
fs.writeFileSync(paths.zipPath, zipBuffer);
|
|
213
|
+
fs.writeFileSync(paths.installPath, installText, 'utf8');
|
|
214
|
+
fs.writeFileSync(paths.validationPath, validationText, 'utf8');
|
|
215
|
+
fs.writeFileSync(paths.summaryPath, summaryText, 'utf8');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* @param {Object} params
|
|
220
|
+
* @param {Object} params.runtimeValues
|
|
221
|
+
* @param {string} params.templatesDir
|
|
222
|
+
* @param {string} params.outputPath
|
|
223
|
+
* @param {boolean} [params.dryRun]
|
|
224
|
+
* @param {(content: string) => void} params.assertNoSecretsInArtifact
|
|
225
|
+
* @param {(templatePath: string, context: Object) => string} params.renderTemplateFile
|
|
226
|
+
* @returns {{ outputPath: string, artifactHash: string, content: string, installPath: string }}
|
|
227
|
+
*/
|
|
228
|
+
function generateMicrosoftCopilotPackage(params) {
|
|
229
|
+
const runtimeValues = params.runtimeValues || {};
|
|
230
|
+
const paths = buildCopilotOutputPaths(params.outputPath, runtimeValues);
|
|
231
|
+
const icons = getPlaceholderIcons();
|
|
232
|
+
const { sourceFiles, installText } = renderCopilotTemplates(
|
|
233
|
+
runtimeValues,
|
|
234
|
+
params.renderTemplateFile,
|
|
235
|
+
params.templatesDir
|
|
236
|
+
);
|
|
237
|
+
assertNoSecretsInSourceFiles(sourceFiles, params.assertNoSecretsInArtifact);
|
|
238
|
+
params.assertNoSecretsInArtifact(installText);
|
|
239
|
+
const zipBuffer = buildCopilotZipBuffer(sourceFiles, icons);
|
|
240
|
+
const validation = buildCopilotValidation(runtimeValues, paths, Object.keys(sourceFiles));
|
|
241
|
+
const validationText = JSON.stringify(validation, null, 2);
|
|
242
|
+
const artifactHash = computeCopilotArtifactHash([
|
|
243
|
+
sourceFiles['manifest.json'],
|
|
244
|
+
sourceFiles['declarativeAgent.json'],
|
|
245
|
+
sourceFiles['ai-plugin.json'],
|
|
246
|
+
installText,
|
|
247
|
+
validationText,
|
|
248
|
+
zipBuffer
|
|
249
|
+
]);
|
|
250
|
+
const summary = buildCopilotSummary(runtimeValues, paths, validation, artifactHash);
|
|
251
|
+
const summaryText = JSON.stringify(summary, null, 2);
|
|
252
|
+
params.assertNoSecretsInArtifact(summaryText);
|
|
253
|
+
if (!params.dryRun) {
|
|
254
|
+
writeCopilotArtifacts({
|
|
255
|
+
paths,
|
|
256
|
+
sourceFiles,
|
|
257
|
+
icons,
|
|
258
|
+
zipBuffer,
|
|
259
|
+
installText,
|
|
260
|
+
validationText,
|
|
261
|
+
summaryText
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
outputPath: paths.summaryPath,
|
|
266
|
+
artifactHash,
|
|
267
|
+
content: summaryText,
|
|
268
|
+
installPath: paths.installPath
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
module.exports = {
|
|
273
|
+
generateMicrosoftCopilotPackage,
|
|
274
|
+
buildCopilotOutputPaths,
|
|
275
|
+
renderCopilotTemplates,
|
|
276
|
+
buildCopilotZipBuffer,
|
|
277
|
+
buildCopilotValidation,
|
|
278
|
+
buildCopilotSummary,
|
|
279
|
+
computeCopilotArtifactHash
|
|
280
|
+
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve Runtime Layer B tool names from Dataplane.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Runtime-orchestration MCP tool discovery for channel artifacts and probes
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.1.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const { createDataplaneApiClient } = require('../api/index');
|
|
12
|
+
const { getChannelTarget } = require('./channel-targets');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Prefer the first-party MCP gateway catalog, then MCP OpenAPI, then legacy paths.
|
|
16
|
+
*/
|
|
17
|
+
const TOOL_CATALOG_BUILDERS = Object.freeze([
|
|
18
|
+
(systemKey) =>
|
|
19
|
+
`/api/v1/mcp/servers/tools?filter=${encodeURIComponent(`systemKey:eq:${systemKey}`)}&pageSize=100`,
|
|
20
|
+
(systemKey) => `/api/v1/mcp/${systemKey}/docs/mcp-openapi.json`,
|
|
21
|
+
(systemKey) => `/api/v1/external/systems/${systemKey}/mcp/tools`,
|
|
22
|
+
(systemKey) => `/api/v1/external/systems/${systemKey}/mcp/tools/list`,
|
|
23
|
+
(systemKey) => `/api/v1/external/systems/${systemKey}/mcp/catalog`,
|
|
24
|
+
(systemKey) => `/api/v1/external/systems/${systemKey}/tools`
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {unknown} value
|
|
29
|
+
* @returns {string[]}
|
|
30
|
+
*/
|
|
31
|
+
function extractToolNames(value) {
|
|
32
|
+
if (Array.isArray(value)) {
|
|
33
|
+
return value
|
|
34
|
+
.map((item) => {
|
|
35
|
+
if (typeof item === 'string') return item.trim();
|
|
36
|
+
if (!item || typeof item !== 'object') return '';
|
|
37
|
+
return String(item.name || item.toolName || item.key || '').trim();
|
|
38
|
+
})
|
|
39
|
+
.filter(Boolean);
|
|
40
|
+
}
|
|
41
|
+
if (!value || typeof value !== 'object') {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
const body = /** @type {Record<string, unknown>} */ (value);
|
|
45
|
+
if (body.paths && typeof body.paths === 'object') {
|
|
46
|
+
return extractToolNamesFromOpenApiPaths(body.paths);
|
|
47
|
+
}
|
|
48
|
+
const candidates = [body.tools, body.items, body.data, body.result];
|
|
49
|
+
for (const candidate of candidates) {
|
|
50
|
+
const names = extractToolNames(candidate);
|
|
51
|
+
if (names.length > 0) {
|
|
52
|
+
return names;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {Record<string, unknown>} paths
|
|
60
|
+
* @returns {string[]}
|
|
61
|
+
*/
|
|
62
|
+
function extractToolNamesFromOpenApiPaths(paths) {
|
|
63
|
+
const names = [];
|
|
64
|
+
for (const pathKey of Object.keys(paths || {})) {
|
|
65
|
+
const match = String(pathKey).match(/\/tools\/([^/]+)\/execute$/);
|
|
66
|
+
if (match && match[1] && match[1] !== 'execute') {
|
|
67
|
+
names.push(match[1]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return [...new Set(names)];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {string} routeSystemKey
|
|
75
|
+
* @returns {string[]}
|
|
76
|
+
*/
|
|
77
|
+
function buildToolCatalogEndpoints(routeSystemKey) {
|
|
78
|
+
return TOOL_CATALOG_BUILDERS.map((build) => build(routeSystemKey));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {Object} response
|
|
83
|
+
* @returns {string[]}
|
|
84
|
+
*/
|
|
85
|
+
function extractToolNamesFromClientResponse(response) {
|
|
86
|
+
const direct = extractToolNames(response);
|
|
87
|
+
if (direct.length > 0) return preferLayerBToolNames(direct);
|
|
88
|
+
return preferLayerBToolNames(extractToolNames(response?.data ?? response));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Prefer MCP Layer B names (`work.start`) over REST-ish operation ids.
|
|
93
|
+
*
|
|
94
|
+
* @param {string[]} names
|
|
95
|
+
* @returns {string[]}
|
|
96
|
+
*/
|
|
97
|
+
function preferLayerBToolNames(names) {
|
|
98
|
+
const list = Array.isArray(names) ? names : [];
|
|
99
|
+
const dotted = list.filter((name) => /^[a-z][a-z0-9]*\.[a-z][a-z0-9.]*$/i.test(name));
|
|
100
|
+
return dotted.length > 0 ? dotted : list;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {string} dataplaneUrl
|
|
105
|
+
* @param {Object} authConfig
|
|
106
|
+
* @param {string} channelTarget
|
|
107
|
+
* @returns {Promise<string[]>}
|
|
108
|
+
*/
|
|
109
|
+
async function fetchRuntimeLayerBToolNames(dataplaneUrl, authConfig, channelTarget) {
|
|
110
|
+
const target = getChannelTarget(channelTarget);
|
|
111
|
+
const routeSystemKey = target.routeSystemKey || target.key;
|
|
112
|
+
const client = createDataplaneApiClient(dataplaneUrl, authConfig);
|
|
113
|
+
const endpoints = buildToolCatalogEndpoints(routeSystemKey);
|
|
114
|
+
for (const endpoint of endpoints) {
|
|
115
|
+
try {
|
|
116
|
+
const response = await client.get(endpoint, { timeoutMs: 10000 });
|
|
117
|
+
const names = extractToolNamesFromClientResponse(response);
|
|
118
|
+
if (Array.isArray(names) && names.length > 0) {
|
|
119
|
+
return [...new Set(names)].sort();
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
// Try the next endpoint candidate.
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* True when Runtime MCP gateway docs or tool catalog is reachable.
|
|
130
|
+
*
|
|
131
|
+
* @async
|
|
132
|
+
* @param {string} dataplaneUrl
|
|
133
|
+
* @param {Object} authConfig
|
|
134
|
+
* @param {string} routeSystemKey
|
|
135
|
+
* @returns {Promise<boolean>}
|
|
136
|
+
*/
|
|
137
|
+
async function isRuntimeMcpGatewayReachable(dataplaneUrl, authConfig, routeSystemKey) {
|
|
138
|
+
if (!dataplaneUrl || !routeSystemKey) return false;
|
|
139
|
+
const client = createDataplaneApiClient(dataplaneUrl, authConfig);
|
|
140
|
+
const candidates = [
|
|
141
|
+
`/api/v1/mcp/${routeSystemKey}/docs/mcp-openapi.json`,
|
|
142
|
+
`/api/v1/mcp/servers/tools?filter=${encodeURIComponent(`systemKey:eq:${routeSystemKey}`)}&pageSize=1`
|
|
143
|
+
];
|
|
144
|
+
for (const endpoint of candidates) {
|
|
145
|
+
try {
|
|
146
|
+
const response = await client.get(endpoint, { timeoutMs: 8000 });
|
|
147
|
+
if (response && response.success !== false) {
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
// try next
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
module.exports = {
|
|
158
|
+
TOOL_CATALOG_BUILDERS,
|
|
159
|
+
extractToolNames,
|
|
160
|
+
extractToolNamesFromOpenApiPaths,
|
|
161
|
+
preferLayerBToolNames,
|
|
162
|
+
buildToolCatalogEndpoints,
|
|
163
|
+
fetchRuntimeLayerBToolNames,
|
|
164
|
+
isRuntimeMcpGatewayReachable
|
|
165
|
+
};
|
package/lib/cli/index.js
CHANGED
|
@@ -24,6 +24,7 @@ const { setupDatasourceCommands } = require('../commands/datasource');
|
|
|
24
24
|
const { setupDimensionCommands } = require('../commands/dimension');
|
|
25
25
|
const { setupResourceTypeCommands } = require('../commands/resource-type');
|
|
26
26
|
const { setupRoleCommands } = require('../commands/role');
|
|
27
|
+
const { setupRoleAssistantCommands } = require('../commands/role-assistant');
|
|
27
28
|
const { setupEvidenceKindCommands } = require('../commands/evidence-kind');
|
|
28
29
|
const { setupEnterpriseKnowledgeCommands } = require('../commands/enterprise-knowledge');
|
|
29
30
|
const { setupIdentityCommands } = require('../commands/identity');
|
|
@@ -76,6 +77,7 @@ function setupExtendedCliCommands(program) {
|
|
|
76
77
|
setupDimensionCommands(program);
|
|
77
78
|
setupResourceTypeCommands(program);
|
|
78
79
|
setupRoleCommands(program);
|
|
80
|
+
setupRoleAssistantCommands(program);
|
|
79
81
|
setupEvidenceKindCommands(program);
|
|
80
82
|
setupEnterpriseKnowledgeCommands(program);
|
|
81
83
|
setupIdentityCommands(program);
|
package/lib/cli/setup-app.js
CHANGED
|
@@ -56,12 +56,16 @@ Examples:
|
|
|
56
56
|
$ aifabrix validate hubspot-demo
|
|
57
57
|
$ aifabrix upload hubspot-demo --probe
|
|
58
58
|
$ aifabrix deploy hubspot-demo --probe -v
|
|
59
|
+
# Prove pillars in tst/CI, then promote (plain deploy seals cert on published Deploy)
|
|
59
60
|
$ aifabrix deploy ek-sales --gate full --subject-email ops@example.com
|
|
61
|
+
$ aifabrix deploy ek-sales
|
|
60
62
|
|
|
61
63
|
Notes:
|
|
62
64
|
- Login or set MISO_CONTROLLER_URL + environment (aifabrix login / aifabrix auth).
|
|
63
65
|
- CI: register once (aifabrix app register <appKey>), store DEV_MISO_CLIENTID and DEV_MISO_CLIENTSECRET in pipeline secrets.
|
|
64
66
|
- upload = dataplane publish for dev iteration; deploy = controller validate + deploy (Azure or local Docker).
|
|
67
|
+
- Prove (tst/CI): use --gate full (optional --subject-email) before promote; plain deploy promotes without live E2E gates.
|
|
68
|
+
- After a successful published Deploy, the platform seals the integration certificate (no separate certificates issue step).
|
|
65
69
|
- Default output is a compact status card; use -v for expanded preflight and readiness detail.
|
|
66
70
|
- Controller deploy always polls to a terminal status (no --poll / --no-poll on this command).
|
|
67
71
|
- --repository-url overrides repo URL for pipeline validate (default: application.yaml repository.repositoryUrl).
|
|
@@ -10,6 +10,7 @@ Examples:
|
|
|
10
10
|
$ aifabrix upload hubspot-demo
|
|
11
11
|
$ aifabrix upload hubspot-demo -v
|
|
12
12
|
$ aifabrix upload hubspot-demo --probe
|
|
13
|
+
$ aifabrix upload hubspot-demo --timeout 600000
|
|
13
14
|
|
|
14
15
|
# Build and validate payload only (no API calls)
|
|
15
16
|
$ aifabrix upload hubspot-demo --dry-run
|
|
@@ -18,11 +19,16 @@ Examples:
|
|
|
18
19
|
$ aifabrix upload .protection
|
|
19
20
|
$ aifabrix upload .protection --warnings-as-errors
|
|
20
21
|
|
|
22
|
+
# Role Assistant package (settings section + Evidence sidecars)
|
|
23
|
+
$ aifabrix upload sales-representative --env dev
|
|
24
|
+
|
|
21
25
|
Notes:
|
|
22
26
|
- Requires login (aifabrix auth status).
|
|
23
|
-
- Reads files under integration/<systemKey>/
|
|
27
|
+
- Reads files under integration/<systemKey>/ or role-assistant/<roleKey>/ for digital workers.
|
|
24
28
|
- Default output is a compact readiness summary; use -v for expanded detail and server-side validate warnings.
|
|
25
|
-
- --probe runs dataplane runtime checks after publish (slower
|
|
29
|
+
- --probe runs dataplane runtime checks after publish (slower).
|
|
30
|
+
- --timeout sets publish budget in ms (HTTP client and dataplane); default 300000.
|
|
31
|
+
- --env selects role-assistant.yaml section (dev|tst|pro) for digital workers; default dev.
|
|
26
32
|
- test-e2e and test-integration publish local files first by default; use --no-publish on those commands to skip.
|
|
27
33
|
- After dimension/FK edits on active datasources, use --force (or test-e2e / test-integration / datasource test-* with --sync --force).
|
|
28
34
|
- For one datasource only: aifabrix datasource upload <datasourceKey>
|
|
@@ -45,13 +45,16 @@ async function handleUploadCommandAction(systemKey, options) {
|
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
47
|
const upload = require('../commands/upload');
|
|
48
|
+
const { resolvePipelineUploadTimeoutMs } = require('../api/pipeline.api');
|
|
48
49
|
const probeTimeout =
|
|
49
50
|
options.probeTimeout === undefined || options.probeTimeout === null
|
|
50
51
|
? 120000
|
|
51
52
|
: Number(options.probeTimeout);
|
|
53
|
+
const timeoutMs = resolvePipelineUploadTimeoutMs(options.timeout);
|
|
52
54
|
await upload.uploadExternalSystem(systemKey, {
|
|
53
55
|
...options,
|
|
54
56
|
probeTimeout: Number.isFinite(probeTimeout) ? probeTimeout : 120000,
|
|
57
|
+
timeoutMs,
|
|
55
58
|
pathOptions: {}
|
|
56
59
|
});
|
|
57
60
|
}
|
|
@@ -98,10 +101,19 @@ function setupUploadCommand(program) {
|
|
|
98
101
|
.option('-v, --verbose', 'Expanded upload summary and server-side pipeline validate warnings before publish')
|
|
99
102
|
.option('--probe', 'After publish, run dataplane runtime checks (validation/run); slower')
|
|
100
103
|
.option('--probe-timeout <ms>', 'Timeout for --probe (default: 120000)', '120000')
|
|
104
|
+
.option(
|
|
105
|
+
'--timeout <ms>',
|
|
106
|
+
'Publish timeout in ms for HTTP and dataplane budget (default: 300000; min 5000, max 2700000)'
|
|
107
|
+
)
|
|
101
108
|
.option(
|
|
102
109
|
'--force',
|
|
103
110
|
'Bypass schema guards on active datasources during publish (dangerous; shows a warning)'
|
|
104
111
|
)
|
|
112
|
+
.option(
|
|
113
|
+
'-e, --env <env>',
|
|
114
|
+
'Role Assistant settings environment section: dev, tst, or pro (default: dev)',
|
|
115
|
+
'dev'
|
|
116
|
+
)
|
|
105
117
|
.action(async(systemKey, options) => {
|
|
106
118
|
try {
|
|
107
119
|
await handleUploadCommandAction(systemKey, options);
|
package/lib/commands/app-test.js
CHANGED
|
@@ -166,20 +166,8 @@ async function runDockerExec(containerName, testCmd, envFilePath = null) {
|
|
|
166
166
|
* @returns {Promise<number>} Exit code
|
|
167
167
|
*/
|
|
168
168
|
async function runDockerRunEphemeral(fullImage, testCmd, envFilePath = null) {
|
|
169
|
-
const {
|
|
170
|
-
|
|
171
|
-
return new Promise((resolve) => {
|
|
172
|
-
const args = ['run', '--rm'];
|
|
173
|
-
if (envFilePath) args.push('--env-file', envFilePath);
|
|
174
|
-
args.push(fullImage, 'sh', '-c', testCmd);
|
|
175
|
-
const proc = spawn('docker', args, {
|
|
176
|
-
stdio: 'inherit',
|
|
177
|
-
shell: false,
|
|
178
|
-
env: dockerEnv
|
|
179
|
-
});
|
|
180
|
-
proc.on('close', code => resolve(code !== null ? code : 1));
|
|
181
|
-
proc.on('error', () => resolve(1));
|
|
182
|
-
});
|
|
169
|
+
const { runDockerRunEphemeral: runEphemeral } = require('../utils/docker-run-ephemeral');
|
|
170
|
+
return runEphemeral(fullImage, testCmd, envFilePath);
|
|
183
171
|
}
|
|
184
172
|
|
|
185
173
|
/**
|
|
@@ -76,7 +76,8 @@ async function datasourceLoadAction(datasourceKey, options) {
|
|
|
76
76
|
dryRun: options.dryRun,
|
|
77
77
|
batchSize: options.batchSize,
|
|
78
78
|
syncType: options.syncType,
|
|
79
|
-
format: options.format
|
|
79
|
+
format: options.format,
|
|
80
|
+
timeoutMs: options.timeout
|
|
80
81
|
});
|
|
81
82
|
|
|
82
83
|
if (options.json) {
|
|
@@ -141,6 +142,10 @@ function setupDatasourceLoadExportCommands(datasource) {
|
|
|
141
142
|
'Dataplane syncType',
|
|
142
143
|
'incremental'
|
|
143
144
|
)
|
|
145
|
+
.option(
|
|
146
|
+
'--timeout <ms>',
|
|
147
|
+
'Per-batch HTTP timeout for records/bulk (default 120000)'
|
|
148
|
+
)
|
|
144
149
|
.option('--dry-run', 'Parse and validate only; no upload')
|
|
145
150
|
.option('-v, --verbose', 'Per-batch progress')
|
|
146
151
|
.option('--json', 'Machine-readable result JSON')
|
|
@@ -113,6 +113,24 @@ function formatOperationsGateFailure(result) {
|
|
|
113
113
|
return 'E2E validation did not succeed for one or more datasources';
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/**
|
|
117
|
+
* @param {Object} row
|
|
118
|
+
* @returns {string|null}
|
|
119
|
+
*/
|
|
120
|
+
function formatFailedDatasourceRowHint(row) {
|
|
121
|
+
if (!row || row.success !== false) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const key = row.key || row.datasourceKey || 'datasource';
|
|
125
|
+
const issues = row.datasourceTestRun?.validation?.issues;
|
|
126
|
+
if (Array.isArray(issues) && issues.length > 0) {
|
|
127
|
+
const first = issues[0];
|
|
128
|
+
const code = first.code ? `${first.code}: ` : '';
|
|
129
|
+
return `${key}: ${code}${first.message || 'validation failed'}`;
|
|
130
|
+
}
|
|
131
|
+
return row.error ? `${key}: ${row.error}` : null;
|
|
132
|
+
}
|
|
133
|
+
|
|
116
134
|
/**
|
|
117
135
|
* @param {Object} result
|
|
118
136
|
* @returns {string}
|
|
@@ -125,23 +143,7 @@ function formatIntegrationGateFailure(result) {
|
|
|
125
143
|
return String(result.message);
|
|
126
144
|
}
|
|
127
145
|
const rows = Array.isArray(result?.datasourceResults) ? result.datasourceResults : [];
|
|
128
|
-
const hints =
|
|
129
|
-
for (const row of rows) {
|
|
130
|
-
if (!row || row.success !== false) {
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
const key = row.key || row.datasourceKey || 'datasource';
|
|
134
|
-
const issues = row.datasourceTestRun?.validation?.issues;
|
|
135
|
-
if (Array.isArray(issues) && issues.length > 0) {
|
|
136
|
-
const first = issues[0];
|
|
137
|
-
const code = first.code ? `${first.code}: ` : '';
|
|
138
|
-
hints.push(`${key}: ${code}${first.message || 'validation failed'}`);
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
if (row.error) {
|
|
142
|
-
hints.push(`${key}: ${row.error}`);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
146
|
+
const hints = rows.map(formatFailedDatasourceRowHint).filter(Boolean);
|
|
145
147
|
if (hints.length > 0) {
|
|
146
148
|
return hints.slice(0, 3).join('; ');
|
|
147
149
|
}
|
package/lib/commands/dev-down.js
CHANGED
|
@@ -23,18 +23,14 @@ const execAsync = promisify(exec);
|
|
|
23
23
|
* @returns {Promise<void>}
|
|
24
24
|
*/
|
|
25
25
|
async function stopMutagenSessions(developerId) {
|
|
26
|
-
const { getMutagenPath } = require('../utils/mutagen');
|
|
26
|
+
const { getMutagenPath, listSyncSessionNames } = require('../utils/mutagen');
|
|
27
27
|
const mutagenPath = await getMutagenPath();
|
|
28
28
|
if (!mutagenPath) {
|
|
29
29
|
logger.log(chalk.gray('Mutagen not installed; no sync sessions to stop.'));
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
32
|
try {
|
|
33
|
-
const
|
|
34
|
-
encoding: 'utf8',
|
|
35
|
-
timeout: 5000
|
|
36
|
-
});
|
|
37
|
-
const sessions = (stdout || '').trim().split('\n').filter(Boolean);
|
|
33
|
+
const sessions = await listSyncSessionNames(mutagenPath);
|
|
38
34
|
const prefix = `aifabrix-${developerId}-`;
|
|
39
35
|
const toTerminate = sessions.filter(name => name.startsWith(prefix));
|
|
40
36
|
for (const name of toTerminate) {
|