@aifabrix/builder 2.56.0 → 2.57.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.
- package/lib/agent-kit/session-report.js +76 -0
- package/lib/agent-kit/start.js +124 -57
- package/lib/agent-kit/tmux-sessions.js +163 -0
- package/lib/api/environments.api.js +38 -0
- package/lib/api/marketplace-bootstrap.api.js +31 -0
- package/lib/cli/setup-environment.js +76 -19
- package/lib/commands/agent-kit-help.js +32 -15
- package/lib/commands/agent-kit.js +90 -8
- package/lib/deployment/installation/azure-infra-stage.js +160 -0
- package/lib/deployment/installation/azure-preflight.js +102 -0
- package/lib/deployment/installation/azure-readiness.js +71 -0
- package/lib/deployment/installation/docker-register.js +112 -0
- package/lib/deployment/installation/environment-auth.js +76 -0
- package/lib/deployment/installation/environment-manifest.js +165 -0
- package/lib/deployment/installation/environment-stage.js +191 -0
- package/lib/deployment/installation/index.js +39 -0
- package/lib/deployment/installation/infra-catalog.js +165 -0
- package/lib/deployment/installation/infra-manifest.js +161 -0
- package/lib/deployment/installation/infra-projection.js +86 -0
- package/lib/deployment/installation/public-ip.js +65 -0
- package/lib/deployment/installation/stage-input.js +169 -0
- package/lib/deployment/installation/stage-output.js +53 -0
- package/lib/deployment/installation/workspace-env.js +76 -0
- package/lib/deployment/installation/workspace-files.js +178 -0
- package/lib/deployment/installation/workspace-schema.js +40 -0
- package/lib/deployment/installation/workspace-secrets.js +66 -0
- package/lib/deployment/installation/workspace.js +128 -0
- package/lib/deployment/marketplace-environment.js +305 -0
- package/lib/generator/builders.js +4 -0
- package/lib/generator/helpers.js +55 -56
- package/lib/generator/split-variables.js +11 -5
- package/lib/internal/fs-real-sync.js +29 -0
- package/lib/role-assistant/package-validate.js +4 -0
- package/lib/role-assistant/test-runner-clarification-resubmit.js +149 -0
- package/lib/role-assistant/test-runner.js +14 -17
- package/lib/schema/application-schema.json +20 -2
- package/lib/schema/infra-parameter.schema.json +7 -0
- package/lib/schema/infra.parameter.yaml +26 -36
- package/lib/schema/infrastructure-schema.json +1480 -399
- package/lib/schema/installation-environment.schema.json +601 -0
- package/lib/utils/variable-transformer.js +3 -0
- package/lib/validation/required-variables.js +41 -0
- package/lib/validation/validator.js +8 -5
- package/package.json +5 -1
- package/templates/README.md +4 -0
- package/templates/agent-kit/instructions/AGENTKIT.md +6 -6
- package/templates/agent-kit/skills/shared/hosts.md +10 -10
- package/templates/applications/dataplane/application.yaml +1 -1
- package/templates/applications/dataplane/env.template +1 -1
- package/templates/applications/miso-controller/application.yaml +3 -2
- package/templates/applications/miso-controller/env.template +15 -6
- package/templates/marketplace/createUiDefinition.json +77 -0
- package/templates/marketplace/install.parameters.example.json +9 -0
- package/templates/marketplace/main.json +4087 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-readable output for Agent Kit tmux session commands.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
formatBlockingError,
|
|
9
|
+
formatNextActions,
|
|
10
|
+
metadata,
|
|
11
|
+
sectionTitle
|
|
12
|
+
} = require('../utils/cli-layout-chalk');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} value
|
|
16
|
+
* @param {number} width
|
|
17
|
+
* @returns {string}
|
|
18
|
+
*/
|
|
19
|
+
function pad(value, width) {
|
|
20
|
+
return String(value).padEnd(width);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {Array<{ name: string }>} sessions
|
|
25
|
+
* @returns {{ name: number, status: number, windows: number }}
|
|
26
|
+
*/
|
|
27
|
+
function columnWidths(sessions) {
|
|
28
|
+
const longestName = sessions.reduce(
|
|
29
|
+
(width, session) => Math.max(width, String(session.name).length),
|
|
30
|
+
'Name'.length
|
|
31
|
+
);
|
|
32
|
+
return { name: longestName + 2, status: 10, windows: 7 };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {{ sessions: Array<{ name: string, attached: boolean, windows: number }> }} report
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
function formatSessionList(report) {
|
|
40
|
+
const sessions = Array.isArray(report.sessions) ? report.sessions : [];
|
|
41
|
+
const lines = [sectionTitle(`Tmux sessions — ${sessions.length} active:`), ''];
|
|
42
|
+
if (sessions.length === 0) {
|
|
43
|
+
lines.push(metadata(' No active tmux sessions.'));
|
|
44
|
+
return lines.join('\n');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const widths = columnWidths(sessions);
|
|
48
|
+
const header = `${pad('Name', widths.name)}${pad('Status', widths.status)}Windows`;
|
|
49
|
+
lines.push(metadata(header));
|
|
50
|
+
lines.push(metadata('-'.repeat(widths.name + widths.status + widths.windows)));
|
|
51
|
+
sessions.forEach((session) => {
|
|
52
|
+
const status = session.attached ? 'attached' : 'detached';
|
|
53
|
+
lines.push(`${pad(session.name, widths.name)}${pad(status, widths.status)}${session.windows}`);
|
|
54
|
+
});
|
|
55
|
+
return lines.join('\n');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {boolean} asJson
|
|
60
|
+
* @returns {{ stderr: string, stdout?: string }}
|
|
61
|
+
*/
|
|
62
|
+
function formatMissingKillName(asJson) {
|
|
63
|
+
const error = 'Session name is required.';
|
|
64
|
+
if (asJson) {
|
|
65
|
+
return { stderr: JSON.stringify({ command: 'kill', success: false, error }, null, 2) };
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
stderr: formatBlockingError(error),
|
|
69
|
+
stdout: formatNextActions([
|
|
70
|
+
'Run: aifabrix agent-kit list',
|
|
71
|
+
'Run: aifabrix agent-kit kill <name>'
|
|
72
|
+
])
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = { formatSessionList, formatMissingKillName };
|
package/lib/agent-kit/start.js
CHANGED
|
@@ -11,6 +11,7 @@ const { execFileSync, spawnSync } = require('child_process');
|
|
|
11
11
|
const logger = require('../utils/logger');
|
|
12
12
|
const { resolveGitRoot } = require('./repo-guard');
|
|
13
13
|
const { writeApproveAllFiles } = require('./host-approve');
|
|
14
|
+
const { hasOnPath, requireTmux, tmuxSessionExists } = require('./tmux-sessions');
|
|
14
15
|
|
|
15
16
|
const START_APPS = {
|
|
16
17
|
claude: { bin: 'claude' },
|
|
@@ -97,34 +98,6 @@ function resolveRemoteControlName(prefix, session) {
|
|
|
97
98
|
return sanitizeSessionName(`${prefix}-${session}`);
|
|
98
99
|
}
|
|
99
100
|
|
|
100
|
-
/**
|
|
101
|
-
* @param {string} bin
|
|
102
|
-
* @param {Function} exec
|
|
103
|
-
* @returns {boolean}
|
|
104
|
-
*/
|
|
105
|
-
function hasOnPath(bin, exec) {
|
|
106
|
-
try {
|
|
107
|
-
exec('which', [bin], {
|
|
108
|
-
encoding: 'utf8',
|
|
109
|
-
stdio: ['ignore', 'pipe', 'pipe']
|
|
110
|
-
});
|
|
111
|
-
return true;
|
|
112
|
-
} catch {
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* @param {object} options
|
|
119
|
-
* @returns {void}
|
|
120
|
-
*/
|
|
121
|
-
function validateStartOptions(options) {
|
|
122
|
-
const app = String(options.app || '').trim();
|
|
123
|
-
if (options.remoteControl && app !== 'claude') {
|
|
124
|
-
throw new Error('--remote-control is only valid with claude.');
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
101
|
/**
|
|
129
102
|
* Claude argv after the binary (Remote Control names, not Claude --tmux).
|
|
130
103
|
* @param {object} p
|
|
@@ -152,7 +125,7 @@ function buildTmuxSpec(p) {
|
|
|
152
125
|
const extra = p.app === 'claude' ? claudeInnerArgs(p) : [];
|
|
153
126
|
const mode = p.detach
|
|
154
127
|
? ['new-session', '-d', '-s', p.session, '-c', p.cwd]
|
|
155
|
-
: ['new-session', '-
|
|
128
|
+
: ['new-session', '-s', p.session, '-c', p.cwd];
|
|
156
129
|
const remote = p.app === 'claude' && p.remoteControl;
|
|
157
130
|
return {
|
|
158
131
|
bin: 'tmux',
|
|
@@ -179,9 +152,7 @@ function buildLaunchSpec(p) {
|
|
|
179
152
|
* @returns {void}
|
|
180
153
|
*/
|
|
181
154
|
function requireBinaries(spec, exec) {
|
|
182
|
-
|
|
183
|
-
throw new Error('tmux is not on PATH. Install tmux, then retry.');
|
|
184
|
-
}
|
|
155
|
+
requireTmux(exec);
|
|
185
156
|
if (hasOnPath(spec.appBin, exec)) {
|
|
186
157
|
return;
|
|
187
158
|
}
|
|
@@ -193,6 +164,45 @@ function requireBinaries(spec, exec) {
|
|
|
193
164
|
throw new Error('codex is not on PATH. Install with: npm install -g @openai/codex');
|
|
194
165
|
}
|
|
195
166
|
|
|
167
|
+
/**
|
|
168
|
+
* @param {string} cwd
|
|
169
|
+
* @returns {{ bin: string, args: string[], cwd: string }}
|
|
170
|
+
*/
|
|
171
|
+
function buildCodexRemoteSpec(cwd) {
|
|
172
|
+
return {
|
|
173
|
+
bin: 'codex',
|
|
174
|
+
args: ['remote-control', 'start', '--json'],
|
|
175
|
+
cwd
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @param {object} options
|
|
181
|
+
* @param {object} spec
|
|
182
|
+
* @returns {void}
|
|
183
|
+
*/
|
|
184
|
+
function startCodexRemote(options, spec) {
|
|
185
|
+
const exec = options.execFileSync || execFileSync;
|
|
186
|
+
try {
|
|
187
|
+
exec(spec.bin, spec.args, {
|
|
188
|
+
cwd: spec.cwd,
|
|
189
|
+
encoding: 'utf8',
|
|
190
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
191
|
+
});
|
|
192
|
+
} catch (error) {
|
|
193
|
+
const detail = String((error && error.stderr) || (error && error.message) || '');
|
|
194
|
+
if (/app server is running but is not managed by codex app-server daemon/i.test(detail)) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (/unrecognized subcommand|unknown command/i.test(detail)) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
'This Codex CLI does not support Remote Control. Update Codex or pass --no-remote-control.'
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
196
206
|
/**
|
|
197
207
|
* @param {object} options
|
|
198
208
|
* @returns {boolean}
|
|
@@ -220,11 +230,26 @@ function spawnLaunch(options, spec) {
|
|
|
220
230
|
const result = spawn(spec.bin, spec.args, { stdio: 'inherit', cwd: spec.cwd });
|
|
221
231
|
const status = result && typeof result.status === 'number' ? result.status : 0;
|
|
222
232
|
if (status !== 0) {
|
|
233
|
+
const exec = options.execFileSync || execFileSync;
|
|
234
|
+
if (tmuxSessionExists(spec.session, exec)) {
|
|
235
|
+
throw duplicateSessionError(spec.session);
|
|
236
|
+
}
|
|
223
237
|
throw new Error(`${spec.bin} exited with status ${status}`);
|
|
224
238
|
}
|
|
225
239
|
return status;
|
|
226
240
|
}
|
|
227
241
|
|
|
242
|
+
/**
|
|
243
|
+
* @param {string} session
|
|
244
|
+
* @returns {Error}
|
|
245
|
+
*/
|
|
246
|
+
function duplicateSessionError(session) {
|
|
247
|
+
return new Error(
|
|
248
|
+
`tmux session "${session}" already exists. Run: aifabrix agent-kit list; ` +
|
|
249
|
+
`aifabrix agent-kit kill ${session}; or choose another --name.`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
228
253
|
/**
|
|
229
254
|
* @param {object} spec
|
|
230
255
|
* @param {object} extra
|
|
@@ -232,12 +257,17 @@ function spawnLaunch(options, spec) {
|
|
|
232
257
|
*/
|
|
233
258
|
function toStartResult(spec, extra) {
|
|
234
259
|
const launch = [spec.bin, ...spec.args];
|
|
260
|
+
const remoteLaunch = extra.remoteSpec
|
|
261
|
+
? [extra.remoteSpec.bin, ...extra.remoteSpec.args]
|
|
262
|
+
: undefined;
|
|
235
263
|
return {
|
|
236
264
|
command: 'start',
|
|
237
265
|
app: extra.app,
|
|
238
266
|
session: spec.session,
|
|
239
267
|
remoteControlName: spec.remoteControlName,
|
|
240
268
|
remoteControlPrefix: spec.remoteControlPrefix,
|
|
269
|
+
remoteControl: extra.remoteControl,
|
|
270
|
+
remoteLaunch,
|
|
241
271
|
path: extra.cwd,
|
|
242
272
|
launch,
|
|
243
273
|
tmux: spec.needTmux ? launch : undefined,
|
|
@@ -252,45 +282,78 @@ function toStartResult(spec, extra) {
|
|
|
252
282
|
* @param {object} options
|
|
253
283
|
* @returns {object}
|
|
254
284
|
*/
|
|
255
|
-
function
|
|
256
|
-
validateStartOptions(options);
|
|
285
|
+
function resolveStartContext(options) {
|
|
257
286
|
const app = normalizeStartApp(options.app);
|
|
258
287
|
const cwd = resolveGitRoot(options.cwd || process.cwd());
|
|
259
288
|
const session = resolveSessionName(options.name, cwd);
|
|
260
289
|
const remoteControlPrefix = hostUserPrefix(options);
|
|
261
290
|
const remoteControlName = resolveRemoteControlName(remoteControlPrefix, session);
|
|
262
|
-
const
|
|
291
|
+
const remoteControl = options.remoteControl !== false;
|
|
263
292
|
const spec = buildLaunchSpec({
|
|
264
|
-
app,
|
|
265
|
-
|
|
266
|
-
session,
|
|
267
|
-
remoteControlPrefix,
|
|
268
|
-
remoteControlName,
|
|
269
|
-
detach: Boolean(options.detach),
|
|
270
|
-
remoteControl: Boolean(options.remoteControl)
|
|
293
|
+
app, cwd, session, remoteControlPrefix, remoteControlName,
|
|
294
|
+
detach: Boolean(options.detach), remoteControl
|
|
271
295
|
});
|
|
272
296
|
spec.cwd = cwd;
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
297
|
+
const remoteSpec = app === 'codex' && remoteControl
|
|
298
|
+
? buildCodexRemoteSpec(cwd)
|
|
299
|
+
: undefined;
|
|
300
|
+
return { app, cwd, session, remoteControl, spec, remoteSpec };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* @param {object} options
|
|
305
|
+
* @param {object} context
|
|
306
|
+
* @returns {boolean}
|
|
307
|
+
*/
|
|
308
|
+
function prepareStart(options, context) {
|
|
309
|
+
const exec = options.execFileSync || execFileSync;
|
|
280
310
|
if (!options.dryRun) {
|
|
281
|
-
requireBinaries(spec, exec);
|
|
311
|
+
requireBinaries(context.spec, exec);
|
|
282
312
|
}
|
|
283
313
|
const execute = shouldExecute(options);
|
|
284
|
-
if (execute &&
|
|
285
|
-
|
|
314
|
+
if (execute && tmuxSessionExists(context.session, exec)) {
|
|
315
|
+
throw duplicateSessionError(context.session);
|
|
316
|
+
}
|
|
317
|
+
return execute;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* @param {object} options
|
|
322
|
+
* @param {object} context
|
|
323
|
+
* @param {boolean} execute
|
|
324
|
+
* @returns {void}
|
|
325
|
+
*/
|
|
326
|
+
function executeStart(options, context, execute) {
|
|
327
|
+
if (!execute) {
|
|
328
|
+
return;
|
|
286
329
|
}
|
|
287
|
-
|
|
288
|
-
|
|
330
|
+
logger.log(`Starting ${context.app} session ${context.spec.session}`);
|
|
331
|
+
if (context.remoteSpec) {
|
|
332
|
+
startCodexRemote(options, context.remoteSpec);
|
|
289
333
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
334
|
+
spawnLaunch(options, context.spec);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* @param {object} options
|
|
339
|
+
* @returns {object}
|
|
340
|
+
*/
|
|
341
|
+
function runStart(options) {
|
|
342
|
+
const context = resolveStartContext(options);
|
|
343
|
+
const execute = prepareStart(options, context);
|
|
344
|
+
const approvals = writeApproveAllFiles({
|
|
345
|
+
repoRoot: context.cwd,
|
|
346
|
+
hosts: [context.app],
|
|
347
|
+
approveAll: !options.skipApproveAll,
|
|
348
|
+
dryRun: Boolean(options.dryRun)
|
|
349
|
+
});
|
|
350
|
+
executeStart(options, context, execute);
|
|
351
|
+
return toStartResult(context.spec, {
|
|
352
|
+
app: context.app,
|
|
353
|
+
cwd: context.cwd,
|
|
293
354
|
approvals,
|
|
355
|
+
remoteControl: context.remoteControl,
|
|
356
|
+
remoteSpec: context.remoteSpec,
|
|
294
357
|
dryRun: Boolean(options.dryRun),
|
|
295
358
|
detach: Boolean(options.detach),
|
|
296
359
|
executed: execute
|
|
@@ -306,5 +369,9 @@ module.exports = {
|
|
|
306
369
|
resolveRemoteControlName,
|
|
307
370
|
claudeInnerArgs,
|
|
308
371
|
buildTmuxSpec,
|
|
372
|
+
buildCodexRemoteSpec,
|
|
373
|
+
startCodexRemote,
|
|
374
|
+
duplicateSessionError,
|
|
375
|
+
resolveStartContext,
|
|
309
376
|
runStart
|
|
310
377
|
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exact-name tmux session discovery and termination for Agent Kit.
|
|
3
|
+
* @fileoverview Uses argv execution only; never invokes a shell
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const { execFileSync } = require('child_process');
|
|
9
|
+
|
|
10
|
+
const LIST_FORMAT = [
|
|
11
|
+
'#{session_name}',
|
|
12
|
+
'#{session_windows}',
|
|
13
|
+
'#{session_attached}',
|
|
14
|
+
'#{session_created}'
|
|
15
|
+
].join('\t');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {string} bin
|
|
19
|
+
* @param {Function} exec
|
|
20
|
+
* @returns {boolean}
|
|
21
|
+
*/
|
|
22
|
+
function hasOnPath(bin, exec) {
|
|
23
|
+
try {
|
|
24
|
+
exec('which', [bin], {
|
|
25
|
+
encoding: 'utf8',
|
|
26
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
27
|
+
});
|
|
28
|
+
return true;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {Function} exec
|
|
36
|
+
* @returns {void}
|
|
37
|
+
*/
|
|
38
|
+
function requireTmux(exec) {
|
|
39
|
+
if (!hasOnPath('tmux', exec)) {
|
|
40
|
+
throw new Error('tmux is not on PATH. Install tmux, then retry.');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {unknown} value
|
|
46
|
+
* @returns {string}
|
|
47
|
+
*/
|
|
48
|
+
function normalizeExactSessionName(value) {
|
|
49
|
+
const name = String(value || '').trim();
|
|
50
|
+
const hasControl = Array.from(name).some((char) => {
|
|
51
|
+
const code = char.charCodeAt(0);
|
|
52
|
+
return code < 32 || code === 127;
|
|
53
|
+
});
|
|
54
|
+
if (!name || hasControl || name.includes(':') || name.includes('.')) {
|
|
55
|
+
throw new Error('Invalid tmux session name. Use a non-empty name without control characters, colons, or periods.');
|
|
56
|
+
}
|
|
57
|
+
return name;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param {unknown} error
|
|
62
|
+
* @returns {boolean}
|
|
63
|
+
*/
|
|
64
|
+
function isNoTmuxServer(error) {
|
|
65
|
+
const message = [error && error.message, error && error.stderr]
|
|
66
|
+
.filter(Boolean)
|
|
67
|
+
.map(String)
|
|
68
|
+
.join(' ');
|
|
69
|
+
return /no server running|failed to connect to server/i.test(message);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {string} name
|
|
74
|
+
* @param {Function} [exec]
|
|
75
|
+
* @returns {boolean}
|
|
76
|
+
*/
|
|
77
|
+
function tmuxSessionExists(name, exec = execFileSync) {
|
|
78
|
+
const exact = normalizeExactSessionName(name);
|
|
79
|
+
try {
|
|
80
|
+
exec('tmux', ['has-session', '-t', `=${exact}`], {
|
|
81
|
+
stdio: ['ignore', 'ignore', 'ignore']
|
|
82
|
+
});
|
|
83
|
+
return true;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (error && (error.status === 1 || isNoTmuxServer(error))) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {string} line
|
|
94
|
+
* @returns {object|null}
|
|
95
|
+
*/
|
|
96
|
+
function parseSessionLine(line) {
|
|
97
|
+
if (!line) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const [name, windows, attached, created] = line.split('\t');
|
|
101
|
+
return {
|
|
102
|
+
name,
|
|
103
|
+
windows: Number(windows),
|
|
104
|
+
attached: Number(attached) > 0,
|
|
105
|
+
createdAt: Number(created)
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @param {object} [options]
|
|
111
|
+
* @returns {{ command: string, sessions: object[], count: number }}
|
|
112
|
+
*/
|
|
113
|
+
function runList(options = {}) {
|
|
114
|
+
const exec = options.execFileSync || execFileSync;
|
|
115
|
+
requireTmux(exec);
|
|
116
|
+
let output;
|
|
117
|
+
try {
|
|
118
|
+
output = exec('tmux', ['list-sessions', '-F', LIST_FORMAT], {
|
|
119
|
+
encoding: 'utf8',
|
|
120
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
121
|
+
});
|
|
122
|
+
} catch (error) {
|
|
123
|
+
if (isNoTmuxServer(error)) {
|
|
124
|
+
return { command: 'list', sessions: [], count: 0 };
|
|
125
|
+
}
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
const sessions = String(output || '')
|
|
129
|
+
.trim()
|
|
130
|
+
.split(/\r?\n/)
|
|
131
|
+
.map(parseSessionLine)
|
|
132
|
+
.filter(Boolean);
|
|
133
|
+
return { command: 'list', sessions, count: sessions.length };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @param {object} options
|
|
138
|
+
* @returns {{ command: string, session: string, killed: boolean }}
|
|
139
|
+
*/
|
|
140
|
+
function runKill(options) {
|
|
141
|
+
const exec = options.execFileSync || execFileSync;
|
|
142
|
+
const session = normalizeExactSessionName(options.name);
|
|
143
|
+
requireTmux(exec);
|
|
144
|
+
if (!tmuxSessionExists(session, exec)) {
|
|
145
|
+
throw new Error(`tmux session "${session}" is not running. Run: aifabrix agent-kit list`);
|
|
146
|
+
}
|
|
147
|
+
exec('tmux', ['kill-session', '-t', `=${session}`], {
|
|
148
|
+
stdio: ['ignore', 'ignore', 'pipe']
|
|
149
|
+
});
|
|
150
|
+
return { command: 'kill', session, killed: true };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = {
|
|
154
|
+
LIST_FORMAT,
|
|
155
|
+
hasOnPath,
|
|
156
|
+
requireTmux,
|
|
157
|
+
normalizeExactSessionName,
|
|
158
|
+
isNoTmuxServer,
|
|
159
|
+
tmuxSessionExists,
|
|
160
|
+
parseSessionLine,
|
|
161
|
+
runList,
|
|
162
|
+
runKill
|
|
163
|
+
};
|
|
@@ -240,7 +240,45 @@ async function listEnvironmentDatasources(controllerUrl, envKey, authConfig, opt
|
|
|
240
240
|
});
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Validate an environment deployment request without deploying
|
|
245
|
+
* POST /api/v1/environments/{envKey}/validate
|
|
246
|
+
* @requiresPermission {Controller} controller:deploy
|
|
247
|
+
* @async
|
|
248
|
+
* @function validateEnvironmentDeployment
|
|
249
|
+
* @param {string} controllerUrl - Controller base URL
|
|
250
|
+
* @param {string} envKey - Environment key
|
|
251
|
+
* @param {Object} authConfig - Authentication configuration
|
|
252
|
+
* @param {Object} request - Body with environmentConfig
|
|
253
|
+
* @returns {Promise<Object>} Validation response (`data.valid`, errors, warnings)
|
|
254
|
+
* @throws {Error} If request fails
|
|
255
|
+
*/
|
|
256
|
+
async function validateEnvironmentDeployment(controllerUrl, envKey, authConfig, request) {
|
|
257
|
+
const client = new ApiClient(controllerUrl, authConfig);
|
|
258
|
+
return await client.post(`/api/v1/environments/${envKey}/validate`, {
|
|
259
|
+
body: request
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Get server-reported tenant activation status
|
|
265
|
+
* GET /api/v1/tenant/status
|
|
266
|
+
* @requiresPermission {Controller} Authenticated (bearer)
|
|
267
|
+
* @async
|
|
268
|
+
* @function getTenantStatus
|
|
269
|
+
* @param {string} controllerUrl - Controller base URL
|
|
270
|
+
* @param {Object} authConfig - Authentication configuration
|
|
271
|
+
* @returns {Promise<Object>} Tenant status response (`status`: PENDING, ACTIVE, INACTIVE)
|
|
272
|
+
* @throws {Error} If request fails
|
|
273
|
+
*/
|
|
274
|
+
async function getTenantStatus(controllerUrl, authConfig) {
|
|
275
|
+
const client = new ApiClient(controllerUrl, authConfig);
|
|
276
|
+
return await client.get('/api/v1/tenant/status');
|
|
277
|
+
}
|
|
278
|
+
|
|
243
279
|
module.exports = {
|
|
280
|
+
validateEnvironmentDeployment,
|
|
281
|
+
getTenantStatus,
|
|
244
282
|
listEnvironments,
|
|
245
283
|
createEnvironment,
|
|
246
284
|
getEnvironment,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Mori Marketplace installation bootstrap API (public, unauthenticated).
|
|
3
|
+
* Used only for DOCKER installation registration; AZURE bootstrap runs inside the Marketplace
|
|
4
|
+
* template deployment script.
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { ApiClient } = require('./index');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Register an installation and receive its Service API Key exactly once.
|
|
13
|
+
* POST /api/v1/marketplace/bootstrap
|
|
14
|
+
* @requiresPermission Public (Mori; no authentication)
|
|
15
|
+
* @async
|
|
16
|
+
* @function bootstrapMarketplaceInstallation
|
|
17
|
+
* @param {string} moriBaseUrl - Mori base URL (no trailing slash)
|
|
18
|
+
* @param {Object} body - Request body
|
|
19
|
+
* @param {string} body.subscriptionId - Stable installation UUID
|
|
20
|
+
* @param {string} body.tenantId - Stable owner UUID
|
|
21
|
+
* @param {string} body.resourceGroupName - Installation label
|
|
22
|
+
* @param {string} body.cloudVendor - AZURE or DOCKER
|
|
23
|
+
* @param {string} [body.marketplaceSubscriptionId] - Marketplace SaaS subscription; empty selects Community
|
|
24
|
+
* @returns {Promise<Object>} API response; `data.apiKey` is secret and must never be logged
|
|
25
|
+
*/
|
|
26
|
+
async function bootstrapMarketplaceInstallation(moriBaseUrl, body) {
|
|
27
|
+
const client = new ApiClient(moriBaseUrl, {});
|
|
28
|
+
return await client.post('/api/v1/marketplace/bootstrap', { body });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { bootstrapMarketplaceInstallation };
|