severally-mcp 2.3.1 → 2.4.2

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.
Files changed (45) hide show
  1. package/README.ja.md +52 -17
  2. package/README.md +52 -16
  3. package/bin/severally-mcp.mjs +14 -2
  4. package/docs/install.ja.md +48 -20
  5. package/docs/install.md +48 -21
  6. package/package.json +15 -3
  7. package/plugins/severally/.claude-plugin/plugin.json +1 -1
  8. package/plugins/severally/.codex-plugin/plugin.json +1 -1
  9. package/plugins/severally/dist/severally-mcp.mjs +6573 -5978
  10. package/plugins/severally/skills/_template/reference.md.tmpl +2 -2
  11. package/plugins/severally/skills/antigravity/severally/reference.md +2 -2
  12. package/plugins/severally/skills/claude/severally/reference.md +2 -2
  13. package/plugins/severally/skills/codex/severally/reference.md +2 -2
  14. package/plugins/severally/skills/opencode/severally/reference.md +2 -2
  15. package/scripts/build.mjs +76 -68
  16. package/scripts/doctor.mjs +66 -20
  17. package/scripts/init-config.mjs +8 -6
  18. package/scripts/install.mjs +327 -90
  19. package/scripts/live-check.mjs +62 -23
  20. package/scripts/live-mcp-check.mjs +52 -16
  21. package/scripts/test.mjs +10 -4
  22. package/src/adapters/antigravity-sandbox.mjs +62 -28
  23. package/src/adapters/antigravity.mjs +111 -30
  24. package/src/adapters/claude-code.mjs +121 -28
  25. package/src/adapters/codex.mjs +125 -33
  26. package/src/adapters/opencode-sandbox.mjs +59 -22
  27. package/src/adapters/opencode.mjs +144 -30
  28. package/src/brief.mjs +148 -47
  29. package/src/consultants.mjs +255 -48
  30. package/src/cross-spawn-escape.d.ts +7 -0
  31. package/src/export.mjs +99 -18
  32. package/src/expose-paths.mjs +310 -50
  33. package/src/failures.mjs +31 -14
  34. package/src/jobs.mjs +863 -249
  35. package/src/jsonc.mjs +11 -3
  36. package/src/parse-result.mjs +172 -40
  37. package/src/platform.mjs +56 -8
  38. package/src/policy.mjs +245 -52
  39. package/src/redact.mjs +59 -16
  40. package/src/result-schema.mjs +13 -0
  41. package/src/run.mjs +225 -30
  42. package/src/schema.mjs +132 -27
  43. package/src/server.mjs +178 -81
  44. package/src/status.mjs +94 -13
  45. package/src/store.mjs +418 -35
@@ -3,9 +3,18 @@
3
3
  // no validation keywords beyond type/enum) so it can be handed verbatim to
4
4
  // Codex's --output-schema and Claude Code's --json-schema.
5
5
 
6
+ /** @param {string} desc */
6
7
  const s = (desc) => ({ type: 'string', description: desc });
8
+ /**
9
+ * @param {string[]} values
10
+ * @param {string} desc
11
+ */
7
12
  const enumOf = (values, desc) => ({ type: 'string', enum: values, description: desc });
8
13
 
14
+ /**
15
+ * @param {Record<string, object>} properties
16
+ * @param {string} [description]
17
+ */
9
18
  const obj = (properties, description) => ({
10
19
  type: 'object',
11
20
  description,
@@ -14,6 +23,10 @@ const obj = (properties, description) => ({
14
23
  additionalProperties: false,
15
24
  });
16
25
 
26
+ /**
27
+ * @param {object} items
28
+ * @param {string} description
29
+ */
17
30
  const arr = (items, description) => ({ type: 'array', description, items });
18
31
 
19
32
  export const CONSULT_RESULT_SCHEMA = obj(
package/src/run.mjs CHANGED
@@ -5,10 +5,7 @@ import { spawnCommand as spawn } from './platform.mjs';
5
5
  import path from 'node:path';
6
6
  import { POLICY } from './policy.mjs';
7
7
 
8
- const DROP_EXACT = new Set([
9
- 'CLAUDECODE', 'CLAUDE_PID', 'CLAUDE_EFFORT', 'AI_AGENT',
10
- 'ANTHROPIC_LOG', 'NODE_OPTIONS',
11
- ]);
8
+ const DROP_EXACT = new Set(['CLAUDECODE', 'CLAUDE_PID', 'CLAUDE_EFFORT', 'AI_AGENT', 'ANTHROPIC_LOG', 'NODE_OPTIONS']);
12
9
  const DROP_PREFIX = ['CLAUDE_CODE_', 'SEVERALLY_', 'MCP_'];
13
10
 
14
11
  // Each consultant sees only its own vendor's credentials.
@@ -49,6 +46,7 @@ const DROP_PREFIX = ['CLAUDE_CODE_', 'SEVERALLY_', 'MCP_'];
49
46
  // The names came out of the binary with `strings`; only XDG_CONFIG_HOME was
50
47
  // probed behaviourally, so read the AGY_* line as "no evidence of a
51
48
  // config-dir override under that prefix", not as proof that none exists.
49
+ /** @type {Record<string, string[]>} */
52
50
  const TARGET_DROP_PREFIX = {
53
51
  codex: ['ANTHROPIC_', 'GEMINI_', 'GOOGLE_', 'AGY_', 'ANTIGRAVITY_', 'ZAI_'],
54
52
  'claude-code': ['OPENAI_', 'CODEX_', 'GEMINI_', 'GOOGLE_', 'AGY_', 'ANTIGRAVITY_', 'ZAI_'],
@@ -65,6 +63,7 @@ const TARGET_DROP_PREFIX = {
65
63
  // Exact names dropped for one target only (see the note above). Kept separate
66
64
  // from DROP_EXACT so the two other consultants, whose isolation has been
67
65
  // verified with these variables present, are not changed.
66
+ /** @type {Record<string, string[]>} */
68
67
  const TARGET_DROP_EXACT = {
69
68
  antigravity: ['XDG_CONFIG_HOME'],
70
69
  };
@@ -74,10 +73,15 @@ const TARGET_DROP_EXACT = {
74
73
  * sockets, session ids, entrypoint markers) is removed, as are the other two
75
74
  * vendors' credentials -- each consultant keeps only its own; and a recursion
76
75
  * marker is added.
76
+ * @param {string} target
77
+ * @param {Record<string, string>} [extra]
78
+ * @param {Record<string, string>} [operator]
79
+ * @returns {Record<string, string>}
77
80
  */
78
81
  export function childEnv(target, extra = {}, operator = {}) {
79
82
  const { dropPrefixes, dropExact } = dropRules(target);
80
83
  const keep = providerEnvNames(target);
84
+ /** @type {Record<string, string>} */
81
85
  const env = {};
82
86
  for (const [k, v] of Object.entries(process.env)) {
83
87
  if (v === undefined) continue;
@@ -104,6 +108,7 @@ export function childEnv(target, extra = {}, operator = {}) {
104
108
  const SECRET_PREFIX = ['AWS_', 'AZURE_', 'GITHUB_', 'GH_', 'GITLAB_', 'NPM_', 'DOCKER_', 'HF_', 'SLACK_', 'STRIPE_', 'OP_'];
105
109
  const SECRET_SUFFIX = /(TOKEN|SECRET|PASSWORD|PASSWD|PASSPHRASE|API_?KEY|ACCESS_KEY|PRIVATE_KEY|CREDENTIALS?)$/i;
106
110
  const SECRET_EXACT = new Set(['SSH_AUTH_SOCK', 'KUBECONFIG', 'DATABASE_URL']);
111
+ /** @type {Record<string, string[]>} */
107
112
  const OWN_PREFIX = {
108
113
  codex: ['OPENAI_', 'CODEX_'],
109
114
  'claude-code': ['ANTHROPIC_'],
@@ -113,17 +118,25 @@ const OWN_PREFIX = {
113
118
 
114
119
  // Names the operator's OpenCode provider definitions read as "{env:NAME}":
115
120
  // the consultant needs them, wherever they were exported.
121
+ /** @param {string} target */
116
122
  function providerEnvNames(target) {
117
- const provider = JSON.stringify(POLICY.clis?.[target]?.provider ?? {});
118
- return new Set([...provider.matchAll(/\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g)].map((m) => m[1]));
123
+ // Only OpenCode's section carries provider definitions.
124
+ const provider = JSON.stringify(target === 'opencode' ? POLICY.clis.opencode.provider : {});
125
+ return new Set([...provider.matchAll(/\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g)].flatMap(([, name]) => name ?? []));
119
126
  }
120
127
 
128
+ /**
129
+ * @param {string} target
130
+ * @param {string} key
131
+ * @param {Set<string>} keep
132
+ */
121
133
  function foreignSecret(target, key, keep) {
122
134
  if (keep.has(key) || (OWN_PREFIX[target] ?? []).some((p) => key.startsWith(p))) return false;
123
135
  const upper = key.toUpperCase();
124
136
  return SECRET_EXACT.has(upper) || SECRET_PREFIX.some((p) => upper.startsWith(p)) || SECRET_SUFFIX.test(key);
125
137
  }
126
138
 
139
+ /** @param {string} target */
127
140
  function dropRules(target) {
128
141
  return {
129
142
  dropPrefixes: [...DROP_PREFIX, ...(TARGET_DROP_PREFIX[target] ?? [])],
@@ -139,11 +152,15 @@ function dropRules(target) {
139
152
  * by config is refused rather than honoured; so is any name the per-job
140
153
  * sandbox sets (HOME and friends), which would otherwise be silently
141
154
  * overwritten.
155
+ * @param {string} target
156
+ * @param {unknown} env
157
+ * @param {string[]} [sandboxKeys]
158
+ * @returns {string | null}
142
159
  */
143
160
  export function operatorEnvProblem(target, env, sandboxKeys = []) {
144
161
  if (env === null || typeof env !== 'object' || Array.isArray(env)) return 'must be an object of string values';
145
162
  const { dropPrefixes, dropExact } = dropRules(target);
146
- const fold = (k) => (process.platform === 'win32' ? k.toUpperCase() : k);
163
+ const fold = (/** @type {string} */ k) => (process.platform === 'win32' ? k.toUpperCase() : k);
147
164
  const reserved = new Set(sandboxKeys.map(fold));
148
165
  for (const [k, v] of Object.entries(env)) {
149
166
  if (typeof v !== 'string') return `${k} must be a string`;
@@ -156,6 +173,10 @@ export function operatorEnvProblem(target, env, sandboxKeys = []) {
156
173
  }
157
174
 
158
175
  export class ProcHandle {
176
+ /**
177
+ * @param {import('node:child_process').ChildProcess} child
178
+ * @param {() => string} [readStdout]
179
+ */
159
180
  constructor(child, readStdout = () => '') {
160
181
  this.child = child;
161
182
  this.killed = false;
@@ -168,6 +189,7 @@ export class ProcHandle {
168
189
  * Kill the child and every descendant it spawned -- also after the child
169
190
  * itself has exited: a descendant still holding its pipes keeps the
170
191
  * consultation from closing, and on POSIX it is still in the group.
192
+ * @param {NodeJS.Signals} [signal]
171
193
  */
172
194
  stop(signal = 'SIGTERM') {
173
195
  if (this.killed || !this.child) return;
@@ -179,14 +201,33 @@ export class ProcHandle {
179
201
  // before its parent exits and its descendants become unreachable. Once
180
202
  // it has exited, its pid may already be another process's: leave it.
181
203
  if (this.child.exitCode !== null || this.child.signalCode !== null) return;
182
- const killer = spawn(path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'taskkill.exe'),
183
- ['/pid', String(pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' });
184
- const fallback = () => { try { this.child.kill('SIGKILL'); } catch { /* gone */ } };
204
+ const killer = spawn(
205
+ path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'taskkill.exe'),
206
+ ['/pid', String(pid), '/T', '/F'],
207
+ { windowsHide: true, stdio: 'ignore' },
208
+ );
209
+ const fallback = () => {
210
+ try {
211
+ this.child.kill('SIGKILL');
212
+ } catch {
213
+ /* gone */
214
+ }
215
+ };
185
216
  killer.on('error', fallback);
186
- killer.on('exit', (code) => { if (code !== 0) fallback(); });
217
+ killer.on('exit', (code) => {
218
+ if (code !== 0) fallback();
219
+ });
187
220
  return;
188
221
  }
189
- try { process.kill(-pid, signal); } catch { try { this.child.kill(signal); } catch { /* gone */ } }
222
+ try {
223
+ process.kill(-pid, signal);
224
+ } catch {
225
+ try {
226
+ this.child.kill(signal);
227
+ } catch {
228
+ /* gone */
229
+ }
230
+ }
190
231
  setTimeout(() => this.forceKill(), POLICY.killGraceMs).unref();
191
232
  }
192
233
 
@@ -200,21 +241,153 @@ export class ProcHandle {
200
241
  forceKill() {
201
242
  const pid = this.child?.pid;
202
243
  if (!pid || process.platform === 'win32') return;
203
- try { process.kill(-pid, 'SIGKILL'); } catch { /* gone */ }
244
+ try {
245
+ process.kill(-pid, 'SIGKILL');
246
+ } catch {
247
+ /* gone */
248
+ }
204
249
  }
205
250
  }
206
251
 
207
252
  /**
208
- * @returns {{handle: ProcHandle, done: Promise<{code, signal, stdout, stderr, timedOut, cancelled, truncated}>}}
253
+ * @typedef {object} ChildResult
254
+ * @property {number | null} code
255
+ * @property {NodeJS.Signals | null} signal
256
+ * @property {string} stdout
257
+ * @property {string} stderr
258
+ * @property {boolean} truncated
259
+ * @property {boolean} timedOut
260
+ * @property {boolean} cancelled
261
+ * @property {boolean} stalled
262
+ * @property {boolean} pipesForced
263
+ * @property {string} [spawnError]
264
+ */
265
+
266
+ /**
267
+ * What an adapter's buildInvocation returns.
268
+ * @typedef {object} Invocation
269
+ * @property {string} command
270
+ * @property {string[]} args
271
+ * @property {string} model
272
+ * @property {string} [workdir]
273
+ * @property {string} [lastMessagePath]
274
+ */
275
+
276
+ /**
277
+ * The synthesised HOME of one consultation, as an adapter's prepareSandbox
278
+ * returns it; what the adapters share of it.
279
+ * @typedef {object} Sandbox
280
+ * @property {string} root
281
+ * @property {string} credentials 'symlink', 'copy' or 'missing'
282
+ * @property {string} credentialsSource
283
+ * @property {readonly string[]} tools
284
+ * @property {Record<string, string>} env
285
+ * @property {() => void} cleanup
286
+ * @property {(provider: string) => boolean} [seed] insert the provider's credential into the session database (OpenCode only)
287
+ */
288
+
289
+ /**
290
+ * What a consultant was launched with, read off the invocation: the boundary
291
+ * record kept beside each answer. Facts about the launch, not a claim about
292
+ * what the consultant did.
293
+ * @typedef {object} Boundary
294
+ * @property {string} reads
295
+ * @property {string} shell
296
+ * @property {boolean} web
297
+ * @property {boolean} writes
298
+ * @property {boolean} mcp
299
+ * @property {string} protected_paths
300
+ * @property {string[] | null} tools
301
+ */
302
+
303
+ /**
304
+ * What a consultation used, as far as its CLI says; null where it does not say.
305
+ * @typedef {object} UsageRecord
306
+ * @property {number | null} input_tokens
307
+ * @property {number | null} cached_input_tokens
308
+ * @property {number | null} output_tokens
309
+ * @property {number | null} total_tokens
310
+ * @property {number | null} cost_usd
311
+ * @property {number | null} web_search_requests
312
+ * @property {number | null} [turns]
313
+ * @property {number | null} [thinking_tokens]
314
+ * @property {number | null} [reasoning_tokens]
315
+ * @property {string[] | null} [denied_actions]
316
+ */
317
+
318
+ /**
319
+ * What jobs.mjs hands buildInvocation; each adapter reads the fields it needs.
320
+ * @typedef {object} BuildRequest
321
+ * @property {string} workdir
322
+ * @property {string} schemaPath
323
+ * @property {string} guardrails
324
+ * @property {Sandbox | null} sandbox the adapter's own prepareSandbox result, if it has one
325
+ * @property {import('./consultants.mjs').Consultant} consultant
326
+ * @property {boolean} [hasExposedPaths]
327
+ * @property {'on' | 'off' | undefined} [network]
328
+ */
329
+
330
+ /**
331
+ * What an adapter's interpret makes of a finished child: an answer (text is a
332
+ * JSON object or the raw text to mine one out of), or a classified failure.
333
+ * @typedef {{ ok: true, text: string | Record<string, unknown>, usageRaw: unknown, payload?: unknown, events?: number, denials?: unknown[], recoveredError?: { failureKind: string, message: string } | null, earlierTextParts?: string[] }} InterpretedOk
334
+ * @typedef {{ ok: false, failureKind: string, message: string, usageRaw: unknown, payload?: unknown, events?: number }} InterpretedFail
335
+ * @typedef {InterpretedOk | InterpretedFail} Interpreted
209
336
  */
337
+
338
+ /**
339
+ * The shape every adapter module (codex, claude-code, antigravity, opencode)
340
+ * exports; the optional members are the ones only some CLIs have.
341
+ * @typedef {object} Adapter
342
+ * @property {readonly string[]} FORBIDDEN_FLAGS
343
+ * @property {readonly string[]} RESERVED_FLAGS
344
+ * @property {(req: BuildRequest) => Invocation} buildInvocation
345
+ * @property {(run: Partial<ChildResult> & { lastMessageText?: string }) => Interpreted} interpret
346
+ * @property {(raw: unknown) => UsageRecord | null} usageRecord
347
+ * @property {(args: { args: string[], sandbox: Sandbox | null, network?: 'on' | 'off' | undefined, hasExposedPaths?: boolean }) => Boundary} [boundary]
348
+ * @property {(run: { stdout: string }) => string | null} [progress]
349
+ * @property {(run: { stdout: string }) => Record<string, number> | null} [toolCalls]
350
+ * @property {readonly string[]} [FILE_READ_TOOLS] the names toolCalls reports for reading a file
351
+ * @property {readonly string[]} [ALLOWED_TOOLS] the only tools the consultant may call; any other is refused
352
+ * @property {() => Promise<void>} [prepare]
353
+ * @property {(args: { workdir: string, network?: 'on' | 'off' | undefined }) => Sandbox} [prepareSandbox]
354
+ * @property {(args: { run: ChildResult, sandbox: Sandbox }) => string} [diagnostics]
355
+ * @property {(args: { interpreted: Interpreted, sandbox: Sandbox, model: string }) => boolean} [recoverAuthFailure]
356
+ * @property {(line: string) => string} [compactLine]
357
+ * @property {(line: string) => number | null | undefined} [settleAfter]
358
+ * @property {NodeJS.Signals} [stopSignal]
359
+ */
360
+
210
361
  /**
211
362
  * `pipesForced` in the result: the child exited but something it started kept
212
363
  * stdout or stderr open past the kill grace, so the group was stopped and the
213
364
  * pipes closed from this side. A descendant outside the group (setsid, or on
214
365
  * Windows once the parent is gone) may still be running.
366
+ * @param {object} options
367
+ * @param {string} options.command
368
+ * @param {readonly string[]} options.args
369
+ * @param {string} [options.cwd]
370
+ * @param {NodeJS.ProcessEnv} [options.env]
371
+ * @param {string | null} [options.input]
372
+ * @param {number} options.timeoutMs
373
+ * @param {(cancel: () => void) => void} [options.onCancelSignal]
374
+ * @param {((line: string) => string) | null} [options.compactLine]
375
+ * @param {((line: string) => number | null | undefined) | null} [options.settleAfter]
376
+ * @param {NodeJS.Signals} [options.stopSignal]
377
+ * @returns {{handle: ProcHandle, done: Promise<ChildResult>, pid: number | undefined}}
215
378
  */
216
- export function runChild({ command, args, cwd, env, input, timeoutMs, onCancelSignal, compactLine = null,
217
- settleAfter = null, stopSignal = 'SIGTERM' }) {
379
+ export function runChild({
380
+ command,
381
+ args,
382
+ cwd,
383
+ env,
384
+ input,
385
+ timeoutMs,
386
+ onCancelSignal,
387
+ compactLine = null,
388
+ settleAfter = null,
389
+ stopSignal = 'SIGTERM',
390
+ }) {
218
391
  const child = spawn(command, args, {
219
392
  cwd,
220
393
  // PWD is the parent's otherwise, and opencode takes its project root from
@@ -237,24 +410,34 @@ export function runChild({ command, args, cwd, env, input, timeoutMs, onCancelSi
237
410
  // says it is done; past that it is stopped and `stalled` set. A later line
238
411
  // can shorten the wait, never lengthen it.
239
412
  let stalled = false;
240
- let settleTimer = null;
413
+ /** @type {NodeJS.Timeout | undefined} */
414
+ let settleTimer;
241
415
  let settleAt = Infinity;
242
- const watch = (line) => {
243
- const ms = settleAfter(line);
416
+ const watch = (/** @type {string} */ line) => {
417
+ const ms = settleAfter?.(line);
244
418
  if (ms == null || Date.now() + ms >= settleAt) return;
245
419
  settleAt = Date.now() + ms;
246
420
  clearTimeout(settleTimer);
247
- settleTimer = setTimeout(() => { stalled = true; handle.stop(stopSignal); }, ms);
421
+ settleTimer = setTimeout(() => {
422
+ stalled = true;
423
+ handle.stop(stopSignal);
424
+ }, ms);
248
425
  };
249
426
 
250
- const append = (which, chunk) => {
427
+ const append = (/** @type {'out' | 'err'} */ which, /** @type {string | Buffer} */ chunk) => {
251
428
  const text = chunk.toString('utf8');
252
429
  if (which === 'out') {
253
- if (stdout.length >= cap) { truncated = true; return; }
430
+ if (stdout.length >= cap) {
431
+ truncated = true;
432
+ return;
433
+ }
254
434
  stdout += text.slice(0, cap - stdout.length);
255
435
  if (stdout.length >= cap) truncated = true;
256
436
  } else {
257
- if (stderr.length >= cap) { truncated = true; return; }
437
+ if (stderr.length >= cap) {
438
+ truncated = true;
439
+ return;
440
+ }
258
441
  stderr += text.slice(0, cap - stderr.length);
259
442
  if (stderr.length >= cap) truncated = true;
260
443
  }
@@ -265,15 +448,18 @@ export function runChild({ command, args, cwd, env, input, timeoutMs, onCancelSi
265
448
  // partial line is bounded by the cap too; past it, the line is kept as is.
266
449
  let pending = '';
267
450
  const byLine = Boolean(compactLine || settleAfter);
268
- const compact = compactLine ?? ((l) => l);
451
+ const compact = compactLine ?? ((/** @type {string} */ l) => l);
269
452
  const flushLines = () => {
270
453
  const lines = pending.split('\n');
271
- pending = lines.pop();
454
+ pending = lines.pop() ?? '';
272
455
  for (const l of lines) {
273
456
  if (settleAfter) watch(l);
274
457
  append('out', `${compact(l)}\n`);
275
458
  }
276
- if (pending.length > cap) { append('out', pending); pending = ''; }
459
+ if (pending.length > cap) {
460
+ append('out', pending);
461
+ pending = '';
462
+ }
277
463
  };
278
464
  // Decode as a stream: a multibyte character split across two chunks would
279
465
  // otherwise turn into replacement characters inside the answer text.
@@ -298,11 +484,13 @@ export function runChild({ command, args, cwd, env, input, timeoutMs, onCancelSi
298
484
  });
299
485
  }
300
486
 
487
+ /** @type {Promise<ChildResult>} */
301
488
  const done = new Promise((resolve) => {
302
489
  let settled = false;
303
490
  let pipesForced = false;
304
- let orphanTimer = null;
305
- const finish = (payload) => {
491
+ /** @type {NodeJS.Timeout | undefined} */
492
+ let orphanTimer;
493
+ const finish = (/** @type {{ code: number | null, signal: NodeJS.Signals | null, spawnError?: string }} */ payload) => {
306
494
  if (settled) return;
307
495
  settled = true;
308
496
  clearTimeout(timer);
@@ -310,7 +498,12 @@ export function runChild({ command, args, cwd, env, input, timeoutMs, onCancelSi
310
498
  clearTimeout(orphanTimer);
311
499
  resolve({ stdout, stderr, truncated, timedOut, cancelled, stalled, pipesForced, ...payload });
312
500
  };
313
- const flushPending = () => { if (byLine && pending) { append('out', compact(pending)); pending = ''; } };
501
+ const flushPending = () => {
502
+ if (byLine && pending) {
503
+ append('out', compact(pending));
504
+ pending = '';
505
+ }
506
+ };
314
507
  child.on('error', (err) => finish({ code: null, signal: null, spawnError: err.message }));
315
508
  // 'close' waits for the pipes as well as the child. Once the child has
316
509
  // exited, a grace later the rest of its group is stopped, and a grace
@@ -336,7 +529,9 @@ export function runChild({ command, args, cwd, env, input, timeoutMs, onCancelSi
336
529
  });
337
530
 
338
531
  if (input !== undefined && input !== null) {
339
- child.stdin.on('error', () => { /* child may exit before draining stdin */ });
532
+ child.stdin.on('error', () => {
533
+ /* child may exit before draining stdin */
534
+ });
340
535
  child.stdin.end(input);
341
536
  } else {
342
537
  child.stdin.end();