cli4ai 1.2.9 → 1.2.11
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/dist/commands/add.js +1 -14
- package/dist/core/config.js +33 -9
- package/dist/core/execute.js +30 -13
- package/dist/core/remote-client.js +48 -13
- package/dist/core/routine-engine.d.ts +1 -0
- package/dist/core/secrets.js +14 -4
- package/dist/dashboard/api/endpoints.js +1 -0
- package/dist/dashboard/db/runs.d.ts +2 -1
- package/dist/dashboard/db/runs.js +8 -3
- package/dist/dashboard/db/schema.d.ts +1 -0
- package/dist/dashboard/db/schema.js +1 -0
- package/{src/dashboard/public/assets/index-pZeAAQwj.js → dist/dashboard/public/assets/index-BKbXA4_4.js} +54 -54
- package/{src → dist}/dashboard/public/index.html +1 -1
- package/package.json +3 -3
- /package/{src → dist}/dashboard/public/assets/index-DN1hIAMO.css +0 -0
package/dist/commands/add.js
CHANGED
|
@@ -19,20 +19,7 @@ const CLI4AI_SCOPED_PKG_PATTERN = /^@cli4ai\/[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
|
|
|
19
19
|
const URL_LIKE_PATTERN = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//;
|
|
20
20
|
function validatePackageSpecifier(pkg) {
|
|
21
21
|
if (URL_LIKE_PATTERN.test(pkg)) {
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
parsed = new URL(pkg);
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
outputError('INVALID_INPUT', 'Invalid URL', { url: pkg });
|
|
28
|
-
}
|
|
29
|
-
if (parsed.protocol !== 'https:') {
|
|
30
|
-
outputError('INVALID_INPUT', 'Unsupported URL protocol', {
|
|
31
|
-
url: pkg,
|
|
32
|
-
protocol: parsed.protocol,
|
|
33
|
-
allowed: ['https:']
|
|
34
|
-
});
|
|
35
|
-
}
|
|
22
|
+
// URLs are not supported - reject immediately
|
|
36
23
|
outputError('INVALID_INPUT', 'Installing from URLs is not supported', {
|
|
37
24
|
url: pkg,
|
|
38
25
|
hint: 'Use a local path (./path) or a package name (e.g. github, @cli4ai/github)'
|
package/dist/core/config.js
CHANGED
|
@@ -476,8 +476,10 @@ export function getGlobalPackages() {
|
|
|
476
476
|
installedAt: new Date().toISOString()
|
|
477
477
|
});
|
|
478
478
|
}
|
|
479
|
-
catch {
|
|
480
|
-
//
|
|
479
|
+
catch (err) {
|
|
480
|
+
// Log warning for debugging, but continue to other packages
|
|
481
|
+
const errMessage = err instanceof Error ? err.message : String(err);
|
|
482
|
+
console.error(`Warning: Failed to load package manifest at ${manifestPath}: ${errMessage}`);
|
|
481
483
|
}
|
|
482
484
|
}
|
|
483
485
|
}
|
|
@@ -512,8 +514,10 @@ export function getLocalPackages(projectDir) {
|
|
|
512
514
|
installedAt: new Date().toISOString()
|
|
513
515
|
});
|
|
514
516
|
}
|
|
515
|
-
catch {
|
|
516
|
-
//
|
|
517
|
+
catch (err) {
|
|
518
|
+
// Log warning for debugging, but continue to other packages
|
|
519
|
+
const errMessage = err instanceof Error ? err.message : String(err);
|
|
520
|
+
console.error(`Warning: Failed to load package manifest at ${manifestPath}: ${errMessage}`);
|
|
517
521
|
}
|
|
518
522
|
}
|
|
519
523
|
}
|
|
@@ -610,7 +614,11 @@ function getPackagesFromGlobalDir(globalDir) {
|
|
|
610
614
|
});
|
|
611
615
|
continue;
|
|
612
616
|
}
|
|
613
|
-
catch {
|
|
617
|
+
catch (err) {
|
|
618
|
+
// Log warning but continue - cli4ai.json is invalid, try package.json
|
|
619
|
+
const errMessage = err instanceof Error ? err.message : String(err);
|
|
620
|
+
console.error(`Warning: Failed to load cli4ai.json at ${cli4aiJsonPath}: ${errMessage}`);
|
|
621
|
+
}
|
|
614
622
|
}
|
|
615
623
|
if (existsSync(pkgJsonPath)) {
|
|
616
624
|
try {
|
|
@@ -623,11 +631,19 @@ function getPackagesFromGlobalDir(globalDir) {
|
|
|
623
631
|
installedAt: new Date().toISOString()
|
|
624
632
|
});
|
|
625
633
|
}
|
|
626
|
-
catch {
|
|
634
|
+
catch (err) {
|
|
635
|
+
// Log warning but continue to next package
|
|
636
|
+
const errMessage = err instanceof Error ? err.message : String(err);
|
|
637
|
+
console.error(`Warning: Failed to load package.json at ${pkgJsonPath}: ${errMessage}`);
|
|
638
|
+
}
|
|
627
639
|
}
|
|
628
640
|
}
|
|
629
641
|
}
|
|
630
|
-
catch {
|
|
642
|
+
catch (err) {
|
|
643
|
+
// Log error but return partial results
|
|
644
|
+
const errMessage = err instanceof Error ? err.message : String(err);
|
|
645
|
+
console.error(`Warning: Error scanning global packages directory ${cli4aiDir}: ${errMessage}`);
|
|
646
|
+
}
|
|
631
647
|
return packages;
|
|
632
648
|
}
|
|
633
649
|
/**
|
|
@@ -685,7 +701,11 @@ function findPackageInGlobalDir(globalDir, name) {
|
|
|
685
701
|
installedAt: new Date().toISOString()
|
|
686
702
|
};
|
|
687
703
|
}
|
|
688
|
-
catch {
|
|
704
|
+
catch (err) {
|
|
705
|
+
// Log warning but continue to try package.json
|
|
706
|
+
const errMessage = err instanceof Error ? err.message : String(err);
|
|
707
|
+
console.error(`Warning: Failed to load cli4ai.json at ${manifestPath}: ${errMessage}`);
|
|
708
|
+
}
|
|
689
709
|
}
|
|
690
710
|
// Even without cli4ai.json, try package.json
|
|
691
711
|
const pkgJsonPath = resolve(scopedPath, 'package.json');
|
|
@@ -700,7 +720,11 @@ function findPackageInGlobalDir(globalDir, name) {
|
|
|
700
720
|
installedAt: new Date().toISOString()
|
|
701
721
|
};
|
|
702
722
|
}
|
|
703
|
-
catch {
|
|
723
|
+
catch (err) {
|
|
724
|
+
// Log warning - package has no valid manifest
|
|
725
|
+
const errMessage = err instanceof Error ? err.message : String(err);
|
|
726
|
+
console.error(`Warning: Failed to load package.json at ${pkgJsonPath}: ${errMessage}`);
|
|
727
|
+
}
|
|
704
728
|
}
|
|
705
729
|
return null;
|
|
706
730
|
}
|
package/dist/core/execute.js
CHANGED
|
@@ -429,22 +429,31 @@ export async function executeTool(options) {
|
|
|
429
429
|
...(options.env ?? {})
|
|
430
430
|
}
|
|
431
431
|
});
|
|
432
|
+
// Handle stdin - add error handler to prevent unhandled errors on broken pipes
|
|
433
|
+
proc.stdin.on('error', (err) => {
|
|
434
|
+
// Ignore EPIPE errors - they occur when the child process exits before reading stdin
|
|
435
|
+
if (err.code !== 'EPIPE') {
|
|
436
|
+
// Log other errors but don't throw - the process will handle termination
|
|
437
|
+
console.error(`stdin error: ${err.message}`);
|
|
438
|
+
}
|
|
439
|
+
});
|
|
432
440
|
if (options.stdin !== undefined) {
|
|
433
441
|
proc.stdin.write(options.stdin);
|
|
434
442
|
proc.stdin.end();
|
|
435
443
|
}
|
|
436
444
|
else {
|
|
437
|
-
// Don
|
|
445
|
+
// Don't block on stdin if nothing is provided
|
|
438
446
|
proc.stdin.end();
|
|
439
447
|
}
|
|
440
448
|
// Set up line-by-line streaming for callbacks
|
|
441
449
|
const stdoutLines = [];
|
|
442
450
|
const stderrLines = [];
|
|
443
|
-
|
|
451
|
+
// Track readline interfaces so we can close them on timeout/exit
|
|
452
|
+
let stdoutRl;
|
|
453
|
+
let stderrRl;
|
|
444
454
|
if (proc.stdout) {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
rl.on('line', (line) => {
|
|
455
|
+
stdoutRl = createInterface({ input: proc.stdout, crlfDelay: Infinity });
|
|
456
|
+
stdoutRl.on('line', (line) => {
|
|
448
457
|
stdoutLines.push(line);
|
|
449
458
|
if (options.onStdoutLine) {
|
|
450
459
|
options.onStdoutLine(line);
|
|
@@ -452,9 +461,8 @@ export async function executeTool(options) {
|
|
|
452
461
|
});
|
|
453
462
|
}
|
|
454
463
|
if (proc.stderr) {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
rl.on('line', (line) => {
|
|
464
|
+
stderrRl = createInterface({ input: proc.stderr, crlfDelay: Infinity });
|
|
465
|
+
stderrRl.on('line', (line) => {
|
|
458
466
|
stderrLines.push(line);
|
|
459
467
|
if (teeStderr) {
|
|
460
468
|
process.stderr.write(line + '\n');
|
|
@@ -464,9 +472,21 @@ export async function executeTool(options) {
|
|
|
464
472
|
}
|
|
465
473
|
});
|
|
466
474
|
}
|
|
475
|
+
// Helper to clean up readline interfaces
|
|
476
|
+
const cleanupReadlines = () => {
|
|
477
|
+
if (stdoutRl) {
|
|
478
|
+
stdoutRl.close();
|
|
479
|
+
stdoutRl = undefined;
|
|
480
|
+
}
|
|
481
|
+
if (stderrRl) {
|
|
482
|
+
stderrRl.close();
|
|
483
|
+
stderrRl = undefined;
|
|
484
|
+
}
|
|
485
|
+
};
|
|
467
486
|
let timeout;
|
|
468
487
|
if (options.timeoutMs && options.timeoutMs > 0) {
|
|
469
488
|
timeout = setTimeout(() => {
|
|
489
|
+
cleanupReadlines();
|
|
470
490
|
try {
|
|
471
491
|
proc.kill('SIGTERM');
|
|
472
492
|
}
|
|
@@ -485,13 +505,10 @@ export async function executeTool(options) {
|
|
|
485
505
|
}).finally(() => {
|
|
486
506
|
if (timeout)
|
|
487
507
|
clearTimeout(timeout);
|
|
488
|
-
// Close all readline interfaces
|
|
489
|
-
for (const rl of readlineInterfaces) {
|
|
490
|
-
rl.close();
|
|
491
|
-
}
|
|
492
508
|
});
|
|
493
|
-
// Small delay to ensure all readline events are processed
|
|
509
|
+
// Small delay to ensure all readline events are processed before cleanup
|
|
494
510
|
await new Promise(r => setTimeout(r, 10));
|
|
511
|
+
cleanupReadlines();
|
|
495
512
|
return {
|
|
496
513
|
exitCode,
|
|
497
514
|
durationMs: Date.now() - startTime,
|
|
@@ -34,6 +34,24 @@ export class RemoteApiError extends Error {
|
|
|
34
34
|
this.name = 'RemoteApiError';
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Safely parse JSON response body, returning null on parse errors
|
|
39
|
+
*/
|
|
40
|
+
function safeJsonParse(body) {
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(body);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Extract error info from response body
|
|
50
|
+
*/
|
|
51
|
+
function extractErrorInfo(body) {
|
|
52
|
+
const parsed = safeJsonParse(body);
|
|
53
|
+
return parsed?.error ?? {};
|
|
54
|
+
}
|
|
37
55
|
function makeRequest(url, method, headers, body, timeoutMs = 30000) {
|
|
38
56
|
return new Promise((resolve, reject) => {
|
|
39
57
|
const isHttps = url.protocol === 'https:';
|
|
@@ -97,10 +115,13 @@ export async function remoteHealth(remoteName) {
|
|
|
97
115
|
try {
|
|
98
116
|
const response = await makeRequest(url, 'GET', buildHeaders(remote));
|
|
99
117
|
if (response.statusCode !== 200) {
|
|
100
|
-
const error =
|
|
101
|
-
throw new RemoteApiError(remoteName, response.statusCode, error
|
|
118
|
+
const error = extractErrorInfo(response.body);
|
|
119
|
+
throw new RemoteApiError(remoteName, response.statusCode, error.code ?? 'API_ERROR', error.message ?? 'Unknown error', error.details);
|
|
120
|
+
}
|
|
121
|
+
const data = safeJsonParse(response.body);
|
|
122
|
+
if (!data) {
|
|
123
|
+
throw new RemoteApiError(remoteName, response.statusCode, 'PARSE_ERROR', 'Invalid JSON response from server');
|
|
102
124
|
}
|
|
103
|
-
const data = JSON.parse(response.body);
|
|
104
125
|
updateRemoteLastConnected(remoteName);
|
|
105
126
|
return data;
|
|
106
127
|
}
|
|
@@ -119,11 +140,15 @@ export async function remoteListPackages(remoteName) {
|
|
|
119
140
|
try {
|
|
120
141
|
const response = await makeRequest(url, 'GET', buildHeaders(remote));
|
|
121
142
|
if (response.statusCode !== 200) {
|
|
122
|
-
const error =
|
|
123
|
-
throw new RemoteApiError(remoteName, response.statusCode, error
|
|
143
|
+
const error = extractErrorInfo(response.body);
|
|
144
|
+
throw new RemoteApiError(remoteName, response.statusCode, error.code ?? 'API_ERROR', error.message ?? 'Unknown error', error.details);
|
|
145
|
+
}
|
|
146
|
+
const data = safeJsonParse(response.body);
|
|
147
|
+
if (!data) {
|
|
148
|
+
throw new RemoteApiError(remoteName, response.statusCode, 'PARSE_ERROR', 'Invalid JSON response from server');
|
|
124
149
|
}
|
|
125
150
|
updateRemoteLastConnected(remoteName);
|
|
126
|
-
return
|
|
151
|
+
return data;
|
|
127
152
|
}
|
|
128
153
|
catch (err) {
|
|
129
154
|
if (err instanceof RemoteApiError)
|
|
@@ -143,11 +168,15 @@ export async function remotePackageInfo(remoteName, packageName) {
|
|
|
143
168
|
return null;
|
|
144
169
|
}
|
|
145
170
|
if (response.statusCode !== 200) {
|
|
146
|
-
const error =
|
|
147
|
-
throw new RemoteApiError(remoteName, response.statusCode, error
|
|
171
|
+
const error = extractErrorInfo(response.body);
|
|
172
|
+
throw new RemoteApiError(remoteName, response.statusCode, error.code ?? 'API_ERROR', error.message ?? 'Unknown error', error.details);
|
|
173
|
+
}
|
|
174
|
+
const data = safeJsonParse(response.body);
|
|
175
|
+
if (!data) {
|
|
176
|
+
throw new RemoteApiError(remoteName, response.statusCode, 'PARSE_ERROR', 'Invalid JSON response from server');
|
|
148
177
|
}
|
|
149
178
|
updateRemoteLastConnected(remoteName);
|
|
150
|
-
return
|
|
179
|
+
return data;
|
|
151
180
|
}
|
|
152
181
|
catch (err) {
|
|
153
182
|
if (err instanceof RemoteApiError)
|
|
@@ -174,9 +203,12 @@ export async function remoteRunTool(remoteName, options) {
|
|
|
174
203
|
const requestTimeout = (options.timeout ?? 30000) + 10000;
|
|
175
204
|
try {
|
|
176
205
|
const response = await makeRequest(url, 'POST', buildHeaders(remote), body, requestTimeout);
|
|
177
|
-
const data =
|
|
178
|
-
|
|
179
|
-
|
|
206
|
+
const data = safeJsonParse(response.body);
|
|
207
|
+
if (!data) {
|
|
208
|
+
throw new RemoteApiError(remoteName, response.statusCode, 'PARSE_ERROR', 'Invalid JSON response from server');
|
|
209
|
+
}
|
|
210
|
+
// Check for API-level error (4xx errors except 500)
|
|
211
|
+
if (data.error && response.statusCode >= 400 && response.statusCode < 500) {
|
|
180
212
|
throw new RemoteApiError(remoteName, response.statusCode, data.error.code ?? 'API_ERROR', data.error.message ?? 'Unknown error', data.error.details);
|
|
181
213
|
}
|
|
182
214
|
updateRemoteLastConnected(remoteName);
|
|
@@ -201,7 +233,10 @@ export async function remoteRunRoutine(remoteName, routineName, vars) {
|
|
|
201
233
|
if (response.statusCode === 404) {
|
|
202
234
|
throw new RemoteApiError(remoteName, 404, 'NOT_FOUND', `Routine not found: ${routineName}`);
|
|
203
235
|
}
|
|
204
|
-
const data =
|
|
236
|
+
const data = safeJsonParse(response.body);
|
|
237
|
+
if (!data) {
|
|
238
|
+
throw new RemoteApiError(remoteName, response.statusCode, 'PARSE_ERROR', 'Invalid JSON response from server');
|
|
239
|
+
}
|
|
205
240
|
if (data.error && response.statusCode >= 400) {
|
|
206
241
|
throw new RemoteApiError(remoteName, response.statusCode, data.error.code ?? 'API_ERROR', data.error.message ?? 'Unknown error', data.error.details);
|
|
207
242
|
}
|
package/dist/core/secrets.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* This ensures that even if an attacker knows the hostname and username,
|
|
18
18
|
* they cannot reconstruct the key without access to the salt file.
|
|
19
19
|
*/
|
|
20
|
-
import { readFileSync, writeFileSync, existsSync, chmodSync, statSync, mkdirSync } from 'fs';
|
|
20
|
+
import { readFileSync, writeFileSync, existsSync, chmodSync, statSync, mkdirSync, renameSync } from 'fs';
|
|
21
21
|
import { createCipheriv, createDecipheriv, randomBytes, createHash, pbkdf2Sync } from 'crypto';
|
|
22
22
|
import { hostname, userInfo, platform } from 'os';
|
|
23
23
|
import { dirname, resolve } from 'path';
|
|
@@ -224,6 +224,15 @@ function checkFilePermissions(filePath, fileName) {
|
|
|
224
224
|
// Ignore errors
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Atomically write to a file using write-then-rename pattern.
|
|
229
|
+
* This prevents race conditions and ensures the file is never in a partial state.
|
|
230
|
+
*/
|
|
231
|
+
function atomicWriteFile(filePath, content, mode) {
|
|
232
|
+
const tmpPath = filePath + '.tmp.' + process.pid + '.' + Date.now();
|
|
233
|
+
writeFileSync(tmpPath, content, { mode });
|
|
234
|
+
renameSync(tmpPath, filePath);
|
|
235
|
+
}
|
|
227
236
|
/**
|
|
228
237
|
* Load all secrets from vault
|
|
229
238
|
*/
|
|
@@ -259,13 +268,14 @@ function loadSecrets() {
|
|
|
259
268
|
}
|
|
260
269
|
}
|
|
261
270
|
// If we successfully decrypted legacy secrets, rewrite those entries using the current scheme.
|
|
271
|
+
// Use atomic write to prevent race conditions with concurrent processes.
|
|
262
272
|
if (Object.keys(migrated).length > 0) {
|
|
263
273
|
try {
|
|
264
274
|
const updated = { ...encrypted };
|
|
265
275
|
for (const [k, v] of Object.entries(migrated)) {
|
|
266
276
|
updated[k] = encrypt(v);
|
|
267
277
|
}
|
|
268
|
-
|
|
278
|
+
atomicWriteFile(secretsFilePath, JSON.stringify(updated, null, 2), 0o600);
|
|
269
279
|
}
|
|
270
280
|
catch {
|
|
271
281
|
// Best-effort migration only. If we can't write (e.g. restricted FS), still return decrypted secrets.
|
|
@@ -278,7 +288,7 @@ function loadSecrets() {
|
|
|
278
288
|
}
|
|
279
289
|
}
|
|
280
290
|
/**
|
|
281
|
-
* Save all secrets to vault
|
|
291
|
+
* Save all secrets to vault using atomic write
|
|
282
292
|
*/
|
|
283
293
|
function saveSecrets(secrets) {
|
|
284
294
|
const secretsFilePath = getSecretsFilePath();
|
|
@@ -287,7 +297,7 @@ function saveSecrets(secrets) {
|
|
|
287
297
|
for (const [key, value] of Object.entries(secrets)) {
|
|
288
298
|
encrypted[key] = encrypt(value);
|
|
289
299
|
}
|
|
290
|
-
|
|
300
|
+
atomicWriteFile(secretsFilePath, JSON.stringify(encrypted, null, 2), 0o600);
|
|
291
301
|
}
|
|
292
302
|
/**
|
|
293
303
|
* Get a secret value (env var takes precedence)
|
|
@@ -24,6 +24,7 @@ export interface CreateStepInput {
|
|
|
24
24
|
status: StepStatus;
|
|
25
25
|
exitCode?: number;
|
|
26
26
|
durationMs?: number;
|
|
27
|
+
inputJson?: unknown;
|
|
27
28
|
stdout?: string;
|
|
28
29
|
stderr?: string;
|
|
29
30
|
jsonOutput?: unknown;
|
|
@@ -109,7 +110,7 @@ export declare function upsertStep(input: CreateStepInput): RunStepRecord;
|
|
|
109
110
|
/**
|
|
110
111
|
* Update a step
|
|
111
112
|
*/
|
|
112
|
-
export declare function updateStep(id: string, updates: Partial<Pick<CreateStepInput, 'status' | 'exitCode' | 'durationMs' | 'stdout' | 'stderr' | 'jsonOutput' | 'errorCode' | 'errorMessage'>>): RunStepRecord | null;
|
|
113
|
+
export declare function updateStep(id: string, updates: Partial<Pick<CreateStepInput, 'status' | 'exitCode' | 'durationMs' | 'inputJson' | 'stdout' | 'stderr' | 'jsonOutput' | 'errorCode' | 'errorMessage'>>): RunStepRecord | null;
|
|
113
114
|
/**
|
|
114
115
|
* Append a log line
|
|
115
116
|
*/
|
|
@@ -204,10 +204,10 @@ export function createStep(input) {
|
|
|
204
204
|
const stmt = db.prepare(`
|
|
205
205
|
INSERT INTO run_steps (
|
|
206
206
|
id, run_id, step_id, step_type, status, exit_code, duration_ms,
|
|
207
|
-
stdout, stderr, json_output, error_code, error_message, created_at
|
|
208
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
207
|
+
input_json, stdout, stderr, json_output, error_code, error_message, created_at
|
|
208
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
209
209
|
`);
|
|
210
|
-
stmt.run(id, input.runId, input.stepId, input.stepType, input.status, input.exitCode ?? null, input.durationMs ?? null, input.stdout ?? null, input.stderr ?? null, input.jsonOutput ? JSON.stringify(input.jsonOutput) : null, input.errorCode ?? null, input.errorMessage ?? null, now);
|
|
210
|
+
stmt.run(id, input.runId, input.stepId, input.stepType, input.status, input.exitCode ?? null, input.durationMs ?? null, input.inputJson ? JSON.stringify(input.inputJson) : null, input.stdout ?? null, input.stderr ?? null, input.jsonOutput ? JSON.stringify(input.jsonOutput) : null, input.errorCode ?? null, input.errorMessage ?? null, now);
|
|
211
211
|
return getStep(id);
|
|
212
212
|
}
|
|
213
213
|
catch (error) {
|
|
@@ -249,6 +249,7 @@ export function upsertStep(input) {
|
|
|
249
249
|
status: input.status,
|
|
250
250
|
exitCode: input.exitCode,
|
|
251
251
|
durationMs: input.durationMs,
|
|
252
|
+
inputJson: input.inputJson,
|
|
252
253
|
stdout: input.stdout,
|
|
253
254
|
stderr: input.stderr,
|
|
254
255
|
jsonOutput: input.jsonOutput,
|
|
@@ -280,6 +281,10 @@ export function updateStep(id, updates) {
|
|
|
280
281
|
fields.push('duration_ms = ?');
|
|
281
282
|
values.push(updates.durationMs);
|
|
282
283
|
}
|
|
284
|
+
if (updates.inputJson !== undefined) {
|
|
285
|
+
fields.push('input_json = ?');
|
|
286
|
+
values.push(JSON.stringify(updates.inputJson));
|
|
287
|
+
}
|
|
283
288
|
if (updates.stdout !== undefined) {
|
|
284
289
|
fields.push('stdout = ?');
|
|
285
290
|
values.push(updates.stdout);
|