start-command 0.32.1 → 0.34.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/CHANGELOG.md +27 -0
- package/README.md +3 -0
- package/eslint.config.mjs +8 -0
- package/package.json +7 -3
- package/src/bin/cli.js +32 -137
- package/src/lib/args-parser-queries.js +228 -0
- package/src/lib/args-parser.js +38 -179
- package/src/lib/detached-finalize.js +281 -0
- package/src/lib/docker-cleanup.js +240 -39
- package/src/lib/docker-post-mortem.js +289 -0
- package/src/lib/execution-attach.js +245 -0
- package/src/lib/execution-resume-all.js +211 -0
- package/src/lib/execution-resume.js +455 -0
- package/src/lib/execution-store.js +93 -6
- package/src/lib/exit-reason.js +264 -0
- package/src/lib/failure-handler.js +43 -41
- package/src/lib/isolation-log-utils.js +66 -1
- package/src/lib/isolation.js +28 -15
- package/src/lib/query-commands.js +202 -0
- package/src/lib/screen-isolation.js +2 -1
- package/src/lib/session-probe.js +121 -0
- package/src/lib/shell-utils.js +164 -10
- package/src/lib/status-formatter.js +233 -69
- package/src/lib/usage.js +13 -1
- package/src/lib/user-manager.js +18 -1
- package/test/args-parser-attach-resume.js +159 -0
- package/test/args-parser-control.js +3 -3
- package/test/args-parser.js +1 -1
- package/test/check-file-size.js +91 -0
- package/test/ci-workflow-invariants.js +191 -0
- package/test/echo-integration.js +20 -13
- package/test/execution-attach.js +221 -0
- package/test/execution-resume.js +433 -0
- package/test/execution-store.js +57 -2
- package/test/exit-reason.js +209 -0
- package/test/failure-handler.js +107 -0
- package/test/load-command-stream.mjs +83 -0
- package/test/regression-162.js +320 -0
- package/test/regression-164.js +308 -0
- package/test/regression-165.js +310 -0
- package/test/regression-170.js +321 -0
- package/test/regression-171.js +562 -0
- package/test/regression-84.js +8 -3
- package/test/release-name.mjs +57 -0
- package/test/session-probe.js +150 -0
- package/test/user-manager.js +23 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attach to a tracked detached execution (issue #162).
|
|
3
|
+
*
|
|
4
|
+
* `--stop`/`--terminate` can already address a stored session name; `--attach`
|
|
5
|
+
* completes the set by re-entering the session's terminal instead of only
|
|
6
|
+
* tailing its log. `--attach --read-only` follows the output without
|
|
7
|
+
* forwarding stdin, which is the safe default for supervisors that must not
|
|
8
|
+
* accidentally type into a long-running agent session.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { spawnSync } = require('child_process');
|
|
12
|
+
const { escapeForLinksNotation } = require('./output-blocks');
|
|
13
|
+
const { getDockerCommand, getDockerSpawnOptions } = require('./docker-cleanup');
|
|
14
|
+
const { runCommand } = require('./execution-control');
|
|
15
|
+
const { SessionState, probeSession } = require('./session-probe');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Run a command with the parent terminal attached (stdin included unless the
|
|
19
|
+
* plan is read-only). Used for `screen -r`, `tmux attach-session`,
|
|
20
|
+
* `docker attach`, and log following.
|
|
21
|
+
* @param {string} command - Executable
|
|
22
|
+
* @param {string[]} args - Arguments
|
|
23
|
+
* @param {object} plan - Attach plan (interactive flag)
|
|
24
|
+
* @returns {{success: boolean, status: ?number, error: ?string}}
|
|
25
|
+
*/
|
|
26
|
+
function runInteractive(command, args, plan = {}) {
|
|
27
|
+
const options = {
|
|
28
|
+
stdio: plan.interactive ? 'inherit' : ['ignore', 'inherit', 'inherit'],
|
|
29
|
+
};
|
|
30
|
+
const result = spawnSync(
|
|
31
|
+
command,
|
|
32
|
+
args,
|
|
33
|
+
command === getDockerCommand() ? getDockerSpawnOptions(options) : options
|
|
34
|
+
);
|
|
35
|
+
return {
|
|
36
|
+
success: !result.error && result.status === 0,
|
|
37
|
+
status: result.status,
|
|
38
|
+
error: result.error ? result.error.message : null,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function logFollowPlan(record, backend, sessionName) {
|
|
43
|
+
if (!record.logPath) {
|
|
44
|
+
return {
|
|
45
|
+
error: `Execution "${record.uuid}" has no stored log to follow.`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
backend,
|
|
50
|
+
sessionName,
|
|
51
|
+
command: 'tail',
|
|
52
|
+
args: ['-f', record.logPath],
|
|
53
|
+
interactive: false,
|
|
54
|
+
method: 'LOG_FOLLOW',
|
|
55
|
+
message: `Following stored log for ${backend} session: ${sessionName}`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function notRunningError(record, probe) {
|
|
60
|
+
const sessionName = probe.sessionName;
|
|
61
|
+
const uuid = record.uuid;
|
|
62
|
+
if (probe.state === SessionState.MISSING) {
|
|
63
|
+
return {
|
|
64
|
+
error:
|
|
65
|
+
`Session "${sessionName}" no longer exists, so there is nothing to attach to. ` +
|
|
66
|
+
`Use \`$ --resume ${uuid}\` to start it again, or \`$ --status ${uuid}\` to inspect the recorded result.`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
error:
|
|
71
|
+
`Session "${sessionName}" is not running (state: ${probe.containerStatus || probe.state}). ` +
|
|
72
|
+
`Use \`$ --resume ${uuid}\` to continue it in the same container, or \`$ --status ${uuid}\` to inspect the recorded result.`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Build the attach plan for an execution record.
|
|
78
|
+
* @param {object} record - Execution record
|
|
79
|
+
* @param {object} options - {readOnly: boolean, probe: object}
|
|
80
|
+
* @returns {object} Plan with command/args/interactive/method/message, or {error}
|
|
81
|
+
*/
|
|
82
|
+
function buildAttachPlan(record, options = {}) {
|
|
83
|
+
const readOnly = options.readOnly === true;
|
|
84
|
+
const probe = options.probe || {};
|
|
85
|
+
const opts = (record && record.options) || {};
|
|
86
|
+
const backend = opts.isolated;
|
|
87
|
+
const sessionName = opts.sessionName;
|
|
88
|
+
|
|
89
|
+
if (!sessionName) {
|
|
90
|
+
return {
|
|
91
|
+
error: 'Execution record does not contain an isolation session name.',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (opts.isolationMode !== 'detached') {
|
|
96
|
+
return {
|
|
97
|
+
error: 'Only detached isolated executions can be attached to.',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ssh sessions cannot be probed or re-entered locally: the stored log is the
|
|
102
|
+
// only channel back into them.
|
|
103
|
+
if (backend === 'ssh') {
|
|
104
|
+
return logFollowPlan(record, backend, sessionName);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!probe.alive) {
|
|
108
|
+
return notRunningError(record, { ...probe, sessionName });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
switch (backend) {
|
|
112
|
+
case 'screen':
|
|
113
|
+
return readOnly
|
|
114
|
+
? logFollowPlan(record, backend, sessionName)
|
|
115
|
+
: {
|
|
116
|
+
backend,
|
|
117
|
+
sessionName,
|
|
118
|
+
command: 'screen',
|
|
119
|
+
args: ['-r', sessionName],
|
|
120
|
+
interactive: true,
|
|
121
|
+
method: 'SCREEN_ATTACH',
|
|
122
|
+
message: `Attaching to detached screen session: ${sessionName}`,
|
|
123
|
+
};
|
|
124
|
+
case 'tmux':
|
|
125
|
+
return {
|
|
126
|
+
backend,
|
|
127
|
+
sessionName,
|
|
128
|
+
command: 'tmux',
|
|
129
|
+
args: readOnly
|
|
130
|
+
? ['attach-session', '-r', '-t', sessionName]
|
|
131
|
+
: ['attach-session', '-t', sessionName],
|
|
132
|
+
interactive: true,
|
|
133
|
+
method: readOnly ? 'TMUX_ATTACH_READONLY' : 'TMUX_ATTACH',
|
|
134
|
+
message: `Attaching to detached tmux session: ${sessionName}${readOnly ? ' (read-only)' : ''}`,
|
|
135
|
+
};
|
|
136
|
+
case 'docker':
|
|
137
|
+
return readOnly
|
|
138
|
+
? {
|
|
139
|
+
backend,
|
|
140
|
+
sessionName,
|
|
141
|
+
command: getDockerCommand(),
|
|
142
|
+
args: ['logs', '-f', sessionName],
|
|
143
|
+
interactive: false,
|
|
144
|
+
method: 'DOCKER_LOG_FOLLOW',
|
|
145
|
+
message: `Following logs of detached docker container: ${sessionName}`,
|
|
146
|
+
}
|
|
147
|
+
: {
|
|
148
|
+
backend,
|
|
149
|
+
sessionName,
|
|
150
|
+
command: getDockerCommand(),
|
|
151
|
+
args: ['attach', sessionName],
|
|
152
|
+
interactive: true,
|
|
153
|
+
method: 'DOCKER_ATTACH',
|
|
154
|
+
message: `Attaching to detached docker container: ${sessionName}`,
|
|
155
|
+
};
|
|
156
|
+
default:
|
|
157
|
+
return {
|
|
158
|
+
error: `Attaching to detached ${backend || 'unknown'} executions is not supported.`,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Format an attach result as links notation.
|
|
165
|
+
* @param {object} result - Attach result fields
|
|
166
|
+
* @returns {string} Links notation block
|
|
167
|
+
*/
|
|
168
|
+
function formatAttachResultAsLinksNotation(result) {
|
|
169
|
+
return [
|
|
170
|
+
'executionAttach',
|
|
171
|
+
` identifier ${escapeForLinksNotation(result.identifier)}`,
|
|
172
|
+
` uuid ${escapeForLinksNotation(result.uuid)}`,
|
|
173
|
+
` backend ${escapeForLinksNotation(result.backend)}`,
|
|
174
|
+
` sessionName ${escapeForLinksNotation(result.sessionName)}`,
|
|
175
|
+
` method ${escapeForLinksNotation(result.method)}`,
|
|
176
|
+
` readOnly ${result.readOnly === true}`,
|
|
177
|
+
` command ${escapeForLinksNotation(result.command)}`,
|
|
178
|
+
` message ${escapeForLinksNotation(result.message)}`,
|
|
179
|
+
].join('\n');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Attach to a tracked execution by UUID or session name.
|
|
184
|
+
* @param {?object} store - Execution store
|
|
185
|
+
* @param {string} identifier - UUID or session name
|
|
186
|
+
* @param {object} deps - {readOnly, probe, interactiveRunner, runner}
|
|
187
|
+
* @returns {{success: boolean, output?: string, error?: string, exitCode?: number}}
|
|
188
|
+
*/
|
|
189
|
+
function attachExecution(store, identifier, deps = {}) {
|
|
190
|
+
if (!store) {
|
|
191
|
+
return { success: false, error: 'Execution tracking is disabled.' };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const record = store.get(identifier);
|
|
195
|
+
if (!record) {
|
|
196
|
+
return {
|
|
197
|
+
success: false,
|
|
198
|
+
error: `No execution found with UUID or session name: ${identifier}`,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const runner = deps.runner || runCommand;
|
|
203
|
+
const probeFn = deps.probe || ((r) => probeSession(r, runner));
|
|
204
|
+
const plan = buildAttachPlan(record, {
|
|
205
|
+
readOnly: deps.readOnly === true,
|
|
206
|
+
probe: probeFn(record),
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
if (plan.error) {
|
|
210
|
+
return { success: false, error: plan.error };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const interactiveRunner = deps.interactiveRunner || runInteractive;
|
|
214
|
+
const commandLine = [plan.command, ...plan.args].join(' ');
|
|
215
|
+
const runResult = interactiveRunner(plan.command, plan.args, plan);
|
|
216
|
+
|
|
217
|
+
const output = formatAttachResultAsLinksNotation({
|
|
218
|
+
identifier,
|
|
219
|
+
uuid: record.uuid,
|
|
220
|
+
backend: plan.backend,
|
|
221
|
+
sessionName: plan.sessionName,
|
|
222
|
+
method: plan.method,
|
|
223
|
+
readOnly: deps.readOnly === true,
|
|
224
|
+
command: commandLine,
|
|
225
|
+
message: plan.message,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
if (!runResult.success) {
|
|
229
|
+
const detail = runResult.error || `exit code ${runResult.status}`;
|
|
230
|
+
return {
|
|
231
|
+
success: false,
|
|
232
|
+
error: `Failed to attach to ${plan.backend} session "${plan.sessionName}": ${detail}`,
|
|
233
|
+
exitCode: typeof runResult.status === 'number' ? runResult.status : 1,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return { success: true, output };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
module.exports = {
|
|
241
|
+
attachExecution,
|
|
242
|
+
buildAttachPlan,
|
|
243
|
+
formatAttachResultAsLinksNotation,
|
|
244
|
+
runInteractive,
|
|
245
|
+
};
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--resume-all`: re-attach or reconcile every execution still marked running.
|
|
3
|
+
*
|
|
4
|
+
* When the supervisor host restarts, the detached docker completion watcher
|
|
5
|
+
* (`docker logs -f ... ; docker inspect ... ; footer`) dies with it. The
|
|
6
|
+
* container keeps running but nothing streams its output into the session log
|
|
7
|
+
* any more, and nothing will ever write the exit footer — the record stays
|
|
8
|
+
* `executing` forever. `--resume-all` repairs that state:
|
|
9
|
+
*
|
|
10
|
+
* - reattached: a live docker container gets a fresh completion watcher.
|
|
11
|
+
* - running: a live screen/tmux session needs nothing (its logging is
|
|
12
|
+
* in-session), it is only reported.
|
|
13
|
+
* - reconciled: the session is gone, so the record is finalized from the same
|
|
14
|
+
* evidence `--status` uses (docker exit code, log footer).
|
|
15
|
+
* - unknown: the backend cannot be probed locally (ssh), left untouched.
|
|
16
|
+
*
|
|
17
|
+
* Commands are never silently restarted here: resuming actual work is an
|
|
18
|
+
* explicit, per-session decision made with `--resume`.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const {
|
|
22
|
+
getDockerContainerCleanupPolicy,
|
|
23
|
+
startDetachedDockerCompletionWatcher,
|
|
24
|
+
} = require('./docker-cleanup');
|
|
25
|
+
const { escapeForLinksNotation } = require('./output-blocks');
|
|
26
|
+
const { runCommand } = require('./execution-control');
|
|
27
|
+
const { SessionState, probeSession } = require('./session-probe');
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Outcomes reported for each execution that was still marked running.
|
|
31
|
+
*/
|
|
32
|
+
const ResumeAllAction = {
|
|
33
|
+
REATTACHED: 'reattached',
|
|
34
|
+
RUNNING: 'running',
|
|
35
|
+
RECONCILED: 'reconciled',
|
|
36
|
+
UNKNOWN: 'unknown',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function formatResumeAllAsLinksNotation(summary) {
|
|
40
|
+
const lines = ['executionResumeAll', ` count ${summary.count}`];
|
|
41
|
+
for (const entry of summary.executions) {
|
|
42
|
+
lines.push(' execution');
|
|
43
|
+
lines.push(` uuid ${escapeForLinksNotation(entry.uuid)}`);
|
|
44
|
+
lines.push(` backend ${escapeForLinksNotation(entry.backend)}`);
|
|
45
|
+
lines.push(` sessionName ${escapeForLinksNotation(entry.sessionName)}`);
|
|
46
|
+
lines.push(` state ${escapeForLinksNotation(entry.state)}`);
|
|
47
|
+
lines.push(` action ${escapeForLinksNotation(entry.action)}`);
|
|
48
|
+
if (entry.exitCode !== null && entry.exitCode !== undefined) {
|
|
49
|
+
lines.push(` exitCode ${entry.exitCode}`);
|
|
50
|
+
}
|
|
51
|
+
lines.push(` message ${escapeForLinksNotation(entry.message)}`);
|
|
52
|
+
}
|
|
53
|
+
return lines.join('\n');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function formatResumeAllAsText(summary) {
|
|
57
|
+
if (summary.count === 0) {
|
|
58
|
+
return 'No executions are currently marked as running.';
|
|
59
|
+
}
|
|
60
|
+
const lines = [`Executions still marked running: ${summary.count}`, ''];
|
|
61
|
+
for (const entry of summary.executions) {
|
|
62
|
+
lines.push(`${entry.action.toUpperCase()} ${entry.uuid}`);
|
|
63
|
+
lines.push(` Backend: ${entry.backend}`);
|
|
64
|
+
lines.push(` Session Name: ${entry.sessionName}`);
|
|
65
|
+
lines.push(` State: ${entry.state}`);
|
|
66
|
+
lines.push(` ${entry.message}`);
|
|
67
|
+
lines.push('');
|
|
68
|
+
}
|
|
69
|
+
return lines.join('\n').trimEnd();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function formatResumeAll(summary, outputFormat) {
|
|
73
|
+
if (outputFormat === 'json') {
|
|
74
|
+
return JSON.stringify(summary, null, 2);
|
|
75
|
+
}
|
|
76
|
+
if (outputFormat === 'text') {
|
|
77
|
+
return formatResumeAllAsText(summary);
|
|
78
|
+
}
|
|
79
|
+
return formatResumeAllAsLinksNotation(summary);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function describeRecord(record, probe, action, message, exitCode = null) {
|
|
83
|
+
return {
|
|
84
|
+
uuid: record.uuid,
|
|
85
|
+
backend:
|
|
86
|
+
probe.backend || (record.options && record.options.isolated) || null,
|
|
87
|
+
sessionName:
|
|
88
|
+
probe.sessionName ||
|
|
89
|
+
(record.options && record.options.sessionName) ||
|
|
90
|
+
null,
|
|
91
|
+
state: probe.state,
|
|
92
|
+
action,
|
|
93
|
+
exitCode,
|
|
94
|
+
message,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Re-attach or reconcile every execution still marked running.
|
|
100
|
+
* @param {?object} store - Execution store
|
|
101
|
+
* @param {object} deps - {outputFormat, probe, runner, startWatcher, reconcile}
|
|
102
|
+
* @returns {{success: boolean, output?: string, error?: string}}
|
|
103
|
+
*/
|
|
104
|
+
function resumeAllExecutions(store, deps = {}) {
|
|
105
|
+
if (!store) {
|
|
106
|
+
return { success: false, error: 'Execution tracking is disabled.' };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const runner = deps.runner || runCommand;
|
|
110
|
+
const probeFn = deps.probe || ((record) => probeSession(record, runner));
|
|
111
|
+
const startWatcher =
|
|
112
|
+
deps.startWatcher || startDetachedDockerCompletionWatcher;
|
|
113
|
+
const reconcile =
|
|
114
|
+
deps.reconcile || require('./status-formatter').enrichDetachedStatus;
|
|
115
|
+
|
|
116
|
+
let records;
|
|
117
|
+
try {
|
|
118
|
+
records = store.getExecuting();
|
|
119
|
+
} catch (err) {
|
|
120
|
+
return { success: false, error: err.message };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const executions = [];
|
|
124
|
+
|
|
125
|
+
for (const record of records) {
|
|
126
|
+
const probe = probeFn(record);
|
|
127
|
+
const sessionName = probe.sessionName;
|
|
128
|
+
|
|
129
|
+
if (probe.alive && probe.backend === 'docker') {
|
|
130
|
+
startWatcher(
|
|
131
|
+
sessionName,
|
|
132
|
+
getDockerContainerCleanupPolicy(record.options || {}),
|
|
133
|
+
record.logPath || null,
|
|
134
|
+
record.uuid || null
|
|
135
|
+
);
|
|
136
|
+
executions.push(
|
|
137
|
+
describeRecord(
|
|
138
|
+
record,
|
|
139
|
+
probe,
|
|
140
|
+
ResumeAllAction.REATTACHED,
|
|
141
|
+
`Re-attached completion watcher to running container: ${sessionName}`
|
|
142
|
+
)
|
|
143
|
+
);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (probe.alive) {
|
|
148
|
+
executions.push(
|
|
149
|
+
describeRecord(
|
|
150
|
+
record,
|
|
151
|
+
probe,
|
|
152
|
+
ResumeAllAction.RUNNING,
|
|
153
|
+
`Session is still running: ${sessionName}`
|
|
154
|
+
)
|
|
155
|
+
);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (probe.state === SessionState.UNKNOWN) {
|
|
160
|
+
executions.push(
|
|
161
|
+
describeRecord(
|
|
162
|
+
record,
|
|
163
|
+
probe,
|
|
164
|
+
ResumeAllAction.UNKNOWN,
|
|
165
|
+
`Session liveness cannot be probed locally: ${sessionName || record.uuid}`
|
|
166
|
+
)
|
|
167
|
+
);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const reconciled = reconcile(record);
|
|
172
|
+
if (reconciled && reconciled.status === 'executed') {
|
|
173
|
+
store.save(reconciled);
|
|
174
|
+
executions.push(
|
|
175
|
+
describeRecord(
|
|
176
|
+
record,
|
|
177
|
+
probe,
|
|
178
|
+
ResumeAllAction.RECONCILED,
|
|
179
|
+
`Session ended while unsupervised; record finalized. Use \`$ --resume ${record.uuid}\` to continue it.`,
|
|
180
|
+
reconciled.exitCode
|
|
181
|
+
)
|
|
182
|
+
);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
executions.push(
|
|
187
|
+
describeRecord(
|
|
188
|
+
record,
|
|
189
|
+
probe,
|
|
190
|
+
ResumeAllAction.UNKNOWN,
|
|
191
|
+
`Session is not running but no terminal result could be resolved: ${sessionName || record.uuid}`
|
|
192
|
+
)
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
success: true,
|
|
198
|
+
output: formatResumeAll(
|
|
199
|
+
{ count: executions.length, executions },
|
|
200
|
+
deps.outputFormat
|
|
201
|
+
),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
module.exports = {
|
|
206
|
+
ResumeAllAction,
|
|
207
|
+
formatResumeAll,
|
|
208
|
+
formatResumeAllAsLinksNotation,
|
|
209
|
+
formatResumeAllAsText,
|
|
210
|
+
resumeAllExecutions,
|
|
211
|
+
};
|