@cocorograph/hub-agent 0.8.109 → 0.8.110
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/package.json +1 -1
- package/src/hub-bundle.mjs +70 -12
package/package.json
CHANGED
package/src/hub-bundle.mjs
CHANGED
|
@@ -330,22 +330,48 @@ export function hasInheritedOutputChild() {
|
|
|
330
330
|
return inheritedOutputChildren > 0
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
-
function runOnce(argv, { logger } = {}) {
|
|
333
|
+
export function runOnce(argv, { logger, redact = [] } = {}) {
|
|
334
|
+
const secrets = redact.filter((value) => value.length > 0).sort((a, b) => b.length - a.length)
|
|
335
|
+
const pattern = secrets.length
|
|
336
|
+
? new RegExp(secrets.map(_escapeRegExp).join("|"), "g")
|
|
337
|
+
: null
|
|
338
|
+
const sanitize = (value) => pattern ? value.replace(pattern, "***") : value
|
|
339
|
+
const safeError = (err) => pattern ? new Error(sanitize(err.message)) : err
|
|
334
340
|
return new Promise((resolve, reject) => {
|
|
335
341
|
const [bin, ...args] = argv
|
|
336
|
-
|
|
342
|
+
const cmd = sanitize(argv.join(" "))
|
|
343
|
+
if (!pattern) inheritedOutputChildren++
|
|
337
344
|
let released = false
|
|
338
345
|
const release = () => {
|
|
339
346
|
if (released) return
|
|
340
347
|
released = true
|
|
341
|
-
inheritedOutputChildren--
|
|
348
|
+
if (!pattern) inheritedOutputChildren--
|
|
342
349
|
}
|
|
343
350
|
let child
|
|
344
351
|
try {
|
|
345
|
-
child = spawn(bin, args, { stdio: "inherit" })
|
|
352
|
+
child = spawn(bin, args, { stdio: pattern ? ["inherit", "pipe", "pipe"] : "inherit" })
|
|
346
353
|
} catch (err) {
|
|
347
354
|
release()
|
|
348
|
-
throw err
|
|
355
|
+
throw safeError(err)
|
|
356
|
+
}
|
|
357
|
+
// close を待って全バイトを結合し、チャンク境界(UTF-8 の途中を含む)での漏出を防ぐ。
|
|
358
|
+
// 上限を超えたら収集済みも捨てて読み捨てに切り替える(途中で切ると資格の断片が残りうるため、
|
|
359
|
+
// 部分表示はしない)。pipe は close まで drain し続け、子を書き込み待ちで止めない。
|
|
360
|
+
const captured = { stdout: { chunks: [], bytes: 0, overflow: false }, stderr: { chunks: [], bytes: 0, overflow: false } }
|
|
361
|
+
if (pattern) {
|
|
362
|
+
for (const name of ["stdout", "stderr"]) {
|
|
363
|
+
const buf = captured[name]
|
|
364
|
+
child[name].on("data", (chunk) => {
|
|
365
|
+
if (buf.overflow) return
|
|
366
|
+
buf.bytes += chunk.length
|
|
367
|
+
if (buf.bytes > REDACTED_OUTPUT_MAX_BYTES) {
|
|
368
|
+
buf.overflow = true
|
|
369
|
+
buf.chunks = []
|
|
370
|
+
return
|
|
371
|
+
}
|
|
372
|
+
buf.chunks.push(chunk)
|
|
373
|
+
})
|
|
374
|
+
}
|
|
349
375
|
}
|
|
350
376
|
// spawn 失敗 (ENOENT 等) は exit が来ないことがある。error でも必ず settle させ、
|
|
351
377
|
// bundle 同期を未完了のまま残さない (resolve/reject は最初の 1 回だけ有効)。
|
|
@@ -355,22 +381,54 @@ function runOnce(argv, { logger } = {}) {
|
|
|
355
381
|
}
|
|
356
382
|
child.once("error", (err) => {
|
|
357
383
|
release()
|
|
358
|
-
reject(err)
|
|
359
|
-
safeLog("warn", { cmd
|
|
384
|
+
reject(safeError(err))
|
|
385
|
+
safeLog("warn", { cmd, error: sanitize(err.message) }, "hub-bundle post_install failed")
|
|
360
386
|
})
|
|
361
|
-
child.on("exit", (code) => {
|
|
387
|
+
child.on(pattern ? "close" : "exit", (code) => {
|
|
362
388
|
release()
|
|
389
|
+
// 先に settle する。転送先の障害(ENOSPC / EPIPE 等)でコマンドの成否を変えない。
|
|
363
390
|
if (code === 0) {
|
|
364
391
|
resolve(code)
|
|
365
|
-
safeLog("info", { cmd: argv.join(" ") }, "hub-bundle post_install ok")
|
|
366
392
|
} else {
|
|
367
|
-
reject(new Error(`post_install command failed (exit ${code}): ${
|
|
368
|
-
|
|
393
|
+
reject(new Error(`post_install command failed (exit ${code}): ${cmd}`))
|
|
394
|
+
}
|
|
395
|
+
if (pattern) {
|
|
396
|
+
for (const name of ["stdout", "stderr"]) {
|
|
397
|
+
const buf = captured[name]
|
|
398
|
+
const text = buf.overflow
|
|
399
|
+
? `[hub-agent] 出力が ${REDACTED_OUTPUT_MAX_BYTES} バイトを超えたため省略しました\n`
|
|
400
|
+
: sanitize(Buffer.concat(buf.chunks).toString("utf8"))
|
|
401
|
+
if (text) forwardRedactedOutput(process[name], text)
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (code === 0) {
|
|
405
|
+
safeLog("info", { cmd }, "hub-bundle post_install ok")
|
|
406
|
+
} else {
|
|
407
|
+
safeLog("warn", { cmd, code }, "hub-bundle post_install failed")
|
|
369
408
|
}
|
|
370
409
|
})
|
|
371
410
|
})
|
|
372
411
|
}
|
|
373
412
|
|
|
413
|
+
// 伏せ字済み出力の 1 ストリームあたりの収集上限。claude mcp add の通常出力は数百バイト。
|
|
414
|
+
const REDACTED_OUTPUT_MAX_BYTES = 1024 * 1024
|
|
415
|
+
|
|
416
|
+
// 伏せ字済み出力を親の stdout / stderr へ書く。書き込み失敗は診断出力の欠落として扱い、
|
|
417
|
+
// 同期例外も非同期の error も外へ出さない。error listener はこの書き込みの間だけ付け、
|
|
418
|
+
// 共有ストリームの他のエラーを恒久的に握り潰さない。
|
|
419
|
+
function forwardRedactedOutput(stream, text) {
|
|
420
|
+
const onError = () => {}
|
|
421
|
+
stream.on("error", onError)
|
|
422
|
+
try {
|
|
423
|
+
stream.write(text, () => {
|
|
424
|
+
// callback 後に error が emit される実装があるため、1 tick 置いてから外す。
|
|
425
|
+
setImmediate(() => stream.removeListener("error", onError))
|
|
426
|
+
})
|
|
427
|
+
} catch {
|
|
428
|
+
stream.removeListener("error", onError)
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
374
432
|
/**
|
|
375
433
|
* Bundle response の `mcp_servers` を Claude Code CLI に登録する。
|
|
376
434
|
*
|
|
@@ -474,7 +532,7 @@ export async function applyMcpServers(
|
|
|
474
532
|
// 未登録 → remove は失敗するが正常系。
|
|
475
533
|
}
|
|
476
534
|
}
|
|
477
|
-
await run(argv, { logger })
|
|
535
|
+
await run(argv, wantHeader ? { logger, redact: [agentToken] } : { logger })
|
|
478
536
|
added.push(s.name)
|
|
479
537
|
} catch (err) {
|
|
480
538
|
// ヘッダ不要の外部 MCP は既存なら "already exists" で失敗する。これは「既に正しく
|