@coworker-jp/aidr 0.1.321 → 0.1.323
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/agents/codex.mjs +6 -4
- package/src/templates.mjs +129 -0
- package/src/toml-merge.mjs +117 -29
package/package.json
CHANGED
package/src/agents/codex.mjs
CHANGED
|
@@ -52,8 +52,10 @@ const CODEX_AGENTS_MD = `# AIDR Scanner
|
|
|
52
52
|
|
|
53
53
|
AIDR security scanner is active for this Codex session. Set the access key
|
|
54
54
|
via the \`AI_SCANNER_ACCESS_KEY\` environment variable. Hooks are registered
|
|
55
|
-
in \`.codex/hooks.json\` and require \`[features]
|
|
56
|
-
\`.codex/config.toml\` (this setup writes both for you).
|
|
55
|
+
in \`.codex/hooks.json\` and require \`[features] hooks = true\` in
|
|
56
|
+
\`.codex/config.toml\` (this setup writes both for you). The older
|
|
57
|
+
\`[features] codex_hooks\` spelling is deprecated by Codex — if your config
|
|
58
|
+
still has it, setup renames it to \`hooks\` in place.
|
|
57
59
|
|
|
58
60
|
Codex currently emits Bash tool events only. Read / Edit / WebFetch / WebSearch
|
|
59
61
|
are not hookable — for those, use Claude Code.
|
|
@@ -183,7 +185,7 @@ async function mergeCodexConfigToml(target, { dryRun, stderr = process.stderr })
|
|
|
183
185
|
if (stat) {
|
|
184
186
|
stderr.write(
|
|
185
187
|
`WARNING: ${target} already exists. (dry-run: would back up)\n` +
|
|
186
|
-
`Merging [features] codex_hooks; your other settings are preserved.\n`
|
|
188
|
+
`Merging [features] hooks (the deprecated codex_hooks key is migrated if present); your other settings are preserved.\n`
|
|
187
189
|
);
|
|
188
190
|
}
|
|
189
191
|
process.stdout.write(
|
|
@@ -199,7 +201,7 @@ async function mergeCodexConfigToml(target, { dryRun, stderr = process.stderr })
|
|
|
199
201
|
await fsp.copyFile(target, backupPath);
|
|
200
202
|
stderr.write(
|
|
201
203
|
`WARNING: ${target} already exists. Backed up to ${backupPath}.\n` +
|
|
202
|
-
`Merging [features] codex_hooks; your other settings are preserved.\n`
|
|
204
|
+
`Merging [features] hooks (the deprecated codex_hooks key is migrated if present); your other settings are preserved.\n`
|
|
203
205
|
);
|
|
204
206
|
}
|
|
205
207
|
|
package/src/templates.mjs
CHANGED
|
@@ -11,11 +11,44 @@ export function getSettingsJson(accessKey, scope = "project") {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
// start_scanner.sh — parameterized by the verify endpoint (prod / dev).
|
|
14
|
+
// ── このスクリプトが端末へ出す文言は ja / en を併記する (issue #2372 条件 3) ──
|
|
15
|
+
//
|
|
16
|
+
// **決定 (2026-09-09): (a) 併記する。** `docs/product/UI_COPY.md` の表の
|
|
17
|
+
// 「`aidr` CLI (端末のターミナルに出る文字列)」の行が既にこう決めており、
|
|
18
|
+
// **その行の理由がこの面にそのまま当たる**:
|
|
19
|
+
//
|
|
20
|
+
// * **読み手を実行時に特定できない。** 同じ端末で情シスも開発者も読む。
|
|
21
|
+
// * **出し分ける先が無い。** フックはロケールを持たない bash なので、
|
|
22
|
+
// 1 つの出力を全員が読む。**片方だけにすると、読めない側にとっては
|
|
23
|
+
// 何も書かれていないのと同じである。**
|
|
24
|
+
//
|
|
25
|
+
// この 2 つは「利用者が打ったコマンド」かどうかに依存しない。むしろこの面は
|
|
26
|
+
// CLI より弱い —— **利用者はこの出力を頼んでいない**ので、読めない言語で
|
|
27
|
+
// 出された行は、そのまま読み飛ばされる。
|
|
28
|
+
//
|
|
29
|
+
// **(b)「フックの stderr は en のみ」と明文化する案は採らなかった。** 採れば
|
|
30
|
+
// この面だけが表の 4 行のどれとも違う基準を持つことになり、**なぜここだけ
|
|
31
|
+
// 違うのかを説明できない** (英語だけだったのは判断の結果ではなく、
|
|
32
|
+
// 基準を持たないまま書かれ続けた結果である —— `UI_COPY.md` の
|
|
33
|
+
// `GAP-2355-CLI-COPY-JA-EN-UNEVEN` がその状態を名指ししている)。
|
|
34
|
+
//
|
|
35
|
+
// **★ 短くするために事実や次の行動を削らないこと** (`UI_COPY.md` 【非交渉】)。
|
|
36
|
+
// 1 文が長くなるなら**文を割る**。en 行はそのまま残してある —— 併記は
|
|
37
|
+
// 「英語を日本語で置き換える」ことではない。
|
|
38
|
+
//
|
|
39
|
+
// **ここで揃うのはこのスクリプトだけである** (`GAP-2372-AIDR-JA-EN-PARTIAL`)。
|
|
40
|
+
// `packages/aidr` の他の出力 (`red-team.mjs::redTeamInstallFailureText` など) は
|
|
41
|
+
// 英語のままで、揃えるのは別の作業である。**「aidr は併記済み」と読まないこと。**
|
|
14
42
|
// `agentType` is currently unused inside the script (endpoint-info has moved
|
|
15
43
|
// to schedule-only invocation via aidr --scheduled), but the parameter
|
|
16
44
|
// is kept for signature stability with callers and the wider TS template.
|
|
17
45
|
// eslint-disable-next-line no-unused-vars
|
|
18
46
|
export function getStartScannerSh(downloadBase = "https://pytd2jd5p7.execute-api.ap-northeast-1.amazonaws.com/download", agentType = "claude-code") {
|
|
47
|
+
// `agentType` is the endpoint-info spelling (`claude-code`); `setup --agent`
|
|
48
|
+
// takes the installer/portal spelling (`claude`). They differ for exactly one
|
|
49
|
+
// agent, so the mapping is stated here rather than guessed at the call site —
|
|
50
|
+
// guessing would make the self-repair below configure the WRONG agent.
|
|
51
|
+
const setupAgentName = agentType === "claude-code" ? "claude" : agentType;
|
|
19
52
|
// Which installer to quote back at the user, derived from the endpoint this
|
|
20
53
|
// script was generated against. The DEV package must never tell anyone to
|
|
21
54
|
// install PROD (same rule as cli.mjs's INSTALL_COMMAND_HINT) — and here the
|
|
@@ -198,6 +231,7 @@ ensure_binary() {
|
|
|
198
231
|
url="\${DOWNLOAD_BASE}/\${product}/\${PLATFORM_SLUG}"
|
|
199
232
|
fi
|
|
200
233
|
if download_binary "$url" "$dest"; then
|
|
234
|
+
echo "$product のバイナリを導入しました (\${PLATFORM_SLUG}、チェックサム検証済み)。" >&2
|
|
201
235
|
echo "$product binary installed (\${PLATFORM_SLUG}, checksum verified)" >&2
|
|
202
236
|
return 0
|
|
203
237
|
fi
|
|
@@ -221,6 +255,7 @@ report_unscanned_history() {
|
|
|
221
255
|
# itself is the fallback — printing nothing here would drop the fact.
|
|
222
256
|
local when
|
|
223
257
|
when=$(date -d "@$first" 2>/dev/null || date -r "$first" 2>/dev/null || echo "epoch $first")
|
|
258
|
+
echo "ai-scanner: この端末では、すでに $count 件のツールコールが検査されないまま実行されています (最初は $when)。" >&2
|
|
224
259
|
echo "ai-scanner: $count tool call(s) have already run unscanned on this machine (first at $when)." >&2
|
|
225
260
|
}
|
|
226
261
|
|
|
@@ -229,8 +264,10 @@ report_unscanned_history() {
|
|
|
229
264
|
# reads as cosmetic (docs/product/UI_COPY.md 【非交渉】) — and unlike a failed
|
|
230
265
|
# download, re-running the installer cannot fix it, so the action is different.
|
|
231
266
|
unsupported_platform() {
|
|
267
|
+
echo "ai-scanner: このセッションは検査されていません。このプラットフォーム ($1) 向けのスキャナーが存在しません。" >&2
|
|
232
268
|
echo "ai-scanner: this session is NOT being scanned — there is no scanner build for this platform ($1)." >&2
|
|
233
269
|
report_unscanned_history
|
|
270
|
+
echo "ai-scanner: ツールコールは動き続けますが、この端末では検査されないまま実行されます。このメッセージを添えて、社内の IT 管理者にご連絡ください。" >&2
|
|
234
271
|
echo "ai-scanner: tool calls keep working, and they run unscanned on this machine. Contact your IT administrator with this message." >&2
|
|
235
272
|
}
|
|
236
273
|
|
|
@@ -282,8 +319,10 @@ if ensure_binary "$HARNESS" ai-harness; then
|
|
|
282
319
|
# record only ever describes the machine's CURRENT state.
|
|
283
320
|
rm -f "$UNSCANNED_STAMP"
|
|
284
321
|
else
|
|
322
|
+
echo "ai-scanner: このセッションは検査されていません。ツールコール検査機 (ai-harness) がこの端末になく、今回も導入できませんでした: \${UNVERIFIED_REASON:-ダウンロードに失敗しました}。" >&2
|
|
285
323
|
echo "ai-scanner: this session is NOT being scanned — the tool-call scanner (ai-harness) is not installed on this machine and could not be installed now: \${UNVERIFIED_REASON:-the download failed}." >&2
|
|
286
324
|
report_unscanned_history
|
|
325
|
+
echo "ai-scanner: ツールコールは動き続けますが、導入されるまで検査されないまま実行されます。導入コマンド (${installCommand}) をもう一度実行してください。それでも直らなければ、このメッセージを添えて社内の IT 管理者にご連絡ください。" >&2
|
|
287
326
|
echo "ai-scanner: tool calls keep working, and they run unscanned until it is in place. Re-run the installer (${installCommand}), or contact your IT administrator with this message." >&2
|
|
288
327
|
fi
|
|
289
328
|
|
|
@@ -295,8 +334,10 @@ if ! ensure_binary "$BIN" ai-scanner; then
|
|
|
295
334
|
# Only claims scanning is unaffected when it actually is — the harness may
|
|
296
335
|
# have failed too, and the line above is the one that says so.
|
|
297
336
|
if [ "$SCANNING" = 1 ]; then
|
|
337
|
+
echo "ai-scanner: ツールコールの検査は続いています。ただし定期の端末情報収集は導入できませんでした: \${UNVERIFIED_REASON:-ダウンロードに失敗しました}。この端末がポータルに現れなくなったら、導入コマンド (${installCommand}) をもう一度実行してください。" >&2
|
|
298
338
|
echo "ai-scanner: tool calls are still being scanned, but the periodic endpoint-inventory collector could not be installed: \${UNVERIFIED_REASON:-the download failed}. Re-run the installer (${installCommand}) if this endpoint stops appearing in the portal." >&2
|
|
299
339
|
else
|
|
340
|
+
echo "ai-scanner: 定期の端末情報収集も導入できませんでした: \${UNVERIFIED_REASON:-ダウンロードに失敗しました}。導入コマンド (${installCommand}) をもう一度実行してください。それでも直らなければ、このメッセージを添えて社内の IT 管理者にご連絡ください。" >&2
|
|
300
341
|
echo "ai-scanner: the periodic endpoint-inventory collector could not be installed either: \${UNVERIFIED_REASON:-the download failed}. Re-run the installer (${installCommand}), or contact your IT administrator with this message." >&2
|
|
301
342
|
fi
|
|
302
343
|
fi
|
|
@@ -325,13 +366,17 @@ if should_check_update; then
|
|
|
325
366
|
want_host=$(printf '%s' "$DOWNLOAD_BASE" | sed -E 's#^[a-z]+://([^/]+).*#\\1#')
|
|
326
367
|
have_host=$(timeout "$VERSION_TIMEOUT" "$BIN" version 2>/dev/null | sed -n 's#^server: [a-z]*://\\([^/]*\\).*#\\1#p' | head -1)
|
|
327
368
|
if [ -n "$have_host" ] && [ -n "$want_host" ] && [ "$have_host" != "$want_host" ]; then
|
|
369
|
+
echo "ai-scanner: 導入済みのバイナリは $have_host を見ていますが、この導入は $want_host から配信しています。アクセスキーは決して受け付けられず、検知ログは拒否されて捨てられます。一致するバイナリを取り直しています。" >&2
|
|
328
370
|
echo "ai-scanner: the installed binary talks to $have_host, but this installation provisions from $want_host, so its access key can never be accepted (detection logs are refused and discarded). Re-downloading the matching binary." >&2
|
|
329
371
|
download_binary "\${DOWNLOAD_BASE}/\${PLATFORM_SLUG}" "$BIN" || \\
|
|
372
|
+
echo "ai-scanner: 食い違ったバイナリを置き換えられませんでした: \${UNVERIFIED_REASON:-ダウンロードに失敗しました}。手元の検査は続きますが、検知ログは拒否され続けます。導入コマンドをもう一度実行してください。それでも直らなければ、このメッセージを添えて社内の IT 管理者にご連絡ください。" >&2
|
|
330
373
|
echo "ai-scanner: could not replace the mismatched binary: \${UNVERIFIED_REASON:-the download failed}. Scanning continues locally, but detection logs will keep being refused. Re-run the installer, or contact your IT administrator with this message." >&2
|
|
331
374
|
elif [ -z "$have_host" ]; then
|
|
375
|
+
echo "ai-scanner: 導入済みのバイナリがどのライセンスサーバーを見ているか読めなかったので、環境が一致するかは確認していません。検知ログが拒否されているようなら、導入コマンドをもう一度実行してください。" >&2
|
|
332
376
|
echo "ai-scanner: could not read which license server the installed binary uses, so the environment match was NOT checked. If detection logs are being refused, re-run the installer." >&2
|
|
333
377
|
fi
|
|
334
378
|
timeout "$UPDATE_TIMEOUT" "$BIN" update 2>/dev/null || \\
|
|
379
|
+
echo "ai-scanner: スキャナーは今回、自分を更新できませんでした。すでに持っているルールで検査は続きます。これが繰り返されるなら、導入コマンドをもう一度実行してください。それでも直らなければ社内の IT 管理者にご連絡ください。" >&2
|
|
335
380
|
echo "ai-scanner: the scanner could not update itself just now. It keeps scanning with the rules it already has; if this repeats, re-run the installer or contact your IT administrator." >&2
|
|
336
381
|
|
|
337
382
|
# Keep the hook binary on the SAME build as the periodic one (issue #2366).
|
|
@@ -353,14 +398,98 @@ if should_check_update; then
|
|
|
353
398
|
scanner_ver=$(timeout "$VERSION_TIMEOUT" "$BIN" version 2>/dev/null | awk 'NR==1{print $2}')
|
|
354
399
|
harness_ver=$(timeout "$VERSION_TIMEOUT" "$HARNESS" version 2>/dev/null | awk 'NR==1{print $2}')
|
|
355
400
|
if [ -n "$scanner_ver" ] && [ -n "$harness_ver" ] && [ "$scanner_ver" != "$harness_ver" ]; then
|
|
401
|
+
echo "ai-scanner: ツールコール検査機は $harness_ver、この端末は $scanner_ver です。同じビルドで揃うよう、いま取り直しています。" >&2
|
|
356
402
|
echo "ai-scanner: the tool-call scanner is on $harness_ver while this endpoint is on $scanner_ver. Refreshing it so both run one build." >&2
|
|
357
403
|
download_binary "\${DOWNLOAD_BASE}/ai-harness/\${PLATFORM_SLUG}" "$HARNESS" || \\
|
|
404
|
+
echo "ai-scanner: ツールコール検査機を取り直せませんでした: \${UNVERIFIED_REASON:-ダウンロードに失敗しました}。検査は $harness_ver のまま続きます。これが繰り返されるなら、導入コマンド (${installCommand}) をもう一度実行してください。" >&2
|
|
358
405
|
echo "ai-scanner: could not refresh the tool-call scanner: \${UNVERIFIED_REASON:-the download failed}. Scanning continues on $harness_ver; re-run the installer (${installCommand}) if this repeats." >&2
|
|
359
406
|
fi
|
|
360
407
|
fi
|
|
361
408
|
date +%s > "$PULL_STAMP"
|
|
362
409
|
fi
|
|
363
410
|
|
|
411
|
+
# ── 既に撒かれたラッパーを現行の形へ収束させる (issue #2372、案 1) ────────────
|
|
412
|
+
#
|
|
413
|
+
# **バイナリは自動で届くが、設定は届かない。** アダプタは導入時に 1 度だけ
|
|
414
|
+
# 書かれ、以後どの経路でも書き換わらない一方、自己更新はそれが名指すバイナリを
|
|
415
|
+
# 黙って差し替える。その非対称が #2372 の本体で、ここはその片側を閉じる。
|
|
416
|
+
#
|
|
417
|
+
# **なぜ SessionStart なのか (同意の範囲)**: これは**利用者が自分でエディタを
|
|
418
|
+
# 起動したときにだけ、その利用者として**走る。「そのエディタのフックを直す」と
|
|
419
|
+
# いう範囲から出ない。常駐が利用者の home を書く案 (#2372 案 2) は
|
|
420
|
+
# **同意の範囲の判断なので採らない** —— --agent が常駐を入れないという既存の
|
|
421
|
+
# 判断の、ちょうど裏返しである。
|
|
422
|
+
#
|
|
423
|
+
# **★ この経路が届かない端末が在る (正直に置く)。** 収束させるコードはこの
|
|
424
|
+
# スクリプトの中に在るので、**この版より前の start_scanner.sh を持つ端末には
|
|
425
|
+
# 届かない。** そこは agent_hooks の hook_wiring (endpoint-info) が
|
|
426
|
+
# **数として見えるようにする**部分が受け持つ —— だから #2372 の案 3 は
|
|
427
|
+
# 「あとで」ではなく必須である。GAP-2372-PRE-REPAIR-ADAPTERS-UNREACHED。
|
|
428
|
+
#
|
|
429
|
+
# 4 つの門。**どれも「直せない」を「直した」と読ませないため**にある:
|
|
430
|
+
# 1. hook のバイナリが在る (無ければ上の節が既に名指している)
|
|
431
|
+
# 2. その版が setup を持つ (旧い版は持たない。持たないことは失敗ではない)
|
|
432
|
+
# 3. アクセスキーが env に在る —— **無いまま書くと、鍵の無い settings.json を
|
|
433
|
+
# 置くことになり、いま効いている保護を壊す**
|
|
434
|
+
# 4. 兄弟のラッパーが実際に旧い形である (新しい形なら 1 バイトも書かない)
|
|
435
|
+
CONFIG_ROOT="$(cd "$(dirname "$0")/../.." 2>/dev/null && pwd)"
|
|
436
|
+
SELF_DIR="$(dirname "$0")"
|
|
437
|
+
if [ -x "$HARNESS" ] && [ -n "$CONFIG_ROOT" ] && [ -n "\${AI_SCANNER_ACCESS_KEY:-}" ]; then
|
|
438
|
+
# 旧い形の判定は endpoint-info の hook_wiring と**同じ集合・同じ述語**である
|
|
439
|
+
# (agent_hooks.rs の HookWiring::from_wrapper_dir / from_wrapper_text)。
|
|
440
|
+
# 2 つ目の判定を作らない ——作ると、ポータルが数えている集合と、
|
|
441
|
+
# ここが直す集合がずれる。
|
|
442
|
+
#
|
|
443
|
+
# **【2026-09-10 訂正 —— issue #2407】かつてこの行は「同じ述語である」と
|
|
444
|
+
# 宣言しながら、実体は別のファイルを読んでいた。** 数える側は印
|
|
445
|
+
# (\`hook_marker\`) 1 本を読んでおり、claude-code のそれは
|
|
446
|
+
# **\`start_scanner.sh\` そのもの** = 分割後のビルドでは必ず \`ai-harness\` を
|
|
447
|
+
# 名指すスクリプトである。一方ここが直すのは**その兄弟**なので、
|
|
448
|
+
# **「新しい start_scanner.sh + 旧い兄弟」= まさに収束に失敗した端末が
|
|
449
|
+
# \`ai-harness\` (移行済み) として数から消えていた** (DDB 読み戻しで実測)。
|
|
450
|
+
# **数える側をこの集合へ合わせて直した** —— 直す側に \`start_scanner.sh\` を
|
|
451
|
+
# 足すのは、数える側の食い違いを 1 ミリも動かさない。
|
|
452
|
+
#
|
|
453
|
+
# **下の 4 行 (glob / 除外の case / 2 本の grep) は実体から抜き出されて
|
|
454
|
+
# 機械照合される** —— \`agent_hooks::tests::w1_the_repair_loop_and_this_module_read_one_set\`
|
|
455
|
+
# が綴りだけでなく**向き** (\`|| continue\` か \`|| stale=1\` か) まで見る。
|
|
456
|
+
# 書き換えるときは、あちらの \`WIRING_EXCLUDED_WRAPPERS\` /
|
|
457
|
+
# \`SCANNER_TOKENS\` / \`HARNESS_TOKENS\` も同じ PR で直すこと。
|
|
458
|
+
# なぜ grep -E なのか (issue #2372。実測で 1 度落ちている)。
|
|
459
|
+
# ここは長く BRE の grep -q 'ai-scanner\\|AI_SCANNER_BIN' と書かれていたが、
|
|
460
|
+
# **この本文は JS のテンプレートリテラルなので、書いたバックスラッシュ 1 つは
|
|
461
|
+
# 出力に残らない** —— 端末に置かれていたのは 'ai-scanner|AI_SCANNER_BIN' で、
|
|
462
|
+
# BRE の | はただの文字なので **この行は決して一致せず、収束は 1 度も
|
|
463
|
+
# 走らなかった** (2026-09-09 に linux-testbed で実測: 旧い形のラッパーを
|
|
464
|
+
# 置いて start_scanner.sh を回しても 1 バイトも書き換わらない)。
|
|
465
|
+
# **症状は出ない** —— 収束しないことと「収束すべきものが無い」ことが
|
|
466
|
+
# 端末側から区別できない。-E にすれば JS の逃がしが要らず (パターンに
|
|
467
|
+
# バックスラッシュが 1 つも無い)、BSD grep (macOS) が BRE の \\| を
|
|
468
|
+
# 解さない問題も同時に消える。**BRE へ戻さないこと。**
|
|
469
|
+
stale=0
|
|
470
|
+
for w in "$SELF_DIR"/*.sh; do
|
|
471
|
+
[ -r "$w" ] || continue
|
|
472
|
+
case "$w" in *"/start_scanner.sh"|*"/stop_scanner.sh") continue ;; esac
|
|
473
|
+
grep -Eq 'ai-scanner|AI_SCANNER_BIN' "$w" || continue
|
|
474
|
+
grep -Eq 'ai-harness|AI_HARNESS_BIN' "$w" || stale=1
|
|
475
|
+
done
|
|
476
|
+
if [ "$stale" = 1 ] && timeout "$VERSION_TIMEOUT" "$HARNESS" setup --help >/dev/null 2>&1; then
|
|
477
|
+
# --home を渡すので根の解決は起きない。--scope は settings.json に焼く
|
|
478
|
+
# 根の綴り ("$CLAUDE_PROJECT_DIR" か "$HOME") を決めるだけなので、
|
|
479
|
+
# **いまの設置場所から導く** —— 推測すると、リポジトリに置いた設定が
|
|
480
|
+
# 端末個別の設定として書き直される。
|
|
481
|
+
if [ "$CONFIG_ROOT" = "$HOME" ]; then repair_scope=user; else repair_scope=project; fi
|
|
482
|
+
echo "ai-scanner: この端末のフックアダプタは分割前の形で、ツールコール検査機ではなく定期実行のバイナリを呼んでいます。いま更新しています。お手元での操作は要りません。" >&2
|
|
483
|
+
echo "ai-scanner: this endpoint's hook adapters are the pre-split shape, so they still call the periodic binary instead of the tool-call scanner. Updating them now; no action is needed from you." >&2
|
|
484
|
+
timeout "$UPDATE_TIMEOUT" "$HARNESS" setup \\
|
|
485
|
+
--agent ${setupAgentName} --scope "$repair_scope" \\
|
|
486
|
+
--access-key "$AI_SCANNER_ACCESS_KEY" \\
|
|
487
|
+
--download-base "$DOWNLOAD_BASE" --home "$CONFIG_ROOT" >/dev/null 2>&1 || \\
|
|
488
|
+
echo "ai-scanner: この端末のフックアダプタは分割前の形のままで、今回は更新できませんでした。ツールコールは、すでに導入済みのバイナリが検査し続けます。このメッセージが繰り返されるなら、導入コマンド (${installCommand}) をもう一度実行してください。それでも直らなければ、このメッセージを添えて社内の IT 管理者にご連絡ください。" >&2
|
|
489
|
+
echo "ai-scanner: the hook adapters on this endpoint are still the pre-split shape and could not be updated just now. Tool calls keep being scanned by the binary already installed; if this message repeats, re-run the installer (${installCommand}) or contact your IT administrator with this message." >&2
|
|
490
|
+
fi
|
|
491
|
+
fi
|
|
492
|
+
|
|
364
493
|
exit 0
|
|
365
494
|
`;
|
|
366
495
|
}
|
package/src/toml-merge.mjs
CHANGED
|
@@ -1,14 +1,54 @@
|
|
|
1
1
|
// Line-based TOML merger for Codex's ~/.codex/config.toml feature flag.
|
|
2
|
-
//
|
|
2
|
+
//
|
|
3
|
+
// Codex requires `[features] hooks = true` to activate hook dispatch. The older
|
|
4
|
+
// spelling `[features] codex_hooks = true` is a **deprecated alias**: Codex still
|
|
5
|
+
// honours it, but prints
|
|
6
|
+
//
|
|
7
|
+
// ⚠ `[features].codex_hooks` is deprecated. Use `[features].hooks` instead.
|
|
8
|
+
//
|
|
9
|
+
// on every start (issue #2367). So we write the canonical key and *migrate* any
|
|
10
|
+
// legacy key we find, instead of leaving both around.
|
|
11
|
+
//
|
|
3
12
|
// Users may already have their own config (model settings, other features), so
|
|
4
13
|
// we must merge rather than overwrite.
|
|
5
14
|
//
|
|
6
|
-
// Scope: *only* handles a single
|
|
7
|
-
// parser, no external deps. Preserves comments, blank lines, ordering, and
|
|
15
|
+
// Scope: *only* handles a single logical flag inside a single section. No full
|
|
16
|
+
// TOML parser, no external deps. Preserves comments, blank lines, ordering, and
|
|
8
17
|
// other sections exactly.
|
|
18
|
+
//
|
|
19
|
+
// ── Migration decision table (merge) ─────────────────────────────────
|
|
20
|
+
// Only the *first* `[features]` section is considered (duplicate table headers
|
|
21
|
+
// are invalid TOML anyway — see the known limitation at the bottom of this file).
|
|
22
|
+
//
|
|
23
|
+
// old key | new key | result
|
|
24
|
+
// --------+---------+--------------------------------------------------------
|
|
25
|
+
// absent | absent | insert `hooks = true` right under the section header
|
|
26
|
+
// absent | true | no-op (byte-identical output)
|
|
27
|
+
// absent | false | value flipped to true, line position/comment preserved
|
|
28
|
+
// true | absent | line RENAMED in place -> `hooks = true`
|
|
29
|
+
// (indentation, position and trailing comment preserved)
|
|
30
|
+
// false | absent | same rename, and the value is set to true
|
|
31
|
+
// any | any | the canonical `hooks` line wins and is set to true;
|
|
32
|
+
// the deprecated `codex_hooks` line is REMOVED entirely.
|
|
33
|
+
// This is the only case where a trailing comment can be
|
|
34
|
+
// lost, because it is attached to the line we delete.
|
|
35
|
+
// Conflicting values need no tie-break: setup means
|
|
36
|
+
// "hooks on", so the result is always `hooks = true`.
|
|
37
|
+
//
|
|
38
|
+
// Standalone comment lines are never deleted, even when they sit directly above
|
|
39
|
+
// a removed legacy key — deleting prose a user wrote is worse than leaving it.
|
|
40
|
+
//
|
|
41
|
+
// ── unmerge ──────────────────────────────────────────────────────────
|
|
42
|
+
// Strips BOTH spellings, so a config written by an older aidr (legacy key) is
|
|
43
|
+
// fully cleaned up by a newer aidr's uninstall. Everything else in `[features]`
|
|
44
|
+
// (the user's own flags and comments) survives; the section header is dropped
|
|
45
|
+
// only when nothing but blanks/comments would remain.
|
|
9
46
|
|
|
10
47
|
const SECTION = "features";
|
|
11
|
-
|
|
48
|
+
// Canonical key. Codex >= the version that introduced `[features].hooks`.
|
|
49
|
+
export const FEATURE_KEY = "hooks";
|
|
50
|
+
// Deprecated alias still accepted by Codex, but it prints a warning (#2367).
|
|
51
|
+
export const LEGACY_FEATURE_KEY = "codex_hooks";
|
|
12
52
|
const VALUE = "true";
|
|
13
53
|
|
|
14
54
|
// Match a section header line: optional leading whitespace, `[name]`, optional trailing comment.
|
|
@@ -20,10 +60,22 @@ function sectionHeader(name) {
|
|
|
20
60
|
// Match any section header (used to find section boundaries).
|
|
21
61
|
const ANY_SECTION_RE = /^\s*\[\s*[^\]]+\s*\](?:\s*#.*)?$/;
|
|
22
62
|
|
|
23
|
-
// Match `key = value` with optional trailing comment
|
|
63
|
+
// Match `key = value` with optional trailing comment.
|
|
64
|
+
// Groups: 1 = indent, 2 = key, 3 = ` = `, 4 = value token, 5 = trailing comment.
|
|
65
|
+
// The `^\s*` anchor means the `hooks` matcher can never hit a `codex_hooks`
|
|
66
|
+
// line (and vice versa) — the key must start the line after whitespace only.
|
|
24
67
|
function keyAssignment(key) {
|
|
25
68
|
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
26
|
-
return new RegExp(`^(\\s
|
|
69
|
+
return new RegExp(`^(\\s*)(${escaped})(\\s*=\\s*)([^#\\n]*?)(\\s*(?:#.*)?)$`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const CANONICAL_RE = keyAssignment(FEATURE_KEY);
|
|
73
|
+
const LEGACY_RE = keyAssignment(LEGACY_FEATURE_KEY);
|
|
74
|
+
|
|
75
|
+
// Rewrite a matched assignment line as `<indent>hooks<eq>true<trailing>`.
|
|
76
|
+
function rewriteAsEnabled(line, re) {
|
|
77
|
+
const m = re.exec(line);
|
|
78
|
+
return `${m[1]}${FEATURE_KEY}${m[3]}${VALUE}${m[5]}`;
|
|
27
79
|
}
|
|
28
80
|
|
|
29
81
|
// Detect obvious malformed TOML so we fail closed instead of silently corrupting.
|
|
@@ -61,37 +113,64 @@ function findSectionRange(lines, section) {
|
|
|
61
113
|
return { start, end };
|
|
62
114
|
}
|
|
63
115
|
|
|
64
|
-
//
|
|
65
|
-
|
|
116
|
+
// Locate the first canonical / first legacy assignment inside a section range.
|
|
117
|
+
function findKeys(lines, range) {
|
|
118
|
+
let canonical = -1;
|
|
119
|
+
let legacy = -1;
|
|
120
|
+
for (let i = range.start + 1; i < range.end; i++) {
|
|
121
|
+
if (canonical === -1 && CANONICAL_RE.test(lines[i])) canonical = i;
|
|
122
|
+
if (legacy === -1 && LEGACY_RE.test(lines[i])) legacy = i;
|
|
123
|
+
}
|
|
124
|
+
return { canonical, legacy };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Merge `[features] hooks = true` into the given TOML text, migrating the
|
|
128
|
+
// deprecated `codex_hooks` spelling and preserving everything else.
|
|
129
|
+
// Idempotent: re-running on an already-merged file returns the input verbatim.
|
|
66
130
|
export function mergeCodexHookFeature(text) {
|
|
67
131
|
const input = text ?? "";
|
|
68
132
|
if (input !== "") validate(input);
|
|
69
133
|
|
|
70
134
|
// Empty file / whitespace-only: write a fresh minimal TOML.
|
|
71
135
|
if (input.trim() === "") {
|
|
72
|
-
return `[${SECTION}]\n${
|
|
136
|
+
return `[${SECTION}]\n${FEATURE_KEY} = ${VALUE}\n`;
|
|
73
137
|
}
|
|
74
138
|
|
|
75
139
|
const lines = input.split("\n");
|
|
76
140
|
// Preserve final newline state — JSON.stringify-style: if original ended with
|
|
77
141
|
// "\n", .split gives a trailing "" element we'll keep and rejoin with.
|
|
78
142
|
const range = findSectionRange(lines, SECTION);
|
|
79
|
-
const assignRe = keyAssignment(KEY);
|
|
80
143
|
|
|
81
144
|
if (range) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
145
|
+
const { canonical, legacy } = findKeys(lines, range);
|
|
146
|
+
|
|
147
|
+
if (canonical !== -1 && legacy !== -1) {
|
|
148
|
+
// Both spellings present. The canonical line wins (position + comment
|
|
149
|
+
// kept); the deprecated alias line goes away, which is what silences the
|
|
150
|
+
// warning. Conflicting values resolve to `true` because that is what this
|
|
151
|
+
// setup means.
|
|
152
|
+
lines[canonical] = rewriteAsEnabled(lines[canonical], CANONICAL_RE);
|
|
153
|
+
lines.splice(legacy, 1);
|
|
154
|
+
return lines.join("\n");
|
|
91
155
|
}
|
|
156
|
+
|
|
157
|
+
if (legacy !== -1) {
|
|
158
|
+
// Legacy only — rename in place so ordering, indentation and any trailing
|
|
159
|
+
// comment survive. `codex_hooks = false` becomes `hooks = true`.
|
|
160
|
+
lines[legacy] = rewriteAsEnabled(lines[legacy], LEGACY_RE);
|
|
161
|
+
return lines.join("\n");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (canonical !== -1) {
|
|
165
|
+
const m = CANONICAL_RE.exec(lines[canonical]);
|
|
166
|
+
if (m[4].trim() === VALUE) return input; // already correct, no-op
|
|
167
|
+
lines[canonical] = rewriteAsEnabled(lines[canonical], CANONICAL_RE);
|
|
168
|
+
return lines.join("\n");
|
|
169
|
+
}
|
|
170
|
+
|
|
92
171
|
// Key absent — insert right after the section header (before any subkeys),
|
|
93
172
|
// so the new line doesn't get buried under a long section.
|
|
94
|
-
lines.splice(range.start + 1, 0, `${
|
|
173
|
+
lines.splice(range.start + 1, 0, `${FEATURE_KEY} = ${VALUE}`);
|
|
95
174
|
return lines.join("\n");
|
|
96
175
|
}
|
|
97
176
|
|
|
@@ -100,12 +179,14 @@ export function mergeCodexHookFeature(text) {
|
|
|
100
179
|
const needsLeadingBlank = input.length > 0 && !input.endsWith("\n\n");
|
|
101
180
|
const prefix = input.endsWith("\n") ? "" : "\n";
|
|
102
181
|
const spacer = needsLeadingBlank ? "\n" : "";
|
|
103
|
-
return `${input}${prefix}${spacer}[${SECTION}]\n${
|
|
182
|
+
return `${input}${prefix}${spacer}[${SECTION}]\n${FEATURE_KEY} = ${VALUE}\n`;
|
|
104
183
|
}
|
|
105
184
|
|
|
106
|
-
// Reverse of mergeCodexHookFeature: strip our
|
|
107
|
-
// - Removes the
|
|
108
|
-
//
|
|
185
|
+
// Reverse of mergeCodexHookFeature: strip our feature flag line(s).
|
|
186
|
+
// - Removes `hooks` AND the deprecated `codex_hooks` (a config written by an
|
|
187
|
+
// older aidr carries the legacy spelling; leaving it behind would keep the
|
|
188
|
+
// deprecation warning alive after uninstall).
|
|
189
|
+
// - Removes an empty `[features]` section if our line(s) were the only content.
|
|
109
190
|
// Returns { text, empty } — empty=true means the TOML is now effectively blank
|
|
110
191
|
// so the caller can delete the file entirely.
|
|
111
192
|
export function unmergeCodexHookFeature(text) {
|
|
@@ -120,17 +201,17 @@ export function unmergeCodexHookFeature(text) {
|
|
|
120
201
|
return { text: input, empty: input.trim() === "" };
|
|
121
202
|
}
|
|
122
203
|
|
|
123
|
-
const
|
|
124
|
-
let removedIdx = -1;
|
|
204
|
+
const removed = [];
|
|
125
205
|
for (let i = range.start + 1; i < range.end; i++) {
|
|
126
|
-
if (
|
|
206
|
+
if (CANONICAL_RE.test(lines[i]) || LEGACY_RE.test(lines[i])) removed.push(i);
|
|
127
207
|
}
|
|
128
208
|
|
|
129
|
-
if (
|
|
209
|
+
if (removed.length === 0) {
|
|
130
210
|
return { text: input, empty: input.trim() === "" };
|
|
131
211
|
}
|
|
132
212
|
|
|
133
|
-
|
|
213
|
+
// Delete back-to-front so earlier indices stay valid.
|
|
214
|
+
for (let k = removed.length - 1; k >= 0; k--) lines.splice(removed[k], 1);
|
|
134
215
|
|
|
135
216
|
// Re-evaluate the section bounds after removal.
|
|
136
217
|
const newRange = findSectionRange(lines, SECTION);
|
|
@@ -165,3 +246,10 @@ export function unmergeCodexHookFeature(text) {
|
|
|
165
246
|
const result = lines.join("\n");
|
|
166
247
|
return { text: result, empty: result.trim() === "" };
|
|
167
248
|
}
|
|
249
|
+
|
|
250
|
+
// ── Known limitation (declared, not fixed) ───────────────────────────
|
|
251
|
+
// Only the FIRST `[features]` table is inspected. A file with two `[features]`
|
|
252
|
+
// headers is already invalid TOML (duplicate table), so Codex would reject it
|
|
253
|
+
// before our flag mattered; handling it here would mean writing a real parser.
|
|
254
|
+
// If such a file ever appears, a legacy key living in the second block would
|
|
255
|
+
// survive the migration and the deprecation warning would persist.
|