aiterm-mcp 0.59.0 → 0.60.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 +65 -1
- package/README.ja.md +1 -1
- package/README.md +2 -2
- package/dist/agent-shared.js +42 -0
- package/dist/aiterm-wait-cli.js +17 -3
- package/dist/claude-stop-hook.js +22 -1
- package/dist/core.js +101 -18
- package/dist/harnesses/codex.js +78 -30
- package/dist/index.js +27 -7
- package/dist/interim-words.js +80 -8
- package/dist/parent-delivery.js +2 -1
- package/dist/steer-follow.js +610 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,68 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.60.0] - 2026-10-13
|
|
11
|
+
|
|
12
|
+
### 追加
|
|
13
|
+
|
|
14
|
+
- BellTeamが、差し込んだ文を取った番の終わりと、自分で始めていない番を追える口(ADR 0122)。要求の`_meta["aiterm/caller"]`が`BellTeam`の時だけ足す。
|
|
15
|
+
ツールの説明・引数・`structuredContent`の形と、印の無い呼び手への返りは変えない。答えの本文は運ばず、位置だけを返す。
|
|
16
|
+
- `pty_send`の差し込み(`mode: "agent_steer"`)の返りに、`event_cursor`(打つ直前の完了の位置)と`wait_process`
|
|
17
|
+
(`aiterm-wait --session <席> --cursor <N> --steer <steer_id>`)。結果の`_meta["aiterm/steer"]`に`{ schema: "aiterm.steer-ref.v1", steer_id, interim_from }`。
|
|
18
|
+
- `aiterm-wait --steer <steer_id>`は、その文を取った番の終わりで返る。結果に`steer: { placement, taken_at }`を足す。
|
|
19
|
+
`placement`は`same_turn`(動いていた番が取り込んだ)・`next_turn`(番の後で、その文が新しい番を始めた)・`unknown`(記録の中で見分けられず、打った後の
|
|
20
|
+
最初の番の終わりで返した)。文が会話の記録に入っていない時は`outcome: "not_taken"`で、終了コードは8。
|
|
21
|
+
- `pty_observe`の要求の`_meta["aiterm/interim_from"]`に、上か下が返した値を渡すと、その位置より後の合間の言葉を返す(`seq`は1から)。
|
|
22
|
+
- `pty_list`の結果の`_meta["aiterm/seat_turns"]`に、席の今の番。`{ schema: "aiterm.seat-turns.v1", seats: [{ session_id, launch_id, harness, turn }] }`で、
|
|
23
|
+
`turn`は`{ state: "running", turn_id, started_at, event_cursor, wait_process, interim_from }`・`{ state: "idle" }`・`null`(分からない)。
|
|
24
|
+
Codexの席だけで、会話の記録から読む(画面は読まない)。動いている番の位置は番の頭で、そこから待てばその番の終わりで返る。
|
|
25
|
+
- 文の見分けは、打つ直前に控えた文の頭と、harnessの会話の記録の突き合わせ。CodexとClaude Codeで見分ける。GrokとCursorの差し込みは必ず今の番へ入るので、
|
|
26
|
+
打った後の最初の完了を`same_turn`で返す。別端末の席(`remote`)への送信は、まだこの手がかりを返さない。
|
|
27
|
+
|
|
28
|
+
### 変更
|
|
29
|
+
|
|
30
|
+
- 試験: `test/core-agent.test.mjs`を、話題の切れ目で5つのfileへ分けた(共通の準備は`test/core-agent-support.mjs`)。試験の中身と本数は同じ。
|
|
31
|
+
fileの中の試験は1本ずつ順に走るので、この1 fileがWindowsで687秒かかり、全体の試験(720秒)の長さを決めていた。分けたfileは並んで走る。
|
|
32
|
+
同じ訳で、`test/runtime-error-store.test.mjs`(Windowsで236秒)も2つに分けた(共通の準備は`test/runtime-error-store-support.mjs`)。
|
|
33
|
+
- 試験: 試験は、動かした利用者・席・runnerの置き場と鍵に触らない(ADR 0121、ADR 0087の追記)。製品の動きは変わらない。
|
|
34
|
+
- 試験の前処理(`test/seat-env.mjs`)が、試験へ渡す環境を要る物だけにし、homeとharness・gh・git・npmの置き場を、試験ごとの空の置き場の中に向ける。
|
|
35
|
+
試験fileは最初のimportで前処理を読むので、入口(`npm test`)を通らずに流されても同じに効く。
|
|
36
|
+
- 一般の試験は、端末に入っている本物のClaude Code・Codex・Grok・Cursorを起こさない。実物で確かめる試験は、`AITERM_TEST_`で始まる変数で
|
|
37
|
+
実行ファイルを渡した時だけ動く。
|
|
38
|
+
- CIの入口と`npm test`は、この確かめ(`test/seat-env.test.mjs`)を先に流し、落ちたらほかの試験を起こさない。CIの入口は、
|
|
39
|
+
端末の本体(tmux・psmux・pwsh)が見えない時と、選んだ試験の名前が空や不正の時に、組む前に落ちる。
|
|
40
|
+
- 混んだ端末で上限に当たった待ち(席が起きるのを待つ所)を延ばした。
|
|
41
|
+
- 試験: Codexのhookの試験の偽の受け口(`test/fixtures/codex-hook-queue.mjs`)が、キューのfileをその場で書いていた。同時に動くhookの数だけ
|
|
42
|
+
偽物が起きて同じfileを見るので、空にしてから書き終えるまでの間に読んだ偽物が異常終了し、「同時に二つのhookが動いても…」が
|
|
43
|
+
`CODEX_RECEIVER_TRANSPORT_FAILED`で落ちる事があった(macOSのCI)。一時fileからの置き換えにした。製品の動きは変わらない。
|
|
44
|
+
|
|
45
|
+
### 修正
|
|
46
|
+
|
|
47
|
+
- Claude CodeのStopのhookが、席の印を継いだ別の会話(席が起こした子や、裏の会話)の番の終わりで、席の答えのfileを上書きし、席の番の印を
|
|
48
|
+
消していた件を直す(ADR 0119)。席の会話と違う会話のStopでは、席の記録に触らない。席の会話IDは、席の記録を先に、無ければ環境の値で見る。
|
|
49
|
+
- **設定を替えた直後に同じ席へ届いた文が、替える前の設定で動く事があった**(ADR 0120)。`agent_configure`は、命令を打つ前の確かめに
|
|
50
|
+
数秒かかる間、席ごとの送信の行列(ADR 0088)に並んでいなかった。その間に届いた`pty_send`が先に席へ入り、設定替えは番の後になった
|
|
51
|
+
(実例: ファストを入れた3秒後の1通が通常の速度で動いた)。設定替えを同じ行列に並べ、替えている間に届いた文は替え終えてから席へ入れる。
|
|
52
|
+
- `pty_send`の返りは、同じ席の設定替えが終わるまで遅れる。
|
|
53
|
+
- 番が動いている席への設定替えは、行列を持たずに番の終わりを待つ。その間の差し込みは今までどおり通る。
|
|
54
|
+
- 順番が来なかった時(POSIXは60秒、Windowsは180秒)、設定替えは`AGENT_SEND_BUSY`と「設定は変えていません。」で断る。
|
|
55
|
+
送信の断りの文は変えていない。
|
|
56
|
+
- **Codexの完了待ちが、長く続いた番で「Codex transcript のturn増分が大きすぎます」と誤りで止まっていた件を直す。**
|
|
57
|
+
待ち始めの位置から16MiBを超えて書いた番で起きた(2026-10-13の夜の番は、開始の後ろが約8.4MBまで行った)。会話の記録を4MiBずつ読み、
|
|
58
|
+
番の大きさには上限を置かない。改行が来ないまま16MiBを超えた1行だけ、持たずに読み飛ばす。
|
|
59
|
+
- 片付け済み(archived)などで断られたCodexへの配送を、会話を戻した後に同じ配送IDで渡し直せなかった件を直す
|
|
60
|
+
(`aiterm-parent-delivery codex submit`が`PARENT_DELIVERY_DUPLICATE`で断っていた)。共通部品`aiterm-steer-delivery`を`^0.4.7`へ上げる。
|
|
61
|
+
|
|
62
|
+
## [0.59.1] - 2026-10-13
|
|
63
|
+
|
|
64
|
+
### 修正
|
|
65
|
+
|
|
66
|
+
- **長く続いているCodexの番へ、回答を流している間に届いた文が、30秒待って断られていた。**(ADR 0101 の直し)
|
|
67
|
+
回答を流している間のCodexは画面に動作中の行を出さないので、番が動いているかは会話の記録で確かめている。その開始の印を記録の末尾1MiBの中でしか
|
|
68
|
+
探しておらず、番が1MiBより多く書いた後は「動いていない」と読んで、新しいturnの道で入力待ちを30秒待ち、
|
|
69
|
+
`…TUI が入力受付状態になりません。文字列は送信していません。`で断っていた(2026-10-13、20分を過ぎた番で4通)。
|
|
70
|
+
開始の印を、境界に当たるまで記録をさかのぼって探す。動作中の行が出ている間に届いた文と、止まっている席への文は、今までと同じ。
|
|
71
|
+
|
|
10
72
|
## [0.59.0] - 2026-10-13
|
|
11
73
|
|
|
12
74
|
### 追加
|
|
@@ -2619,7 +2681,9 @@ prototype (preserved under `prototype/python/` as the porting source and referen
|
|
|
2619
2681
|
`ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
|
|
2620
2682
|
provenance.
|
|
2621
2683
|
|
|
2622
|
-
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.
|
|
2684
|
+
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.60.0...HEAD
|
|
2685
|
+
[0.60.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.59.1...v0.60.0
|
|
2686
|
+
[0.59.1]: https://github.com/kitepon/aiterm-mcp/compare/v0.59.0...v0.59.1
|
|
2623
2687
|
[0.59.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.58.4...v0.59.0
|
|
2624
2688
|
[0.58.4]: https://github.com/kitepon/aiterm-mcp/compare/v0.58.3...v0.58.4
|
|
2625
2689
|
[0.58.3]: https://github.com/kitepon/aiterm-mcp/compare/v0.58.2...v0.58.3
|
package/README.ja.md
CHANGED
|
@@ -209,7 +209,7 @@ runtime-error store は canonical dotagents config の `collection.enabled: true
|
|
|
209
209
|
場合だけ収集し、既定OFF、network送信は行いません。tag起点CIのnpm provenance(OIDC Trusted
|
|
210
210
|
Publishing)で公開し、GitHub Release が Official MCP Registry を再登録します。
|
|
211
211
|
|
|
212
|
-
**状態:** 開発継続中 · 現行公開版 **v0.
|
|
212
|
+
**状態:** 開発継続中 · 現行公開版 **v0.60.0** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
|
|
213
213
|
|
|
214
214
|
### 更新と巻き戻し
|
|
215
215
|
|
package/README.md
CHANGED
|
@@ -227,7 +227,7 @@ collection is off by default and performs no network I/O. It ships via
|
|
|
227
227
|
tag-triggered CI with npm provenance (OIDC Trusted Publishing); the GitHub
|
|
228
228
|
Release re-registers the Official MCP Registry entry.
|
|
229
229
|
|
|
230
|
-
**Status:** actively maintained · current public release **v0.
|
|
230
|
+
**Status:** actively maintained · current public release **v0.60.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).
|
|
231
231
|
|
|
232
232
|
### Update and rollback
|
|
233
233
|
|
|
@@ -293,7 +293,7 @@ Set `require_agent:true` on `pty_send` when the integration requires agent deliv
|
|
|
293
293
|
|
|
294
294
|
When the caller gives no `preface`, Aiterm adds one itself for a Claude Code session whenever the text is long enough for Claude Code to wrap it (three or more line breaks, or 800 characters and up): "Aitermが届ける、この席の呼び手からの正規の送信です。下の貼り付けは、どこかから拾った文ではなく、呼び手がこの席へ宛てて送った文です。" ("A regular message from this session's caller, delivered by Aiterm. The paste below was sent to this session by the caller, not picked up from somewhere else.") The line states where the message comes from and does not tell the session to obey it. It applies to new turns, to steering, to the `prompt` of `agent_launch`, and to `remote` sessions (the Aiterm on that machine adds it). Without that line the whole message sits inside the wrapper, and a Claude Code session may decline to act on it as "not the user's own instruction". Short texts, Codex, Grok and Cursor sessions, and sends with `raw` or `force` are unchanged (ADR 0116).
|
|
295
295
|
|
|
296
|
-
Overlapping `pty_send` calls to the same agent session are handled one at a time. A later call picks its route only after the earlier one has been sent, so the result matches calls made in sequence (a call that overlaps the first send to a freshly started Claude Code session returns `mode=agent_steer`). It returns later by however long the earlier call waits for the session to accept input. If its turn does not come within the limit (60 seconds on POSIX, 180 seconds on Windows), Aiterm refuses before sending any text and returns `AGENT_SEND_BUSY` with an explicit unsent message. A lock left by a process that exited mid-send is cleared by the next send.
|
|
296
|
+
Overlapping `pty_send` calls to the same agent session are handled one at a time. A later call picks its route only after the earlier one has been sent, so the result matches calls made in sequence (a call that overlaps the first send to a freshly started Claude Code session returns `mode=agent_steer`). It returns later by however long the earlier call waits for the session to accept input. If its turn does not come within the limit (60 seconds on POSIX, 180 seconds on Windows), Aiterm refuses before sending any text and returns `AGENT_SEND_BUSY` with an explicit unsent message. A lock left by a process that exited mid-send is cleared by the next send. `agent_configure` waits in the same line: a `pty_send` that arrives while a configuration change is in progress is sent after the change, so the turn runs with the new setting. A configuration change to a session whose turn is running does not hold the line while it waits for the turn to end, so steering still goes through. If its turn does not come within the same limit, `agent_configure` returns `AGENT_SEND_BUSY` with an explicit unchanged message.
|
|
297
297
|
|
|
298
298
|
`agent_launch` and `pty_send` (to an agent session) 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.
|
|
299
299
|
|
package/dist/agent-shared.js
CHANGED
|
@@ -33,6 +33,47 @@ export function readTailLinesNewestFirst(file, maxBytes) {
|
|
|
33
33
|
lines.shift();
|
|
34
34
|
return lines.filter(line => line.trim() !== "").reverse();
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* 記録を末尾から先頭へさかのぼり、行を新しい順に`visit`へ渡す。`visit`がundefined以外を返したら、そこで止めてその値を返す。
|
|
38
|
+
* 先頭まで読んで何も返らなければundefined。書いている途中の行(改行で終わっていない末尾)がある時と、読めなかった時はnull。
|
|
39
|
+
* 1回に読むのは`chunkBytes`ずつ。行が読む範囲に収まらない時は、収まるまで範囲を広げる(行の途中では切らない)。
|
|
40
|
+
* `visit`の2つめは、その行の頭のbyte位置。
|
|
41
|
+
*/
|
|
42
|
+
export function scanLinesNewestFirst(file, visit, chunkBytes) {
|
|
43
|
+
let end = safeStatSize(file);
|
|
44
|
+
for (let tail = true; end > 0; tail = false) {
|
|
45
|
+
let span = chunkBytes;
|
|
46
|
+
let start = Math.max(0, end - span);
|
|
47
|
+
let chunk = readFileRange(file, start, end);
|
|
48
|
+
// 範囲の先頭で切れた行は、次の回で読む。改行が範囲の最後の1つだけなら、その行は範囲に収まっていない。
|
|
49
|
+
let cut = start === 0 ? 0 : chunk.indexOf(0x0a) + 1;
|
|
50
|
+
while (chunk.length === end - start && start > 0 && (cut === 0 || cut === chunk.length)) {
|
|
51
|
+
span *= 2;
|
|
52
|
+
start = Math.max(0, end - span);
|
|
53
|
+
chunk = readFileRange(file, start, end);
|
|
54
|
+
cut = start === 0 ? 0 : chunk.indexOf(0x0a) + 1;
|
|
55
|
+
}
|
|
56
|
+
if (chunk.length !== end - start)
|
|
57
|
+
return null;
|
|
58
|
+
if (tail && chunk[chunk.length - 1] !== 0x0a)
|
|
59
|
+
return null;
|
|
60
|
+
const body = chunk.subarray(cut);
|
|
61
|
+
for (let stop = body.length; stop > 0;) {
|
|
62
|
+
if (body[stop - 1] === 0x0a)
|
|
63
|
+
stop -= 1;
|
|
64
|
+
const begin = stop === 0 ? 0 : body.lastIndexOf(0x0a, stop - 1) + 1;
|
|
65
|
+
const line = body.subarray(begin, stop).toString("utf8");
|
|
66
|
+
if (line.trim() !== "") {
|
|
67
|
+
const found = visit(line, start + cut + begin);
|
|
68
|
+
if (found !== undefined)
|
|
69
|
+
return found;
|
|
70
|
+
}
|
|
71
|
+
stop = begin;
|
|
72
|
+
}
|
|
73
|
+
end = start + cut;
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
36
77
|
export function readFileRange(p, from, to) {
|
|
37
78
|
const len = Math.max(0, to - from);
|
|
38
79
|
if (len === 0)
|
|
@@ -121,6 +162,7 @@ export function pollGate(intervalMs, now = () => performance.now()) {
|
|
|
121
162
|
export const AGENT_EVENT_MAX_BYTES = 1024 * 1024;
|
|
122
163
|
export const AGENT_EVENT_TAIL_BYTES = 64 * 1024;
|
|
123
164
|
// turnの境界(開始・終わり)を、harnessの記録の末尾から探す時に読む大きさ。終わった席では境界は末尾の近くにある。
|
|
165
|
+
// 動いている番の開始は、番が長いとこれより前になる。開始を探す所は、この大きさずつさかのぼる(scanLinesNewestFirst)。
|
|
124
166
|
export const AGENT_TURN_BOUNDARY_TAIL_BYTES = 1024 * 1024;
|
|
125
167
|
export const CODEX_TRANSCRIPT_INCREMENT_MAX_BYTES = 16 * 1024 * 1024;
|
|
126
168
|
export const GROK_TRANSCRIPT_INCREMENT_MAX_BYTES = 16 * 1024 * 1024;
|
package/dist/aiterm-wait-cli.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// aiterm-wait — agent turn 完了eventの純リーダー観測CLI。
|
|
3
3
|
// 完了/timeout/close を1行のJSON receiptで返してexitする。lock・PTY・dispatch状態には一切触れない。
|
|
4
4
|
// 親AIホストのバックグラウンドタスクとして起動し、exitを「観測終了の通知」として使う。
|
|
5
|
-
// exit≠完了: exit code は outcome を映す(0=done / 5=running=未完了 / 3=timeout=未完了 / 4=closed / 6=rate_limited=harness利用上限 / 7=error=turnがエラー終了 / 1=waiter自身のエラー)。
|
|
5
|
+
// exit≠完了: exit code は outcome を映す(0=done / 5=running=未完了 / 3=timeout=未完了 / 4=closed / 6=rate_limited=harness利用上限 / 7=error=turnがエラー終了 / 8=not_taken=差し込んだ文が会話に入っていない / 1=waiter自身のエラー)。
|
|
6
|
+
// --steer は、差し込みの返り(BellTeamだけに返す。ADR 0122)のwait_processが付ける。その差し込みを取った番の終わりを待つ。
|
|
6
7
|
// --timeout 0 は待たずに一度だけ観測する照会で、未完了は running(timeout と混同させない)。
|
|
7
8
|
// receipt の outcome が正で、done 以外は未完了。timeout の既定は core の DEFAULT_AGENT_DONE_TIMEOUT(600秒)。
|
|
8
9
|
import { fileURLToPath } from "node:url";
|
|
@@ -10,6 +11,7 @@ import * as fs from "node:fs";
|
|
|
10
11
|
import { AitermError, detectAgentRateLimit, observeAgentDone } from "./core.js";
|
|
11
12
|
const SESSION_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
12
13
|
const OPERATION_RE = /^sha256:[0-9a-f]{64}$/;
|
|
14
|
+
const STEER_RE = /^[0-9a-f]{16}$/;
|
|
13
15
|
const USAGE = "usage: aiterm-wait --session <name> [--cursor <event_cursor>] [--operation sha256:<64hex>] [--timeout <sec>]" +
|
|
14
16
|
"(--timeout 0 は待たずに一度だけ観測する照会で、未完了は outcome=running / exit 5)";
|
|
15
17
|
export function parseArgs(argv) {
|
|
@@ -17,9 +19,10 @@ export function parseArgs(argv) {
|
|
|
17
19
|
let operationId = null;
|
|
18
20
|
let timeout = null;
|
|
19
21
|
let cursor = null;
|
|
22
|
+
let steer = null;
|
|
20
23
|
for (let i = 0; i < argv.length; i++) {
|
|
21
24
|
const a = argv[i];
|
|
22
|
-
if (a === "--session" || a === "--operation" || a === "--timeout" || a === "--cursor") {
|
|
25
|
+
if (a === "--session" || a === "--operation" || a === "--timeout" || a === "--cursor" || a === "--steer") {
|
|
23
26
|
const v = argv[i + 1];
|
|
24
27
|
if (v === undefined)
|
|
25
28
|
throw new Error(`${a} に値がありません。${USAGE}`);
|
|
@@ -45,6 +48,13 @@ export function parseArgs(argv) {
|
|
|
45
48
|
throw new Error(`--cursor は0以上の整数完了境界だけを受理します。${USAGE}`);
|
|
46
49
|
cursor = Number(v);
|
|
47
50
|
}
|
|
51
|
+
else if (a === "--steer") {
|
|
52
|
+
if (steer !== null)
|
|
53
|
+
throw new Error(`--steer が重複しています。${USAGE}`);
|
|
54
|
+
if (!STEER_RE.test(v))
|
|
55
|
+
throw new Error(`--steer が不正です。${USAGE}`);
|
|
56
|
+
steer = v;
|
|
57
|
+
}
|
|
48
58
|
else {
|
|
49
59
|
if (timeout !== null)
|
|
50
60
|
throw new Error(`--timeout が重複しています。${USAGE}`);
|
|
@@ -61,7 +71,9 @@ export function parseArgs(argv) {
|
|
|
61
71
|
}
|
|
62
72
|
if (session === null)
|
|
63
73
|
throw new Error(`--session は必須です。${USAGE}`);
|
|
64
|
-
|
|
74
|
+
if (steer !== null && operationId !== null)
|
|
75
|
+
throw new Error(`--steer は --operation と併用できません。${USAGE}`);
|
|
76
|
+
return { session, operationId, timeout: timeout ?? 600, cursor, steer };
|
|
65
77
|
}
|
|
66
78
|
function emit(value) {
|
|
67
79
|
process.stdout.write(JSON.stringify(value) + "\n");
|
|
@@ -76,6 +88,7 @@ const OUTCOME_EXIT_CODES = {
|
|
|
76
88
|
closed: 4,
|
|
77
89
|
rate_limited: 6,
|
|
78
90
|
error: 7,
|
|
91
|
+
not_taken: 8,
|
|
79
92
|
};
|
|
80
93
|
export async function main(argv) {
|
|
81
94
|
const cmd = parseArgs(argv);
|
|
@@ -85,6 +98,7 @@ export async function main(argv) {
|
|
|
85
98
|
operation_id: cmd.operationId,
|
|
86
99
|
timeout: cmd.timeout,
|
|
87
100
|
cursor: cmd.cursor,
|
|
101
|
+
steer: cmd.steer,
|
|
88
102
|
});
|
|
89
103
|
}
|
|
90
104
|
catch (e) {
|
package/dist/claude-stop-hook.js
CHANGED
|
@@ -138,6 +138,22 @@ function consumeOperationMarker(file, marker) {
|
|
|
138
138
|
}
|
|
139
139
|
fs.unlinkSync(file);
|
|
140
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* 席の会話ID。席の記録(Aitermが書く `…agent.json`)を先に読み、無い・読めない時だけ、起動時に環境へ入れた値を見る。
|
|
143
|
+
* 記録を先にするのは、席の会話を後から結び直した時に、環境の値は起動時のまま残るため。
|
|
144
|
+
* どちらも無い時は null(今までどおり、印だけで席の物と数える)。
|
|
145
|
+
*/
|
|
146
|
+
function seatVendorSessionId(agents, session, launchId) {
|
|
147
|
+
try {
|
|
148
|
+
const meta = JSON.parse(fs.readFileSync(path.join(agents, `${session}.${launchId}.agent.json`), "utf8"));
|
|
149
|
+
if (typeof meta.vendor_session_id === "string" && meta.vendor_session_id)
|
|
150
|
+
return meta.vendor_session_id;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// 席の記録が無い・読めない。
|
|
154
|
+
}
|
|
155
|
+
return process.env.AITERM_AGENT_VENDOR_SESSION_ID || null;
|
|
156
|
+
}
|
|
141
157
|
async function main() {
|
|
142
158
|
if (!hasAitermEnv())
|
|
143
159
|
noop();
|
|
@@ -163,12 +179,17 @@ async function main() {
|
|
|
163
179
|
fail("Claude session_id がありません");
|
|
164
180
|
if (typeof payload.last_assistant_message !== "string")
|
|
165
181
|
fail("last_assistant_message がありません");
|
|
182
|
+
// 席の印(環境変数)は、席のprocessが起こした子や、Claude Codeの裏の常駐を通じて、席と関係の無い会話にも継がれる。
|
|
183
|
+
// 席の会話と違う会話のStopは、席の記録(答え・event・番の印)に触らない(ADR 0119)。
|
|
184
|
+
const agents = agentsDir();
|
|
185
|
+
const seatConversation = seatVendorSessionId(agents, session, launchId);
|
|
186
|
+
if (seatConversation !== null && seatConversation !== payload.session_id)
|
|
187
|
+
noop();
|
|
166
188
|
const text = payload.last_assistant_message;
|
|
167
189
|
const resultBytes = Buffer.byteLength(text, "utf8");
|
|
168
190
|
if (resultBytes > MAX_RESULT_BYTES)
|
|
169
191
|
fail(`assistant result が${MAX_RESULT_BYTES} bytesを超えています`);
|
|
170
192
|
const resultDigest = createHash("sha256").update(text, "utf8").digest("hex");
|
|
171
|
-
const agents = agentsDir();
|
|
172
193
|
const resultFile = path.join(agents, `${session}.${launchId}.claude-result.json`);
|
|
173
194
|
const eventFile = path.join(agents, `${session}.${launchId}.events.jsonl`);
|
|
174
195
|
const operationFile = path.join(agents, `${session}.${launchId}.claude-operation.json`);
|
package/dist/core.js
CHANGED
|
@@ -29,7 +29,8 @@ import { grokAuthPlan, grokAuthStatus, grokAuthPane, GROK_MODEL_DEFAULTS, realGr
|
|
|
29
29
|
import { codexAuthPlan, codexAuthStatus, codexAuthPane, realCodexHome, readCodexConfigPins, bindCodexTranscriptSession, latestCodexCompletion, codexTurnSettled, codexRunningTurn, observeCodexDone, buildCodexAgentCmd, codexLaunchNote, codexTuiReady, codexPaneObservation, codexTuiBusy, codexHelperProcess, codexRateLimitModelSwitchDialog, codexApprovalDialog, codexStartupAction, CODEX_COMPOSER_MARKER_RE, codexModelChoice, codexEffortChoice, codexMoreReasoningChoice, codexTranscriptText, createCodexAgentMetadata, codexModelChoices, codexFastCommandOffered, codexFooterShowsFast, codexServiceTierNotices, restoreCodexDefaults, restoreCodexDefaultInFile, } from "./harnesses/codex.js";
|
|
30
30
|
import { claudeAuthPlan, claudeAuthStatus, claudeAuthPane, OPERATION_ID_RE, CLAUDE_RESULT_MAX_BYTES, CLAUDE_EFFORTS, agentManagedClaudeSettingsPath, agentClaudeResultPath, agentClaudeOperationPath, agentClaudeApprovalReceiptPath, agentClaudeDispatchReceiptPath, validateOperationId, readClaudeResultText, assertClaudeAuthenticationReady, claudeModelChoices, claudeConfigDir, claudeFastIndicatorShown, claudeFastResult, buildClaudeAgentCmd, claudeLaunchNote, claudeTuiReady, claudePaneObservation, claudeUsageLimit, claudeLiveSelectMenu, claudeModelPicker, claudeModelPickerRow, claudeModelSwitchConfirm, claudeModelSwitchResult, claudeStartupAction, claudeLoginMethodMenu, CLAUDE_COMPOSER_MARKER_RE, createClaudeAgentMetadata, claudeSessionTranscriptPath, claudeApiErrorFromLine, claudeApiErrorAfter, } from "./harnesses/claude.js";
|
|
31
31
|
import { cursorAuthPlan, cursorAuthStatus, cursorAuthPane, bindCursorTranscriptSession, cursorTurnBoundary, latestCursorCompletion, observeCursorDone, cursorTranscriptText, assertCursorAuthenticationReady, assertCursorModelAvailable, cursorModelChoices, cursorModelIsFastVariant, cursorFastParameter, cursorHeaderShowsFast, cursorPickerView, cursorEffortSelected, cursorParameterRows, cursorUserConfigPath, CURSOR_DEFAULT_MODEL_KEYS, realCursorHome, buildCursorAgentCmd, cursorAgentArgv, cursorPwshLaunchLine, cursorPromptWithLineage, createCursorAgentMetadata, cursorLaunchNote, cursorEffortNavigation, cursorTuiReady, cursorPaneObservation, cursorPromptHooksRunning, cursorUsageLimit, CURSOR_SUBMIT_SEQUENCE, CURSOR_COMPOSER_CONTENT_MARKER_RE, validateCursorModelEffort, } from "./harnesses/cursor.js";
|
|
32
|
-
import { readInterimWords, recordInterimBoundary } from "./interim-words.js";
|
|
32
|
+
import { interimFromNow, readInterimWords, recordInterimBoundary } from "./interim-words.js";
|
|
33
|
+
import { codexSeatTurn, observeSteerDone, readSteerEntry, recordSteer } from "./steer-follow.js";
|
|
33
34
|
import { resolveAgentBin, resolveThroughlineBin, runThroughlineHandoffContext, isWindowsNativeExecutable, agentBinForPaneShell, resolveWinPaneShell } from "./agent-resolver.js";
|
|
34
35
|
export { AitermError } from "./errors.js";
|
|
35
36
|
export { tmuxSpawnEnv } from "./tmux-runtime.js";
|
|
@@ -330,6 +331,7 @@ function cleanupAgentState(name) {
|
|
|
330
331
|
f.endsWith(".claude-approval.json") ||
|
|
331
332
|
f.endsWith(".claude-dispatch") ||
|
|
332
333
|
f.endsWith(".interim.json") ||
|
|
334
|
+
f.endsWith(".steers.jsonl") ||
|
|
333
335
|
f.endsWith(".auth.json"))
|
|
334
336
|
fs.unlinkSync(p);
|
|
335
337
|
else if (f.endsWith(".codex-home") || f.endsWith(".grok-home") || f.endsWith(".cursor-plugin") || f.endsWith(".home")) {
|
|
@@ -2235,13 +2237,16 @@ function reapDeadAgentSendLock(name, replacedToken = null) {
|
|
|
2235
2237
|
}
|
|
2236
2238
|
return "done";
|
|
2237
2239
|
}
|
|
2240
|
+
// 順番が来なかった時の断りの末尾。同じ行列に並ぶ設定替え(ADR 0120)は、何も変えていない事を書く。
|
|
2241
|
+
const AGENT_SEND_UNSENT = "文字列は送信していません。少し後で再度 pty_send してください。";
|
|
2242
|
+
const AGENT_CONFIGURE_UNCHANGED = "設定は変えていません。少し後で再度 agent_configure してください。";
|
|
2238
2243
|
// BellTeamのため: 先頭の「AGENT_SEND_BUSY: agent session '<名前>' は」と、文中の「文字列は送信していません。」で
|
|
2239
2244
|
// 未送信の断りを見分けている(2026-10-05)。この2か所を変える時は、変える前に知らせる。
|
|
2240
|
-
function agentSendBusyError(name, pid, waitMs) {
|
|
2245
|
+
function agentSendBusyError(name, pid, waitMs, untouched = AGENT_SEND_UNSENT) {
|
|
2241
2246
|
return new AitermError(`AGENT_SEND_BUSY: agent session '${name}' は${pid != null ? `別プロセス(pid ${pid})` : "先に受け付けた送信"}の処理中です。` +
|
|
2242
|
-
`${waitMs}ms
|
|
2247
|
+
`${waitMs}ms待っても順番が来ませんでした。${untouched}`, 2);
|
|
2243
2248
|
}
|
|
2244
|
-
async function acquireAgentSendFileLock(name, deadline, waitMs) {
|
|
2249
|
+
async function acquireAgentSendFileLock(name, deadline, waitMs, untouched = AGENT_SEND_UNSENT) {
|
|
2245
2250
|
const p = agentSendLockPath(name);
|
|
2246
2251
|
let checkedOwner = null;
|
|
2247
2252
|
let checkedOwnerReplaced = false;
|
|
@@ -2279,11 +2284,11 @@ async function acquireAgentSendFileLock(name, deadline, waitMs) {
|
|
|
2279
2284
|
`pty_listで対象を確認し、pty_closeでsessionを閉じてから同じIDで再作成してください`, 2);
|
|
2280
2285
|
}
|
|
2281
2286
|
if (performance.now() >= deadline)
|
|
2282
|
-
throw agentSendBusyError(name, dead ? null : probe.pid, waitMs);
|
|
2287
|
+
throw agentSendBusyError(name, dead ? null : probe.pid, waitMs, untouched);
|
|
2283
2288
|
await sleep(AGENT_SEND_LOCK_POLL_MS);
|
|
2284
2289
|
}
|
|
2285
2290
|
}
|
|
2286
|
-
async function withAgentSendLock(name, run) {
|
|
2291
|
+
async function withAgentSendLock(name, run, untouched = AGENT_SEND_UNSENT) {
|
|
2287
2292
|
const waitMs = agentSendLockWaitMs;
|
|
2288
2293
|
const deadline = performance.now() + waitMs;
|
|
2289
2294
|
const previous = agentSendQueues.get(name) ?? Promise.resolve();
|
|
@@ -2308,8 +2313,8 @@ async function withAgentSendLock(name, run) {
|
|
|
2308
2313
|
]);
|
|
2309
2314
|
clearTimeout(timer);
|
|
2310
2315
|
if (!myTurn)
|
|
2311
|
-
throw agentSendBusyError(name, null, waitMs);
|
|
2312
|
-
const release = await acquireAgentSendFileLock(name, deadline, waitMs);
|
|
2316
|
+
throw agentSendBusyError(name, null, waitMs, untouched);
|
|
2317
|
+
const release = await acquireAgentSendFileLock(name, deadline, waitMs, untouched);
|
|
2313
2318
|
agentSendHeld.add(name);
|
|
2314
2319
|
try {
|
|
2315
2320
|
return await run();
|
|
@@ -2881,13 +2886,37 @@ async function settlePublishedClaudeCompletionMarker(meta, marker) {
|
|
|
2881
2886
|
}
|
|
2882
2887
|
return active;
|
|
2883
2888
|
}
|
|
2884
|
-
/**
|
|
2885
|
-
|
|
2889
|
+
/**
|
|
2890
|
+
* BellTeamのため: agent sessionの現在(または直前)のturnの合間の言葉。agent sessionでなければnull。
|
|
2891
|
+
* fromを渡した時は、その位置(差し込みの返りか、席の今の番が渡した位置)より後の言葉(ADR 0122)。
|
|
2892
|
+
*/
|
|
2893
|
+
export function readSessionInterimWords(name, after, from = null) {
|
|
2886
2894
|
assertSessionName(name);
|
|
2887
2895
|
const dir = existingAgentsDir();
|
|
2888
2896
|
if (!dir || !fs.readdirSync(dir).some((f) => f.startsWith(`${name}.`) && f.endsWith(".agent.json")))
|
|
2889
2897
|
return null;
|
|
2890
|
-
return readInterimWords(loadAgentMetadata(name), after);
|
|
2898
|
+
return readInterimWords(loadAgentMetadata(name), after, from);
|
|
2899
|
+
}
|
|
2900
|
+
/**
|
|
2901
|
+
* BellTeamのため(ADR 0122): agent sessionの、記録の上の今の番。画面は読まない。
|
|
2902
|
+
* BellTeamは、自分の送信で始めていない番(Aitermの子の結果が始めた番など)を、ここで見つけて追う。
|
|
2903
|
+
* 動いている番のwait_processは、その番の終わりで返る。
|
|
2904
|
+
*/
|
|
2905
|
+
export function readSeatTurns(sessionIds) {
|
|
2906
|
+
const seats = [];
|
|
2907
|
+
for (const name of sessionIds) {
|
|
2908
|
+
const meta = tryLoadAgentMetadata(name);
|
|
2909
|
+
if (!meta)
|
|
2910
|
+
continue;
|
|
2911
|
+
let turn = null;
|
|
2912
|
+
try {
|
|
2913
|
+
turn = codexSeatTurn(meta);
|
|
2914
|
+
}
|
|
2915
|
+
catch { /* 読めない席は、分からないまま返す */ }
|
|
2916
|
+
seats.push({ session_id: name, launch_id: meta.launch_id, harness: agentHarness(meta.kind),
|
|
2917
|
+
turn: turn?.state === "running" ? { ...turn, wait_process: agentWaitProcess(name, turn.event_cursor) } : turn });
|
|
2918
|
+
}
|
|
2919
|
+
return { schema: "aiterm.seat-turns.v1", seats };
|
|
2891
2920
|
}
|
|
2892
2921
|
/** agent harness の構造化 transcript から直近完了ターンの最終回答と相関情報を読む。 */
|
|
2893
2922
|
export async function readAgentTranscriptResult(name, o = {}) {
|
|
@@ -3053,7 +3082,7 @@ export function windowsStartProcessArgumentList(args) {
|
|
|
3053
3082
|
}
|
|
3054
3083
|
// npmのplatform別bin shimをcallerに解釈させず、現在稼働中のNodeと同梱CLIを直接起動する。
|
|
3055
3084
|
// Windowsでもbackendはpsmux、対話shellはPowerShell 7のまま。これはwaiter processの入口だけを所有する。
|
|
3056
|
-
export function agentWaitProcess(session, cursor, runtime = {}) {
|
|
3085
|
+
export function agentWaitProcess(session, cursor, runtime = {}, steer = null) {
|
|
3057
3086
|
const executable = runtime.executable ?? liveNodeExecutable();
|
|
3058
3087
|
const args = [
|
|
3059
3088
|
runtime.cliPath ?? fileURLToPath(new URL("./aiterm-wait-cli.js", import.meta.url)),
|
|
@@ -3061,6 +3090,8 @@ export function agentWaitProcess(session, cursor, runtime = {}) {
|
|
|
3061
3090
|
session,
|
|
3062
3091
|
"--cursor",
|
|
3063
3092
|
String(cursor),
|
|
3093
|
+
// 差し込みを取った番の終わりを待つ(ADR 0122)。
|
|
3094
|
+
...(steer ? ["--steer", steer] : []),
|
|
3064
3095
|
];
|
|
3065
3096
|
return {
|
|
3066
3097
|
executable,
|
|
@@ -3139,6 +3170,7 @@ export function detectAgentRateLimit(kind, aitermSession) {
|
|
|
3139
3170
|
// 外部waiterプロセス用の純リーダー観測。lock・PTY・metadata書込・dispatch状態には一切触れない。
|
|
3140
3171
|
// Codex/Cursor/Grokは各harnessのtranscript、Claudeはevent fileを増分走査し、vendor_session_idのbind永続化を
|
|
3141
3172
|
// 行わない(waiterは観測者であって所有者でない)。
|
|
3173
|
+
// steerを渡した時は、その差し込み(pty_sendの返りのsteer_id)を取った番の終わりを待つ(ADR 0122。steer-follow.ts)。
|
|
3142
3174
|
export async function observeAgentDone(name, o = {}) {
|
|
3143
3175
|
const meta = loadAgentMetadata(name);
|
|
3144
3176
|
const operationId = o.operation_id == null ? null : validateOperationId(o.operation_id);
|
|
@@ -3149,6 +3181,23 @@ export async function observeAgentDone(name, o = {}) {
|
|
|
3149
3181
|
throw new AitermError("cursor は0以上の整数完了境界で指定してください", 2);
|
|
3150
3182
|
}
|
|
3151
3183
|
const timeout = o.timeout ?? DEFAULT_AGENT_DONE_TIMEOUT;
|
|
3184
|
+
if (o.steer != null) {
|
|
3185
|
+
if (operationId)
|
|
3186
|
+
throw new AitermError("steer は operation_id と併用できません", 2);
|
|
3187
|
+
const plain = (cursor, timeoutSeconds) => observeAgentDone(name, { timeout: timeoutSeconds, cursor, signal: o.signal });
|
|
3188
|
+
const entry = readSteerEntry(meta, o.steer);
|
|
3189
|
+
if (!entry) {
|
|
3190
|
+
// 控えが無い(古くて消えた)。位置が渡されていれば、今までの待ち(その位置より後の最初の完了)で返す。
|
|
3191
|
+
if (o.cursor == null)
|
|
3192
|
+
throw new AitermError("STEER_NOT_FOUND: この席に、その差し込みの控えがありません。", 2);
|
|
3193
|
+
return { ...await plain(o.cursor, timeout), steer: { placement: "unknown", taken_at: null } };
|
|
3194
|
+
}
|
|
3195
|
+
return observeSteerDone(meta, entry, timeout, {
|
|
3196
|
+
signal: o.signal, plain,
|
|
3197
|
+
parseDone: line => parseAgentDoneEvent(line, meta).event,
|
|
3198
|
+
rateLimit: () => detectAgentRateLimit(meta.kind, name),
|
|
3199
|
+
});
|
|
3200
|
+
}
|
|
3152
3201
|
if (meta.kind === "codex" && meta.completion_route === "codex_transcript") {
|
|
3153
3202
|
return observeCodexDone(meta, timeout, o.cursor, o.signal);
|
|
3154
3203
|
}
|
|
@@ -4851,11 +4900,28 @@ export async function configureAgent(name, opts) {
|
|
|
4851
4900
|
if ((model && /[\r\n]/.test(model)) || (effort && /[\r\n]/.test(effort))) {
|
|
4852
4901
|
throw new AitermError("model/reasoning_effort に改行を含められません", 2);
|
|
4853
4902
|
}
|
|
4854
|
-
|
|
4855
|
-
if (fast === false && modelIsFastVariant(meta.kind, model)) {
|
|
4903
|
+
if (fast === false && modelIsFastVariant(loadAgentMetadata(name).kind, model)) {
|
|
4856
4904
|
throw new FastModeError("FAST_MODE_MODEL_CONFLICT", `model=${model} はfast版のIDですが、fast: false が渡されました。元のmodel IDを渡すか、fastを省いてください。設定は変えていません。`);
|
|
4857
4905
|
}
|
|
4906
|
+
// 設定替えは席の入力欄へ命令を打つので、送信と同じ行列(ADR 0088)に並ぶ。替えている間に届いた文は、替え終えてから席へ入る(ADR 0120)。
|
|
4907
|
+
// 番が動いている席では、行列を持ったまま番の終わりを待たない(待つ間も、その席への差し込みを通す)。
|
|
4908
|
+
const deadline = performance.now() + AGENT_TUI_READY_TIMEOUT_MS;
|
|
4909
|
+
for (;;) {
|
|
4910
|
+
const result = await withAgentSendLock(name, () => configureAgentInLock(name, model, effort, fast), AGENT_CONFIGURE_UNCHANGED);
|
|
4911
|
+
if (result)
|
|
4912
|
+
return result;
|
|
4913
|
+
if (performance.now() >= deadline)
|
|
4914
|
+
throw new AitermError(`agent session '${name}' は入力待ちではありません`, 2);
|
|
4915
|
+
await sleep(AGENT_TUI_READY_POLL_MS);
|
|
4916
|
+
}
|
|
4917
|
+
}
|
|
4918
|
+
/** 返りの null は、席の画面が番の最中を示していて、何も打っていない時。 */
|
|
4919
|
+
async function configureAgentInLock(name, model, effort, fast) {
|
|
4920
|
+
// 席の控えは、行列の順番が来てから読む(前の送信が書いた分を、古い控えで上書きしない)。
|
|
4921
|
+
const meta = loadAgentMetadata(name);
|
|
4858
4922
|
bindCompletedInitialPrompt(meta);
|
|
4923
|
+
if (isAgentTuiBusy(meta.kind, captureScreen(name, AGENT_TUI_READY_LINES)))
|
|
4924
|
+
return null;
|
|
4859
4925
|
const { ready, codexRateLimitModelSwitch } = await waitAgentTuiReadyAfterCodexRateLimitRecovery(name, meta, AGENT_TUI_READY_TIMEOUT_MS);
|
|
4860
4926
|
if (!ready.ready) {
|
|
4861
4927
|
if (codexRateLimitModelSwitch) {
|
|
@@ -5433,7 +5499,7 @@ async function sendAgentMessageInLock(name, text, o) {
|
|
|
5433
5499
|
"前の文の完了待ちが終わってから送り直してください。Grokは120秒受け取れないと、前の文を自分から取り消します。", 2);
|
|
5434
5500
|
}
|
|
5435
5501
|
if (running) {
|
|
5436
|
-
return steerRunningTurn(name, meta, text, { raw: o.raw, pane_input_recovery: paneInputRecovery, preface: o.preface });
|
|
5502
|
+
return steerRunningTurn(name, meta, text, { raw: o.raw, pane_input_recovery: paneInputRecovery, preface: o.preface, follow: o.follow });
|
|
5437
5503
|
}
|
|
5438
5504
|
return dispatchAgentTurnInLock(name, text, { raw: o.raw, before_send: o.before_send, pane_input_recovery: paneInputRecovery, preface: o.preface });
|
|
5439
5505
|
}
|
|
@@ -5445,7 +5511,26 @@ async function sendAgentMessageInLock(name, text, o) {
|
|
|
5445
5511
|
* - Grok: 待ち行列へ入れた後に「send now」を押す。旧turnは`cancelled`(trigger=send_now)で閉じ、
|
|
5446
5512
|
* 新turnが作業を継ぐ。完了判定はこの継ぎ目を完了と数えない(grokCompletionEvent)。
|
|
5447
5513
|
*/
|
|
5514
|
+
/**
|
|
5515
|
+
* 差し込みを打つ直前の位置と、文の頭を控える(ADR 0122)。控えられなくても差し込みは止めない。
|
|
5516
|
+
* 完了の位置は、そこから今までの待ちを始めると「打った後の最初の完了」で返る位置。Cursorの位置はuser発話の数で、
|
|
5517
|
+
* 差し込みが新しいuser発話として書かれない時も今の番の終わりで返るよう、今の番の手前を指す。
|
|
5518
|
+
*/
|
|
5519
|
+
function recordSteerPosition(meta, body) {
|
|
5520
|
+
try {
|
|
5521
|
+
const completion = agentCompletionCursor(meta);
|
|
5522
|
+
const cursor = meta.kind === "cursor" ? Math.max(0, completion - 1) : completion;
|
|
5523
|
+
const entry = recordSteer(meta, interimFromNow(meta, cursor), body);
|
|
5524
|
+
return entry ? { steer_id: entry.steer_id, event_cursor: entry.event_cursor, interim_from: entry.interim_from } : null;
|
|
5525
|
+
}
|
|
5526
|
+
catch {
|
|
5527
|
+
return null;
|
|
5528
|
+
}
|
|
5529
|
+
}
|
|
5448
5530
|
async function steerRunningTurn(name, meta, text, o) {
|
|
5531
|
+
const prefaced = splitAgentPreface(meta.kind, o.preface, text, { defaultForWrapped: !o.raw });
|
|
5532
|
+
text = prefaced.body;
|
|
5533
|
+
prepareSendText(text, { raw: o.raw });
|
|
5449
5534
|
const receipt = {
|
|
5450
5535
|
schema: "aiterm.agent-steer.v1",
|
|
5451
5536
|
session_id: meta.aiterm_session,
|
|
@@ -5453,10 +5538,8 @@ async function steerRunningTurn(name, meta, text, o) {
|
|
|
5453
5538
|
vendor: meta.kind,
|
|
5454
5539
|
harness: agentHarness(meta.kind),
|
|
5455
5540
|
pane_input_recovery: o.pane_input_recovery,
|
|
5541
|
+
...(o.follow ? { follow: recordSteerPosition(meta, text) } : {}),
|
|
5456
5542
|
};
|
|
5457
|
-
const prefaced = splitAgentPreface(meta.kind, o.preface, text, { defaultForWrapped: !o.raw });
|
|
5458
|
-
text = prefaced.body;
|
|
5459
|
-
prepareSendText(text, { raw: o.raw });
|
|
5460
5543
|
// Claudeのoperation相関は変えない。差し込みは実行中turnの一部であり、新しいturnを予約しない。
|
|
5461
5544
|
const preserveAgentOperation = meta.kind === "claude";
|
|
5462
5545
|
send(name, text, {
|