ccgx-workflow 2.0.0 → 2.1.1
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/cli.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import cac from 'cac';
|
|
3
3
|
import ansis from 'ansis';
|
|
4
|
-
import { d as diagnoseMcpConfig, i as isWindows, r as readClaudeCodeConfig, f as fixWindowsMcpConfig, w as writeClaudeCodeConfig, a as readCcgConfig, b as initI18n, c as i18n, s as showMainMenu, e as init, g as configMcp, v as version } from './shared/ccgx-workflow.
|
|
4
|
+
import { d as diagnoseMcpConfig, i as isWindows, r as readClaudeCodeConfig, f as fixWindowsMcpConfig, w as writeClaudeCodeConfig, a as readCcgConfig, b as initI18n, c as i18n, s as showMainMenu, e as init, g as configMcp, v as version } from './shared/ccgx-workflow.BIcRT31-.mjs';
|
|
5
5
|
import { execSync } from 'node:child_process';
|
|
6
6
|
import 'inquirer';
|
|
7
7
|
import 'ora';
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { h as collectSkills, j as resolvePluginBashCommand } from './shared/ccgx-workflow.
|
|
2
|
-
export { k as changeLanguage, G as checkForUpdates, I as collectInvocableSkills, H as compareVersions, m as createDefaultConfig, n as createDefaultRouting, J as generateCommandContent, o as getCcgDir, p as getConfigPath, E as getCurrentVersion, F as getLatestVersion, t as getWorkflowById, q as getWorkflowConfigs, c as i18n, e as init, b as initI18n, y as installAceTool, z as installAceToolRs, K as installSkillCommands, x as installWorkflows, C as migrateToV1_4_0, D as needsMigration, L as parseFrontmatter, a as readCcgConfig, s as showMainMenu, B as uninstallAceTool, A as uninstallWorkflows, u as update, l as writeCcgConfig } from './shared/ccgx-workflow.
|
|
1
|
+
import { h as collectSkills, j as resolvePluginBashCommand } from './shared/ccgx-workflow.BIcRT31-.mjs';
|
|
2
|
+
export { k as changeLanguage, G as checkForUpdates, I as collectInvocableSkills, H as compareVersions, m as createDefaultConfig, n as createDefaultRouting, J as generateCommandContent, o as getCcgDir, p as getConfigPath, E as getCurrentVersion, F as getLatestVersion, t as getWorkflowById, q as getWorkflowConfigs, c as i18n, e as init, b as initI18n, y as installAceTool, z as installAceToolRs, K as installSkillCommands, x as installWorkflows, C as migrateToV1_4_0, D as needsMigration, L as parseFrontmatter, a as readCcgConfig, s as showMainMenu, B as uninstallAceTool, A as uninstallWorkflows, u as update, l as writeCcgConfig } from './shared/ccgx-workflow.BIcRT31-.mjs';
|
|
3
3
|
import { existsSync, readFileSync, mkdirSync, writeFileSync, statSync, readdirSync } from 'node:fs';
|
|
4
4
|
import { join, dirname } from 'node:path';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
@@ -13,7 +13,7 @@ import { join as join$2 } from 'node:path/posix';
|
|
|
13
13
|
import { join as join$1 } from 'node:path';
|
|
14
14
|
import i18next from 'i18next';
|
|
15
15
|
|
|
16
|
-
const version = "2.
|
|
16
|
+
const version = "2.1.1";
|
|
17
17
|
|
|
18
18
|
function cmd(id, order, category, name, nameEn, description, descriptionEn, cmdOverride) {
|
|
19
19
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccgx-workflow",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Multi-model orchestration for Claude Code. Codex + Gemini parallel collaboration with fresh-context subagent protocols, OS-level process isolation, and Plan-Critic-Verify quality tiers. Successor to ccg-workflow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@10.17.1",
|
|
@@ -66,20 +66,20 @@ argument-hint: "[代码或描述] [--adversarial] [--fix [--all] [--auto]] [--ro
|
|
|
66
66
|
Step 1. 用 Write 工具把【小任务描述】写到 tmpfile
|
|
67
67
|
⚠️ 内容必须是任务说明(≤ 2KB),不要塞 git diff / 文件内容
|
|
68
68
|
Write({
|
|
69
|
-
file_path: "/tmp/ccg-review-codex-$JOB.txt",
|
|
69
|
+
file_path: ".context/tmp/ccg-review-codex-$JOB.txt",
|
|
70
70
|
content: <下方"任务描述模板">
|
|
71
71
|
})
|
|
72
72
|
Write({ 同上,gemini 版本 })
|
|
73
73
|
|
|
74
74
|
Step 2. 用 Bash 调 helper(占位符已渲染):
|
|
75
75
|
Bash({
|
|
76
|
-
command: `{{CODEX_BASH_TASK}} --prompt-file /tmp/ccg-review-codex-$JOB.txt`,
|
|
76
|
+
command: `{{CODEX_BASH_TASK}} --prompt-file .context/tmp/ccg-review-codex-$JOB.txt`,
|
|
77
77
|
description: "Review: backend (codex direct)",
|
|
78
78
|
run_in_background: true,
|
|
79
79
|
timeout: 3600000
|
|
80
80
|
})
|
|
81
81
|
Bash({
|
|
82
|
-
command: `{{GEMINI_BASH_TASK}} --prompt-file /tmp/ccg-review-gemini-$JOB.txt`,
|
|
82
|
+
command: `{{GEMINI_BASH_TASK}} --prompt-file .context/tmp/ccg-review-gemini-$JOB.txt`,
|
|
83
83
|
description: "Review: frontend (gemini direct)",
|
|
84
84
|
run_in_background: true,
|
|
85
85
|
timeout: 3600000
|
|
@@ -148,14 +148,14 @@ CLI 空答 / auth 过期时 wrapper 受 instruction-tuning 驱动**自答 fabric
|
|
|
148
148
|
|
|
149
149
|
```
|
|
150
150
|
Bash({
|
|
151
|
-
command: "cat /tmp/ccg-review-codex-$JOB.txt | ~/.claude/bin/codeagent-wrapper {{LITE_MODE_FLAG}}--progress --backend codex - \"{{WORKDIR}}\"",
|
|
151
|
+
command: "cat .context/tmp/ccg-review-codex-$JOB.txt | ~/.claude/bin/codeagent-wrapper {{LITE_MODE_FLAG}}--progress --backend codex - \"{{WORKDIR}}\"",
|
|
152
152
|
run_in_background: true,
|
|
153
153
|
timeout: 3600000,
|
|
154
154
|
description: "Review: backend (wrapper fallback)"
|
|
155
155
|
})
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
`.context/tmp/ccg-review-codex-$JOB.txt` 同 通道 A 的"任务描述模板"——不变。
|
|
159
159
|
|
|
160
160
|
> ⚠️ 通道 B `codeagent-wrapper` 已标 **deprecated**,仅 plugin 未装时使用。
|
|
161
161
|
|
|
@@ -227,11 +227,11 @@ Bash({
|
|
|
227
227
|
|
|
228
228
|
```
|
|
229
229
|
Step 1. 用 Write 把任务描述写入 tmpfile(同样不塞 diff,让 codex 自己读):
|
|
230
|
-
Write({ file_path: "/tmp/ccg-review-adv-$JOB.txt", content: <下方任务描述> })
|
|
230
|
+
Write({ file_path: ".context/tmp/ccg-review-adv-$JOB.txt", content: <下方任务描述> })
|
|
231
231
|
|
|
232
232
|
Step 2. 调 helper:
|
|
233
233
|
Bash({
|
|
234
|
-
command: `{{CODEX_BASH_TASK}} --prompt-file /tmp/ccg-review-adv-$JOB.txt`,
|
|
234
|
+
command: `{{CODEX_BASH_TASK}} --prompt-file .context/tmp/ccg-review-adv-$JOB.txt`,
|
|
235
235
|
description: "Adversarial review (codex direct)",
|
|
236
236
|
run_in_background: true,
|
|
237
237
|
timeout: 3600000
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
// --prompt-file <path> Required. Path to file containing prompt body.
|
|
45
45
|
// --json Pass --json to companion (default: true).
|
|
46
46
|
// --no-json Disable --json (text output).
|
|
47
|
-
// --timeout-ms <N> Kill companion
|
|
47
|
+
// --timeout-ms <N> Kill companion if total wall-time exceeds N ms (default: 7200000, 2h; pass 0 to disable).
|
|
48
48
|
// --max-budget-usd <N> Forwarded to companion (default: 50).
|
|
49
49
|
//
|
|
50
50
|
// Cross-cutting:
|
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
// =============================================================================
|
|
56
56
|
|
|
57
57
|
import { spawn } from 'node:child_process'
|
|
58
|
-
import { existsSync, readFileSync } from 'node:fs'
|
|
58
|
+
import { existsSync, readFileSync, realpathSync, unlinkSync } from 'node:fs'
|
|
59
59
|
import { homedir, platform } from 'node:os'
|
|
60
|
-
import { join } from 'node:path'
|
|
60
|
+
import { basename, join, resolve, sep } from 'node:path'
|
|
61
61
|
import { fileURLToPath } from 'node:url'
|
|
62
62
|
|
|
63
63
|
// ---------------------------------------------------------------------------
|
|
@@ -73,6 +73,27 @@ const VENDOR_KEYS = {
|
|
|
73
73
|
gemini: ['gemini@gemini-ccgx', 'gemini@google-gemini'],
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// Vendor → preferred entry script (relative to <installPath>/scripts/).
|
|
78
|
+
//
|
|
79
|
+
// gemini: prefer `gemini-batch.mjs` (CCG-only, bypasses ACP broker entirely
|
|
80
|
+
// — direct gemini-cli batch mode via stdin + --output-format json). Falls
|
|
81
|
+
// back to the legacy `gemini-companion.mjs task` (ACP path) only if the
|
|
82
|
+
// batch entry isn't shipped.
|
|
83
|
+
//
|
|
84
|
+
// Why bypass ACP: gemini-cli 0.40+'s session/new RPC runs MCP setup + auth
|
|
85
|
+
// refresh + chat startup synchronously inside the agent. On Windows the
|
|
86
|
+
// gemini-plugin-cc ACP broker + named-pipe transport reliably hangs trivial
|
|
87
|
+
// trips for 5+ minutes even when the direct CLI returns in 10-30s. Batch
|
|
88
|
+
// mode side-steps the entire transport stack (no broker, no detached
|
|
89
|
+
// process, no orphan MCP children).
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
const VENDOR_ENTRY_SCRIPTS = {
|
|
93
|
+
codex: ['codex-companion.mjs'],
|
|
94
|
+
gemini: ['gemini-batch.mjs', 'gemini-companion.mjs'],
|
|
95
|
+
}
|
|
96
|
+
|
|
76
97
|
// ---------------------------------------------------------------------------
|
|
77
98
|
// Argument parsing — minimal, KISS, no external CLI lib.
|
|
78
99
|
// ---------------------------------------------------------------------------
|
|
@@ -82,8 +103,25 @@ function parseArgs(argv) {
|
|
|
82
103
|
vendor: null,
|
|
83
104
|
promptFile: null,
|
|
84
105
|
json: true,
|
|
85
|
-
|
|
106
|
+
// 2.1.1: wall-time bumped 600s → 7200s (2h). Real codex audits /
|
|
107
|
+
// multi-file reviews / planning tasks routinely run 5-30 min; the old
|
|
108
|
+
// 600s default was SIGTERM-ing healthy tasks mid-thought. 2h is a
|
|
109
|
+
// generous safety ceiling — pass 0 to disable entirely.
|
|
110
|
+
timeoutMs: 7200000,
|
|
111
|
+
// 2.1.1: NEW idle timeout. Wall-time alone is the wrong signal — a
|
|
112
|
+
// healthy long-running audit produces continuous stdout/stderr (tool
|
|
113
|
+
// calls, progress lines). A truly hung companion produces nothing.
|
|
114
|
+
// We track lastActivityAt on every stdout/stderr chunk and SIGTERM if
|
|
115
|
+
// silent for idleTimeoutMs (default 600s = 10min). Pass 0 to disable.
|
|
116
|
+
idleTimeoutMs: 600000,
|
|
86
117
|
maxBudgetUsd: 50,
|
|
118
|
+
// 1.0.5 regression fix: pre-1.0.5 callers always passed --write directly
|
|
119
|
+
// to codex-companion. Default true preserves BC; --no-write opt-out for
|
|
120
|
+
// read-only review flows.
|
|
121
|
+
write: true,
|
|
122
|
+
model: null,
|
|
123
|
+
effort: null,
|
|
124
|
+
cwd: null,
|
|
87
125
|
}
|
|
88
126
|
|
|
89
127
|
const positional = []
|
|
@@ -99,7 +137,13 @@ function parseArgs(argv) {
|
|
|
99
137
|
case '--json': opts.json = true; break
|
|
100
138
|
case '--no-json': opts.json = false; break
|
|
101
139
|
case '--timeout-ms': opts.timeoutMs = Number.parseInt(next(), 10); break
|
|
140
|
+
case '--idle-timeout-ms': opts.idleTimeoutMs = Number.parseInt(next(), 10); break
|
|
102
141
|
case '--max-budget-usd': opts.maxBudgetUsd = Number.parseFloat(next()); break
|
|
142
|
+
case '--write': opts.write = true; break
|
|
143
|
+
case '--no-write': opts.write = false; break
|
|
144
|
+
case '--model': opts.model = next(); break
|
|
145
|
+
case '--effort': opts.effort = next(); break
|
|
146
|
+
case '--cwd': opts.cwd = next(); break
|
|
103
147
|
case '--help':
|
|
104
148
|
case '-h':
|
|
105
149
|
printHelp()
|
|
@@ -133,8 +177,14 @@ Required:
|
|
|
133
177
|
Optional:
|
|
134
178
|
--json Pass --json to companion (default: true)
|
|
135
179
|
--no-json Disable --json (text output)
|
|
136
|
-
--timeout-ms <N> Kill companion
|
|
180
|
+
--timeout-ms <N> Kill companion if total wall-time exceeds N ms (default: 7200000, 2h; pass 0 to disable)
|
|
181
|
+
--idle-timeout-ms <N> Kill companion if no stdout/stderr output for N ms (default: 600000, 10min; pass 0 to disable). Idle detection is the right signal for "hung" — healthy long-running audits keep producing tool-call progress.
|
|
137
182
|
--max-budget-usd <N> Per-call cost cap (default: 50)
|
|
183
|
+
--write Enable workspace-write sandbox (default: true; codex needs this to spawn read commands too under default approval policy)
|
|
184
|
+
--no-write Force read-only sandbox (companion approval-policy will decline most commands)
|
|
185
|
+
--model <name> Override model (codex only; gemini ignores)
|
|
186
|
+
--effort <level> Reasoning effort: none|minimal|low|medium|high|xhigh (codex only)
|
|
187
|
+
--cwd <path> Override working directory for the companion call. codex sandbox is cwd-bound; set this when auditing a repo other than the caller's cwd.
|
|
138
188
|
|
|
139
189
|
Outputs JSON to stdout: {status, vendor, version, durationMs, exitCode, stdout, stderr, error?}
|
|
140
190
|
`)
|
|
@@ -180,9 +230,19 @@ function discoverCompanion(vendor, homeDir = homedir()) {
|
|
|
180
230
|
return { error: `plugin ${matchedKey} has no installPath in installed_plugins.json` }
|
|
181
231
|
}
|
|
182
232
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
233
|
+
// Walk the preferred entry-script list. First existing file wins.
|
|
234
|
+
// BC: vendors with only the legacy companion still resolve correctly.
|
|
235
|
+
const entryCandidates = VENDOR_ENTRY_SCRIPTS[vendor] ?? [`${vendor}-companion.mjs`]
|
|
236
|
+
let companionPath = null
|
|
237
|
+
for (const name of entryCandidates) {
|
|
238
|
+
const candidate = join(installPath, 'scripts', name)
|
|
239
|
+
if (existsSync(candidate)) {
|
|
240
|
+
companionPath = candidate
|
|
241
|
+
break
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (!companionPath) {
|
|
245
|
+
return { error: `no entry script found for ${vendor} (tried: ${entryCandidates.join(', ')}) in ${join(installPath, 'scripts')}` }
|
|
186
246
|
}
|
|
187
247
|
|
|
188
248
|
return {
|
|
@@ -237,6 +297,35 @@ async function main(argv) {
|
|
|
237
297
|
process.exit(66) // EX_NOINPUT
|
|
238
298
|
}
|
|
239
299
|
|
|
300
|
+
// 2.1.1: auto-delete the prompt file after a successful read iff the
|
|
301
|
+
// PHYSICAL path sits inside the caller's `<cwd>/.context/tmp/` AND the
|
|
302
|
+
// filename starts with `ccg-`. This is the workflow-managed throwaway-
|
|
303
|
+
// prompt directory (see templates/commands/review.md — Step 1 writes
|
|
304
|
+
// prompts here).
|
|
305
|
+
//
|
|
306
|
+
// CRITICAL — codex audit 2026-05-12 raised: lexical `resolve()` is
|
|
307
|
+
// insufficient. If `.context` or `.context/tmp` is a symlink/junction
|
|
308
|
+
// pointing outside the workspace, OR the caller passes a path whose
|
|
309
|
+
// lexical form is `<cwd>/.context/tmp/ccg-x.txt` but its physical
|
|
310
|
+
// target is `/etc/passwd` (or worse, `C:\Windows\System32\...`), the
|
|
311
|
+
// lexical-only check would still authorize the unlink. realpathSync
|
|
312
|
+
// canonicalizes ALL symlinks/junctions/`..` segments before the
|
|
313
|
+
// whitelist compare — both for the candidate file AND the safeRoot.
|
|
314
|
+
// Two ANDed gates: physical prefix + filename prefix.
|
|
315
|
+
try {
|
|
316
|
+
const resolvedFile = realpathSync(opts.promptFile)
|
|
317
|
+
const safeRoot = realpathSync(resolve(process.cwd(), '.context', 'tmp')) + sep
|
|
318
|
+
if (resolvedFile.startsWith(safeRoot) && basename(resolvedFile).startsWith('ccg-')) {
|
|
319
|
+
unlinkSync(resolvedFile)
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
// Best-effort. ENOENT (.context/tmp absent, file already gone, etc.)
|
|
324
|
+
// is the dominant non-error case here; we deliberately don't surface
|
|
325
|
+
// it. Real failures (permission etc.) will leave a file behind that
|
|
326
|
+
// .gitignore catches and the user's next run overwrites.
|
|
327
|
+
}
|
|
328
|
+
|
|
240
329
|
// Discover companion via SSoT (no glob, no head -1)
|
|
241
330
|
const disc = discoverCompanion(opts.vendor)
|
|
242
331
|
if (disc.error) {
|
|
@@ -252,6 +341,15 @@ async function main(argv) {
|
|
|
252
341
|
promptBody,
|
|
253
342
|
]
|
|
254
343
|
if (opts.json) companionArgs.push('--json')
|
|
344
|
+
// codex-companion flag pass-through (1.0.5 regression fix). gemini-companion
|
|
345
|
+
// ignores unknown flags safely; both vendors accept this surface.
|
|
346
|
+
if (opts.write) companionArgs.push('--write')
|
|
347
|
+
if (opts.model) companionArgs.push('--model', opts.model)
|
|
348
|
+
if (opts.effort) companionArgs.push('--effort', opts.effort)
|
|
349
|
+
// --cwd pass-through: codex sandbox is cwd-bound. For cross-repo audits
|
|
350
|
+
// the caller must set this to the repo being audited (codex-companion
|
|
351
|
+
// does not expose --add-dir; multi-workspace requires deeper changes).
|
|
352
|
+
if (opts.cwd) companionArgs.push('--cwd', opts.cwd)
|
|
255
353
|
|
|
256
354
|
const startedAt = Date.now()
|
|
257
355
|
|
|
@@ -270,24 +368,53 @@ async function main(argv) {
|
|
|
270
368
|
|
|
271
369
|
let stdoutBuf = ''
|
|
272
370
|
let stderrBuf = ''
|
|
273
|
-
|
|
274
|
-
child.
|
|
371
|
+
let lastActivityAt = Date.now()
|
|
372
|
+
child.stdout.on('data', (chunk) => {
|
|
373
|
+
stdoutBuf += chunk.toString('utf-8')
|
|
374
|
+
lastActivityAt = Date.now()
|
|
375
|
+
})
|
|
376
|
+
child.stderr.on('data', (chunk) => {
|
|
377
|
+
stderrBuf += chunk.toString('utf-8')
|
|
378
|
+
lastActivityAt = Date.now()
|
|
379
|
+
})
|
|
275
380
|
|
|
276
|
-
//
|
|
381
|
+
// 2.1.1: two-layer timeout. wall-time is the safety ceiling; idle is the
|
|
382
|
+
// primary "stuck" detector — a healthy long task keeps producing tool-call
|
|
383
|
+
// / progress chunks, a truly hung companion produces nothing. SIGTERM is
|
|
384
|
+
// best-effort on Windows where the kernel doesn't propagate to the
|
|
385
|
+
// grandchild tree; companion processes on this code path are expected to
|
|
386
|
+
// do their own subtree cleanup (taskkill /T) when they receive SIGTERM.
|
|
277
387
|
let timedOut = false
|
|
278
|
-
|
|
388
|
+
let timeoutReason = null
|
|
389
|
+
const killChild = (reason) => {
|
|
279
390
|
timedOut = true
|
|
391
|
+
timeoutReason = reason
|
|
280
392
|
try { child.kill('SIGTERM') } catch { /* ignore */ }
|
|
281
393
|
setTimeout(() => {
|
|
282
394
|
try { child.kill('SIGKILL') } catch { /* ignore */ }
|
|
283
395
|
}, 5000).unref?.()
|
|
284
|
-
}
|
|
396
|
+
}
|
|
397
|
+
const wallTimer = opts.timeoutMs > 0
|
|
398
|
+
? setTimeout(() => killChild(`wall-time ${opts.timeoutMs}ms`), opts.timeoutMs)
|
|
399
|
+
: null
|
|
400
|
+
// Check idle every 30s. A 30s sampling granularity adds at most 30s slop
|
|
401
|
+
// to the configured idle threshold, which is negligible at 600s default.
|
|
402
|
+
const idleChecker = opts.idleTimeoutMs > 0
|
|
403
|
+
? setInterval(() => {
|
|
404
|
+
const silent = Date.now() - lastActivityAt
|
|
405
|
+
if (silent >= opts.idleTimeoutMs) {
|
|
406
|
+
killChild(`idle ${silent}ms exceeds ${opts.idleTimeoutMs}ms`)
|
|
407
|
+
}
|
|
408
|
+
}, 30000)
|
|
409
|
+
: null
|
|
410
|
+
if (idleChecker?.unref) idleChecker.unref()
|
|
285
411
|
|
|
286
412
|
const exit = await new Promise((resolve) => {
|
|
287
413
|
child.once('exit', (code, signal) => resolve({ code, signal }))
|
|
288
414
|
child.once('error', (err) => resolve({ code: 70, signal: null, errorMsg: err.message }))
|
|
289
415
|
})
|
|
290
|
-
clearTimeout(
|
|
416
|
+
if (wallTimer) clearTimeout(wallTimer)
|
|
417
|
+
if (idleChecker) clearInterval(idleChecker)
|
|
291
418
|
|
|
292
419
|
const durationMs = Date.now() - startedAt
|
|
293
420
|
const status = (!timedOut && exit.code === 0) ? 'ok' : 'error'
|
|
@@ -300,7 +427,7 @@ async function main(argv) {
|
|
|
300
427
|
stdout: stdoutBuf,
|
|
301
428
|
stderr: stderrBuf,
|
|
302
429
|
}
|
|
303
|
-
if (timedOut) result.error = `
|
|
430
|
+
if (timedOut) result.error = `companion killed: ${timeoutReason}`
|
|
304
431
|
else if (exit.errorMsg) result.error = `spawn error: ${exit.errorMsg}`
|
|
305
432
|
else if (exit.code !== 0) result.error = `companion exited with code ${exit.code}`
|
|
306
433
|
|