aiterm-mcp 0.37.0 → 0.37.2
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 +18 -1
- package/README.ja.md +6 -4
- package/README.md +6 -4
- package/dist/codex-relay-arguments.js +36 -0
- package/dist/codex-relay-launcher.js +24 -61
- package/dist/codex-relay-setup.js +44 -0
- package/dist/codex-relay-stdio.js +81 -0
- package/dist/codex-stdio-relay.js +25 -92
- package/dist/core.js +15 -2
- package/dist/harnesses/codex.js +2 -2
- package/dist/setup-codex-relay.js +20 -53
- package/dist/windows-codex-connection.js +9 -17
- package/dist/windows-codex-launcher.js +61 -0
- package/dist/windows-codex-native.js +118 -0
- package/dist/windows-codex-relay.js +67 -127
- package/dist/windows-codex-setup.js +26 -107
- package/docs/DESIGN.md +13 -8
- package/docs/RELEASE.md +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.37.2] - 2026-09-13
|
|
11
|
+
|
|
12
|
+
### 修正
|
|
13
|
+
|
|
14
|
+
- Codex Steerの引数、通信中継、設定の有効化・復元をmacOSとWindowsで共通化した。OS差は起動API・接続・権限・設定保存に限定する。
|
|
15
|
+
- Windowsで公式Codexの直接の親がDesktopではなくなり、他アプリの公式CLI検出を妨げていた構成を修正した。公式CodexをDesktopの直接の子、中継を公式Codexの子として起動する。
|
|
16
|
+
- Windowsだけの既存launcher共有を廃止し、Macと同じくAitermのlauncherへ設定して解除時に元へ戻す。旧共有設定も次のenableで移行する。
|
|
17
|
+
|
|
18
|
+
## [0.37.1] - 2026-09-13
|
|
19
|
+
|
|
20
|
+
### 修正
|
|
21
|
+
|
|
22
|
+
- WindowsのCodexでhook確認画面の見出しが画面上部へ出た場合も認識し、`trust_project: true`による起動準備を完了する。
|
|
23
|
+
- npmのshell shimとnative Codexの間にNode processがある場合、同じ起動を複数候補と誤判定して停止する問題を修正した。
|
|
24
|
+
|
|
10
25
|
## [0.37.0] - 2026-09-13
|
|
11
26
|
|
|
12
27
|
### 追加
|
|
@@ -1664,7 +1679,9 @@ prototype (preserved under `prototype/python/` as the porting source and referen
|
|
|
1664
1679
|
`ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
|
|
1665
1680
|
provenance.
|
|
1666
1681
|
|
|
1667
|
-
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.37.
|
|
1682
|
+
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.37.2...HEAD
|
|
1683
|
+
[0.37.2]: https://github.com/kitepon/aiterm-mcp/compare/v0.37.1...v0.37.2
|
|
1684
|
+
[0.37.1]: https://github.com/kitepon/aiterm-mcp/compare/v0.37.0...v0.37.1
|
|
1668
1685
|
[0.37.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.36.0...v0.37.0
|
|
1669
1686
|
[0.36.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.35.1...v0.36.0
|
|
1670
1687
|
[0.35.1]: https://github.com/kitepon/aiterm-mcp/compare/v0.35.0...v0.35.1
|
package/README.ja.md
CHANGED
|
@@ -57,7 +57,7 @@ aiterm-setup --json --codex-steer enable
|
|
|
57
57
|
|
|
58
58
|
公式Codex Desktopに同梱されたApp Serverを使い、実行中の親には同じターンへ回答を送り、
|
|
59
59
|
終了後に届いた回答でも同じタスクを自動再開します。App Serverの改造・再ビルド・別配布は不要です。
|
|
60
|
-
|
|
60
|
+
新しいlauncherを設定した場合は`restart_required`(終了コード3)を返します。Codexを完全終了して再起動し、
|
|
61
61
|
`aiterm-setup --codex-steer status`で`ready`を確認してください。接続できない時にキューへ自動退避しません。
|
|
62
62
|
|
|
63
63
|
選択と復元情報は`~/.config/aiterm-mcp/codex-relay/`へ保存します。macOSはユーザーLaunchAgent、
|
|
@@ -66,8 +66,9 @@ Windowsはユーザー環境変数`CODEX_CLI_PATH`で起動設定を維持しま
|
|
|
66
66
|
SteerはmacOS・Windowsの公式Codex Desktopと同梱CLI 0.154以上に対応します。Steer有効時の宛先は中継で起動したDesktopに限ります。
|
|
67
67
|
Windowsでは公式Desktopが展開した実行ファイルを照合し、認証付きのローカル接続へ中継します。
|
|
68
68
|
Aitermのpackageがlauncher・中継・設定・親への配送を含み、gpt-connectorやdotagentsの導入は不要です。
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
両OSで公式CodexをDesktopの直接の子として保ち、中継は公式Codexの子として動きます。
|
|
70
|
+
有効化時は元の起動設定を保存してAitermのlauncherを設定し、解除時に元へ戻します。選択後に他から変更された値は上書きしません。
|
|
71
|
+
WindowsのDesktop更新後はsetupを再実行してください。
|
|
71
72
|
LinuxのSteer付き導入は理由付き`unsupported`を返します。Aiterm単品は従来どおり利用できます。
|
|
72
73
|
|
|
73
74
|
cloneもビルドも不要。どのクライアントでも公開パッケージを次のコマンドで起動する:
|
|
@@ -195,7 +196,7 @@ runtime-error store は canonical dotagents config の `collection.enabled: true
|
|
|
195
196
|
場合だけ収集し、既定OFF、network送信は行いません。tag起点CIのnpm provenance(OIDC Trusted
|
|
196
197
|
Publishing)で公開し、GitHub Release が Official MCP Registry を再登録します。
|
|
197
198
|
|
|
198
|
-
**状態:** 開発継続中 · 現行公開版 **v0.37.
|
|
199
|
+
**状態:** 開発継続中 · 現行公開版 **v0.37.2** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
|
|
199
200
|
|
|
200
201
|
### 更新と巻き戻し
|
|
201
202
|
|
|
@@ -475,6 +476,7 @@ aiterm は同じ核心の洞察——端末を出会いの場にする——を
|
|
|
475
476
|
|
|
476
477
|
`agent_launch({ harness, cwd, trust_project: true })`はpromptなしでも既知のworkspace・project hooks・MCP初期同意を
|
|
477
478
|
進め、入力受付とharness生存を確認して`startup.status="ready"`を返す。指定なしのpromptなし起動は`not_checked`。
|
|
479
|
+
WindowsのCodexもhook確認を認識し、npm shim経由の起動を一つのharnessとして識別する。
|
|
478
480
|
初手の`initial_prompt.status`は`not_requested`/`not_sent`/`submitted_unconfirmed`/`started`を区別する。
|
|
479
481
|
未送信・未確認の失敗もsession付きstructuredContentを保持する。未確認のpromptを再送せず、返ったcursorで観測する。
|
|
480
482
|
|
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ aiterm-setup --json --codex-steer enable
|
|
|
57
57
|
|
|
58
58
|
公式Codex Desktopに同梱されたApp Serverを使い、実行中の親には同じターンへ回答を送り、
|
|
59
59
|
終了後に届いた回答でも同じタスクを自動再開します。App Serverの改造・再ビルド・別配布は不要です。
|
|
60
|
-
|
|
60
|
+
新しいlauncherを設定した場合は`restart_required`(終了コード3)を返します。Codexを完全終了して再起動し、
|
|
61
61
|
`aiterm-setup --codex-steer status`で`ready`を確認してください。接続できない時にキューへ自動退避しません。
|
|
62
62
|
|
|
63
63
|
選択と復元情報は`~/.config/aiterm-mcp/codex-relay/`へ保存します。macOSはユーザーLaunchAgent、
|
|
@@ -66,8 +66,9 @@ Windowsはユーザー環境変数`CODEX_CLI_PATH`で起動設定を維持しま
|
|
|
66
66
|
SteerはmacOS・Windowsの公式Codex Desktopと同梱CLI 0.154以上に対応します。Steer有効時の宛先は中継で起動したDesktopに限ります。
|
|
67
67
|
Windowsでは公式Desktopが展開した実行ファイルを照合し、認証付きのローカル接続へ中継します。
|
|
68
68
|
Aitermのpackageがlauncher・中継・設定・親への配送を含み、gpt-connectorやdotagentsの導入は不要です。
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
両OSで公式CodexをDesktopの直接の子として保ち、中継は公式Codexの子として動きます。
|
|
70
|
+
有効化時は元の起動設定を保存してAitermのlauncherを設定し、解除時に元へ戻します。選択後に他から変更された値は上書きしません。
|
|
71
|
+
WindowsのDesktop更新後はsetupを再実行してください。
|
|
71
72
|
LinuxのSteer付き導入は理由付き`unsupported`を返します。Aiterm単品は従来どおり利用できます。
|
|
72
73
|
|
|
73
74
|
No clone or build is required. Each client launches the published package with:
|
|
@@ -209,7 +210,7 @@ collection is off by default and performs no network I/O. It ships via
|
|
|
209
210
|
tag-triggered CI with npm provenance (OIDC Trusted Publishing); the GitHub
|
|
210
211
|
Release re-registers the Official MCP Registry entry.
|
|
211
212
|
|
|
212
|
-
**Status:** actively maintained · current public release **v0.37.
|
|
213
|
+
**Status:** actively maintained · current public release **v0.37.2** · 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).
|
|
213
214
|
|
|
214
215
|
### Update and rollback
|
|
215
216
|
|
|
@@ -508,6 +509,7 @@ displayed token count or null. Callers do not need raw argv or pane-text parsing
|
|
|
508
509
|
consent even without a prompt, then verifies input readiness and harness liveness before returning `startup.status="ready"`.
|
|
509
510
|
A prompt-free launch without this option retains `startup.status="not_checked"`. `initial_prompt.status` distinguishes
|
|
510
511
|
`not_requested`, `not_sent`, `submitted_unconfirmed`, and `started`. Failure responses retain structured session information.
|
|
512
|
+
WindowsのCodexもhook確認を認識し、npm shim経由の起動を一つのharnessとして識別する。
|
|
511
513
|
Do not resend an unconfirmed prompt; observe or wait using its returned cursor.
|
|
512
514
|
|
|
513
515
|
For a live Codex approval, inspect with `agent_approval({ action: "inspect", session_id })`, review `prompt` and `choices`,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// transportを切り替える条件と引数の扱いは全OSで共通にする。
|
|
2
|
+
export function codexServerArguments(args) {
|
|
3
|
+
let server = false;
|
|
4
|
+
const result = [];
|
|
5
|
+
for (let i = 0; i < args.length; i++) {
|
|
6
|
+
const arg = args[i];
|
|
7
|
+
if (["-c", "--config", "--enable", "--disable"].includes(arg)) {
|
|
8
|
+
result.push(arg, args[++i]);
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
if (/^(?:--config=|--enable=|--disable=|-c.)/.test(arg)) {
|
|
12
|
+
result.push(arg);
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
if (!server) {
|
|
16
|
+
if (arg !== "app-server")
|
|
17
|
+
return null;
|
|
18
|
+
server = true;
|
|
19
|
+
result.push(arg);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (["proxy", "daemon", "start", "stop", "status", "generate-ts", "generate-json-schema", "--help", "-h", "--version", "-V"].includes(arg))
|
|
23
|
+
return null;
|
|
24
|
+
if (arg === "--stdio" || arg === "--listen=stdio://")
|
|
25
|
+
continue;
|
|
26
|
+
if (arg === "--listen") {
|
|
27
|
+
if (args[++i] !== "stdio://")
|
|
28
|
+
throw new Error("stdio以外の接続指定は変更できません");
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (arg.startsWith("--listen=") || arg.startsWith("--ws-"))
|
|
32
|
+
throw new Error("既存の接続・認証指定は変更できません");
|
|
33
|
+
result.push(arg);
|
|
34
|
+
}
|
|
35
|
+
return server ? result : null;
|
|
36
|
+
}
|
|
@@ -1,63 +1,26 @@
|
|
|
1
|
-
// POSIXのexecで公式CLI
|
|
2
|
-
|
|
1
|
+
// POSIXのexecで公式CLIとDesktopの直接の親子関係を維持する。
|
|
2
|
+
import { codexServerArguments } from "./codex-relay-arguments.js";
|
|
3
|
+
import { prepareRelayDirectory } from "./codex-relay-config.js";
|
|
4
|
+
const quote = (text) => "'" + text.replace(/'/g, "'\"'\"'") + "'";
|
|
3
5
|
export function codexRelayLauncher(options) {
|
|
4
|
-
return
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
proxy|start|stop|status|generate-ts|generate-json-schema|--help|-h|--version|-V) mode=other; break ;;
|
|
25
|
-
esac
|
|
26
|
-
fi
|
|
27
|
-
done
|
|
28
|
-
if [ "$mode" != server ]; then exec "$binary" "$@"; fi
|
|
29
|
-
|
|
30
|
-
# 引数を一巡させ、stdio指定だけを公式Unix受付へ変更する。
|
|
31
|
-
remaining=$#
|
|
32
|
-
value=no
|
|
33
|
-
while [ "$remaining" -gt 0 ]; do
|
|
34
|
-
arg=$1
|
|
35
|
-
shift
|
|
36
|
-
remaining=$((remaining - 1))
|
|
37
|
-
if [ "$value" = yes ]; then
|
|
38
|
-
set -- "$@" "$arg"
|
|
39
|
-
value=no
|
|
40
|
-
continue
|
|
41
|
-
fi
|
|
42
|
-
case "$arg" in
|
|
43
|
-
-c|--config|--enable|--disable) value=yes; set -- "$@" "$arg" ;;
|
|
44
|
-
--stdio) ;;
|
|
45
|
-
--listen)
|
|
46
|
-
if [ "$remaining" -eq 0 ] || [ "$1" != stdio:// ]; then
|
|
47
|
-
echo 'aiterm-relay: stdio以外の接続指定は変更できません' >&2
|
|
48
|
-
exit 2
|
|
49
|
-
fi
|
|
50
|
-
shift
|
|
51
|
-
remaining=$((remaining - 1)) ;;
|
|
52
|
-
--listen=stdio://) ;;
|
|
53
|
-
--listen=*) echo 'aiterm-relay: stdio以外の接続指定は変更できません' >&2; exit 2 ;;
|
|
54
|
-
*) set -- "$@" "$arg" ;;
|
|
55
|
-
esac
|
|
56
|
-
done
|
|
57
|
-
"$node" "$relay" --prepare "$root" || exit $?
|
|
58
|
-
socket="$root/$$.sock"
|
|
59
|
-
exec 3<&0
|
|
60
|
-
"$node" "$relay" "$socket" "$$" <&3 3<&- &
|
|
61
|
-
exec "$binary" "$@" --listen "unix://$socket" </dev/null >/dev/null 3<&-
|
|
62
|
-
`;
|
|
6
|
+
return [
|
|
7
|
+
"#!/bin/sh",
|
|
8
|
+
"command=$(" + [options.node, options.relay, "--prepare", options.socket_root, options.binary, options.node, options.relay].map(quote).join(" ") + ' "$$" "$@") || exit $?',
|
|
9
|
+
'exec /bin/sh -c "$command"',
|
|
10
|
+
"",
|
|
11
|
+
].join("\n");
|
|
12
|
+
}
|
|
13
|
+
export function preparePosixRelayLaunch(root, binary, node, relay, pid, args) {
|
|
14
|
+
const serverArgs = codexServerArguments(args);
|
|
15
|
+
if (!serverArgs)
|
|
16
|
+
return ["exec", binary, ...args].map((arg, i) => i === 0 ? arg : quote(arg)).join(" ");
|
|
17
|
+
if (!/^[1-9][0-9]*$/.test(pid) || Buffer.byteLength(root + "/9999999999.sock") >= 104)
|
|
18
|
+
throw new Error("socketのpathまたは親processの指定が不正です");
|
|
19
|
+
prepareRelayDirectory(root);
|
|
20
|
+
const socket = root + "/" + pid + ".sock";
|
|
21
|
+
return [
|
|
22
|
+
"exec 3<&0",
|
|
23
|
+
[node, relay, socket, pid].map(quote).join(" ") + " <&3 3<&- &",
|
|
24
|
+
"exec " + [binary, ...serverArgs, "--listen", "unix://" + socket].map(quote).join(" ") + " </dev/null >/dev/null 3<&-",
|
|
25
|
+
].join("\n");
|
|
63
26
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readRelayConfig } from "./codex-relay-config.js";
|
|
2
|
+
import { SetupError } from "./setup-platform.js";
|
|
3
|
+
// 設定・競合・再実行・復元・実効確認の順序は全OSで同じにする。
|
|
4
|
+
export async function configureRelay(action, runtime) {
|
|
5
|
+
const previous = readRelayConfig(runtime.directory);
|
|
6
|
+
if (action === "status") {
|
|
7
|
+
if (!previous?.enabled)
|
|
8
|
+
return { status: "disabled" };
|
|
9
|
+
if (runtime.getGui("CODEX_CLI_PATH") !== previous.launcher)
|
|
10
|
+
return { status: "failed", reason_code: "codex_steer_configuration_changed" };
|
|
11
|
+
return await runtime.live(previous) ? { status: "ready" } : { status: "restart_required", reason_code: "codex_restart_required" };
|
|
12
|
+
}
|
|
13
|
+
if (action === "disable") {
|
|
14
|
+
if (!previous?.enabled)
|
|
15
|
+
return { status: "disabled" };
|
|
16
|
+
if (![previous.launcher, previous.previous_cli_path].includes(runtime.getGui("CODEX_CLI_PATH")))
|
|
17
|
+
throw new SetupError("codex_steer_configuration_changed", "起動設定が他から変更されています。所有外の値は上書きしません");
|
|
18
|
+
runtime.unpersist(previous.launcher);
|
|
19
|
+
runtime.setGui("CODEX_CLI_PATH", previous.previous_cli_path);
|
|
20
|
+
if (runtime.getGui("CODEX_CLI_PATH") !== previous.previous_cli_path)
|
|
21
|
+
throw new SetupError("codex_steer_readback_failed", "元の起動設定を確認できません");
|
|
22
|
+
runtime.save(runtime.directory, { ...previous, enabled: false });
|
|
23
|
+
return { status: "restart_required", reason_code: "codex_restart_required" };
|
|
24
|
+
}
|
|
25
|
+
for (const key of ["CODEX_APP_SERVER_WS_URL", "CODEX_APP_SERVER_USE_LOCAL_DAEMON", "CODEX_APP_SERVER_FORCE_CLI"]) {
|
|
26
|
+
if (runtime.getGui(key))
|
|
27
|
+
throw new SetupError("codex_steer_configuration_conflict", `${key}が設定されています。既存の接続設定は上書きしません`);
|
|
28
|
+
}
|
|
29
|
+
const binary = runtime.findBinary();
|
|
30
|
+
const current = runtime.getGui("CODEX_CLI_PATH");
|
|
31
|
+
if (previous?.enabled && ![previous.launcher, previous.previous_cli_path].includes(current))
|
|
32
|
+
throw new SetupError("codex_steer_configuration_changed", "起動設定が他から変更されています");
|
|
33
|
+
runtime.prepare(runtime.directory);
|
|
34
|
+
const launcher = await runtime.build(binary);
|
|
35
|
+
const config = { schema: "aiterm.codex-relay.v1", enabled: true, binary, node: runtime.node,
|
|
36
|
+
launcher, socket_root: runtime.socket_root, previous_cli_path: previous?.enabled ? previous.previous_cli_path : current };
|
|
37
|
+
// 起動設定の変更前に復元値を保存する。読戻し不一致を成功扱いしない。
|
|
38
|
+
runtime.save(runtime.directory, config);
|
|
39
|
+
runtime.persist(launcher);
|
|
40
|
+
runtime.setGui("CODEX_CLI_PATH", launcher);
|
|
41
|
+
if (runtime.getGui("CODEX_CLI_PATH") !== launcher)
|
|
42
|
+
throw new SetupError("codex_steer_readback_failed", "Steerの起動設定を確認できません");
|
|
43
|
+
return await runtime.live(config) ? { status: "ready" } : { status: "restart_required", reason_code: "codex_restart_required" };
|
|
44
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Desktopと公式App Serverを結ぶ共通のJSONL中継。OS adapterは接続先とprocess終了だけを供給する。
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
import { once } from "node:events";
|
|
4
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
5
|
+
export async function relayCodexStdio(adapter) {
|
|
6
|
+
let socket;
|
|
7
|
+
let ending = false;
|
|
8
|
+
let failure;
|
|
9
|
+
let stopping;
|
|
10
|
+
const stop = () => stopping ??= adapter.stop();
|
|
11
|
+
const input = createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
12
|
+
// 接続準備中に届いたinitializeとEOFも同じ順序で扱う。
|
|
13
|
+
const lines = input[Symbol.asyncIterator]();
|
|
14
|
+
const end = () => { ending = true; void stop().catch(error => { failure = error; }); socket?.terminate(); input.close(); process.stdin.destroy(); };
|
|
15
|
+
const outputFailed = (error) => { failure = error; end(); };
|
|
16
|
+
process.stdout.once("error", outputFailed);
|
|
17
|
+
const drain = () => socket?.resume();
|
|
18
|
+
process.stdout.on("drain", drain);
|
|
19
|
+
try {
|
|
20
|
+
const deadline = Date.now() + 15_000;
|
|
21
|
+
while (!ending) {
|
|
22
|
+
if (!adapter.alive())
|
|
23
|
+
throw new Error("公式App Serverが起動中に終了しました");
|
|
24
|
+
socket = adapter.socket();
|
|
25
|
+
const candidate = socket;
|
|
26
|
+
let opened = false;
|
|
27
|
+
candidate.once("open", () => { opened = true; });
|
|
28
|
+
// upgradeと最初のframeが同じ受信単位でも、openのawaitより先に受け付ける。
|
|
29
|
+
candidate.on("message", (data, binary) => {
|
|
30
|
+
if (binary) {
|
|
31
|
+
failure = new Error("公式App Serverから予期しないバイナリ応答を受信しました");
|
|
32
|
+
end();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (!process.stdout.write(data.toString() + "\n"))
|
|
36
|
+
candidate.pause();
|
|
37
|
+
});
|
|
38
|
+
candidate.on("error", error => { if (opened) {
|
|
39
|
+
failure = error;
|
|
40
|
+
end();
|
|
41
|
+
} });
|
|
42
|
+
candidate.on("close", () => {
|
|
43
|
+
if (opened && !ending) {
|
|
44
|
+
failure = new Error("公式App Serverとの接続が終了しました");
|
|
45
|
+
end();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
try {
|
|
49
|
+
await once(socket, "open");
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
socket.on("error", () => { });
|
|
54
|
+
socket.terminate();
|
|
55
|
+
if (ending)
|
|
56
|
+
break;
|
|
57
|
+
// 公式受付の作成だけを待つ。送信済みRPCは再送しない。
|
|
58
|
+
if (!["ENOENT", "ECONNREFUSED"].includes(error.code ?? "") || Date.now() >= deadline)
|
|
59
|
+
throw error;
|
|
60
|
+
await delay(25);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (ending) {
|
|
64
|
+
if (failure)
|
|
65
|
+
throw failure;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const connected = socket;
|
|
69
|
+
adapter.connected?.();
|
|
70
|
+
for await (const line of lines)
|
|
71
|
+
await new Promise((resolve, reject) => connected.send(line, error => error ? reject(error) : resolve()));
|
|
72
|
+
if (failure)
|
|
73
|
+
throw failure;
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
end();
|
|
77
|
+
await stop();
|
|
78
|
+
process.stdout.off("error", outputFailed);
|
|
79
|
+
process.stdout.off("drain", drain);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
2
|
+
// Unix socketとPOSIX signalのadapter。中継するJSONLと終了契約はWindowsと共通。
|
|
3
3
|
import { createConnection } from "node:net";
|
|
4
|
-
import { createInterface } from "node:readline";
|
|
5
4
|
import { setTimeout as delay } from "node:timers/promises";
|
|
6
|
-
import { once } from "node:events";
|
|
7
5
|
import { fileURLToPath } from "node:url";
|
|
8
6
|
import WebSocket from "ws";
|
|
9
|
-
import {
|
|
7
|
+
import { preparePosixRelayLaunch } from "./codex-relay-launcher.js";
|
|
8
|
+
import { relayCodexStdio } from "./codex-relay-stdio.js";
|
|
10
9
|
export async function runCodexStdioRelay(socketPath, serverPid) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
await relayCodexStdio({
|
|
11
|
+
alive: () => process.ppid === serverPid,
|
|
12
|
+
socket: () => new WebSocket("ws://localhost/rpc", {
|
|
13
|
+
createConnection: () => createConnection(socketPath), handshakeTimeout: 10_000, perMessageDeflate: false,
|
|
14
|
+
}),
|
|
15
|
+
stop: async () => {
|
|
16
16
|
// 公式Unix受付は1回目でturn完了待ち、2回目で終了する。
|
|
17
17
|
for (let i = 0; i < 2; i++) {
|
|
18
18
|
if (process.ppid !== serverPid)
|
|
@@ -28,93 +28,26 @@ export async function runCodexStdioRelay(socketPath, serverPid) {
|
|
|
28
28
|
if (i === 0)
|
|
29
29
|
await delay(100);
|
|
30
30
|
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
async function connectDuringStartup() {
|
|
34
|
-
const deadline = Date.now() + 15_000;
|
|
35
|
-
while (true) {
|
|
36
|
-
if (process.ppid !== serverPid)
|
|
37
|
-
throw new Error("公式App Serverが起動中に終了しました");
|
|
38
|
-
const candidate = new WebSocket("ws://localhost/rpc", {
|
|
39
|
-
createConnection: () => createConnection(socketPath), handshakeTimeout: 10_000, perMessageDeflate: false,
|
|
40
|
-
});
|
|
41
|
-
try {
|
|
42
|
-
await once(candidate, "open");
|
|
43
|
-
return candidate;
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
candidate.on("error", () => { });
|
|
47
|
-
candidate.terminate();
|
|
48
|
-
// exec直後のsocket作成だけを待つ。送信済みRPCは再送しない。
|
|
49
|
-
if (!["ENOENT", "ECONNREFUSED"].includes(error.code ?? "") || Date.now() >= deadline)
|
|
50
|
-
throw error;
|
|
51
|
-
await delay(25);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
try {
|
|
56
|
-
socket = await connectDuringStartup();
|
|
57
|
-
const connected = socket;
|
|
58
|
-
connected.on("message", (data, binary) => {
|
|
59
|
-
if (binary) {
|
|
60
|
-
process.stderr.write("aiterm-relay: 予期しないバイナリ応答\n");
|
|
61
|
-
process.exitCode = 1;
|
|
62
|
-
void stopServer();
|
|
63
|
-
connected.terminate();
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
if (!process.stdout.write(`${data.toString()}\n`))
|
|
67
|
-
connected.pause();
|
|
68
|
-
});
|
|
69
|
-
process.stdout.on("drain", () => connected.resume());
|
|
70
|
-
connected.on("error", () => {
|
|
71
|
-
process.stderr.write("aiterm-relay: 通信に失敗しました\n");
|
|
72
|
-
process.exitCode = 1;
|
|
73
|
-
void stopServer();
|
|
74
|
-
});
|
|
75
|
-
connected.on("close", () => {
|
|
76
|
-
if (!ending) {
|
|
77
|
-
process.stderr.write("aiterm-relay: 公式App Serverとの接続が終了しました\n");
|
|
78
|
-
process.exitCode = 1;
|
|
79
|
-
}
|
|
80
|
-
process.stdin.destroy();
|
|
81
|
-
});
|
|
82
|
-
process.stdout.on("error", () => { ending = true; void stopServer(); connected.terminate(); process.stdin.destroy(); });
|
|
83
|
-
const lines = createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
84
|
-
for await (const line of lines) {
|
|
85
|
-
await new Promise((resolve, reject) => connected.send(line, error => error ? reject(error) : resolve()));
|
|
86
|
-
}
|
|
87
|
-
ending = true;
|
|
88
|
-
await stopServer();
|
|
89
|
-
connected.terminate();
|
|
90
|
-
}
|
|
91
|
-
catch (error) {
|
|
92
|
-
process.stderr.write(`aiterm-relay: 中継失敗: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
93
|
-
process.exitCode = 1;
|
|
94
|
-
await stopServer();
|
|
95
|
-
socket?.terminate();
|
|
96
|
-
process.stdin.destroy();
|
|
97
|
-
}
|
|
31
|
+
},
|
|
32
|
+
});
|
|
98
33
|
}
|
|
99
34
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
100
|
-
const [socket, pid] = process.argv.slice(2);
|
|
101
|
-
|
|
102
|
-
|
|
35
|
+
const [socket, pid, ...args] = process.argv.slice(2);
|
|
36
|
+
try {
|
|
37
|
+
if (socket === "--prepare") {
|
|
103
38
|
if (process.platform === "win32")
|
|
104
|
-
throw new Error("
|
|
105
|
-
if (!pid ||
|
|
106
|
-
throw new Error("
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
catch (error) {
|
|
110
|
-
process.stderr.write(`aiterm-relay: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
111
|
-
process.exitCode = 2;
|
|
39
|
+
throw new Error("POSIX launcherはこのOSで利用できません");
|
|
40
|
+
if (!pid || args.length < 4)
|
|
41
|
+
throw new Error("中継の起動指定がありません");
|
|
42
|
+
process.stdout.write(preparePosixRelayLaunch(pid, args[0], args[1], args[2], args[3], args.slice(4)));
|
|
112
43
|
}
|
|
44
|
+
else if (!socket || !/^[1-9][0-9]*$/.test(pid ?? "") || Number(pid) !== process.ppid)
|
|
45
|
+
throw new Error("親processの指定が不正です");
|
|
46
|
+
else
|
|
47
|
+
await runCodexStdioRelay(socket, Number(pid));
|
|
113
48
|
}
|
|
114
|
-
|
|
115
|
-
process.stderr.write("aiterm-relay:
|
|
116
|
-
process.exitCode =
|
|
49
|
+
catch (error) {
|
|
50
|
+
process.stderr.write("aiterm-relay: " + (error instanceof Error ? error.message : String(error)) + "\n");
|
|
51
|
+
process.exitCode = 1;
|
|
117
52
|
}
|
|
118
|
-
else
|
|
119
|
-
await runCodexStdioRelay(socket, Number(pid));
|
|
120
53
|
}
|
package/dist/core.js
CHANGED
|
@@ -1051,7 +1051,7 @@ function decodeActivityCursor(cursor, name) {
|
|
|
1051
1051
|
throw new AitermError("活動観測cursorが対象sessionの形式と一致しません", 2);
|
|
1052
1052
|
return value;
|
|
1053
1053
|
}
|
|
1054
|
-
function selectHarnessProcesses(meta, rows, subtree) {
|
|
1054
|
+
export function selectHarnessProcesses(meta, rows, subtree) {
|
|
1055
1055
|
const matches = (row) => {
|
|
1056
1056
|
const first = /^(?:"([^"]+)"|(\S+))/.exec(row.command);
|
|
1057
1057
|
const executable = path.posix.basename((first?.[1] ?? first?.[2] ?? "").replace(/\\/g, "/"))
|
|
@@ -1065,7 +1065,20 @@ function selectHarnessProcesses(meta, rows, subtree) {
|
|
|
1065
1065
|
// WindowsのMSYS execでnative親子関係が切れる場合も、launch固有引数で相関する。
|
|
1066
1066
|
const correlated = rows.filter(row => matches(row) && row.command.includes(meta.launch_id));
|
|
1067
1067
|
const candidates = correlated.length ? correlated : subtree.filter(matches);
|
|
1068
|
-
const
|
|
1068
|
+
const byPid = new Map(rows.map(row => [row.pid, row]));
|
|
1069
|
+
const candidatePids = new Set(candidates.map(row => row.pid));
|
|
1070
|
+
// npm shimとnative本体の間にNodeなどの非候補processがいても、同じ起動の祖先を辿る。
|
|
1071
|
+
const roots = candidates.filter(row => {
|
|
1072
|
+
const seen = new Set();
|
|
1073
|
+
let parent = byPid.get(row.parent_pid);
|
|
1074
|
+
while (parent && !seen.has(parent.pid)) {
|
|
1075
|
+
if (candidatePids.has(parent.pid))
|
|
1076
|
+
return false;
|
|
1077
|
+
seen.add(parent.pid);
|
|
1078
|
+
parent = byPid.get(parent.parent_pid);
|
|
1079
|
+
}
|
|
1080
|
+
return true;
|
|
1081
|
+
});
|
|
1069
1082
|
return roots;
|
|
1070
1083
|
}
|
|
1071
1084
|
export function observeSession(name, cursor) {
|
package/dist/harnesses/codex.js
CHANGED
|
@@ -386,7 +386,7 @@ export function codexTuiReady(screen) {
|
|
|
386
386
|
return codexFrontend && /(^|\n)[ \t]*[›>](?![ \t]*\d+\.)/.test(screen);
|
|
387
387
|
}
|
|
388
388
|
function currentCodexDialog(screen) {
|
|
389
|
-
const footers = [...screen.matchAll(/Press enter to confirm or esc to cancel|enter to submit\s*\|\s*esc to cancel/gi)];
|
|
389
|
+
const footers = [...screen.matchAll(/Press enter to confirm or esc to (?:cancel|go back)|enter to submit\s*\|\s*esc to cancel/gi)];
|
|
390
390
|
// 過去の完了footerより前の質問・選択番号を、現在のdialogへ持ち込まない。
|
|
391
391
|
const previousFooter = footers.at(-2);
|
|
392
392
|
const current = previousFooter ? screen.slice(previousFooter.index + previousFooter[0].length) : screen;
|
|
@@ -400,7 +400,7 @@ export function codexPaneObservation(screen) {
|
|
|
400
400
|
const tail = screen.split("\n").slice(-24).join("\n");
|
|
401
401
|
// 現在のmodal footerがある時だけ、折返しで上へ出た質問を画面全体から探す。
|
|
402
402
|
const lastComposer = [...tail.matchAll(/(?:^|\n)[ \t]*[›>](?![ \t]*\d+\.)/g)].at(-1)?.index ?? -1;
|
|
403
|
-
const lastDialog = [...tail.matchAll(/Press enter to confirm or esc to cancel|enter to submit\s*\|\s*esc to cancel|Would you like to run the following command\?|Allow the [^\n]+ MCP server to run tool|Hooks need review|Do you trust the contents of this directory|Update available!/gi)].at(-1)?.index ?? -1;
|
|
403
|
+
const lastDialog = [...tail.matchAll(/Press enter to confirm or esc to (?:cancel|go back)|enter to submit\s*\|\s*esc to cancel|Would you like to run the following command\?|Allow the [^\n]+ MCP server to run tool|Hooks need review|Do you trust the contents of this directory|Update available!/gi)].at(-1)?.index ?? -1;
|
|
404
404
|
const modal = lastDialog > lastComposer;
|
|
405
405
|
if (!modal) {
|
|
406
406
|
if (/esc to interrupt/i.test(tail))
|