ccgx-workflow 1.0.9 → 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)) {
|
|
@@ -92,7 +103,10 @@ function listNodeProcessesWindows() {
|
|
|
92
103
|
$cd = $_.CreationDate
|
|
93
104
|
$h = if ($cd) { ((Get-Date) - $cd).TotalHours } else { 0 }
|
|
94
105
|
$cmd = if ($_.CommandLine) { $_.CommandLine } else { '' }
|
|
95
|
-
|
|
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"
|
|
96
110
|
}`;
|
|
97
111
|
let out;
|
|
98
112
|
try {
|
|
@@ -111,43 +125,56 @@ function listNodeProcessesWindows() {
|
|
|
111
125
|
if (!trimmed) continue;
|
|
112
126
|
const sep1 = trimmed.indexOf("|");
|
|
113
127
|
const sep2 = trimmed.indexOf("|", sep1 + 1);
|
|
114
|
-
|
|
128
|
+
const sep3 = trimmed.indexOf("|", sep2 + 1);
|
|
129
|
+
if (sep1 < 0 || sep2 < 0 || sep3 < 0) continue;
|
|
115
130
|
const pid = Number.parseInt(trimmed.slice(0, sep1), 10);
|
|
116
131
|
const ageHours = Number.parseFloat(trimmed.slice(sep1 + 1, sep2));
|
|
117
|
-
const
|
|
132
|
+
const cpuSeconds = Number.parseFloat(trimmed.slice(sep2 + 1, sep3));
|
|
133
|
+
const cmdLine = trimmed.slice(sep3 + 1);
|
|
118
134
|
if (Number.isNaN(pid)) continue;
|
|
119
135
|
procs.push({
|
|
120
136
|
pid,
|
|
121
137
|
ageHours: Number.isFinite(ageHours) ? ageHours : 0,
|
|
138
|
+
cpuSeconds: Number.isFinite(cpuSeconds) ? cpuSeconds : 0,
|
|
122
139
|
cmdLine,
|
|
123
140
|
category: categorize(cmdLine)
|
|
124
141
|
});
|
|
125
142
|
}
|
|
126
143
|
return procs;
|
|
127
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
|
+
}
|
|
128
152
|
function listNodeProcessesPosix() {
|
|
129
153
|
let out;
|
|
130
154
|
try {
|
|
131
|
-
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 });
|
|
132
156
|
} catch {
|
|
133
157
|
return [];
|
|
134
158
|
}
|
|
135
159
|
const procs = [];
|
|
136
160
|
for (const line of out.split("\n").slice(1)) {
|
|
137
|
-
const m = line.match(/^\s*(\d+)\s+(\S+)\s+(.+)$/);
|
|
161
|
+
const m = line.match(/^\s*(\d+)\s+(\S+)\s+(\S+)\s+(.+)$/);
|
|
138
162
|
if (!m) continue;
|
|
139
|
-
const cmdLine = m[
|
|
163
|
+
const cmdLine = m[4];
|
|
140
164
|
if (!/(^|\/)node(\s|$)/.test(cmdLine)) continue;
|
|
141
165
|
const pid = Number.parseInt(m[1], 10);
|
|
142
166
|
const etime = m[2];
|
|
167
|
+
const cpuTime = m[3];
|
|
143
168
|
const parts = etime.split(/[-:]/).map((s) => Number.parseInt(s, 10));
|
|
144
169
|
let ageHours = 0;
|
|
145
170
|
if (parts.length === 4) ageHours = parts[0] * 24 + parts[1] + parts[2] / 60;
|
|
146
171
|
else if (parts.length === 3) ageHours = parts[0] + parts[1] / 60;
|
|
147
172
|
else if (parts.length === 2) ageHours = parts[0] / 60;
|
|
173
|
+
const cpuSeconds = parseHmsToSeconds(cpuTime);
|
|
148
174
|
procs.push({
|
|
149
175
|
pid,
|
|
150
176
|
ageHours,
|
|
177
|
+
cpuSeconds,
|
|
151
178
|
cmdLine,
|
|
152
179
|
category: categorize(cmdLine)
|
|
153
180
|
});
|
|
@@ -159,13 +186,13 @@ function listNodeProcesses() {
|
|
|
159
186
|
}
|
|
160
187
|
function killProcessWindows(pid) {
|
|
161
188
|
try {
|
|
162
|
-
execSync(`
|
|
163
|
-
return { ok: true, method: "
|
|
189
|
+
execSync(`taskkill /F /T /PID ${pid}`, { stdio: "pipe" });
|
|
190
|
+
return { ok: true, method: "taskkill /F /T" };
|
|
164
191
|
} catch {
|
|
165
192
|
}
|
|
166
193
|
try {
|
|
167
|
-
execSync(`
|
|
168
|
-
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" };
|
|
169
196
|
} catch {
|
|
170
197
|
}
|
|
171
198
|
try {
|
|
@@ -176,6 +203,11 @@ function killProcessWindows(pid) {
|
|
|
176
203
|
}
|
|
177
204
|
}
|
|
178
205
|
function killProcessPosix(pid) {
|
|
206
|
+
try {
|
|
207
|
+
execSync(`kill -TERM -- -${pid}`, { stdio: "pipe" });
|
|
208
|
+
return { ok: true, method: "SIGTERM (group)" };
|
|
209
|
+
} catch {
|
|
210
|
+
}
|
|
179
211
|
try {
|
|
180
212
|
execSync(`kill -TERM ${pid}`, { stdio: "pipe" });
|
|
181
213
|
return { ok: true, method: "SIGTERM" };
|
|
@@ -191,8 +223,10 @@ function killProcessPosix(pid) {
|
|
|
191
223
|
async function killOrphans(options = {}) {
|
|
192
224
|
const dryRun = options.dryRun ?? true;
|
|
193
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`;
|
|
194
228
|
console.log(ansis.cyan.bold("\n ccgx kill-orphans"));
|
|
195
|
-
console.log(ansis.gray(` ${dryRun ? "[DRY-RUN]" : "[KILL MODE]"} target:
|
|
229
|
+
console.log(ansis.gray(` ${dryRun ? "[DRY-RUN]" : "[KILL MODE]"} target: ${targetDesc}
|
|
196
230
|
`));
|
|
197
231
|
const all = listNodeProcesses();
|
|
198
232
|
if (all.length === 0) {
|
|
@@ -209,24 +243,37 @@ async function killOrphans(options = {}) {
|
|
|
209
243
|
const list = groups.get(cat) ?? [];
|
|
210
244
|
if (list.length === 0) continue;
|
|
211
245
|
const oldestH = Math.max(...list.map((p) => p.ageHours));
|
|
246
|
+
const stuckCount = list.filter(isStuck).length;
|
|
212
247
|
const tag = cat === "dev-server" ? ansis.green("SAFE") : cat === "other" ? ansis.gray("UNKN") : ansis.yellow("ORPH");
|
|
213
|
-
|
|
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}`);
|
|
214
250
|
}
|
|
215
251
|
console.log();
|
|
216
252
|
const ORPHAN_CATS = ["mcp-server", "codex-cli", "gemini-cli", "phase-runner"];
|
|
217
|
-
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
|
+
});
|
|
218
258
|
if (targets.length === 0) {
|
|
219
|
-
|
|
259
|
+
const reason = stuckOnly ? "stuck" : `>${minAgeHours}h`;
|
|
260
|
+
console.log(ansis.green(` \u2713 No ${reason} orphans to clean.`));
|
|
220
261
|
return;
|
|
221
262
|
}
|
|
222
263
|
console.log(ansis.bold(` Targets (${targets.length}):`));
|
|
223
264
|
for (const p of targets) {
|
|
224
|
-
const cmdShort = p.cmdLine.length >
|
|
225
|
-
|
|
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
|
+
);
|
|
226
272
|
}
|
|
227
273
|
console.log();
|
|
228
274
|
if (dryRun) {
|
|
229
|
-
|
|
275
|
+
const flag = stuckOnly ? "--stuck --kill" : "--kill";
|
|
276
|
+
console.log(ansis.gray(` Run with ${flag} to actually terminate (skips dev-server / other categories).`));
|
|
230
277
|
return;
|
|
231
278
|
}
|
|
232
279
|
const killer = isWindows() ? killProcessWindows : killProcessPosix;
|
|
@@ -327,10 +374,11 @@ async function setupCommands(cli) {
|
|
|
327
374
|
cli.command("diagnose-mcp", i18n.t("cli:help.commandDescriptions.diagnoseMcp")).action(async () => {
|
|
328
375
|
await diagnoseMcp();
|
|
329
376
|
});
|
|
330
|
-
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) => {
|
|
331
378
|
await killOrphans({
|
|
332
379
|
dryRun: !options.kill,
|
|
333
|
-
minAgeHours: options.minAgeHours ? Number.parseFloat(options.minAgeHours) : 1
|
|
380
|
+
minAgeHours: options.minAgeHours ? Number.parseFloat(options.minAgeHours) : 1,
|
|
381
|
+
stuckOnly: options.stuck
|
|
334
382
|
});
|
|
335
383
|
});
|
|
336
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;
|