ccgx-workflow 1.0.8 → 1.0.10
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.9nxM0FK2.mjs';
|
|
5
5
|
import { execSync } from 'node:child_process';
|
|
6
6
|
import 'inquirer';
|
|
7
7
|
import 'ora';
|
|
@@ -75,6 +75,17 @@ async function fixMcp() {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
const STUCK_MIN_AGE_SECONDS = 300;
|
|
79
|
+
const STUCK_CPU_RATIO_THRESHOLD = 0.01;
|
|
80
|
+
function isBrokerProcess(p) {
|
|
81
|
+
return /acp-broker|app-server-broker|broker-lifecycle/.test(p.cmdLine);
|
|
82
|
+
}
|
|
83
|
+
function isStuck(p) {
|
|
84
|
+
if (!isBrokerProcess(p)) return false;
|
|
85
|
+
const wallSeconds = p.ageHours * 3600;
|
|
86
|
+
if (wallSeconds < STUCK_MIN_AGE_SECONDS) return false;
|
|
87
|
+
return p.cpuSeconds / wallSeconds < STUCK_CPU_RATIO_THRESHOLD;
|
|
88
|
+
}
|
|
78
89
|
function categorize(cmdLine) {
|
|
79
90
|
const cmd = cmdLine.toLowerCase();
|
|
80
91
|
if (/quasar|vite|webpack|next\b|nuxt|astro|svelte-kit|pnpm.*run.*dev|npm.*run.*dev|yarn.*run.*dev|yarn dev|pnpm dev|npm run dev/.test(cmd)) {
|
|
@@ -87,17 +98,23 @@ function categorize(cmdLine) {
|
|
|
87
98
|
return "other";
|
|
88
99
|
}
|
|
89
100
|
function listNodeProcessesWindows() {
|
|
90
|
-
const ps =
|
|
101
|
+
const ps = `$ProgressPreference = 'SilentlyContinue'
|
|
102
|
+
Get-CimInstance Win32_Process -Filter "Name='node.exe'" | ForEach-Object {
|
|
91
103
|
$cd = $_.CreationDate
|
|
92
104
|
$h = if ($cd) { ((Get-Date) - $cd).TotalHours } else { 0 }
|
|
93
105
|
$cmd = if ($_.CommandLine) { $_.CommandLine } else { '' }
|
|
94
|
-
|
|
106
|
+
$kt = if ($_.KernelModeTime) { $_.KernelModeTime } else { 0 }
|
|
107
|
+
$ut = if ($_.UserModeTime) { $_.UserModeTime } else { 0 }
|
|
108
|
+
$cpuS = ($kt + $ut) / 10000000.0
|
|
109
|
+
"$($_.ProcessId)|$h|$cpuS|$cmd"
|
|
95
110
|
}`;
|
|
96
111
|
let out;
|
|
97
112
|
try {
|
|
98
|
-
|
|
113
|
+
const encoded = Buffer.from(ps, "utf16le").toString("base64");
|
|
114
|
+
out = execSync(`powershell -NoProfile -EncodedCommand ${encoded}`, {
|
|
99
115
|
encoding: "utf-8",
|
|
100
|
-
maxBuffer: 16 * 1024 * 1024
|
|
116
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
117
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
101
118
|
});
|
|
102
119
|
} catch {
|
|
103
120
|
return [];
|
|
@@ -108,43 +125,56 @@ function listNodeProcessesWindows() {
|
|
|
108
125
|
if (!trimmed) continue;
|
|
109
126
|
const sep1 = trimmed.indexOf("|");
|
|
110
127
|
const sep2 = trimmed.indexOf("|", sep1 + 1);
|
|
111
|
-
|
|
128
|
+
const sep3 = trimmed.indexOf("|", sep2 + 1);
|
|
129
|
+
if (sep1 < 0 || sep2 < 0 || sep3 < 0) continue;
|
|
112
130
|
const pid = Number.parseInt(trimmed.slice(0, sep1), 10);
|
|
113
131
|
const ageHours = Number.parseFloat(trimmed.slice(sep1 + 1, sep2));
|
|
114
|
-
const
|
|
132
|
+
const cpuSeconds = Number.parseFloat(trimmed.slice(sep2 + 1, sep3));
|
|
133
|
+
const cmdLine = trimmed.slice(sep3 + 1);
|
|
115
134
|
if (Number.isNaN(pid)) continue;
|
|
116
135
|
procs.push({
|
|
117
136
|
pid,
|
|
118
137
|
ageHours: Number.isFinite(ageHours) ? ageHours : 0,
|
|
138
|
+
cpuSeconds: Number.isFinite(cpuSeconds) ? cpuSeconds : 0,
|
|
119
139
|
cmdLine,
|
|
120
140
|
category: categorize(cmdLine)
|
|
121
141
|
});
|
|
122
142
|
}
|
|
123
143
|
return procs;
|
|
124
144
|
}
|
|
145
|
+
function parseHmsToSeconds(spec) {
|
|
146
|
+
const parts = spec.split(/[-:]/).map((s) => Number.parseInt(s, 10));
|
|
147
|
+
if (parts.length === 4) return parts[0] * 86400 + parts[1] * 3600 + parts[2] * 60 + parts[3];
|
|
148
|
+
if (parts.length === 3) return parts[0] * 3600 + parts[1] * 60 + parts[2];
|
|
149
|
+
if (parts.length === 2) return parts[0] * 60 + parts[1];
|
|
150
|
+
return 0;
|
|
151
|
+
}
|
|
125
152
|
function listNodeProcessesPosix() {
|
|
126
153
|
let out;
|
|
127
154
|
try {
|
|
128
|
-
out = execSync("ps -eo pid,etime,command", { encoding: "utf-8", maxBuffer: 16 * 1024 * 1024 });
|
|
155
|
+
out = execSync("ps -eo pid,etime,time,command", { encoding: "utf-8", maxBuffer: 16 * 1024 * 1024 });
|
|
129
156
|
} catch {
|
|
130
157
|
return [];
|
|
131
158
|
}
|
|
132
159
|
const procs = [];
|
|
133
160
|
for (const line of out.split("\n").slice(1)) {
|
|
134
|
-
const m = line.match(/^\s*(\d+)\s+(\S+)\s+(.+)$/);
|
|
161
|
+
const m = line.match(/^\s*(\d+)\s+(\S+)\s+(\S+)\s+(.+)$/);
|
|
135
162
|
if (!m) continue;
|
|
136
|
-
const cmdLine = m[
|
|
163
|
+
const cmdLine = m[4];
|
|
137
164
|
if (!/(^|\/)node(\s|$)/.test(cmdLine)) continue;
|
|
138
165
|
const pid = Number.parseInt(m[1], 10);
|
|
139
166
|
const etime = m[2];
|
|
167
|
+
const cpuTime = m[3];
|
|
140
168
|
const parts = etime.split(/[-:]/).map((s) => Number.parseInt(s, 10));
|
|
141
169
|
let ageHours = 0;
|
|
142
170
|
if (parts.length === 4) ageHours = parts[0] * 24 + parts[1] + parts[2] / 60;
|
|
143
171
|
else if (parts.length === 3) ageHours = parts[0] + parts[1] / 60;
|
|
144
172
|
else if (parts.length === 2) ageHours = parts[0] / 60;
|
|
173
|
+
const cpuSeconds = parseHmsToSeconds(cpuTime);
|
|
145
174
|
procs.push({
|
|
146
175
|
pid,
|
|
147
176
|
ageHours,
|
|
177
|
+
cpuSeconds,
|
|
148
178
|
cmdLine,
|
|
149
179
|
category: categorize(cmdLine)
|
|
150
180
|
});
|
|
@@ -156,13 +186,13 @@ function listNodeProcesses() {
|
|
|
156
186
|
}
|
|
157
187
|
function killProcessWindows(pid) {
|
|
158
188
|
try {
|
|
159
|
-
execSync(`
|
|
160
|
-
return { ok: true, method: "
|
|
189
|
+
execSync(`taskkill /F /T /PID ${pid}`, { stdio: "pipe" });
|
|
190
|
+
return { ok: true, method: "taskkill /F /T" };
|
|
161
191
|
} catch {
|
|
162
192
|
}
|
|
163
193
|
try {
|
|
164
|
-
execSync(`
|
|
165
|
-
return { ok: true, method: "
|
|
194
|
+
execSync(`powershell -NoProfile -Command "Stop-Process -Id ${pid} -Force -ErrorAction Stop"`, { stdio: "pipe" });
|
|
195
|
+
return { ok: true, method: "Stop-Process" };
|
|
166
196
|
} catch {
|
|
167
197
|
}
|
|
168
198
|
try {
|
|
@@ -173,6 +203,11 @@ function killProcessWindows(pid) {
|
|
|
173
203
|
}
|
|
174
204
|
}
|
|
175
205
|
function killProcessPosix(pid) {
|
|
206
|
+
try {
|
|
207
|
+
execSync(`kill -TERM -- -${pid}`, { stdio: "pipe" });
|
|
208
|
+
return { ok: true, method: "SIGTERM (group)" };
|
|
209
|
+
} catch {
|
|
210
|
+
}
|
|
176
211
|
try {
|
|
177
212
|
execSync(`kill -TERM ${pid}`, { stdio: "pipe" });
|
|
178
213
|
return { ok: true, method: "SIGTERM" };
|
|
@@ -188,8 +223,10 @@ function killProcessPosix(pid) {
|
|
|
188
223
|
async function killOrphans(options = {}) {
|
|
189
224
|
const dryRun = options.dryRun ?? true;
|
|
190
225
|
const minAgeHours = options.minAgeHours ?? 1;
|
|
226
|
+
const stuckOnly = options.stuckOnly ?? false;
|
|
227
|
+
const targetDesc = stuckOnly ? "stuck broker daemons only (CPU/wall < 1%, age > 5min, ignores companions/MCP)" : `orphan node processes >${minAgeHours}h`;
|
|
191
228
|
console.log(ansis.cyan.bold("\n ccgx kill-orphans"));
|
|
192
|
-
console.log(ansis.gray(` ${dryRun ? "[DRY-RUN]" : "[KILL MODE]"} target:
|
|
229
|
+
console.log(ansis.gray(` ${dryRun ? "[DRY-RUN]" : "[KILL MODE]"} target: ${targetDesc}
|
|
193
230
|
`));
|
|
194
231
|
const all = listNodeProcesses();
|
|
195
232
|
if (all.length === 0) {
|
|
@@ -206,24 +243,37 @@ async function killOrphans(options = {}) {
|
|
|
206
243
|
const list = groups.get(cat) ?? [];
|
|
207
244
|
if (list.length === 0) continue;
|
|
208
245
|
const oldestH = Math.max(...list.map((p) => p.ageHours));
|
|
246
|
+
const stuckCount = list.filter(isStuck).length;
|
|
209
247
|
const tag = cat === "dev-server" ? ansis.green("SAFE") : cat === "other" ? ansis.gray("UNKN") : ansis.yellow("ORPH");
|
|
210
|
-
|
|
248
|
+
const stuckTag = stuckCount > 0 ? ansis.red(` ${stuckCount} stuck`) : "";
|
|
249
|
+
console.log(` ${tag} ${cat.padEnd(14)} ${String(list.length).padStart(3)} processes (oldest ${oldestH.toFixed(1)}h)${stuckTag}`);
|
|
211
250
|
}
|
|
212
251
|
console.log();
|
|
213
252
|
const ORPHAN_CATS = ["mcp-server", "codex-cli", "gemini-cli", "phase-runner"];
|
|
214
|
-
const targets = all.filter((p) =>
|
|
253
|
+
const targets = all.filter((p) => {
|
|
254
|
+
if (!ORPHAN_CATS.includes(p.category)) return false;
|
|
255
|
+
if (stuckOnly) return isStuck(p);
|
|
256
|
+
return p.ageHours >= minAgeHours;
|
|
257
|
+
});
|
|
215
258
|
if (targets.length === 0) {
|
|
216
|
-
|
|
259
|
+
const reason = stuckOnly ? "stuck" : `>${minAgeHours}h`;
|
|
260
|
+
console.log(ansis.green(` \u2713 No ${reason} orphans to clean.`));
|
|
217
261
|
return;
|
|
218
262
|
}
|
|
219
263
|
console.log(ansis.bold(` Targets (${targets.length}):`));
|
|
220
264
|
for (const p of targets) {
|
|
221
|
-
const cmdShort = p.cmdLine.length >
|
|
222
|
-
|
|
265
|
+
const cmdShort = p.cmdLine.length > 60 ? `${p.cmdLine.slice(0, 60)}...` : p.cmdLine;
|
|
266
|
+
const wallS = p.ageHours * 3600;
|
|
267
|
+
const ratio = wallS > 0 ? p.cpuSeconds / wallS * 100 : 0;
|
|
268
|
+
const stuckMark = isStuck(p) ? ansis.red(" STUCK") : "";
|
|
269
|
+
console.log(
|
|
270
|
+
` PID ${String(p.pid).padStart(6)} ${p.ageHours.toFixed(1).padStart(5)}h CPU ${p.cpuSeconds.toFixed(1).padStart(6)}s (${ratio.toFixed(2).padStart(5)}%) ${ansis.gray(p.category.padEnd(14))} ${cmdShort}${stuckMark}`
|
|
271
|
+
);
|
|
223
272
|
}
|
|
224
273
|
console.log();
|
|
225
274
|
if (dryRun) {
|
|
226
|
-
|
|
275
|
+
const flag = stuckOnly ? "--stuck --kill" : "--kill";
|
|
276
|
+
console.log(ansis.gray(` Run with ${flag} to actually terminate (skips dev-server / other categories).`));
|
|
227
277
|
return;
|
|
228
278
|
}
|
|
229
279
|
const killer = isWindows() ? killProcessWindows : killProcessPosix;
|
|
@@ -324,10 +374,11 @@ async function setupCommands(cli) {
|
|
|
324
374
|
cli.command("diagnose-mcp", i18n.t("cli:help.commandDescriptions.diagnoseMcp")).action(async () => {
|
|
325
375
|
await diagnoseMcp();
|
|
326
376
|
});
|
|
327
|
-
cli.command("kill-orphans", "\u6E05\u7406 Claude Code session \u9000\u51FA\u540E\u6B8B\u7559\u7684\u5B64\u513F node \u8FDB\u7A0B\uFF08MCP server / codex / gemini\uFF09").option("--kill", "\u5B9E\u9645\u6267\u884C kill (\u9ED8\u8BA4 dry-run)").option("--min-age-hours <N>", "\u53EA\u6E05\u7406\u8D85\u8FC7 N \u5C0F\u65F6\u7684\u8FDB\u7A0B (\u9ED8\u8BA4 1)").action(async (options) => {
|
|
377
|
+
cli.command("kill-orphans", "\u6E05\u7406 Claude Code session \u9000\u51FA\u540E\u6B8B\u7559\u7684\u5B64\u513F node \u8FDB\u7A0B\uFF08MCP server / codex / gemini\uFF09").option("--kill", "\u5B9E\u9645\u6267\u884C kill (\u9ED8\u8BA4 dry-run)").option("--min-age-hours <N>", "\u53EA\u6E05\u7406\u8D85\u8FC7 N \u5C0F\u65F6\u7684\u8FDB\u7A0B (\u9ED8\u8BA4 1)").option("--stuck", "\u53EA\u6E05\u7406\u505C\u6EDE\u8FDB\u7A0B\uFF1ACPU \u5360\u6BD4 < 1% \u4E14 wall age > 5min\uFF08\u8986\u76D6 --min-age-hours\uFF09").action(async (options) => {
|
|
328
378
|
await killOrphans({
|
|
329
379
|
dryRun: !options.kill,
|
|
330
|
-
minAgeHours: options.minAgeHours ? Number.parseFloat(options.minAgeHours) : 1
|
|
380
|
+
minAgeHours: options.minAgeHours ? Number.parseFloat(options.minAgeHours) : 1,
|
|
381
|
+
stuckOnly: options.stuck
|
|
331
382
|
});
|
|
332
383
|
});
|
|
333
384
|
cli.command("fix-mcp", i18n.t("cli:help.commandDescriptions.fixMcp")).action(async () => {
|
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.9nxM0FK2.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.9nxM0FK2.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 = "1.0.
|
|
16
|
+
const version = "1.0.10";
|
|
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": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
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",
|
|
@@ -142,6 +142,18 @@ const PATCHES = [
|
|
|
142
142
|
match: /(if \(message\.error\) \{\s*\r?\n\s*)pending\.reject\(message\.error\);(\s*\r?\n\s*\} else \{)/,
|
|
143
143
|
replace: `$1// CCG P-9 patch: wrap JSON-RPC error object in Error instance.\n // Without this, callers doing \`e instanceof Error ? e.message : String(e)\`\n // get "[object Object]" — losing real error info (auth-expired, broker-dead,\n // parse-error, etc). See .ccg-migration/PLUGIN-PATCHES.md P-9.\n const _err = message.error;\n const _wrapped = Object.assign(\n new Error(typeof _err === "object" && _err !== null && _err.message\n ? String(_err.message)\n : String(_err)),\n {\n jsonrpcCode: typeof _err === "object" && _err !== null ? _err.code : undefined,\n jsonrpcData: typeof _err === "object" && _err !== null ? _err.data : undefined,\n },\n );\n pending.reject(_wrapped);$2`,
|
|
144
144
|
},
|
|
145
|
+
{
|
|
146
|
+
id: "P-12",
|
|
147
|
+
file: "gemini-companion.mjs",
|
|
148
|
+
description: "CLAUDE_PLUGIN_DATA env cross-contamination — recompute from script path",
|
|
149
|
+
// Guard: any patched marker present
|
|
150
|
+
guard: /CCG P-12 patch/,
|
|
151
|
+
// Anchor on the last import line (createStreamHandler). Newer plugin
|
|
152
|
+
// versions may add imports after this; if so, the regex misses (reported
|
|
153
|
+
// by the script's [MISS] line) and the patch is re-evaluated upstream.
|
|
154
|
+
match: /(import\s+\{\s*createStreamHandler\s*\}\s+from\s+"\.\/lib\/stream-output\.mjs";?)/,
|
|
155
|
+
replace: `$1\n\n// CCG P-12 patch: prevent CLAUDE_PLUGIN_DATA cross-contamination from previous\n// plugin invocations (e.g., codex). Recompute from this script's physical path\n// so the broker session file lands in our own data dir, enabling broker reuse.\n// See .ccg-migration/PLUGIN-PATCHES.md P-12.\n{\n const _scriptDir = path.dirname(fileURLToPath(import.meta.url));\n const _versionDir = path.dirname(_scriptDir);\n const _pluginDir = path.dirname(_versionDir);\n const _marketplaceDir = path.dirname(_pluginDir);\n const _cacheDir = path.dirname(_marketplaceDir);\n const _pluginsDir = path.dirname(_cacheDir);\n const _pluginName = path.basename(_pluginDir);\n const _marketplaceName = path.basename(_marketplaceDir);\n process.env.CLAUDE_PLUGIN_DATA = path.join(_pluginsDir, "data", _pluginName + "-" + _marketplaceName);\n}`,
|
|
156
|
+
},
|
|
145
157
|
];
|
|
146
158
|
|
|
147
159
|
let applied = 0;
|