aiterm-mcp 0.33.0 → 0.34.0
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/CHANGELOG.md +22 -1
- package/README.ja.md +15 -3
- package/README.md +14 -5
- package/dist/codex-parent-receiver.js +135 -0
- package/dist/core.js +33 -16
- package/dist/harnesses/codex.js +32 -2
- package/dist/harnesses/cursor.js +2 -1
- package/dist/harnesses/grok.js +2 -1
- package/dist/index.js +64 -23
- package/dist/parent-delivery.js +382 -0
- package/dist/setup-integrations.js +11 -0
- package/dist/tmux-runtime.js +12 -4
- package/docs/00_overview.md +4 -0
- package/docs/DESIGN.md +27 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.34.0] - 2026-09-10
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Codex親への子の回答自動配送を追加した。起動・通常dispatch・Claude durable turnの完了をAitermが観測し、回答本文を公式受信キューへ送る。親の待機コマンドと回答回収、子への送信指示は不要になった。
|
|
15
|
+
- 配送IDと状態をreceipt/`pty_observe`へ追加した。再接続後は未送信の記録を再開し、送信結果が不明な場合は本文を保持して自動再送しない。
|
|
16
|
+
- `aiterm-setup`はCodexの公式queue入口を確認する。各dispatchでMCP要求の親threadを確認し、未対応の受信口へ子を送らない。
|
|
17
|
+
|
|
18
|
+
### Compatibility
|
|
19
|
+
|
|
20
|
+
- Codex親は公式の`_meta.threadId`と`thread/queue` APIを提供する環境が必要。Codex CLI 0.154.0で確認した。native sub-agentを親とする外部queue入力はCodexの制約により未対応。
|
|
21
|
+
- Claude等の親の既存waiter契約は維持する。旧版へ戻すと新しい配送記録は処理されないが、既存PTY/harness stateの形式は変わらない。
|
|
22
|
+
|
|
23
|
+
## [0.33.1] - 2026-09-09
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- macOS・Linux上のAitermからSSH先のPowerShellへ`pty_send(mark:true)`するとPOSIXの`printf`を送っていた不具合を修理した。現在のPowerShell promptで方言を判定し、成功・失敗の完了マーカーを生成する。古いpromptや入力途中の行は使わない。
|
|
28
|
+
|
|
10
29
|
## [0.33.0] - 2026-09-09
|
|
11
30
|
|
|
12
31
|
### Added
|
|
@@ -1598,7 +1617,9 @@ prototype (preserved under `prototype/python/` as the porting source and referen
|
|
|
1598
1617
|
`ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
|
|
1599
1618
|
provenance.
|
|
1600
1619
|
|
|
1601
|
-
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.
|
|
1620
|
+
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.34.0...HEAD
|
|
1621
|
+
[0.34.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.33.1...v0.34.0
|
|
1622
|
+
[0.33.1]: https://github.com/kitepon/aiterm-mcp/compare/v0.33.0...v0.33.1
|
|
1602
1623
|
[0.33.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.32.0...v0.33.0
|
|
1603
1624
|
[0.32.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.31.2...v0.32.0
|
|
1604
1625
|
[0.31.2]: https://github.com/kitepon/aiterm-mcp/compare/v0.31.1...v0.31.2
|
package/README.ja.md
CHANGED
|
@@ -163,7 +163,7 @@ v0.20では、待たずに一度だけ観測する
|
|
|
163
163
|
`running`(exit 5)で返すようにしました。v0.19系では相関済みClaude approval中継を追加し、
|
|
164
164
|
複数行shell配送を維持し、native Windowsのfactory diagnosticsを拡張しました。
|
|
165
165
|
v0.16/0.17以来、親エージェントはaiterm上で一切ブロックしません:
|
|
166
|
-
agent session への send は常に非ブロック dispatch
|
|
166
|
+
agent session への send は常に非ブロック dispatch になり、Codex親には回答本文を自動配送し、それ以外の親の完了待ちは `aiterm-wait`
|
|
167
167
|
(exit code が receipt の outcome を映す: 0=done / 3=timeout=未完了 /
|
|
168
168
|
4=closed / 5=running=待たない観測)、初回 prompt 付き
|
|
169
169
|
launch は structured receipt にコピペ可能な `wait_command` を含む。factory diagnostics と local
|
|
@@ -171,7 +171,7 @@ runtime-error store は canonical dotagents config の `collection.enabled: true
|
|
|
171
171
|
場合だけ収集し、既定OFF、network送信は行いません。tag起点CIのnpm provenance(OIDC Trusted
|
|
172
172
|
Publishing)で公開し、GitHub Release が Official MCP Registry を再登録します。
|
|
173
173
|
|
|
174
|
-
**状態:** 開発継続中 · 現行公開版 **v0.
|
|
174
|
+
**状態:** 開発継続中 · 現行公開版 **v0.34.0** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
|
|
175
175
|
|
|
176
176
|
### 更新と巻き戻し
|
|
177
177
|
|
|
@@ -205,7 +205,7 @@ pty_read(id, { wait: true }) → 削減済みの出力を読む(完了
|
|
|
205
205
|
|
|
206
206
|
同じprimitiveが別エージェントのTUIを宿す。`agent_launch`の`harness`はagent loop・認証・hook・session・transcriptを所有する実行基盤、`model`は独立した選択。起動processは直接CLIと同じproject/user環境を使い、通常config、MCP、plugin、skill、permission、trust、memory、historyをcopy・filter・置換しない。
|
|
207
207
|
|
|
208
|
-
`aiterm.agent-launch-result.v1`は正規`harness`を返し、旧`provider`は互換fieldとして残す。同じ`harness`はagent dispatch、`aiterm-wait`、`agent_configure`、`pty_list`のagent行にも載り、旧vendor/provider/agent fieldは互換用に残る。Codexは通常rollout、Grok CLIは通常session event、Claudeはlaunch固有Stop hook、Cursorは通常agent transcript末尾の`turn_ended`を完了正本に使う。`pty_send`は非ブロックdispatchで、vendor別完了境界を表すopaqueな整数`event_cursor
|
|
208
|
+
`aiterm.agent-launch-result.v1`は正規`harness`を返し、旧`provider`は互換fieldとして残す。同じ`harness`はagent dispatch、`aiterm-wait`、`agent_configure`、`pty_list`のagent行にも載り、旧vendor/provider/agent fieldは互換用に残る。Codexは通常rollout、Grok CLIは通常session event、Claudeはlaunch固有Stop hook、Cursorは通常agent transcript末尾の`turn_ended`を完了正本に使う。`pty_send`は非ブロックdispatchで、vendor別完了境界を表すopaqueな整数`event_cursor`を返し、Codex親には回答本文を公式受信キューへ自動配送する。それ以外の親は`aiterm-wait`を親のターンを塞がない別processで受ける。Cursorのsubmitキーはadapterが現行CLIのextended keyboard protocolへ変換する。送信textがCursorのcomposerへ残った場合は成功receiptを返さず失敗する。
|
|
209
209
|
|
|
210
210
|
`agent_launch`・`pty_send`(agent dispatch)・`agent_steer`は任意の`image`(画像ファイルの絶対パスの配列。png/jpg/jpeg/gif/webp)を受ける。aitermが本文末尾へ添付行を付け、どのharnessも自分のfile読取toolでそのpathを画像として開く。呼出し側はharness別の添付手順を覚えない。不正なpathは送信前に拒否する。
|
|
211
211
|
|
|
@@ -225,6 +225,7 @@ agent_launch({ harness: "codex-cli", session_name: "codex1", cwd: "/repo",
|
|
|
225
225
|
→ { session_id: "codex1", … } # Codex が永続端末で稼働開始
|
|
226
226
|
pty_read("codex1", { screen: true }) → 何をしているか読む(トークン削減)
|
|
227
227
|
pty_send("codex1", "also fix the imports it broke") # 非ブロックdispatch=event_cursor入りreceipt
|
|
228
|
+
# Codex親には回答が自動で届く。それ以外の親:
|
|
228
229
|
$ aiterm-wait --session codex1 --cursor <event_cursor> # exit 0=done / 3=timeout(未完了) / 4=closed / 7=error(APIエラー等でturn打ち切り)。回収は pty_read(agent_transcript:true)
|
|
229
230
|
→ 操舵し、Codex の次の入力境界で返る
|
|
230
231
|
```
|
|
@@ -509,8 +510,19 @@ handoff contextを前置きできる。この任意経路は`throughline >= 0.9.
|
|
|
509
510
|
|
|
510
511
|
### 完了検出(5 層)
|
|
511
512
|
|
|
513
|
+
SSH先がPowerShellの場合、`mark:true`は現在の標準`PS ...>`プロンプトから方言を判定する。Aiterm自身がmacOS・Linux上でもPowerShell構文を送り、過去の出力に残ったプロンプトは判定に使わない。
|
|
514
|
+
|
|
512
515
|
`pty_read({ wait: true })`は通常PTYを、process終了/`mark:true` sentinel/`until`一致/shell復帰を伴う出力静止/timeoutの5層で判定する。agent sessionは第6の正確な層を使い、Codexは通常rollout、Grokは通常session event、Claudeはlaunch相関Stop event、Cursorは通常agent transcriptの`turn_ended`を`aiterm-wait --cursor`が観測する。親はブロックもポーリングもしない。
|
|
513
516
|
|
|
517
|
+
### Codex親への回答自動配送
|
|
518
|
+
|
|
519
|
+
Codexから子を起動・通常dispatchした後は、別作業へ進むか親のturnを終了するだけでよい。Aitermが完了を観測し、加工前の回答を保存して、依頼元Codexの公式受信キューへ送る。親はidleになった後に回答を処理する。waiter、`pty_read`による回答回収、子への送信指示は不要。Desktop固有の接続を使わず、CLIでも同じ経路になる。子は全対応harnessから選べる。Claude等の親は既存のwaiter経路を使う。
|
|
520
|
+
|
|
521
|
+
自動配送時はreceiptに`parent_delivery`が付き、`wait_process`/`wait_command`はnullになる。`pty_observe`の`parent_deliveries`で`waiting`、`ready`、`sending`、`submitted`、`failed`、`unknown`を確認できる。`submitted`はキューの受付済みであり、modelの読了ではない。MCP再接続後は未送信の記録を再開し、送信中に接続が切れて結果が分からない場合は本文を保持して`unknown`とする。自動再送はしない。
|
|
522
|
+
|
|
523
|
+
CodexにはMCPの`_meta.threadId`と公式`thread/queue` APIが必要で、Codex CLI 0.154.0で確認している。`aiterm-setup`はインストールされた公式queue入口を確認し、各dispatchでは実際の親threadの受入可否を確認する。Codexのnative sub-agentは外部からのqueue入力を拒否するため、自動配送の親としては未対応。
|
|
524
|
+
|
|
525
|
+
|
|
514
526
|
### トークン削減
|
|
515
527
|
|
|
516
528
|
- `pty_read` は既定で制御文字除去・連続重複圧縮・head+tail 折りたたみ(+復元ヒント・メタ併記)をかける。
|
package/README.md
CHANGED
|
@@ -176,8 +176,8 @@ a non-blocking `aiterm-wait --timeout 0` observation (`running`, exit 5) from a
|
|
|
176
176
|
wait. The v0.19 line added the correlated Claude approval relay,
|
|
177
177
|
preserved multiline shell delivery, and extended factory diagnostics on native
|
|
178
178
|
Windows. As of v0.16/0.17 a parent agent never blocks on aiterm:
|
|
179
|
-
every send to an agent session is a non-blocking dispatch
|
|
180
|
-
|
|
179
|
+
every send to an agent session is a non-blocking dispatch. Codex parents now receive the complete answer automatically; other parents use the
|
|
180
|
+
`aiterm-wait` waiter whose exit codes mirror the receipt outcome
|
|
181
181
|
(`0`=done / `3`=timeout, not finished / `4`=closed / `5`=running for a
|
|
182
182
|
zero-time observation), and a launch with an
|
|
183
183
|
initial prompt returns a ready-made `wait_command` in its structured receipt.
|
|
@@ -187,7 +187,7 @@ collection is off by default and performs no network I/O. It ships via
|
|
|
187
187
|
tag-triggered CI with npm provenance (OIDC Trusted Publishing); the GitHub
|
|
188
188
|
Release re-registers the Official MCP Registry entry.
|
|
189
189
|
|
|
190
|
-
**Status:** actively maintained · current public release **v0.
|
|
190
|
+
**Status:** actively maintained · current public release **v0.34.0** · runs on Linux · WSL2 · macOS · native Windows (tmux on POSIX, the tmux-CLI-compatible [psmux](https://github.com/psmux/psmux) on native Windows — no WSL required) · MIT · see the [CHANGELOG](CHANGELOG.md).
|
|
191
191
|
|
|
192
192
|
### Update and rollback
|
|
193
193
|
|
|
@@ -227,7 +227,7 @@ pty_read(id, { wait: true }) → read the token-reduced output, completion
|
|
|
227
227
|
|
|
228
228
|
The same primitive hosts another agent's TUI. `agent_launch` starts a selected execution harness inside a fresh persistent terminal and returns a `session_id`. `harness` names the component that owns the agent loop, authentication, hooks, session, and transcript; `model` remains an independent choice. The launched process sees the same project and user environment as a direct CLI invocation: normal configuration, MCPs, plugins, skills, permissions, trust decisions, memory, and history are not copied, filtered, or replaced. Aiterm adds only completion correlation and a non-user sub-agent context containing `role=subagent`, the parent session, delegation depth, lineage, and `delegation_allowed=true`.
|
|
229
229
|
|
|
230
|
-
The human-readable launch text is accompanied by an `aiterm.agent-launch-result.v1` structured receipt containing the canonical `harness`; the old `provider` field remains for compatibility. The same `harness` is carried by agent dispatch, `aiterm-wait`, `agent_configure`, and agent rows in `pty_list`, while their old vendor/provider/agent fields remain compatibility fields. Codex completion comes from its normal durable rollout transcript, Grok CLI from its normal session events, Claude Code from a launch-specific Stop hook settings addition, and Cursor from its normal agent transcript's terminal `turn_ended` record. Sending to any agent session is a non-blocking **dispatch** — the call returns immediately with an opaque, harness-specific integer `event_cursor`, and
|
|
230
|
+
The human-readable launch text is accompanied by an `aiterm.agent-launch-result.v1` structured receipt containing the canonical `harness`; the old `provider` field remains for compatibility. The same `harness` is carried by agent dispatch, `aiterm-wait`, `agent_configure`, and agent rows in `pty_list`, while their old vendor/provider/agent fields remain compatibility fields. Codex completion comes from its normal durable rollout transcript, Grok CLI from its normal session events, Claude Code from a launch-specific Stop hook settings addition, and Cursor from its normal agent transcript's terminal `turn_ended` record. Sending to any agent session is a non-blocking **dispatch** — the call returns immediately with an opaque, harness-specific integer `event_cursor`, and Codex parents receive the answer automatically through the official input queue. Other parents use [`aiterm-wait`](#completion-push-for-parent-agents-aiterm-wait). The Cursor adapter translates submit into the current CLI's extended keyboard protocol. If submitted text remains in Cursor's composer, its dispatch fails instead of returning a successful receipt.
|
|
231
231
|
|
|
232
232
|
`agent_launch`, `pty_send` (agent dispatch), and `agent_steer` accept an optional `image`: an array of absolute paths to image files (png/jpg/jpeg/gif/webp). Aiterm appends an attachment block to the prompt, and every harness opens the path with its own file-reading tool and sees the image; the caller never learns harness-specific attachment tricks. Invalid paths are rejected before anything is sent.
|
|
233
233
|
|
|
@@ -250,6 +250,7 @@ agent_launch({ harness: "codex-cli", session_name: "codex1", cwd: "/repo",
|
|
|
250
250
|
pty_read("codex1", { screen: true }) → read what it's doing (token-reduced)
|
|
251
251
|
pty_send("codex1", "also fix the imports it broke")
|
|
252
252
|
→ non-blocking dispatch; receipt carries event_cursor
|
|
253
|
+
# Codex parents receive the answer automatically. For other parents:
|
|
253
254
|
$ aiterm-wait --session codex1 --cursor <event_cursor> # never in the parent's foreground; exit 0=done, 3=timeout (not done), 4=closed, 7=error (turn aborted by an API error)
|
|
254
255
|
pty_read("codex1", { agent_transcript: true }) → collect the full answer
|
|
255
256
|
```
|
|
@@ -544,11 +545,19 @@ When an agent's answer is longer than the on-screen tail (pane height ≈ 24 lin
|
|
|
544
545
|
|
|
545
546
|
### Completion detection (5 layers)
|
|
546
547
|
|
|
548
|
+
For PowerShell over SSH, `mark:true` recognizes the current standard `PS ...>` prompt and emits PowerShell syntax even when Aiterm runs on macOS or Linux. A prompt left in earlier output is not used to select the syntax.
|
|
549
|
+
|
|
547
550
|
`pty_read({ wait: true })` decides "is the command done?" via five layers: process exit / a `mark:true` sentinel / an `until` match / output quiescence with shell return / timeout. `mark` emits the shell's exit status on POSIX shells and `0` (success) or `1` (failure) on PowerShell; fish/csh/tcsh are rejected before send because they do not share either status syntax. When `mark` or `until` is active, that requested evidence takes precedence and a momentarily quiet shell cannot complete the read as quiescent. Agent sessions add a sixth exact layer: Codex observes normal rollout `task_complete`; Grok/Composer observe normal session `turn_ended`; Claude observes its additive launch-correlated Stop event; Cursor observes `turn_ended(status:"success")` in the launch-bound normal agent transcript. `aiterm-wait --cursor` performs that harness-specific observation without the parent blocking or polling. Pre-send readiness failures are MCP errors, and late completion remains recoverable without resending.
|
|
548
551
|
|
|
549
552
|
### Completion push for parent agents (`aiterm-wait`)
|
|
550
553
|
|
|
551
|
-
|
|
554
|
+
**Codex parents receive child answers automatically.** Launch or send a request, then continue other work or end the turn. Aiterm observes the child, saves the unabridged answer, and submits it to the requesting parent's official Codex input queue. The parent processes it when idle. No waiter, `pty_read`, child callback instructions, or Desktop-specific connection is needed. This covers all supported child harnesses; Claude and other parent hosts retain the waiter flow below.
|
|
555
|
+
|
|
556
|
+
Automatic receipts include `parent_delivery` and set `wait_process` / `wait_command` to null. `pty_observe` exposes `parent_deliveries`: `waiting`, `ready`, `sending`, `submitted`, `failed`, or `unknown`. `submitted` means accepted by the queue, not read by the model. Aiterm retains the answer and resumes unsent work after an MCP restart. An interrupted submission becomes `unknown` and is not blindly retried.
|
|
557
|
+
|
|
558
|
+
Use a Codex runtime that supplies MCP `_meta.threadId` and the official `thread/queue` API (verified with Codex CLI 0.154.0). `aiterm-setup` checks the installed queue entry point; Aiterm verifies the requesting thread before each dispatch. Codex native sub-agents reject external queue input and cannot be automatic-delivery parents. Ordinary CLI and Desktop parents use the same supported route.
|
|
559
|
+
|
|
560
|
+
**For other parent hosts**, dispatch and start the receipt's waiter in a separate process:
|
|
552
561
|
|
|
553
562
|
1. Launch the child with `agent_launch({ harness: ... })`; every launch shares the normal project/user environment and adds only completion correlation plus lineage. Send a turn with plain `pty_send` (or `claude_turn issue` for durable Claude operations). The call returns immediately with an `event_cursor` in its structured receipt.
|
|
554
563
|
2. Pass the receipt's `wait_process.executable` and `wait_process.args` unchanged to a true argv process API. PowerShell 7's `Start-Process` is the exception because it joins `-ArgumentList` arrays; pass `windows_start_process_argument_list` as its one ready-made argument string instead. This invokes the bundled waiter through the exact Node runtime that is already running aiterm, including on native Windows where npm's human-facing bin is a PowerShell script shim and install paths may contain spaces. `wait_command` remains a compatibility display string for humans. The waiter observes the harness-owned completion source, plus Claude's additive launch hook, as a **pure reader** and exits with a one-line `aiterm.agent-wait-result.v1` receipt. **Exit ≠ done**: the receipt's `outcome` is authoritative (`0` = `done`, `3` = `timeout`, `4` = `closed`, `1` = error).
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// Codex親の公式受信キューへの接続。親threadのload/resumeやDesktop固有通信は行わない。
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { createInterface } from "node:readline";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { resolveAgentBin } from "./agent-resolver.js";
|
|
6
|
+
import { realCodexHome } from "./harnesses/codex.js";
|
|
7
|
+
import { AitermError } from "./errors.js";
|
|
8
|
+
export class CodexDeliveryError extends AitermError {
|
|
9
|
+
delivery_code;
|
|
10
|
+
outcome_unknown;
|
|
11
|
+
constructor(delivery_code, message, outcome_unknown = false) {
|
|
12
|
+
super(`${delivery_code}: ${message}`, 2);
|
|
13
|
+
this.delivery_code = delivery_code;
|
|
14
|
+
this.outcome_unknown = outcome_unknown;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** modelの引数ではなく、CodexがMCP要求へ付けるmetadataだけを宛先にする。 */
|
|
18
|
+
export function codexParentFromRequest(clientName, metadata) {
|
|
19
|
+
if (clientName !== "codex-mcp-client")
|
|
20
|
+
return null;
|
|
21
|
+
const threadId = metadata?.threadId;
|
|
22
|
+
if (typeof threadId !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(threadId)) {
|
|
23
|
+
throw new CodexDeliveryError("CODEX_PARENT_ID_UNAVAILABLE", "MCP要求に親のthreadIdがありません。対応するCodexへ更新してください");
|
|
24
|
+
}
|
|
25
|
+
return { thread_id: threadId, codex_home: path.resolve(realCodexHome()) };
|
|
26
|
+
}
|
|
27
|
+
async function withCodexReceiver(parent, action, runtime = {}) {
|
|
28
|
+
const executable = runtime.executable ?? resolveAgentBin("codex");
|
|
29
|
+
if (!executable)
|
|
30
|
+
throw new CodexDeliveryError("CODEX_RECEIVER_UNAVAILABLE", "Codexの実行ファイルを確認できません");
|
|
31
|
+
const child = spawn(executable, runtime.args ?? ["app-server", "--listen", "stdio://"], {
|
|
32
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
33
|
+
env: { ...process.env, CODEX_HOME: parent.codex_home },
|
|
34
|
+
windowsHide: true,
|
|
35
|
+
});
|
|
36
|
+
const pending = new Map();
|
|
37
|
+
let sequence = 0;
|
|
38
|
+
let stopped = false;
|
|
39
|
+
let transportError = null;
|
|
40
|
+
const failTransport = (message) => {
|
|
41
|
+
transportError = message;
|
|
42
|
+
for (const item of pending.values()) {
|
|
43
|
+
clearTimeout(item.timer);
|
|
44
|
+
item.reject(new CodexDeliveryError("CODEX_RECEIVER_TRANSPORT_FAILED", message, item.method === "thread/queue/add"));
|
|
45
|
+
}
|
|
46
|
+
pending.clear();
|
|
47
|
+
};
|
|
48
|
+
child.on("error", (error) => failTransport(`Codexを起動できません(${error.code ?? "unknown"})`));
|
|
49
|
+
child.stdin.on("error", (error) => failTransport(`Codexへの書込みに失敗しました(${error.code ?? "unknown"})`));
|
|
50
|
+
const exited = new Promise((resolve) => child.once("close", (code, signal) => {
|
|
51
|
+
if (!stopped)
|
|
52
|
+
failTransport(`Codexの接続が終了しました(exit=${code}, signal=${signal})`);
|
|
53
|
+
resolve();
|
|
54
|
+
}));
|
|
55
|
+
const reader = createInterface({ input: child.stdout });
|
|
56
|
+
reader.on("line", (line) => {
|
|
57
|
+
let value;
|
|
58
|
+
try {
|
|
59
|
+
value = JSON.parse(line);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
failTransport("Codexが不正なJSON応答を返しました");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const item = pending.get(value?.id);
|
|
66
|
+
if (!item)
|
|
67
|
+
return;
|
|
68
|
+
clearTimeout(item.timer);
|
|
69
|
+
pending.delete(value.id);
|
|
70
|
+
if (value.error) {
|
|
71
|
+
item.reject(new CodexDeliveryError("CODEX_RECEIVER_REJECTED", typeof value.error.message === "string" ? value.error.message : "公式受信口が要求を拒否しました"));
|
|
72
|
+
}
|
|
73
|
+
else if ("result" in value)
|
|
74
|
+
item.resolve(value.result);
|
|
75
|
+
else
|
|
76
|
+
item.reject(new CodexDeliveryError("CODEX_RECEIVER_INVALID_RESPONSE", "公式受信口の応答にresultがありません", item.method === "thread/queue/add"));
|
|
77
|
+
});
|
|
78
|
+
const request = (method, params) => new Promise((resolve, reject) => {
|
|
79
|
+
if (transportError) {
|
|
80
|
+
reject(new CodexDeliveryError("CODEX_RECEIVER_TRANSPORT_FAILED", transportError));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const id = ++sequence;
|
|
84
|
+
const timer = setTimeout(() => {
|
|
85
|
+
pending.delete(id);
|
|
86
|
+
reject(new CodexDeliveryError("CODEX_RECEIVER_TIMEOUT", `${method}の応答を確認できません`, method === "thread/queue/add"));
|
|
87
|
+
}, runtime.timeout_ms ?? 15_000);
|
|
88
|
+
pending.set(id, { resolve, reject, timer, method });
|
|
89
|
+
child.stdin.write(JSON.stringify({ id, method, params }) + "\n");
|
|
90
|
+
});
|
|
91
|
+
try {
|
|
92
|
+
await request("initialize", { clientInfo: { name: "aiterm_parent_delivery", version: "1" }, capabilities: { experimentalApi: true } });
|
|
93
|
+
child.stdin.write(JSON.stringify({ method: "initialized" }) + "\n");
|
|
94
|
+
return await action(request);
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
stopped = true;
|
|
98
|
+
for (const item of pending.values())
|
|
99
|
+
clearTimeout(item.timer);
|
|
100
|
+
pending.clear();
|
|
101
|
+
child.stdin.end();
|
|
102
|
+
// stdio終了を公式processへ伝える。終了しない外部processだけを明示的に停止する。
|
|
103
|
+
const terminate = setTimeout(() => child.kill("SIGKILL"), 2_000);
|
|
104
|
+
await exited;
|
|
105
|
+
clearTimeout(terminate);
|
|
106
|
+
reader.close();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** 子へ送る前に、同じstoreの宛先と公式キューの対応を確認する。本文は保存・表示しない。 */
|
|
110
|
+
export async function verifyCodexParent(parent, runtime) {
|
|
111
|
+
await withCodexReceiver(parent, async (request) => {
|
|
112
|
+
const response = await request("thread/read", { threadId: parent.thread_id, includeTurns: false });
|
|
113
|
+
if (response?.thread?.id !== parent.thread_id) {
|
|
114
|
+
throw new CodexDeliveryError("CODEX_PARENT_UNAVAILABLE", "同じCodex環境で親threadを確認できません");
|
|
115
|
+
}
|
|
116
|
+
const subagent = response.thread.source?.subAgent;
|
|
117
|
+
if (subagent && typeof subagent === "object" && "thread_spawn" in subagent) {
|
|
118
|
+
throw new CodexDeliveryError("CODEX_PARENT_UNSUPPORTED", "Codexのnative sub-agentは外部processからのキュー入力を受け付けません");
|
|
119
|
+
}
|
|
120
|
+
await request("thread/queue/list", { threadId: parent.thread_id, limit: 1 });
|
|
121
|
+
}, runtime);
|
|
122
|
+
}
|
|
123
|
+
export async function submitCodexParentAnswer(parent, deliveryId, text, runtime) {
|
|
124
|
+
return withCodexReceiver(parent, async (request) => {
|
|
125
|
+
const result = await request("thread/queue/add", {
|
|
126
|
+
threadId: parent.thread_id,
|
|
127
|
+
input: [{ type: "text", text, text_elements: [] }],
|
|
128
|
+
clientUserMessageId: deliveryId,
|
|
129
|
+
});
|
|
130
|
+
if (typeof result?.queuedSubmission?.id !== "string") {
|
|
131
|
+
throw new CodexDeliveryError("CODEX_RECEIVER_INVALID_RESPONSE", "キューの受付IDを確認できません", true);
|
|
132
|
+
}
|
|
133
|
+
return { queued_submission_id: result.queuedSubmission.id };
|
|
134
|
+
}, runtime);
|
|
135
|
+
}
|
package/dist/core.js
CHANGED
|
@@ -16,7 +16,7 @@ import * as rtk from "./rtk.js";
|
|
|
16
16
|
import { paneTokenHint } from "./harnesses/pane-tokens.js";
|
|
17
17
|
import { readRuntimeProcesses, processSubtree, processIdentity, backgroundProcesses } from "./process-runtime.js";
|
|
18
18
|
import { AitermError, telemetryOwnedFailure, ownTelemetryFailure } from "./errors.js";
|
|
19
|
-
import { isWin, SOCKDIR, tmuxCommand, sendPsmuxPayload, loadPtyBufferChunk, pasteBufferBaseArgs, TMUX_EMPTY_CONFIG, attachCommand, normalizePaneCommand, appendMarkSentinel, settlePaneLog, paneCwdArgument, sessionEnvironmentLaunch, } from "./tmux-runtime.js";
|
|
19
|
+
import { isWin, SOCKDIR, tmuxCommand, sendPsmuxPayload, loadPtyBufferChunk, pasteBufferBaseArgs, TMUX_EMPTY_CONFIG, attachCommand, normalizePaneCommand, appendMarkSentinel, markShellCommand, settlePaneLog, paneCwdArgument, sessionEnvironmentLaunch, } from "./tmux-runtime.js";
|
|
20
20
|
import { sleep, currentUid, runtimeStateBase, safeStatSize, readFileRange, writeJson0600, createEmpty0600, shq, LAUNCH_ID_RE, AGENT_DONE_POLL_MS, AGENT_EVENT_MAX_BYTES, assertSessionName, agentsDir, agentEventPath, agentMetadataPath, writeAgentMetadata, AGENT_EVENT_TAIL_BYTES, agentLabel, agentHarness, subagentInstruction, agentLineageFields, } from "./agent-shared.js";
|
|
21
21
|
import { GROK_MODEL_DEFAULTS, realGrokHome, resolveAndValidateGrokAuth, assertGrokModelAvailable, grokEventsTranscript, latestGrokCompletion, observeGrokDone, buildGrokAgentCmd, grokLaunchNote, grokEnvTokens, grokTuiReady, grokTuiBusy, grokPaneObservation, grokStartupAction, grokLaunchBlockingDialog, assertGrokSandboxNotRejected, GROK_COMPOSER_MARKER_RE, grokFooterHasConfiguration, grokTranscriptText, createGrokAgentMetadata, } from "./harnesses/grok.js";
|
|
22
22
|
import { bindCodexTranscriptSession, latestCodexCompletion, observeCodexDone, buildCodexAgentCmd, codexLaunchNote, codexTuiReady, codexPaneObservation, codexApprovalDialog, codexStartupAction, CODEX_COMPOSER_MARKER_RE, codexModelChoice, codexEffortChoice, codexMoreReasoningChoice, codexTranscriptText, createCodexAgentMetadata, } from "./harnesses/codex.js";
|
|
@@ -235,12 +235,9 @@ export async function ensureAgentOwnsPaneInput(name, kind) {
|
|
|
235
235
|
}
|
|
236
236
|
function paneCurrentCommandForMark(name) {
|
|
237
237
|
const foreground = paneCurrentCommand(name);
|
|
238
|
-
if (!isWin || foreground === "powershell" || foreground === "pwsh")
|
|
238
|
+
if ((!isWin && foreground !== "ssh") || foreground === "powershell" || foreground === "pwsh")
|
|
239
239
|
return foreground;
|
|
240
|
-
|
|
241
|
-
// 現在の末尾画面にPowerShell promptが描画済みなら、mark構文はその実効shellへ合わせる。
|
|
242
|
-
const screen = captureScreen(name, 4).replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, "");
|
|
243
|
-
return /(?:^|\n)PS [^\r\n]*>\s*$/m.test(screen) ? "pwsh" : foreground;
|
|
240
|
+
return markShellCommand(foreground, captureScreen(name, 4));
|
|
244
241
|
}
|
|
245
242
|
function pasteBufferSupportsNoSanitizeFlag() {
|
|
246
243
|
const listed = tmux("list-commands");
|
|
@@ -2390,11 +2387,14 @@ async function settlePublishedClaudeCompletionMarker(meta, marker) {
|
|
|
2390
2387
|
/** agent harness の構造化 transcript から直近完了ターンの最終回答と相関情報を読む。 */
|
|
2391
2388
|
export async function readAgentTranscriptResult(name, o = {}) {
|
|
2392
2389
|
const meta = loadAgentMetadata(name);
|
|
2390
|
+
if (o.completion && (o.completion.outcome !== "done" || o.completion.launch_id !== meta.launch_id || o.completion.session_id !== name)) {
|
|
2391
|
+
throw new AitermError("回収対象の完了情報がagent launchと一致しません", 2);
|
|
2392
|
+
}
|
|
2393
2393
|
const operationId = o.operation_id == null ? null : validateOperationId(o.operation_id);
|
|
2394
2394
|
if (operationId && meta.kind !== "claude") {
|
|
2395
2395
|
throw new AitermError("operation_id付き回収はClaude agent sessionだけで使用できます", 2);
|
|
2396
2396
|
}
|
|
2397
|
-
if (meta.kind === "claude") {
|
|
2397
|
+
if (meta.kind === "claude" && !o.completion) {
|
|
2398
2398
|
let active = readClaudeOperationMarker(meta);
|
|
2399
2399
|
if (active)
|
|
2400
2400
|
active = await settlePublishedClaudeCompletionMarker(meta, active);
|
|
@@ -2410,10 +2410,13 @@ export async function readAgentTranscriptResult(name, o = {}) {
|
|
|
2410
2410
|
throw new AitermError(`agent session '${name}' はまだターンが完了していません。agent_done 完了後に再取得してください。${agentWaitGuide(name)}`, 2);
|
|
2411
2411
|
}
|
|
2412
2412
|
const done = latestAgentDoneEvent(meta, operationId);
|
|
2413
|
+
if (o.completion && meta.kind !== "codex" && (!done || done.turn_id !== o.completion.turn_id || done.operation_id !== o.completion.operation_id)) {
|
|
2414
|
+
throw new AitermError("回収対象の完了情報が置換されました。別の回答は配送しません", 2);
|
|
2415
|
+
}
|
|
2413
2416
|
if (operationId && !done) {
|
|
2414
2417
|
throw new AitermError(`operation ${operationId} はまだ完了していません。同じoperation_idで後から再取得してください。${agentWaitGuide(name)}`, 2);
|
|
2415
2418
|
}
|
|
2416
|
-
const turnId = done?.turn_id ?? null;
|
|
2419
|
+
const turnId = o.completion?.turn_id ?? done?.turn_id ?? null;
|
|
2417
2420
|
let text = "";
|
|
2418
2421
|
if (meta.kind === "claude") {
|
|
2419
2422
|
if (!done)
|
|
@@ -2424,7 +2427,7 @@ export async function readAgentTranscriptResult(name, o = {}) {
|
|
|
2424
2427
|
text = cursorTranscriptText(meta, readTranscriptLines, transcriptUnavailable);
|
|
2425
2428
|
}
|
|
2426
2429
|
else if (meta.kind === "codex") {
|
|
2427
|
-
text = codexTranscriptText(meta, turnId, readTranscriptLines, transcriptUnavailable);
|
|
2430
|
+
text = codexTranscriptText(meta, turnId, readTranscriptLines, transcriptUnavailable, o.completion !== undefined);
|
|
2428
2431
|
}
|
|
2429
2432
|
else {
|
|
2430
2433
|
if (!done)
|
|
@@ -2436,7 +2439,7 @@ export async function readAgentTranscriptResult(name, o = {}) {
|
|
|
2436
2439
|
if (o.lines != null)
|
|
2437
2440
|
text = text.split("\n").slice(-o.lines).join("\n");
|
|
2438
2441
|
const rawChars = text.length;
|
|
2439
|
-
const [body, outputMeta] = reduceOutput(text, name, true);
|
|
2442
|
+
const [body, outputMeta] = o.raw ? [text, ""] : reduceOutput(text, name, true);
|
|
2440
2443
|
const transcriptMeta = [
|
|
2441
2444
|
"agent_transcript",
|
|
2442
2445
|
`vendor=${meta.kind}`,
|
|
@@ -2571,6 +2574,10 @@ export function agentWaitLaunchForm(command) {
|
|
|
2571
2574
|
}
|
|
2572
2575
|
// dispatch / 起動時 prompt 送信後の共通案内。第一文で「待たない」を宣言し、待ち方は後段に置く。
|
|
2573
2576
|
export function agentDispatchGuide(session, cursor) {
|
|
2577
|
+
if (parentClientName === "codex-mcp-client") {
|
|
2578
|
+
return "回答本文はAitermがこのCodex親へ自動配送する。wait起動・ポーリング・通常の回答回収は不要。" +
|
|
2579
|
+
"親は作業を続けるかターンを終え、順番待ちから届く子の回答で続行する。";
|
|
2580
|
+
}
|
|
2574
2581
|
const cmd = `aiterm-wait --session ${session} --cursor ${cursor}`;
|
|
2575
2582
|
return (`投げっぱなしでよい=ここで待たない。親は自分の作業へ戻るか、このターンを終える。\n` +
|
|
2576
2583
|
`完了通知: ${agentWaitLaunchForm(cmd)}。exit が完了通知(${AITERM_WAIT_OUTCOME_NOTE})。\n` +
|
|
@@ -2578,6 +2585,8 @@ export function agentDispatchGuide(session, cursor) {
|
|
|
2578
2585
|
}
|
|
2579
2586
|
// 未完了 session へ触った時の共通案内。ここでも待つのは waiter プロセスであって親ではない。
|
|
2580
2587
|
export function agentWaitGuide(session) {
|
|
2588
|
+
if (parentClientName === "codex-mcp-client")
|
|
2589
|
+
return "回答本文はこのCodex親へ自動配送される。親は作業を続けるかターンを終える。";
|
|
2581
2590
|
const cmd = `aiterm-wait --session ${session ?? "<session_id>"} --cursor 0`;
|
|
2582
2591
|
return `完了通知は ${agentWaitLaunchForm(cmd)} で受ける(親はここで待たない・polling 不要)。receipt の outcome=done を確認してから再取得する。`;
|
|
2583
2592
|
}
|
|
@@ -2637,13 +2646,13 @@ export async function observeAgentDone(name, o = {}) {
|
|
|
2637
2646
|
}
|
|
2638
2647
|
const timeout = o.timeout ?? DEFAULT_AGENT_DONE_TIMEOUT;
|
|
2639
2648
|
if (meta.kind === "codex" && meta.completion_route === "codex_transcript") {
|
|
2640
|
-
return observeCodexDone(meta, timeout, o.cursor, detectAgentRateLimit);
|
|
2649
|
+
return observeCodexDone(meta, timeout, o.cursor, detectAgentRateLimit, o.signal);
|
|
2641
2650
|
}
|
|
2642
2651
|
if (meta.kind === "cursor" && meta.completion_route === "cursor_transcript") {
|
|
2643
|
-
return observeCursorDone(meta, timeout, o.cursor, detectAgentRateLimit);
|
|
2652
|
+
return observeCursorDone(meta, timeout, o.cursor, detectAgentRateLimit, o.signal);
|
|
2644
2653
|
}
|
|
2645
2654
|
if ((meta.kind === "grok" || meta.kind === "composer") && meta.completion_route === "grok_transcript") {
|
|
2646
|
-
return observeGrokDone(meta, timeout, o.cursor, detectAgentRateLimit);
|
|
2655
|
+
return observeGrokDone(meta, timeout, o.cursor, detectAgentRateLimit, o.signal);
|
|
2647
2656
|
}
|
|
2648
2657
|
const metadataFile = agentMetadataPath(meta.aiterm_session, meta.launch_id);
|
|
2649
2658
|
// 境界の優先順: dispatch receipt の event_cursor(起動順序に依存しない)→ operation相関
|
|
@@ -2674,6 +2683,7 @@ export async function observeAgentDone(name, o = {}) {
|
|
|
2674
2683
|
error: apiError?.text ?? null,
|
|
2675
2684
|
});
|
|
2676
2685
|
for (;;) {
|
|
2686
|
+
o.signal?.throwIfAborted();
|
|
2677
2687
|
if (!fs.existsSync(metadataFile))
|
|
2678
2688
|
return observation("closed");
|
|
2679
2689
|
const size = safeStatSize(meta.event_file);
|
|
@@ -3106,6 +3116,9 @@ export async function sendInitialAgentPrompt(name, text, o = {}) {
|
|
|
3106
3116
|
const promptText = meta.kind === "cursor" ? cursorPromptWithLineage(meta, text) : text;
|
|
3107
3117
|
const startOffset = agentCompletionCursor(meta);
|
|
3108
3118
|
try {
|
|
3119
|
+
prepareSendText(promptText, { raw: false });
|
|
3120
|
+
await o.before_send?.({ session_id: name, launch_id: meta.launch_id, vendor: meta.kind,
|
|
3121
|
+
harness: agentHarness(meta.kind), event_cursor: startOffset, operation_id: null });
|
|
3109
3122
|
if (meta.kind === "claude") {
|
|
3110
3123
|
prepareSendText(text, { raw: false });
|
|
3111
3124
|
reserveAnonymousClaudeTurn(meta);
|
|
@@ -3440,6 +3453,9 @@ export async function dispatchAgentTurn(name, text, o = {}) {
|
|
|
3440
3453
|
const dispatchText = meta.kind === "cursor" && !meta.vendor_session_id
|
|
3441
3454
|
? `${subagentInstruction(meta)}\n\n${text}`
|
|
3442
3455
|
: text;
|
|
3456
|
+
prepareSendText(dispatchText, { raw: o.raw });
|
|
3457
|
+
await o.before_send?.({ session_id: name, launch_id: meta.launch_id, vendor: meta.kind,
|
|
3458
|
+
harness: agentHarness(meta.kind), event_cursor: startOffset, operation_id: operationId });
|
|
3443
3459
|
if (meta.kind === "claude") {
|
|
3444
3460
|
// durable/anonymousを分岐する前に同じsend preflightを通す。拒否されるpromptの
|
|
3445
3461
|
// receipt/active markerだけを残して、来ないStopを待つ状態を作らない。
|
|
@@ -3520,7 +3536,7 @@ export async function steerAgentTurn(name, text) {
|
|
|
3520
3536
|
sendKey(name, "Enter");
|
|
3521
3537
|
return { ...receipt, delivery: "steered", pane_input_recovery: paneInputRecovery };
|
|
3522
3538
|
}
|
|
3523
|
-
export async function runClaudeOperation({ session_id: name, action, operation_id: operationIdInput, text, }) {
|
|
3539
|
+
export async function runClaudeOperation({ session_id: name, action, operation_id: operationIdInput, text, before_send, }) {
|
|
3524
3540
|
assertSessionName(name);
|
|
3525
3541
|
if (action !== "issue" && action !== "recover") {
|
|
3526
3542
|
throw new AitermError('action は "issue" または "recover" を指定してください', 2);
|
|
@@ -3535,7 +3551,7 @@ export async function runClaudeOperation({ session_id: name, action, operation_i
|
|
|
3535
3551
|
throw new AitermError("claude_turn issueには空でないtextが必要です", 2);
|
|
3536
3552
|
}
|
|
3537
3553
|
// v0.16.0: issue は dispatch-only。完了通知は aiterm-wait --operation、回収は recover が担う。
|
|
3538
|
-
dispatchReceipt = await dispatchAgentTurn(name, text, { operation_id: operationId });
|
|
3554
|
+
dispatchReceipt = await dispatchAgentTurn(name, text, { operation_id: operationId, before_send });
|
|
3539
3555
|
}
|
|
3540
3556
|
else {
|
|
3541
3557
|
if (text != null)
|
|
@@ -3876,7 +3892,7 @@ export function openAgent(kind, opts = {}) {
|
|
|
3876
3892
|
}
|
|
3877
3893
|
const driveHint = agentDone
|
|
3878
3894
|
? `TUI の描画には数秒かかる。少し置いてから pty_read(${sid}, screen:true) で画面を読み、` +
|
|
3879
|
-
`turnはpty_send(${sid}, "...")で送る(自動で非ブロックdispatch
|
|
3895
|
+
`turnはpty_send(${sid}, "...")で送る(自動で非ブロックdispatch。${parentClientName === "codex-mcp-client" ? "回答本文はCodex親へ自動配送する" : "完了通知はaiterm-wait"})。中断はpty_key(${sid}, "C-c")、` +
|
|
3880
3896
|
`Stopが来ない場合の解除はpty_close(${sid})を使う。`
|
|
3881
3897
|
: `TUI の描画には数秒かかる。少し置いてから pty_read(${sid}, screen:true) で画面を読み、` +
|
|
3882
3898
|
`pty_send(${sid}, "...") で入力・pty_key(${sid}, "Enter"/"Up"/"C-c" 等) で操作する(対話)。`;
|
|
@@ -3974,6 +3990,7 @@ export async function openAgentWithInitialPrompt(kind, opts = {}) {
|
|
|
3974
3990
|
const initial = await sendInitialAgentPrompt(sid, prompt, {
|
|
3975
3991
|
ready_timeout: opts.ready_timeout ?? undefined,
|
|
3976
3992
|
trust_project: opts.trust_project,
|
|
3993
|
+
before_send: opts.before_send,
|
|
3977
3994
|
});
|
|
3978
3995
|
return [sid, `${hint}\n${initial.text}`, initial.event_cursor, initial.submit_residue, initial.initial_prompt,
|
|
3979
3996
|
{ status: "ready", reason: "composer_ready" }];
|
package/dist/harnesses/codex.js
CHANGED
|
@@ -249,7 +249,7 @@ export function latestCodexCompletion(meta, readTranscriptLines) {
|
|
|
249
249
|
}
|
|
250
250
|
return latest;
|
|
251
251
|
}
|
|
252
|
-
export async function observeCodexDone(meta, timeout, requestedCursor, detectRateLimit) {
|
|
252
|
+
export async function observeCodexDone(meta, timeout, requestedCursor, detectRateLimit, signal) {
|
|
253
253
|
const metadataFile = agentMetadataPath(meta.aiterm_session, meta.launch_id);
|
|
254
254
|
let transcript = codexRootTranscript(meta);
|
|
255
255
|
const startOffset = requestedCursor ?? (transcript ? safeStatSize(transcript) : 0);
|
|
@@ -275,6 +275,7 @@ export async function observeCodexDone(meta, timeout, requestedCursor, detectRat
|
|
|
275
275
|
error: null,
|
|
276
276
|
});
|
|
277
277
|
for (;;) {
|
|
278
|
+
signal?.throwIfAborted();
|
|
278
279
|
if (!fs.existsSync(metadataFile))
|
|
279
280
|
return observation("closed");
|
|
280
281
|
transcript ??= codexRootTranscript(meta);
|
|
@@ -539,13 +540,42 @@ export function codexMoreReasoningChoice(screen) {
|
|
|
539
540
|
return null;
|
|
540
541
|
}
|
|
541
542
|
// 回収対象turnの最終assistantメッセージをroot rollout transcriptから抽出する。
|
|
542
|
-
export function codexTranscriptText(meta, turnId, readTranscriptLines, transcriptUnavailable) {
|
|
543
|
+
export function codexTranscriptText(meta, turnId, readTranscriptLines, transcriptUnavailable, exactCompletion = false) {
|
|
543
544
|
if (!meta.codex_home || !meta.vendor_session_id)
|
|
544
545
|
transcriptUnavailable();
|
|
545
546
|
const transcript = findLatestCodexTranscript(meta.codex_home, meta.vendor_session_id);
|
|
546
547
|
if (!transcript)
|
|
547
548
|
transcriptUnavailable();
|
|
548
549
|
const lines = readTranscriptLines(transcript);
|
|
550
|
+
if (exactCompletion) {
|
|
551
|
+
// 同じturnの本文だけを使う。完了event内の本文と、turn ID付きoutput_textの両形式を扱う。
|
|
552
|
+
const matching = [];
|
|
553
|
+
for (const line of lines) {
|
|
554
|
+
let record;
|
|
555
|
+
try {
|
|
556
|
+
record = JSON.parse(line);
|
|
557
|
+
}
|
|
558
|
+
catch {
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
const payload = record?.payload;
|
|
562
|
+
if (record?.type === "response_item" && payload?.type === "message" && payload?.role === "assistant"
|
|
563
|
+
&& payload?.internal_chat_message_metadata_passthrough?.turn_id === turnId && Array.isArray(payload?.content)) {
|
|
564
|
+
for (const item of payload.content) {
|
|
565
|
+
if (item?.type === "output_text" && typeof item.text === "string")
|
|
566
|
+
matching.push(item.text);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (record?.type === "event_msg" && payload?.type === "task_complete" && payload.turn_id === turnId) {
|
|
570
|
+
if (typeof payload.last_agent_message === "string")
|
|
571
|
+
return payload.last_agent_message;
|
|
572
|
+
if (matching.length > 0)
|
|
573
|
+
return matching.join("\n");
|
|
574
|
+
transcriptUnavailable();
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
transcriptUnavailable();
|
|
578
|
+
}
|
|
549
579
|
const matching = [];
|
|
550
580
|
let finalAnswer = "";
|
|
551
581
|
for (const line of lines) {
|
package/dist/harnesses/cursor.js
CHANGED
|
@@ -183,7 +183,7 @@ export function latestCursorCompletion(meta, readTranscriptLines) {
|
|
|
183
183
|
? cursorCompletionEvent(meta, harnessSessionId, state.terminalRecord, `cursor:${state.userTurns}`)
|
|
184
184
|
: null;
|
|
185
185
|
}
|
|
186
|
-
export async function observeCursorDone(meta, timeout, requestedCursor, detectRateLimit) {
|
|
186
|
+
export async function observeCursorDone(meta, timeout, requestedCursor, detectRateLimit, signal) {
|
|
187
187
|
const metadataFile = agentMetadataPath(meta.aiterm_session, meta.launch_id);
|
|
188
188
|
let transcript = cursorTranscript(meta);
|
|
189
189
|
const startBoundary = requestedCursor ?? (transcript ? cursorTranscriptState(transcript).userTurns : 0);
|
|
@@ -205,6 +205,7 @@ export async function observeCursorDone(meta, timeout, requestedCursor, detectRa
|
|
|
205
205
|
error: null,
|
|
206
206
|
});
|
|
207
207
|
for (;;) {
|
|
208
|
+
signal?.throwIfAborted();
|
|
208
209
|
if (!fs.existsSync(metadataFile))
|
|
209
210
|
return observation("closed");
|
|
210
211
|
transcript ??= cursorTranscript(meta);
|
package/dist/harnesses/grok.js
CHANGED
|
@@ -146,7 +146,7 @@ export function grokInitializationComplete(meta) {
|
|
|
146
146
|
}
|
|
147
147
|
return false;
|
|
148
148
|
}
|
|
149
|
-
export async function observeGrokDone(meta, timeout, requestedCursor, detectRateLimit) {
|
|
149
|
+
export async function observeGrokDone(meta, timeout, requestedCursor, detectRateLimit, signal) {
|
|
150
150
|
const metadataFile = agentMetadataPath(meta.aiterm_session, meta.launch_id);
|
|
151
151
|
const transcript = grokEventsTranscript(meta);
|
|
152
152
|
if (!transcript)
|
|
@@ -174,6 +174,7 @@ export async function observeGrokDone(meta, timeout, requestedCursor, detectRate
|
|
|
174
174
|
error: ev?.done_status === "turn_error" ? ev.reason : null,
|
|
175
175
|
});
|
|
176
176
|
for (;;) {
|
|
177
|
+
signal?.throwIfAborted();
|
|
177
178
|
if (!fs.existsSync(metadataFile))
|
|
178
179
|
return observation("closed");
|
|
179
180
|
if (fs.existsSync(transcript)) {
|