Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
{
"name": "parallel-ai-agents",
"source": "./plugins/parallel-ai-agents",
"description": "v2.20.0: first-party codex-pro governance deep-integration (#23). v2.19.0: codexModel/codexEffort contract args (#22, caller-governed cross-model leg). 平行派發任務給多個 AI agent(Claude + Codex),獨立執行後交叉比對結果。Codex 改走直接 HTTP wrapper(bin/codex-call,Swift script)取代 codex exec subprocess,解決 hang 問題且避開 Python 版本飄移",
"version": "2.20.0",
"description": "v2.20.1: codex-call 補上 SSE error 事件的 message 提取路徑 (#25) — 直接呼叫 codex-call 時,HTTP 200 stream 內帶 message 的後端錯誤(如 server_is_overloaded)會顯示真實原因而非籠統的 \"Codex error\";經 ensemble 使用時仍受 #27 限制(消費端硬編碼失敗訊息)。v2.20.0: first-party codex-pro governance deep-integration (#23). v2.19.0: codexModel/codexEffort contract args (#22, caller-governed cross-model leg). 平行派發任務給多個 AI agent(Claude + Codex),獨立執行後交叉比對結果。Codex 改走直接 HTTP wrapper(bin/codex-call,Swift script)取代 codex exec subprocess,解決 hang 問題且避開 Python 版本飄移",
"version": "2.20.1",
"author": {
"name": "Che Cheng"
},
Expand Down
51 changes: 51 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,54 @@ jobs:

- name: node tests
run: for t in test/*.test.mjs; do echo "$t"; node "$t"; done

# codex-call 是 `#!/usr/bin/swift` script(釘 Xcode CLT swift,見 plugin CLAUDE.md),
# 其 bats 測試在 setup() 以 Darwin + CLT swift guard 自我 skip。沒有這個 job,那些
# 測試會在 ubuntu job 上被 skip 而**永遠不執行** —— CI 全綠但 #25 的 regression 錨點
# 零覆蓋(green-but-vacuous,#25 R2 verify HIGH)。macOS runner 內建 Xcode,故 guard
# 會通過、測試真正跑到。
macos-swift-bats:
runs-on: macos-latest
defaults:
run:
working-directory: plugins/parallel-ai-agents
steps:
- uses: actions/checkout@v4

- name: Install bats
run: brew install bats-core

- name: Assert swift toolchain is usable (the real gate — a present shim is not enough)
run: swift --version

# Glob rather than an explicit list, so a future macOS-only bats file is picked up
# instead of silently reverting to zero coverage. Then assert on the run's OUTPUT,
# not on the guard's inputs: `bats` exits 0 when every test SKIPS, which is exactly
# the vacuous green this job exists to prevent (#25 verify).
- name: bats (codex-call, macOS-only) — fail on failure OR on skip
# `shell: bash` is load-bearing: Actions' DEFAULT shell is `bash -e {0}`
# — errexit WITHOUT pipefail — so `bats | tee` would report tee's status
# (always 0) and a failing test would exit the step green. Naming the shell
# explicitly gets `-eo pipefail`. Belt-and-braces: also assert no `not ok`,
# since `grep -q '^ok'` alone is satisfied by any single passing sibling.
shell: bash
run: |
set -o pipefail
TAP="${RUNNER_TEMP:-/tmp}/codex-call-bats.tap"
# Capture bats' status instead of letting errexit abort here: with pipefail
# active a failing run would kill the step before any check below could emit
# its `::error::` line, so the three checks would be decorative and only the
# generic "Process completed with exit code 1" would surface.
rc=0
bats --formatter tap test/codex-call-*.bats | tee "$TAP" || rc=$?
if grep -q '^not ok' "$TAP"; then
echo "::error::a codex-call test failed"; exit 1
fi
# -i because the TAP directive's casing is bats' choice and bats is unpinned
# (`brew install bats-core`); a casing change must not silently reopen the
# vacuous-green hole this check exists to close.
if grep -qi '# skip' "$TAP"; then
echo "::error::tests skipped on the macOS job — the regression anchor is vacuous"; exit 1
fi
grep -q '^ok' "$TAP" || { echo "::error::no tests ran (empty glob or bats bail-out)"; exit 1; }
exit "$rc"
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ venv/

# remember plugin session artifacts
.remember/

# IDD tree-lock (idd-tree-lock.sh #183) — per-machine session state, not tracked
.claude/.idd/tree-lock
4 changes: 2 additions & 2 deletions plugins/parallel-ai-agents/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "parallel-ai-agents",
"description": "v2.20.0: first-party skills deep-integrate codex-pro governance (#23, mirroring issue-driven-dev#264) — new references/codex-governance.md (canonical resolution: MIN_CODEX_PRO 0.7.0 gate, defaults.json base + two profile.yaml layers, fail-fast with install instruction when codexEnabled and codex-pro absent); ensemble-code-review / ensemble-academic-review / ensemble-compose(--codex) resolve and pass codexModel/codexEffort explicitly; engine + bin/codex-call baked defaults become release-time governance SNAPSHOTS (bumped to gpt-5.6-sol) — authoritative source is codex-pro's defaults.json; all first-party prose generation-neutral. v2.19.0: codexModel / codexEffort engine args (#22) — the cross-model codex leg's model and effort become caller-governed contract args (defaults gpt-5.5 / xhigh preserve pre-#22 behavior byte-identically). First consumer: issue-driven-dev passing codex-pro-resolved governance. 平行派發任務給多個 AI agent(Claude + Codex),獨立執行後交叉比對結果。Codex 改走直接 HTTP wrapper(bin/codex-call,Swift script)取代 codex exec subprocess,解決 hang 問題且避開 Python 版本飄移",
"version": "2.20.0",
"description": "v2.20.1: codex-call 補上 SSE error 事件的 message 提取路徑 (#25) — 直接呼叫 codex-call 時,HTTP 200 stream 內帶 message 的後端錯誤(如 server_is_overloaded)會顯示真實原因而非籠統的 \"Codex error\";經 ensemble 使用時仍受 #27 限制(消費端硬編碼失敗訊息)。v2.20.0: first-party skills deep-integrate codex-pro governance (#23, mirroring issue-driven-dev#264) — new references/codex-governance.md (canonical resolution: MIN_CODEX_PRO 0.7.0 gate, defaults.json base + two profile.yaml layers, fail-fast with install instruction when codexEnabled and codex-pro absent); ensemble-code-review / ensemble-academic-review / ensemble-compose(--codex) resolve and pass codexModel/codexEffort explicitly; engine + bin/codex-call baked defaults become release-time governance SNAPSHOTS (bumped to gpt-5.6-sol) — authoritative source is codex-pro's defaults.json; all first-party prose generation-neutral. v2.19.0: codexModel / codexEffort engine args (#22) — the cross-model codex leg's model and effort become caller-governed contract args (defaults gpt-5.5 / xhigh preserve pre-#22 behavior byte-identically). First consumer: issue-driven-dev passing codex-pro-resolved governance. 平行派發任務給多個 AI agent(Claude + Codex),獨立執行後交叉比對結果。Codex 改走直接 HTTP wrapper(bin/codex-call,Swift script)取代 codex exec subprocess,解決 hang 問題且避開 Python 版本飄移",
"version": "2.20.1",
"author": {
"name": "Che Cheng"
}
Expand Down
36 changes: 36 additions & 0 deletions plugins/parallel-ai-agents/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [2.20.1] - 2026-07-31

### Fixed

- **`bin/codex-call` 吞掉 SSE `error` 事件的訊息 (#25)** — 後端在 HTTP 200 stream 內以 `{"type":"error","error":{...,"message":...}}` 回報時(實測觸發:`server_is_overloaded`),原有的兩條提取路徑皆不匹配,塌成 fallback 字面值 `"Codex error"`,使該類失敗無法區分原因。抽出 `extractErrorMessage(_:)` 並補上 `json["error"]["message"]` 路徑。HTTP 4xx 類(實測 model 400 / auth 401 / rate-limit 429)走既有 HTTP 錯誤路徑、本來就正確報告,不受影響。

### Changed

- **後端錯誤訊息現在會被淨化並設上限(使用者可見的行為改變)** — 新增 `sanitizeBackendText`:剝除 C0/DEL/C1 控制字元(保留 newline 與 tab),並以 **UTF-8 byte(2000)+ 行數(20)** 為預算截斷,超出時附加 `…(truncated)`。
預算刻意**不用** `String.count` —— 它數的是 extended grapheme cluster,長度無上限(一個基底字元加 N 個組合記號是**一個** Character),因此 Character-based cap 實際上不約束任何東西。實測前一版:500 個 CJK 字元以 1,500 bytes 通過、500 個各含 20 個組合記號的 cluster 以 20,500 bytes 通過,兩者皆無截斷標記。TTY 版面與 agent context 都是以 byte/行計價,故以此為準。

### Added

- **`--selftest-error-extract <json>` hidden flag** — 餵一則 SSE 事件 payload 給 `extractErrorMessage` 並印出結果,不發 HTTP、不列於 `--help`。`CODEX_URL` 是 hardcoded 常數、無注入點,沒有這個 hook 該提取邏輯結構上無法自動化回歸。
- **`test/codex-call-error-extract.bats`** — **13 case**:提取路徑 5 個(含實測 payload 作 regression 錨點)+ sanitize/budget 8 個。後者以 mutation 驗證有分辨力(換回 grapheme cap → 組合記號與行數兩個 case 轉紅;移除 newline 保留子句 → 分隔符 case 轉紅)。**測試標題須為 ASCII** —— macOS runner 的 `/bin/bash` 3.2 在 `printf '%02x'` 上做 signed-char 符號延伸,會 mangle CJK 標題導致 bats 宣告 N 個卻執行 0 個(見 CI job `macos-swift-bats`)。

### Known limitations

- **經 ensemble 使用時,本修正對使用者尚不可見(#27)** — `workflows/ensemble-workflow.js` 的 codex lens prompt 以硬編碼字串回報失敗,不帶 `codex-call` 的 stderr。本版真正改善的是**直接呼叫 `codex-call`** 的情境。
- **sanitize 尚未覆蓋的類別,與另兩個 sink(#28)** — bidi override(U+202A–U+202E、U+2066–U+2069)、Unicode Tags block、U+FEFF、U+2028/U+2029 仍會通過;同檔另外兩處後端文字(`HTTP <code>: <body>`)仍用 `String.prefix(500)`,與本版修掉的 Character-counting 缺陷相同且未被 sanitize;截斷標記為 in-band、可被後端偽造。這些需要對所有 backend-text sink 做一次整體處理,不宜再以片段修補累加。
- 分幀與終端事件語意 —— UTF-8 切在 byte 邊界導致整個 chunk 被丟棄、殘留 buffer 從不 flush、多個終端事件時的勝出政策、CRLF 分幀 —— 同樣追蹤於 **#28**,該處會先蒐集後端 teardown 的真實 trace 再定政策。
- `extractErrorMessage` 的每條路徑接受任意 String(含 `""` 及 sanitize 後變空者),故空的 top-level `message` 會遮蔽真實的巢狀值;修法需要「資訊量謂詞」而非「存在性檢查」,一併歸 #28。


## [2.20.0] - 2026-07-18

### Added

- **first-party skills 深度整合 codex-pro governance (#23)** — 五個 ensemble-* skill 的 codex leg 不再於 pai 樹內 pin model/effort,改由 codex-pro 的 EXTERNAL-CONSUMER CONTRACT(`references/profile-contract.md` + `references/defaults.json`,0.7.0+)解析;解析流程的 canonical 落在 `references/codex-governance.md`,skills 引用該檔而不內嵌分歧複本。

## [2.19.0] - 2026-07-18

### Added

- **`codexModel` / `codexEffort` engine args (#22)** — `ensemble-workflow.js` 新增這兩個 caller-governed args,讓跨模型 leg 的 model/effort 由呼叫端治理契約決定,而非引擎內部寫死。消費端(如 issue-driven-development 的 idd-verify)據此把 codex-pro 的治理值 thread 進來;引擎若靜默忽略這兩個 arg,canonical tier 的治理鏈會斷(故 consumer 端以最低版本閘門把關)。

## [2.18.0] - 2026-07-02

### Added
Expand Down
Loading
Loading