agent-state-machine 2.0.11 → 2.0.12
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/README.md +4 -15
- package/bin/cli.js +14 -11
- package/lib/llm.js +3 -3
- package/lib/runtime/runtime.js +59 -6
- package/lib/setup.js +25 -17
- package/package.json +1 -1
- package/vercel-server/ui/index.html +37 -3
package/README.md
CHANGED
|
@@ -56,6 +56,7 @@ Workflows live in:
|
|
|
56
56
|
```text
|
|
57
57
|
workflows/<name>/
|
|
58
58
|
├── workflow.js # Native JS workflow (async/await)
|
|
59
|
+
├── config.js # Model/API key configuration
|
|
59
60
|
├── package.json # Sets "type": "module" for this workflow folder
|
|
60
61
|
├── agents/ # Custom agents (.js/.mjs/.cjs or .md)
|
|
61
62
|
├── interactions/ # Human-in-the-loop files (auto-created)
|
|
@@ -67,6 +68,8 @@ workflows/<name>/
|
|
|
67
68
|
|
|
68
69
|
## Writing workflows (native JS)
|
|
69
70
|
|
|
71
|
+
Edit `config.js` to set models and API keys for the workflow.
|
|
72
|
+
|
|
70
73
|
```js
|
|
71
74
|
/**
|
|
72
75
|
/**
|
|
@@ -83,20 +86,6 @@ workflows/<name>/
|
|
|
83
86
|
import { agent, memory, askHuman, parallel } from 'agent-state-machine';
|
|
84
87
|
import { notify } from './scripts/mac-notification.js';
|
|
85
88
|
|
|
86
|
-
// Model configuration (also supports models in a separate config export)
|
|
87
|
-
export const config = {
|
|
88
|
-
models: {
|
|
89
|
-
low: "gemini",
|
|
90
|
-
med: "codex --model gpt-5.2",
|
|
91
|
-
high: "claude -m claude-opus-4-20250514 -p",
|
|
92
|
-
},
|
|
93
|
-
apiKeys: {
|
|
94
|
-
gemini: process.env.GEMINI_API_KEY,
|
|
95
|
-
anthropic: process.env.ANTHROPIC_API_KEY,
|
|
96
|
-
openai: process.env.OPENAI_API_KEY,
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
|
|
100
89
|
export default async function() {
|
|
101
90
|
console.log('Starting project-builder workflow...');
|
|
102
91
|
|
|
@@ -298,7 +287,7 @@ export const config = {
|
|
|
298
287
|
};
|
|
299
288
|
```
|
|
300
289
|
|
|
301
|
-
The runtime captures the fully-built prompt in `state/history.jsonl`, viewable in the browser with live updates when running with the `--local` flag or via the remote URL. Remote follow links persist across runs (stored in `
|
|
290
|
+
The runtime captures the fully-built prompt in `state/history.jsonl`, viewable in the browser with live updates when running with the `--local` flag or via the remote URL. Remote follow links persist across runs (stored in `config.js`) unless you pass `-n`/`--new` to regenerate.
|
|
302
291
|
|
|
303
292
|
---
|
|
304
293
|
|
package/bin/cli.js
CHANGED
|
@@ -63,6 +63,7 @@ Environment Variables:
|
|
|
63
63
|
Workflow Structure:
|
|
64
64
|
workflows/<name>/
|
|
65
65
|
├── workflow.js # Native JS workflow (async/await)
|
|
66
|
+
├── config.js # Model/API key configuration
|
|
66
67
|
├── package.json # Sets "type": "module" for this workflow folder
|
|
67
68
|
├── agents/ # Custom agents (.js/.mjs/.cjs or .md)
|
|
68
69
|
├── interactions/ # Human-in-the-loop files (auto-created)
|
|
@@ -185,8 +186,8 @@ function findConfigObjectRange(source) {
|
|
|
185
186
|
return null;
|
|
186
187
|
}
|
|
187
188
|
|
|
188
|
-
function
|
|
189
|
-
const source = fs.readFileSync(
|
|
189
|
+
function readRemotePathFromConfig(configFile) {
|
|
190
|
+
const source = fs.readFileSync(configFile, 'utf-8');
|
|
190
191
|
const range = findConfigObjectRange(source);
|
|
191
192
|
if (!range) return null;
|
|
192
193
|
const configSource = source.slice(range.start, range.end + 1);
|
|
@@ -194,19 +195,19 @@ function readRemotePathFromWorkflow(workflowFile) {
|
|
|
194
195
|
return match ? match[2] : null;
|
|
195
196
|
}
|
|
196
197
|
|
|
197
|
-
function
|
|
198
|
-
const source = fs.readFileSync(
|
|
198
|
+
function writeRemotePathToConfig(configFile, remotePath) {
|
|
199
|
+
const source = fs.readFileSync(configFile, 'utf-8');
|
|
199
200
|
const range = findConfigObjectRange(source);
|
|
200
201
|
const remoteLine = `remotePath: "${remotePath}"`;
|
|
201
202
|
|
|
202
203
|
if (!range) {
|
|
203
204
|
const hasConfigExport = /export\s+const\s+config\s*=/.test(source);
|
|
204
205
|
if (hasConfigExport) {
|
|
205
|
-
throw new Error('
|
|
206
|
+
throw new Error('Config export is not an object literal; add remotePath manually.');
|
|
206
207
|
}
|
|
207
208
|
const trimmed = source.replace(/\s*$/, '');
|
|
208
209
|
const appended = `${trimmed}\n\nexport const config = {\n ${remoteLine}\n};\n`;
|
|
209
|
-
fs.writeFileSync(
|
|
210
|
+
fs.writeFileSync(configFile, appended);
|
|
210
211
|
return;
|
|
211
212
|
}
|
|
212
213
|
|
|
@@ -247,15 +248,15 @@ function writeRemotePathToWorkflow(workflowFile, remotePath) {
|
|
|
247
248
|
source.slice(0, range.start) +
|
|
248
249
|
updatedConfigSource +
|
|
249
250
|
source.slice(range.end + 1);
|
|
250
|
-
fs.writeFileSync(
|
|
251
|
+
fs.writeFileSync(configFile, updatedSource);
|
|
251
252
|
}
|
|
252
253
|
|
|
253
|
-
function ensureRemotePath(
|
|
254
|
-
const existing =
|
|
254
|
+
function ensureRemotePath(configFile, { forceNew = false } = {}) {
|
|
255
|
+
const existing = readRemotePathFromConfig(configFile);
|
|
255
256
|
if (existing && !forceNew) return existing;
|
|
256
257
|
|
|
257
258
|
const remotePath = generateSessionToken();
|
|
258
|
-
|
|
259
|
+
writeRemotePathToConfig(configFile, remotePath);
|
|
259
260
|
return remotePath;
|
|
260
261
|
}
|
|
261
262
|
|
|
@@ -276,6 +277,7 @@ function summarizeStatus(state) {
|
|
|
276
277
|
if (s === 'COMPLETED') return ' [completed]';
|
|
277
278
|
if (s === 'FAILED') return ' [failed - can resume]';
|
|
278
279
|
if (s === 'PAUSED') return ' [paused - can resume]';
|
|
280
|
+
if (s === 'STOPPED') return ' [stopped - can resume]';
|
|
279
281
|
if (s === 'RUNNING') return ' [running]';
|
|
280
282
|
if (s === 'IDLE') return ' [idle]';
|
|
281
283
|
return state.status ? ` [${state.status}]` : '';
|
|
@@ -361,6 +363,7 @@ async function runOrResume(
|
|
|
361
363
|
}
|
|
362
364
|
|
|
363
365
|
const workflowUrl = pathToFileURL(entry).href;
|
|
366
|
+
const configFile = path.join(workflowDir, 'config.js');
|
|
364
367
|
|
|
365
368
|
let localServer = null;
|
|
366
369
|
let remoteUrl = null;
|
|
@@ -382,7 +385,7 @@ async function runOrResume(
|
|
|
382
385
|
|
|
383
386
|
// Enable remote follow mode if we have a URL
|
|
384
387
|
if (remoteUrl) {
|
|
385
|
-
const sessionToken = ensureRemotePath(
|
|
388
|
+
const sessionToken = ensureRemotePath(configFile, { forceNew: forceNewRemotePath });
|
|
386
389
|
await runtime.enableRemote(remoteUrl, { sessionToken });
|
|
387
390
|
}
|
|
388
391
|
|
package/lib/llm.js
CHANGED
|
@@ -314,7 +314,7 @@ async function executeAPI(provider, model, prompt, apiKey, options = {}) {
|
|
|
314
314
|
*
|
|
315
315
|
* @param {object} context - The workflow context (contains _config, _steering, etc.)
|
|
316
316
|
* @param {object} options - Options for the LLM call
|
|
317
|
-
* @param {string} options.model - Model key from
|
|
317
|
+
* @param {string} options.model - Model key from config.js models config
|
|
318
318
|
* @param {string} options.prompt - The prompt to send
|
|
319
319
|
* @param {boolean} options.includeContext - Whether to include context in prompt (default: true)
|
|
320
320
|
* @param {number} options.maxTokens - Max tokens for API calls (default: 4096)
|
|
@@ -359,7 +359,7 @@ export async function llm(context, options) {
|
|
|
359
359
|
|
|
360
360
|
if (!apiKey) {
|
|
361
361
|
throw new Error(
|
|
362
|
-
`No API key found for ${provider}. Set in
|
|
362
|
+
`No API key found for ${provider}. Set in config.js apiKeys or ${provider.toUpperCase()}_API_KEY env var`
|
|
363
363
|
);
|
|
364
364
|
}
|
|
365
365
|
|
|
@@ -459,4 +459,4 @@ export async function llmJSON(context, options) {
|
|
|
459
459
|
...response,
|
|
460
460
|
data: parseJSON(response.text)
|
|
461
461
|
};
|
|
462
|
-
}
|
|
462
|
+
}
|
package/lib/runtime/runtime.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import fs from 'fs';
|
|
10
10
|
import path from 'path';
|
|
11
11
|
import readline from 'readline';
|
|
12
|
+
import { pathToFileURL } from 'url';
|
|
12
13
|
import { createMemoryProxy } from './memory.js';
|
|
13
14
|
import { RemoteClient } from '../remote/client.js';
|
|
14
15
|
|
|
@@ -218,6 +219,50 @@ export class WorkflowRuntime {
|
|
|
218
219
|
async runWorkflow(workflowPath) {
|
|
219
220
|
setCurrentRuntime(this);
|
|
220
221
|
|
|
222
|
+
// Handle Ctrl+C and termination signals to update status before exit
|
|
223
|
+
const handleShutdown = async (signal) => {
|
|
224
|
+
this.status = 'STOPPED';
|
|
225
|
+
this._error = `Workflow interrupted by ${signal}`;
|
|
226
|
+
this.persist();
|
|
227
|
+
|
|
228
|
+
// Log to history (local file)
|
|
229
|
+
const historyEntry = {
|
|
230
|
+
timestamp: new Date().toISOString(),
|
|
231
|
+
event: 'WORKFLOW_STOPPED',
|
|
232
|
+
reason: signal
|
|
233
|
+
};
|
|
234
|
+
const line = JSON.stringify(historyEntry) + '\n';
|
|
235
|
+
let existing = '';
|
|
236
|
+
if (fs.existsSync(this.historyFile)) {
|
|
237
|
+
existing = fs.readFileSync(this.historyFile, 'utf-8');
|
|
238
|
+
}
|
|
239
|
+
fs.writeFileSync(this.historyFile, line + existing);
|
|
240
|
+
|
|
241
|
+
// Send to remote and wait for it to complete before exiting
|
|
242
|
+
if (this.remoteClient && this.remoteEnabled) {
|
|
243
|
+
try {
|
|
244
|
+
await this.remoteClient.sendEvent(historyEntry);
|
|
245
|
+
} catch {
|
|
246
|
+
// Ignore errors during shutdown
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
console.log(`\n${C.yellow}⚠ Workflow '${this.workflowName}' stopped (${signal})${C.reset}`);
|
|
251
|
+
cleanupSignalHandlers();
|
|
252
|
+
clearCurrentRuntime();
|
|
253
|
+
process.exit(130); // 128 + SIGINT (2)
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const sigintHandler = () => handleShutdown('SIGINT');
|
|
257
|
+
const sigtermHandler = () => handleShutdown('SIGTERM');
|
|
258
|
+
process.on('SIGINT', sigintHandler);
|
|
259
|
+
process.on('SIGTERM', sigtermHandler);
|
|
260
|
+
|
|
261
|
+
const cleanupSignalHandlers = () => {
|
|
262
|
+
process.removeListener('SIGINT', sigintHandler);
|
|
263
|
+
process.removeListener('SIGTERM', sigtermHandler);
|
|
264
|
+
};
|
|
265
|
+
|
|
221
266
|
try {
|
|
222
267
|
this.status = 'RUNNING';
|
|
223
268
|
this._error = null;
|
|
@@ -226,18 +271,24 @@ export class WorkflowRuntime {
|
|
|
226
271
|
|
|
227
272
|
this.prependHistory({ event: 'WORKFLOW_STARTED' });
|
|
228
273
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
274
|
+
const configPath = path.join(this.workflowDir, 'config.js');
|
|
275
|
+
if (!fs.existsSync(configPath)) {
|
|
276
|
+
throw new Error(`config.js not found in ${this.workflowDir}`);
|
|
277
|
+
}
|
|
278
|
+
const configUrl = pathToFileURL(configPath);
|
|
279
|
+
configUrl.searchParams.set('t', Date.now().toString());
|
|
280
|
+
const configModule = await import(configUrl.href);
|
|
281
|
+
const cfg = configModule.config || configModule.default || {};
|
|
235
282
|
this.workflowConfig = {
|
|
236
283
|
models: cfg.models || {},
|
|
237
284
|
apiKeys: cfg.apiKeys || {},
|
|
238
285
|
description: cfg.description || ''
|
|
239
286
|
};
|
|
240
287
|
|
|
288
|
+
// Import workflow module
|
|
289
|
+
const workflowModule = await import(workflowPath);
|
|
290
|
+
const runFn = workflowModule.default || workflowModule.run || workflowModule;
|
|
291
|
+
|
|
241
292
|
if (typeof runFn !== 'function') {
|
|
242
293
|
throw new Error('Workflow module must export a default async function');
|
|
243
294
|
}
|
|
@@ -263,6 +314,7 @@ export class WorkflowRuntime {
|
|
|
263
314
|
console.error(`\n${C.red}✗ Workflow '${this.workflowName}' failed: ${err.message}${C.reset}`);
|
|
264
315
|
throw err;
|
|
265
316
|
} finally {
|
|
317
|
+
cleanupSignalHandlers();
|
|
266
318
|
clearCurrentRuntime();
|
|
267
319
|
}
|
|
268
320
|
}
|
|
@@ -406,6 +458,7 @@ export class WorkflowRuntime {
|
|
|
406
458
|
let statusColor = C.reset;
|
|
407
459
|
if (this.status === 'COMPLETED') statusColor = C.green;
|
|
408
460
|
if (this.status === 'FAILED') statusColor = C.red;
|
|
461
|
+
if (this.status === 'STOPPED') statusColor = C.yellow;
|
|
409
462
|
if (this.status === 'RUNNING') statusColor = C.blue;
|
|
410
463
|
if (this.status === 'IDLE') statusColor = C.gray;
|
|
411
464
|
|
package/lib/setup.js
CHANGED
|
@@ -62,20 +62,6 @@ async function setup(workflowName) {
|
|
|
62
62
|
import { agent, memory, askHuman, parallel } from 'agent-state-machine';
|
|
63
63
|
import { notify } from './scripts/mac-notification.js';
|
|
64
64
|
|
|
65
|
-
// Model configuration (also supports models in a separate config export)
|
|
66
|
-
export const config = {
|
|
67
|
-
models: {
|
|
68
|
-
low: "gemini",
|
|
69
|
-
med: "codex --model gpt-5.2",
|
|
70
|
-
high: "claude -m claude-opus-4-20250514 -p",
|
|
71
|
-
},
|
|
72
|
-
apiKeys: {
|
|
73
|
-
gemini: process.env.GEMINI_API_KEY,
|
|
74
|
-
anthropic: process.env.ANTHROPIC_API_KEY,
|
|
75
|
-
openai: process.env.OPENAI_API_KEY,
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
65
|
export default async function() {
|
|
80
66
|
console.log('Starting ${workflowName} workflow...');
|
|
81
67
|
|
|
@@ -119,6 +105,24 @@ export default async function() {
|
|
|
119
105
|
fs.writeFileSync(workflowFile, workflowJs);
|
|
120
106
|
console.log(` Created: ${path.relative(process.cwd(), workflowFile)}`);
|
|
121
107
|
|
|
108
|
+
const configJs = `export const config = {
|
|
109
|
+
models: {
|
|
110
|
+
low: "gemini",
|
|
111
|
+
med: "codex --model gpt-5.2",
|
|
112
|
+
high: "claude -m claude-opus-4-20250514 -p",
|
|
113
|
+
},
|
|
114
|
+
apiKeys: {
|
|
115
|
+
gemini: process.env.GEMINI_API_KEY,
|
|
116
|
+
anthropic: process.env.ANTHROPIC_API_KEY,
|
|
117
|
+
openai: process.env.OPENAI_API_KEY,
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
`;
|
|
121
|
+
|
|
122
|
+
const configFile = path.join(workflowDir, 'config.js');
|
|
123
|
+
fs.writeFileSync(configFile, configJs);
|
|
124
|
+
console.log(` Created: ${path.relative(process.cwd(), configFile)}`);
|
|
125
|
+
|
|
122
126
|
// Create example JS agent (ESM)
|
|
123
127
|
// Create example JS agent (ESM)
|
|
124
128
|
const exampleAgent = `/**
|
|
@@ -138,7 +142,7 @@ export default async function handler(context) {
|
|
|
138
142
|
console.log('[Agent: example] Steering loaded (' + context._steering.global.length + ' chars)');
|
|
139
143
|
}
|
|
140
144
|
|
|
141
|
-
// Example: Call an LLM (configure models in
|
|
145
|
+
// Example: Call an LLM (configure models in config.js)
|
|
142
146
|
// const response = await llm(context, {
|
|
143
147
|
// model: 'smart',
|
|
144
148
|
// prompt: 'Say hello and describe what you can help with.'
|
|
@@ -295,6 +299,7 @@ A workflow created with agent-state-machine (native JS format).
|
|
|
295
299
|
\\\`\\\`\\\`
|
|
296
300
|
${workflowName}/
|
|
297
301
|
├── workflow.js # Native JS workflow (async/await)
|
|
302
|
+
├── config.js # Model/API key configuration
|
|
298
303
|
├── package.json # Sets "type": "module" for this workflow folder
|
|
299
304
|
├── agents/ # Custom agents (.js/.mjs/.cjs or .md)
|
|
300
305
|
├── interactions/ # Human-in-the-loop inputs (created at runtime)
|
|
@@ -304,6 +309,8 @@ ${workflowName}/
|
|
|
304
309
|
|
|
305
310
|
## Usage
|
|
306
311
|
|
|
312
|
+
Edit \`config.js\` to set models and API keys for this workflow.
|
|
313
|
+
|
|
307
314
|
Run the workflow (or resume if interrupted):
|
|
308
315
|
\\\`\\\`\\\`bash
|
|
309
316
|
state-machine run ${workflowName}
|
|
@@ -411,8 +418,9 @@ Generate a greeting for {{name}}.
|
|
|
411
418
|
console.log(`\n✓ Workflow '${workflowName}' created successfully!\n`);
|
|
412
419
|
console.log('Next steps:');
|
|
413
420
|
console.log(` 1. Edit workflows/${workflowName}/workflow.js to implement your flow`);
|
|
414
|
-
console.log(` 2.
|
|
415
|
-
console.log(` 3.
|
|
421
|
+
console.log(` 2. Edit workflows/${workflowName}/config.js to set models/API keys`);
|
|
422
|
+
console.log(` 3. Add custom agents in workflows/${workflowName}/agents/`);
|
|
423
|
+
console.log(` 4. Run: state-machine run ${workflowName}\n`);
|
|
416
424
|
}
|
|
417
425
|
|
|
418
426
|
export { setup };
|
package/package.json
CHANGED
|
@@ -486,11 +486,24 @@
|
|
|
486
486
|
|
|
487
487
|
useEffect(() => localStorage.setItem("rf_theme", theme), [theme]);
|
|
488
488
|
|
|
489
|
+
// Helper to check if workflow is currently running based on history
|
|
490
|
+
const isWorkflowRunning = (entries) => {
|
|
491
|
+
// Find the most recent workflow lifecycle event (history is newest-first)
|
|
492
|
+
for (const entry of entries) {
|
|
493
|
+
if (entry.event === "WORKFLOW_STARTED") return true;
|
|
494
|
+
if (entry.event === "WORKFLOW_STOPPED" ||
|
|
495
|
+
entry.event === "WORKFLOW_COMPLETED" ||
|
|
496
|
+
entry.event === "WORKFLOW_FAILED") return false;
|
|
497
|
+
}
|
|
498
|
+
return false; // No lifecycle events found
|
|
499
|
+
};
|
|
500
|
+
|
|
489
501
|
useEffect(() => {
|
|
490
502
|
if (history.length === 0) { setPendingInteraction(null); return; }
|
|
491
503
|
|
|
492
504
|
const resolvedSlugs = new Set();
|
|
493
505
|
let pending = null;
|
|
506
|
+
const workflowRunning = isWorkflowRunning(history);
|
|
494
507
|
|
|
495
508
|
for (const entry of history) {
|
|
496
509
|
const isResolution =
|
|
@@ -510,7 +523,8 @@
|
|
|
510
523
|
}
|
|
511
524
|
}
|
|
512
525
|
|
|
513
|
-
|
|
526
|
+
// Only show pending interaction if workflow is running
|
|
527
|
+
setPendingInteraction(workflowRunning ? pending : null);
|
|
514
528
|
}, [history]);
|
|
515
529
|
|
|
516
530
|
const fetchData = async () => {
|
|
@@ -520,8 +534,18 @@
|
|
|
520
534
|
if (data.entries) setHistory(data.entries);
|
|
521
535
|
if (data.workflowName) setWorkflowName(data.workflowName);
|
|
522
536
|
|
|
523
|
-
if
|
|
524
|
-
|
|
537
|
+
// Check if workflow is currently running based on most recent lifecycle event
|
|
538
|
+
const workflowRunning = isWorkflowRunning(data.entries || []);
|
|
539
|
+
|
|
540
|
+
if (workflowRunning) {
|
|
541
|
+
setStatus("connected");
|
|
542
|
+
} else if (token && data.cliConnected !== undefined) {
|
|
543
|
+
setStatus(data.cliConnected ? "connected" : "disconnected");
|
|
544
|
+
} else if (!token) {
|
|
545
|
+
setStatus("connected");
|
|
546
|
+
} else {
|
|
547
|
+
setStatus("disconnected");
|
|
548
|
+
}
|
|
525
549
|
|
|
526
550
|
setLoading(false);
|
|
527
551
|
return true;
|
|
@@ -569,6 +593,8 @@
|
|
|
569
593
|
break;
|
|
570
594
|
case "history":
|
|
571
595
|
setHistory(data.entries || []);
|
|
596
|
+
// Update status based on workflow lifecycle
|
|
597
|
+
setStatus(isWorkflowRunning(data.entries || []) ? "connected" : "disconnected");
|
|
572
598
|
break;
|
|
573
599
|
case "event":
|
|
574
600
|
setHistory((prev) => {
|
|
@@ -578,6 +604,14 @@
|
|
|
578
604
|
}
|
|
579
605
|
return [data, ...prev];
|
|
580
606
|
});
|
|
607
|
+
// Update status based on workflow lifecycle events
|
|
608
|
+
if (data.event === "WORKFLOW_STARTED") {
|
|
609
|
+
setStatus("connected");
|
|
610
|
+
} else if (data.event === "WORKFLOW_STOPPED" ||
|
|
611
|
+
data.event === "WORKFLOW_COMPLETED" ||
|
|
612
|
+
data.event === "WORKFLOW_FAILED") {
|
|
613
|
+
setStatus("disconnected");
|
|
614
|
+
}
|
|
581
615
|
break;
|
|
582
616
|
case "cli_connected":
|
|
583
617
|
case "cli_reconnected":
|