Files
Genarrative/rust/scripts/probe-codex-app-server.sh
T
kdletters 202279c6d9 新增独立 Agent Runtime Rust 工作区
新增 Core、Engine、Runtime、SQLite、Provider、MCP、Skill、Codex、CLI 与 DAG crate

补齐 OpenAI endpoint 配置、Provider 实例/协议路由和统一工具权限边界

加入持久化、lease、checkpoint、reconciliation、审批恢复与消息历史回归

加入独立 workspace CI、依赖边界、能力集和 Fake Agent 测试脚本

同步建设计划、TODO、架构、测试与验收文档
2026-09-06 17:44:54 +08:00

180 lines
5.6 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -euo pipefail
# 可选的 P5 真实 wire 探测:只验证本机 Codex app-server 的 initialize、
# initialized 和 thread/start。它不发送 turn/start,不需要 API key,也不
# 进入默认 CI;真实 Provider/工具副作用仍由调用方显式承担。
codex_bin="${CODEX_BIN:-codex}"
expected_version="${CODEX_EXPECTED_VERSION:-0.153.4}"
tmp_parent="${AGENT_CODEX_PROBE_TMPDIR:-${TMPDIR:-${HOME:?HOME 未设置}/data/tmp}}"
emit_schema=0
while (($# > 0)); do
case "$1" in
--schema)
emit_schema=1
;;
-h|--help)
echo "用法:$0 [--schema]"
echo " --schema 额外生成并摘要真实 app-server v2 schema(仍不发送 turn/start"
exit 0
;;
*)
echo "未知参数:$1" >&2
exit 2
;;
esac
shift
done
if ! command -v python3 >/dev/null 2>&1; then
echo "需要 python3 才能运行 Codex wire probe" >&2
exit 1
fi
if ! command -v "$codex_bin" >/dev/null 2>&1 && [[ "$codex_bin" != */* ]]; then
echo "找不到 Codex 可执行文件:$codex_bin" >&2
exit 1
fi
version_output="$($codex_bin --version 2>/dev/null || true)"
if [[ "$version_output" != *"$expected_version"* ]]; then
echo "Codex 版本不匹配:期望包含 $expected_version" >&2
exit 1
fi
mkdir -p -- "$tmp_parent"
tmp_parent="$(cd -- "$tmp_parent" && pwd -P)"
probe_dir="$(mktemp -d "$tmp_parent/codex-app-server-probe.XXXXXX")"
cleanup() {
# probe_dir 是本次脚本刚创建的精确目录,不触碰 tmp_parent 中其它文件。
rm -rf -- "$probe_dir"
}
trap cleanup EXIT
mkdir -p -- "$probe_dir/home" "$probe_dir/workspace" "$probe_dir/tmp"
if ((emit_schema == 1)); then
mkdir -p -- "$probe_dir/schema"
CODEX_HOME="$probe_dir/home" TMPDIR="$probe_dir/tmp" "$codex_bin" \
app-server generate-json-schema --out "$probe_dir/schema" --experimental \
>/dev/null
schema_file="$probe_dir/schema/codex_app_server_protocol.v2.schemas.json"
[[ -s "$schema_file" ]] || {
echo "Codex schema probe 未生成 v2 schema" >&2
exit 1
}
schema_bytes="$(wc -c < "$schema_file")"
schema_sha="$(sha256sum "$schema_file" | awk '{print $1}')"
echo "Codex app-server schema: version=$expected_version bytes=$schema_bytes sha256=$schema_sha"
fi
CODEX_HOME="$probe_dir/home" TMPDIR="$probe_dir/tmp" python3 - "$codex_bin" \
"$probe_dir/home" "$probe_dir/workspace" <<'PY'
import json
import os
import selectors
import signal
import subprocess
import sys
import time
codex_bin, codex_home, workspace = sys.argv[1:]
env = dict(os.environ)
env["CODEX_HOME"] = codex_home
env["TMPDIR"] = os.environ.get("TMPDIR", os.path.join(codex_home, "tmp"))
process = subprocess.Popen(
[codex_bin, "app-server", "--stdio"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
bufsize=1,
env=env,
cwd=workspace,
start_new_session=True,
)
selector = selectors.DefaultSelector()
selector.register(process.stdout, selectors.EVENT_READ)
def send(frame):
process.stdin.write(json.dumps(frame, separators=(",", ":")) + "\n")
process.stdin.flush()
def read_until(frame_id, timeout=8.0):
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
ready = selector.select(max(0.0, deadline - time.monotonic()))
if not ready:
continue
line = process.stdout.readline()
if not line:
break
try:
frame = json.loads(line)
except json.JSONDecodeError as error:
raise RuntimeError(f"返回了非法 JSON{error.msg}") from error
if frame.get("id") != frame_id:
continue
if "error" in frame:
error = frame["error"]
raise RuntimeError(f"request {frame_id} 失败(code={error.get('code', 'unknown')}")
return frame.get("result")
raise RuntimeError(f"等待 request {frame_id} 响应超时")
try:
send(
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"clientInfo": {"name": "agent-runtime-probe", "version": "0.1.0"},
"capabilities": {},
},
}
)
initialize = read_until(1)
if not isinstance(initialize, dict) or not isinstance(initialize.get("userAgent"), str):
raise RuntimeError("initialize result 缺少 userAgent")
send({"jsonrpc": "2.0", "method": "initialized", "params": {}})
send(
{
"jsonrpc": "2.0",
"id": 2,
"method": "thread/start",
"params": {"cwd": workspace, "approvalPolicy": "never"},
}
)
thread_start = read_until(2)
thread = thread_start.get("thread") if isinstance(thread_start, dict) else None
if not isinstance(thread, dict) or not isinstance(thread.get("id"), str):
raise RuntimeError("thread/start result 缺少 string thread.id")
print("Codex app-server probe passed: initialize + thread/start")
finally:
try:
process.stdin.close()
except Exception:
pass
try:
process.wait(timeout=2)
except subprocess.TimeoutExpired:
try:
os.killpg(process.pid, signal.SIGTERM)
except ProcessLookupError:
pass
try:
process.wait(timeout=2)
except subprocess.TimeoutExpired:
try:
os.killpg(process.pid, signal.SIGKILL)
except ProcessLookupError:
pass
process.wait(timeout=2)
PY
echo "Codex app-server probe finished (version=$expected_version)"