mirror of
https://github.com/openai/codex.git
synced 2026-05-11 14:52:36 +00:00
Compare commits
1 Commits
dev/auto-c
...
webui
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7be5328f34 |
20
.bazelrc
20
.bazelrc
@@ -79,10 +79,6 @@ common:ci --disk_cache=
|
||||
# Shared config for the main Bazel CI workflow.
|
||||
common:ci-bazel --config=ci
|
||||
common:ci-bazel --build_metadata=TAG_workflow=bazel
|
||||
# Bazel CI cross-compiles in several legs, and the V8-backed code-mode tests
|
||||
# are not stable in that setup yet. Keep running the rest of the Rust
|
||||
# integration suites through the workspace-root launcher.
|
||||
common:ci-bazel --test_env=CODEX_BAZEL_TEST_SKIP_FILTERS=suite::code_mode::
|
||||
|
||||
# Shared config for Bazel-backed Rust linting.
|
||||
build:clippy --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect
|
||||
@@ -168,9 +164,9 @@ common:ci-windows-cross --strategy=remote
|
||||
common:ci-windows-cross --strategy=TestRunner=local
|
||||
common:ci-windows-cross --local_test_jobs=4
|
||||
common:ci-windows-cross --test_env=RUST_TEST_THREADS=1
|
||||
# Native Windows CI still covers the PowerShell tests. The cross-built gnullvm
|
||||
# binaries currently hang in PowerShell AST parser tests when those binaries are
|
||||
# run on the Windows runner.
|
||||
# Native Windows CI still covers these tests. The cross-built gnullvm binaries
|
||||
# currently crash in V8-backed code-mode tests and hang in PowerShell AST parser
|
||||
# tests when those binaries are run on the Windows runner.
|
||||
common:ci-windows-cross --test_env=CODEX_BAZEL_TEST_SKIP_FILTERS=suite::code_mode::,powershell
|
||||
common:ci-windows-cross --platforms=//:windows_x86_64_gnullvm
|
||||
common:ci-windows-cross --extra_execution_platforms=//:rbe,//:windows_x86_64_msvc
|
||||
@@ -183,15 +179,5 @@ common:ci-v8 --build_metadata=TAG_os=linux
|
||||
common:ci-v8 --config=remote
|
||||
common:ci-v8 --strategy=remote
|
||||
|
||||
# Source-built Bazel V8 artifacts use the in-process sandbox by default. This
|
||||
# does not affect Cargo's default prebuilt rusty_v8 path.
|
||||
common --@v8//:v8_enable_pointer_compression=True
|
||||
common --@v8//:v8_enable_sandbox=True
|
||||
|
||||
# Keep currently published rusty_v8 release artifacts non-sandboxed until the
|
||||
# artifact migration ships matching Rust feature selection for Cargo consumers.
|
||||
common:v8-release-compat --@v8//:v8_enable_pointer_compression=False
|
||||
common:v8-release-compat --@v8//:v8_enable_sandbox=False
|
||||
|
||||
# Optional per-user local overrides.
|
||||
try-import %workspace%/user.bazelrc
|
||||
|
||||
@@ -53,7 +53,7 @@ Use `--window "past week"` or `--window-hours 168` when the user asks for a non-
|
||||
## Summary
|
||||
No major issues reported by users.
|
||||
|
||||
Source: collector v5, git `abc123def456`, window `2026-04-27T00:00:00Z` to `2026-04-28T00:00:00Z`.
|
||||
Source: collector v4, git `abc123def456`, window `2026-04-27T00:00:00Z` to `2026-04-28T00:00:00Z`.
|
||||
Want details? I can expand this into the issue table.
|
||||
```
|
||||
|
||||
@@ -65,7 +65,7 @@ Two issues are being surfaced by users:
|
||||
🔥🔥 Terminal launch hangs on startup [1](https://github.com/openai/codex/issues/123)
|
||||
🔥 Resume switches model providers unexpectedly [2](https://github.com/openai/codex/issues/456)
|
||||
|
||||
Source: collector v5, git `abc123def456`, window `2026-04-27T00:00:00Z` to `2026-04-28T00:00:00Z`.
|
||||
Source: collector v4, git `abc123def456`, window `2026-04-27T00:00:00Z` to `2026-04-28T00:00:00Z`.
|
||||
Want details? I can expand this into the issue table.
|
||||
```
|
||||
5. In `## Details`, when details are requested, include a compact table only when useful:
|
||||
@@ -76,7 +76,7 @@ Want details? I can expand this into the issue table.
|
||||
- A clear quiet/no-concern sentence when there is no meaningful signal.
|
||||
6. Use the JSON `attention_marker` exactly. It is empty for normal rows, `🔥` for elevated rows, and `🔥🔥` for very high-attention rows. The actual cutoffs are in `attention_thresholds`.
|
||||
7. Use inline numbered references where a row or bullet points to issues, for example `Compaction bugs [1](https://github.com/openai/codex/issues/123), [2](https://github.com/openai/codex/issues/456)`. Do not add a separate footnotes section.
|
||||
8. Label `interactions` as `Interactions`; it counts unique human GitHub users who created a new issue, added a new comment, or reacted during the requested window. Multiple posts/reactions from the same user on the same issue count once.
|
||||
8. Label `interactions` as `Interactions`; it counts posts/comments/reactions during the requested window, not unique people.
|
||||
9. Mention the collector `script_version`, repo checkout `git_head`, and time window in one compact source line. In default mode, put this before the details prompt so the final line still asks whether the user wants details. In details-upfront mode, it can be the footer.
|
||||
|
||||
## Reaction Handling
|
||||
@@ -89,7 +89,7 @@ GitHub issue search is still seeded by issue `updated_at`, so a purely reaction-
|
||||
|
||||
## Attention Markers
|
||||
|
||||
The collector scales attention markers by the requested time window. The baseline is 5 unique human users for `🔥` and 10 unique human users for `🔥🔥` over 24 hours; longer or shorter windows scale those cutoffs linearly and round up. For example, a one-week report uses 35 and 70 interactions. Unique human users are users who authored a new issue, authored a new comment, or reacted during the window, including upvotes. Multiple actions from the same user on the same issue count once. Bot posts and bot reactions are excluded. In prose, explain this as high user interaction rather than naming the emoji.
|
||||
The collector scales attention markers by the requested time window. The baseline is 5 human user interactions for `🔥` and 10 for `🔥🔥` over 24 hours; longer or shorter windows scale those cutoffs linearly and round up. For example, a one-week report uses 35 and 70 interactions. Human user interactions are human-authored new issue posts, human-authored new comments, and human reactions created during the window, including upvotes. Bot posts and bot reactions are excluded. In prose, explain this as high user interaction rather than naming the emoji.
|
||||
|
||||
## Freshness
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote
|
||||
|
||||
SCRIPT_VERSION = 5
|
||||
SCRIPT_VERSION = 4
|
||||
QUALIFYING_KIND_LABELS = ("bug", "enhancement")
|
||||
REACTION_KEYS = ("+1", "-1", "laugh", "hooray", "confused", "heart", "rocket", "eyes")
|
||||
BASE_ATTENTION_WINDOW_HOURS = 24.0
|
||||
@@ -393,15 +393,9 @@ def is_bot_login(login):
|
||||
return bool(login) and login.lower().endswith("[bot]")
|
||||
|
||||
|
||||
def human_login_key(user_obj):
|
||||
login = extract_login(user_obj)
|
||||
if not login or is_bot_login(login):
|
||||
return ""
|
||||
return login.casefold()
|
||||
|
||||
|
||||
def is_human_user(user_obj):
|
||||
return bool(human_login_key(user_obj))
|
||||
login = extract_login(user_obj)
|
||||
return bool(login) and not is_bot_login(login)
|
||||
|
||||
|
||||
def label_names(issue):
|
||||
@@ -473,26 +467,22 @@ def reaction_summary(item):
|
||||
def reaction_event_summary(reactions, since, until):
|
||||
counts = {}
|
||||
total = 0
|
||||
users = set()
|
||||
for reaction in reactions or []:
|
||||
if not isinstance(reaction, dict):
|
||||
continue
|
||||
if not is_in_window(str(reaction.get("created_at") or ""), since, until):
|
||||
continue
|
||||
user_key = human_login_key(reaction.get("user"))
|
||||
if not user_key:
|
||||
if not is_human_user(reaction.get("user")):
|
||||
continue
|
||||
content = str(reaction.get("content") or "")
|
||||
if not content:
|
||||
continue
|
||||
counts[content] = counts.get(content, 0) + 1
|
||||
total += 1
|
||||
users.add(user_key)
|
||||
return {
|
||||
"total": total,
|
||||
"counts": counts,
|
||||
"upvotes": counts.get("+1", 0),
|
||||
"users": sorted(users, key=str.casefold),
|
||||
}
|
||||
|
||||
|
||||
@@ -628,21 +618,13 @@ def summarize_issue(
|
||||
new_comment_reaction_total = sum(
|
||||
comment["reaction_total"] for comment in new_comments
|
||||
)
|
||||
new_issue_user_key = human_login_key(issue.get("user")) if new_issue else ""
|
||||
new_issue_user_interaction = bool(new_issue_user_key)
|
||||
new_issue_user_interaction = new_issue and is_human_user(issue.get("user"))
|
||||
new_comment_user_interactions = sum(
|
||||
1 for comment in new_comments if comment["human_user_interaction"]
|
||||
)
|
||||
interaction_user_keys = set(issue_reaction_events_summary["users"])
|
||||
interaction_user_keys.update(comment_reaction_events_summary["users"])
|
||||
if new_issue_user_key:
|
||||
interaction_user_keys.add(new_issue_user_key)
|
||||
interaction_user_keys.update(
|
||||
comment["author"].casefold()
|
||||
for comment in new_comments
|
||||
if comment["human_user_interaction"]
|
||||
user_interactions = (
|
||||
int(new_issue_user_interaction) + new_comment_user_interactions + new_reactions
|
||||
)
|
||||
user_interactions = len(interaction_user_keys)
|
||||
attention_level = attention_level_for(user_interactions, attention_thresholds)
|
||||
attention_marker = attention_marker_for(user_interactions, attention_thresholds)
|
||||
updated_without_visible_new_post = (
|
||||
@@ -975,7 +957,6 @@ def collect_digest(args):
|
||||
"New issue comments are filtered by comment creation time within the window from the fetched comment set.",
|
||||
"Reaction events are counted by GitHub reaction created_at timestamps for hydrated issues and fetched comments.",
|
||||
"Current reaction totals are standing engagement signals; new_reactions and new_upvotes are windowed activity.",
|
||||
"user_interactions counts unique human users per issue across new issues, new comments, and new reactions; repeated actions by the same user count once.",
|
||||
"The collector does not assign semantic clusters; use summary_inputs as model-ready evidence for report-time clustering.",
|
||||
"Pure reaction-only issues may be missed if GitHub issue search does not surface them via updated_at.",
|
||||
"Issues updated during the window without a new issue body or new comment are retained because label/status edits can still be useful owner signals.",
|
||||
|
||||
@@ -494,70 +494,6 @@ def test_reactions_count_toward_attention_markers():
|
||||
assert summary["new_comments"][0]["new_upvotes"] == 0
|
||||
|
||||
|
||||
def test_user_interactions_are_deduped_by_human_login():
|
||||
since = collect_issue_digest.parse_timestamp("2026-04-25T00:00:00Z", "--since")
|
||||
until = collect_issue_digest.parse_timestamp("2026-04-26T00:00:00Z", "--until")
|
||||
|
||||
def comment(comment_id, login):
|
||||
return {
|
||||
"id": comment_id,
|
||||
"created_at": f"2026-04-25T0{comment_id + 1}:00:00Z",
|
||||
"updated_at": f"2026-04-25T0{comment_id + 1}:00:00Z",
|
||||
"user": {"login": login},
|
||||
"body": "same issue",
|
||||
}
|
||||
|
||||
def reaction(content, login, created_at="2026-04-25T10:00:00Z"):
|
||||
return {
|
||||
"content": content,
|
||||
"created_at": created_at,
|
||||
"user": {"login": login},
|
||||
}
|
||||
|
||||
issue = {
|
||||
"number": 790,
|
||||
"title": "Repeated pings should not boost attention",
|
||||
"html_url": "https://github.com/openai/codex/issues/790",
|
||||
"state": "open",
|
||||
"created_at": "2026-04-25T01:00:00Z",
|
||||
"updated_at": "2026-04-25T12:00:00Z",
|
||||
"user": {"login": "Alice"},
|
||||
"labels": [{"name": "bug"}, {"name": "tui"}],
|
||||
}
|
||||
comments = [comment(1, "alice"), comment(2, "ALICE"), comment(3, "bob")]
|
||||
comments.append(comment(4, "github-actions[bot]"))
|
||||
issue_reactions = [
|
||||
reaction("+1", "alice"),
|
||||
reaction("rocket", "Alice"),
|
||||
reaction("+1", "bob"),
|
||||
reaction("+1", "github-actions[bot]"),
|
||||
reaction("+1", "carol", created_at="2026-04-24T23:00:00Z"),
|
||||
]
|
||||
comment_reactions_by_id = {
|
||||
1: [reaction("heart", "alice")],
|
||||
2: [reaction("+1", "bob")],
|
||||
3: [reaction("eyes", "carol")],
|
||||
}
|
||||
|
||||
summary = collect_issue_digest.summarize_issue(
|
||||
issue,
|
||||
comments,
|
||||
["tui"],
|
||||
since,
|
||||
until,
|
||||
body_chars=100,
|
||||
comment_chars=100,
|
||||
issue_reaction_events=issue_reactions,
|
||||
comment_reactions_by_id=comment_reactions_by_id,
|
||||
)
|
||||
|
||||
assert summary["activity"]["new_human_comments"] == 3
|
||||
assert summary["new_reactions"] == 6
|
||||
assert summary["user_interactions"] == 3
|
||||
assert summary["attention"] is False
|
||||
assert summary["attention_marker"] == ""
|
||||
|
||||
|
||||
def test_digest_rows_are_table_ready_with_concise_descriptions():
|
||||
rows = collect_issue_digest.digest_rows(
|
||||
[
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/3-cli.yml
vendored
5
.github/ISSUE_TEMPLATE/3-cli.yml
vendored
@@ -2,6 +2,7 @@ name: 💻 CLI Bug
|
||||
description: Report an issue in the Codex CLI
|
||||
labels:
|
||||
- bug
|
||||
- needs triage
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
@@ -40,9 +41,9 @@ body:
|
||||
id: terminal
|
||||
attributes:
|
||||
label: What terminal emulator and version are you using (if applicable)?
|
||||
description: Also note any multiplexer in use (screen / tmux / zellij)
|
||||
description: |
|
||||
Also note any multiplexer in use (screen / tmux / zellij).
|
||||
E.g., VS Code, Terminal.app, iTerm2, Ghostty, Windows Terminal (WSL / PowerShell)
|
||||
E.g, VSCode, Terminal.app, iTerm2, Ghostty, Windows Terminal (WSL / PowerShell)
|
||||
- type: textarea
|
||||
id: actual
|
||||
attributes:
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/5-feature-request.yml
vendored
2
.github/ISSUE_TEMPLATE/5-feature-request.yml
vendored
@@ -10,7 +10,7 @@ body:
|
||||
|
||||
Before you submit a feature:
|
||||
1. Search existing issues for similar features. If you find one, 👍 it rather than opening a new one.
|
||||
2. The Codex team will try to balance the varying needs of the community when prioritizing or rejecting new features. Not all features will be accepted. See [Contributing](https://github.com/openai/codex/blob/main/docs/contributing.md) for more details.
|
||||
2. The Codex team will try to balance the varying needs of the community when prioritizing or rejecting new features. Not all features will be accepted. See [Contributing](https://github.com/openai/codex#contributing) for more details.
|
||||
|
||||
- type: input
|
||||
id: variant
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/6-docs-issue.yml
vendored
4
.github/ISSUE_TEMPLATE/6-docs-issue.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: 📗 Documentation Issue
|
||||
description: Tell us if there is missing or incorrect documentation
|
||||
labels: [documentation]
|
||||
labels: [docs]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
@@ -24,4 +24,4 @@ body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Where did you find it?
|
||||
description: If possible, please provide the URL(s) where you found this issue.
|
||||
description: If possible, please provide the URL(s) where you found this issue.
|
||||
2
.github/actions/prepare-bazel-ci/action.yml
vendored
2
.github/actions/prepare-bazel-ci/action.yml
vendored
@@ -50,7 +50,7 @@ runs:
|
||||
- name: Restore bazel repository cache
|
||||
id: cache_bazel_repository_restore
|
||||
continue-on-error: true
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: ${{ steps.setup_bazel.outputs.repository-cache-path }}
|
||||
key: ${{ steps.cache_bazel_repository_key.outputs.repository-cache-key }}
|
||||
|
||||
4
.github/actions/windows-code-sign/action.yml
vendored
4
.github/actions/windows-code-sign/action.yml
vendored
@@ -30,7 +30,7 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Azure login for Trusted Signing (OIDC)
|
||||
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
|
||||
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2
|
||||
with:
|
||||
client-id: ${{ inputs.client-id }}
|
||||
tenant-id: ${{ inputs.tenant-id }}
|
||||
@@ -54,7 +54,7 @@ runs:
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Sign Windows binaries with Azure Trusted Signing
|
||||
uses: azure/trusted-signing-action@1d365fec12862c4aa68fcac418143d73f0cea293 # v0.5.11
|
||||
uses: azure/trusted-signing-action@1d365fec12862c4aa68fcac418143d73f0cea293 # v0
|
||||
with:
|
||||
endpoint: ${{ inputs.endpoint }}
|
||||
trusted-signing-account-name: ${{ inputs.account-name }}
|
||||
|
||||
12
.github/dependabot.yaml
vendored
12
.github/dependabot.yaml
vendored
@@ -6,37 +6,25 @@ updates:
|
||||
directory: .github/actions/codex
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
- package-ecosystem: cargo
|
||||
directories:
|
||||
- codex-rs
|
||||
- codex-rs/*
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
- package-ecosystem: devcontainers
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
- package-ecosystem: docker
|
||||
directory: codex-cli
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
- package-ecosystem: rust-toolchain
|
||||
directory: codex-rs
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
16
.github/dotslash-config.json
vendored
16
.github/dotslash-config.json
vendored
@@ -11,11 +11,11 @@
|
||||
"path": "codex"
|
||||
},
|
||||
"linux-x86_64": {
|
||||
"regex": "^codex-x86_64-unknown-linux-musl-bundle\\.tar\\.zst$",
|
||||
"regex": "^codex-x86_64-unknown-linux-musl\\.zst$",
|
||||
"path": "codex"
|
||||
},
|
||||
"linux-aarch64": {
|
||||
"regex": "^codex-aarch64-unknown-linux-musl-bundle\\.tar\\.zst$",
|
||||
"regex": "^codex-aarch64-unknown-linux-musl\\.zst$",
|
||||
"path": "codex"
|
||||
},
|
||||
"windows-x86_64": {
|
||||
@@ -84,18 +84,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"bwrap": {
|
||||
"platforms": {
|
||||
"linux-x86_64": {
|
||||
"regex": "^bwrap-x86_64-unknown-linux-musl\\.zst$",
|
||||
"path": "bwrap"
|
||||
},
|
||||
"linux-aarch64": {
|
||||
"regex": "^bwrap-aarch64-unknown-linux-musl\\.zst$",
|
||||
"path": "bwrap"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codex-command-runner": {
|
||||
"platforms": {
|
||||
"windows-x86_64": {
|
||||
|
||||
39
.github/scripts/rusty_v8_bazel.py
vendored
39
.github/scripts/rusty_v8_bazel.py
vendored
@@ -63,10 +63,8 @@ def bazel_output_files(
|
||||
platform: str,
|
||||
labels: list[str],
|
||||
compilation_mode: str = "fastbuild",
|
||||
bazel_configs: list[str] | None = None,
|
||||
) -> list[Path]:
|
||||
expression = "set(" + " ".join(labels) + ")"
|
||||
bazel_configs = bazel_configs or []
|
||||
result = subprocess.run(
|
||||
[
|
||||
"bazel",
|
||||
@@ -74,7 +72,6 @@ def bazel_output_files(
|
||||
"-c",
|
||||
compilation_mode,
|
||||
f"--platforms=@llvm//platforms:{platform}",
|
||||
*[f"--config={config}" for config in bazel_configs],
|
||||
"--output=files",
|
||||
expression,
|
||||
],
|
||||
@@ -90,9 +87,7 @@ def bazel_build(
|
||||
platform: str,
|
||||
labels: list[str],
|
||||
compilation_mode: str = "fastbuild",
|
||||
bazel_configs: list[str] | None = None,
|
||||
) -> None:
|
||||
bazel_configs = bazel_configs or []
|
||||
subprocess.run(
|
||||
[
|
||||
"bazel",
|
||||
@@ -100,7 +95,6 @@ def bazel_build(
|
||||
"-c",
|
||||
compilation_mode,
|
||||
f"--platforms=@llvm//platforms:{platform}",
|
||||
*[f"--config={config}" for config in bazel_configs],
|
||||
*labels,
|
||||
],
|
||||
cwd=ROOT,
|
||||
@@ -112,14 +106,13 @@ def ensure_bazel_output_files(
|
||||
platform: str,
|
||||
labels: list[str],
|
||||
compilation_mode: str = "fastbuild",
|
||||
bazel_configs: list[str] | None = None,
|
||||
) -> list[Path]:
|
||||
outputs = bazel_output_files(platform, labels, compilation_mode, bazel_configs)
|
||||
outputs = bazel_output_files(platform, labels, compilation_mode)
|
||||
if all(path.exists() for path in outputs):
|
||||
return outputs
|
||||
|
||||
bazel_build(platform, labels, compilation_mode, bazel_configs)
|
||||
outputs = bazel_output_files(platform, labels, compilation_mode, bazel_configs)
|
||||
bazel_build(platform, labels, compilation_mode)
|
||||
outputs = bazel_output_files(platform, labels, compilation_mode)
|
||||
missing = [str(path) for path in outputs if not path.exists()]
|
||||
if missing:
|
||||
raise SystemExit(f"missing built outputs for {labels}: {missing}")
|
||||
@@ -194,9 +187,8 @@ def single_bazel_output_file(
|
||||
platform: str,
|
||||
label: str,
|
||||
compilation_mode: str = "fastbuild",
|
||||
bazel_configs: list[str] | None = None,
|
||||
) -> Path:
|
||||
outputs = ensure_bazel_output_files(platform, [label], compilation_mode, bazel_configs)
|
||||
outputs = ensure_bazel_output_files(platform, [label], compilation_mode)
|
||||
if len(outputs) != 1:
|
||||
raise SystemExit(f"expected exactly one output for {label}, found {outputs}")
|
||||
return outputs[0]
|
||||
@@ -206,17 +198,11 @@ def merged_musl_archive(
|
||||
platform: str,
|
||||
lib_path: Path,
|
||||
compilation_mode: str = "fastbuild",
|
||||
bazel_configs: list[str] | None = None,
|
||||
) -> Path:
|
||||
llvm_ar = single_bazel_output_file(platform, LLVM_AR_LABEL, compilation_mode, bazel_configs)
|
||||
llvm_ranlib = single_bazel_output_file(
|
||||
platform,
|
||||
LLVM_RANLIB_LABEL,
|
||||
compilation_mode,
|
||||
bazel_configs,
|
||||
)
|
||||
llvm_ar = single_bazel_output_file(platform, LLVM_AR_LABEL, compilation_mode)
|
||||
llvm_ranlib = single_bazel_output_file(platform, LLVM_RANLIB_LABEL, compilation_mode)
|
||||
runtime_archives = [
|
||||
single_bazel_output_file(platform, label, compilation_mode, bazel_configs)
|
||||
single_bazel_output_file(platform, label, compilation_mode)
|
||||
for label in MUSL_RUNTIME_ARCHIVE_LABELS
|
||||
]
|
||||
|
||||
@@ -247,13 +233,11 @@ def stage_release_pair(
|
||||
target: str,
|
||||
output_dir: Path,
|
||||
compilation_mode: str = "fastbuild",
|
||||
bazel_configs: list[str] | None = None,
|
||||
) -> None:
|
||||
outputs = ensure_bazel_output_files(
|
||||
platform,
|
||||
[release_pair_label(target)],
|
||||
compilation_mode,
|
||||
bazel_configs,
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -270,7 +254,7 @@ def stage_release_pair(
|
||||
staged_library = output_dir / staged_archive_name(target, lib_path)
|
||||
staged_binding = output_dir / f"src_binding_release_{target}.rs"
|
||||
source_archive = (
|
||||
merged_musl_archive(platform, lib_path, compilation_mode, bazel_configs)
|
||||
merged_musl_archive(platform, lib_path, compilation_mode)
|
||||
if is_musl_archive_target(target, lib_path)
|
||||
else lib_path
|
||||
)
|
||||
@@ -309,12 +293,6 @@ def parse_args() -> argparse.Namespace:
|
||||
stage_release_pair_parser.add_argument("--platform", required=True)
|
||||
stage_release_pair_parser.add_argument("--target", required=True)
|
||||
stage_release_pair_parser.add_argument("--output-dir", required=True)
|
||||
stage_release_pair_parser.add_argument(
|
||||
"--bazel-config",
|
||||
action="append",
|
||||
default=[],
|
||||
dest="bazel_configs",
|
||||
)
|
||||
stage_release_pair_parser.add_argument(
|
||||
"--compilation-mode",
|
||||
default="fastbuild",
|
||||
@@ -352,7 +330,6 @@ def main() -> int:
|
||||
target=args.target,
|
||||
output_dir=Path(args.output_dir),
|
||||
compilation_mode=args.compilation_mode,
|
||||
bazel_configs=args.bazel_configs,
|
||||
)
|
||||
return 0
|
||||
if args.command == "resolved-v8-crate-version":
|
||||
|
||||
@@ -25,10 +25,7 @@ TOP_LEVEL_NAME_EXCEPTIONS = {
|
||||
UTILITY_NAME_EXCEPTIONS = {
|
||||
"path-utils": "codex-utils-path",
|
||||
}
|
||||
MANIFEST_FEATURE_EXCEPTIONS = {
|
||||
"codex-rs/code-mode/Cargo.toml": {"sandbox": ("v8/v8_enable_sandbox",)},
|
||||
"codex-rs/v8-poc/Cargo.toml": {"sandbox": ("v8/v8_enable_sandbox",)},
|
||||
}
|
||||
MANIFEST_FEATURE_EXCEPTIONS = {}
|
||||
OPTIONAL_DEPENDENCY_EXCEPTIONS = set()
|
||||
INTERNAL_DEPENDENCY_FEATURE_EXCEPTIONS = {}
|
||||
|
||||
|
||||
86
.github/workflows/bazel.yml
vendored
86
.github/workflows/bazel.yml
vendored
@@ -18,9 +18,9 @@ concurrency:
|
||||
jobs:
|
||||
test:
|
||||
# PRs use a fast Windows cross-compiled test leg for pre-merge signal.
|
||||
# Post-merge pushes to main also run the native Windows test job below for
|
||||
# broader Windows signal without putting PR latency back on the critical
|
||||
# path. Cargo CI owns V8/code-mode test coverage for now.
|
||||
# Post-merge pushes to main also run the native Windows test job below,
|
||||
# which keeps V8/code-mode coverage without putting PR latency back on the
|
||||
# critical path.
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -46,8 +46,8 @@ jobs:
|
||||
|
||||
# Windows fast path: build the windows-gnullvm binaries with Linux
|
||||
# RBE, then run the resulting Windows tests on the Windows runner.
|
||||
# Cargo CI preserves V8/code-mode coverage while Bazel CI keeps broad
|
||||
# non-code-mode signal.
|
||||
# The main-only native Windows job below preserves full V8/code-mode
|
||||
# coverage post-merge.
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-gnullvm
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -56,10 +56,7 @@ jobs:
|
||||
name: Bazel test on ${{ matrix.os }} for ${{ matrix.target }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Check rusty_v8 MODULE.bazel checksums
|
||||
if: matrix.os == 'ubuntu-24.04' && matrix.target == 'x86_64-unknown-linux-gnu'
|
||||
@@ -91,11 +88,6 @@ jobs:
|
||||
# path. V8 consumers under `//codex-rs/...` still participate
|
||||
# transitively through `//...`.
|
||||
-//third_party/v8:all
|
||||
# V8-backed code-mode tests are covered by Cargo CI. Bazel CI
|
||||
# cross-compiles in several legs, and those tests are not stable in
|
||||
# that setup yet.
|
||||
-//codex-rs/code-mode:code-mode-unit-tests
|
||||
-//codex-rs/v8-poc:v8-poc-unit-tests
|
||||
)
|
||||
|
||||
bazel_wrapper_args=(
|
||||
@@ -113,6 +105,15 @@ jobs:
|
||||
--windows-cross-compile
|
||||
--remote-download-toplevel
|
||||
)
|
||||
# Tradeoff: the Linux-RBE-built windows-gnullvm V8 archive
|
||||
# currently crashes during direct V8/code-mode smoke tests on the
|
||||
# Windows runner. Keep the broader fast Windows suite in PR CI and
|
||||
# rely on the main-only native Windows job below for full
|
||||
# V8/code-mode signal while we investigate the cross-built archive.
|
||||
bazel_targets+=(
|
||||
-//codex-rs/code-mode:code-mode-unit-tests
|
||||
-//codex-rs/v8-poc:v8-poc-unit-tests
|
||||
)
|
||||
fi
|
||||
|
||||
./.github/scripts/run-bazel-ci.sh \
|
||||
@@ -125,7 +126,7 @@ jobs:
|
||||
- name: Upload Bazel execution logs
|
||||
if: always() && !cancelled()
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: bazel-execution-logs-test-${{ matrix.target }}
|
||||
path: ${{ runner.temp }}/bazel-execution-logs
|
||||
@@ -136,25 +137,23 @@ jobs:
|
||||
- name: Save bazel repository cache
|
||||
if: always() && !cancelled() && steps.prepare_bazel.outputs.repository-cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: ${{ steps.prepare_bazel.outputs.repository-cache-path }}
|
||||
key: ${{ steps.prepare_bazel.outputs.repository-cache-key }}
|
||||
|
||||
test-windows-native-main:
|
||||
# Native Windows Bazel tests are slower and frequently approach the
|
||||
# 30-minute PR budget. Run this only for post-merge commits to main and give
|
||||
# it a larger timeout.
|
||||
# 30-minute PR budget, but they provide the full V8/code-mode signal that
|
||||
# the fast cross-compiled PR leg intentionally trades away. Run this only
|
||||
# for post-merge commits to main and give it a larger timeout.
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
timeout-minutes: 40
|
||||
runs-on: windows-latest
|
||||
name: Bazel test on windows-latest for x86_64-pc-windows-gnullvm (native main)
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Prepare Bazel CI
|
||||
id: prepare_bazel
|
||||
@@ -175,11 +174,6 @@ jobs:
|
||||
# path. V8 consumers under `//codex-rs/...` still participate
|
||||
# transitively through `//...`.
|
||||
-//third_party/v8:all
|
||||
# Keep this aligned with the main Bazel job. The native Windows
|
||||
# job preserves broad post-merge coverage, but code-mode/V8 tests
|
||||
# are covered by Cargo CI rather than Bazel for now.
|
||||
-//codex-rs/code-mode:code-mode-unit-tests
|
||||
-//codex-rs/v8-poc:v8-poc-unit-tests
|
||||
)
|
||||
|
||||
bazel_test_args=(
|
||||
@@ -201,7 +195,7 @@ jobs:
|
||||
- name: Upload Bazel execution logs
|
||||
if: always() && !cancelled()
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: bazel-execution-logs-test-windows-native-x86_64-pc-windows-gnullvm
|
||||
path: ${{ runner.temp }}/bazel-execution-logs
|
||||
@@ -212,7 +206,7 @@ jobs:
|
||||
- name: Save bazel repository cache
|
||||
if: always() && !cancelled() && steps.prepare_bazel.outputs.repository-cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: ${{ steps.prepare_bazel.outputs.repository-cache-path }}
|
||||
key: ${{ steps.prepare_bazel.outputs.repository-cache-key }}
|
||||
@@ -237,10 +231,7 @@ jobs:
|
||||
name: Bazel clippy on ${{ matrix.os }} for ${{ matrix.target }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Prepare Bazel CI
|
||||
id: prepare_bazel
|
||||
@@ -295,7 +286,7 @@ jobs:
|
||||
- name: Upload Bazel execution logs
|
||||
if: always() && !cancelled()
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: bazel-execution-logs-clippy-${{ matrix.target }}
|
||||
path: ${{ runner.temp }}/bazel-execution-logs
|
||||
@@ -306,7 +297,7 @@ jobs:
|
||||
- name: Save bazel repository cache
|
||||
if: always() && !cancelled() && steps.prepare_bazel.outputs.repository-cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: ${{ steps.prepare_bazel.outputs.repository-cache-path }}
|
||||
key: ${{ steps.prepare_bazel.outputs.repository-cache-key }}
|
||||
@@ -327,10 +318,7 @@ jobs:
|
||||
name: Verify release build on ${{ matrix.os }} for ${{ matrix.target }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Prepare Bazel CI
|
||||
id: prepare_bazel
|
||||
@@ -383,26 +371,10 @@ jobs:
|
||||
-- \
|
||||
"${bazel_targets[@]}"
|
||||
|
||||
- name: Verify Bazel builds bwrap
|
||||
if: runner.os == 'Linux'
|
||||
env:
|
||||
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
|
||||
shell: bash
|
||||
run: |
|
||||
./.github/scripts/run-bazel-ci.sh \
|
||||
--remote-download-toplevel \
|
||||
--print-failed-action-summary \
|
||||
-- \
|
||||
build \
|
||||
--build_metadata=COMMIT_SHA=${GITHUB_SHA} \
|
||||
--build_metadata=TAG_job=verify-bwrap \
|
||||
-- \
|
||||
//codex-rs/bwrap:bwrap
|
||||
|
||||
- name: Upload Bazel execution logs
|
||||
if: always() && !cancelled()
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: bazel-execution-logs-verify-release-build-${{ matrix.target }}
|
||||
path: ${{ runner.temp }}/bazel-execution-logs
|
||||
@@ -413,7 +385,7 @@ jobs:
|
||||
- name: Save bazel repository cache
|
||||
if: always() && !cancelled() && steps.prepare_bazel.outputs.repository-cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: ${{ steps.prepare_bazel.outputs.repository-cache-path }}
|
||||
key: ${{ steps.prepare_bazel.outputs.repository-cache-key }}
|
||||
|
||||
8
.github/workflows/blob-size-policy.yml
vendored
8
.github/workflows/blob-size-policy.yml
vendored
@@ -8,19 +8,17 @@ jobs:
|
||||
name: Blob size policy
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Determine PR comparison range
|
||||
id: range
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "base=${{ github.event.pull_request.base.sha }}" >> "$GITHUB_OUTPUT"
|
||||
echo "head=${{ github.event.pull_request.head.sha }}" >> "$GITHUB_OUTPUT"
|
||||
echo "base=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT"
|
||||
echo "head=$(git rev-parse HEAD^2)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Check changed blob sizes
|
||||
env:
|
||||
|
||||
5
.github/workflows/cargo-deny.yml
vendored
5
.github/workflows/cargo-deny.yml
vendored
@@ -14,10 +14,7 @@ jobs:
|
||||
working-directory: ./codex-rs
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
|
||||
11
.github/workflows/ci.yml
vendored
11
.github/workflows/ci.yml
vendored
@@ -12,10 +12,7 @@ jobs:
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Verify codex-rs Cargo manifests inherit workspace settings
|
||||
run: python3 .github/scripts/verify_cargo_workspace_manifests.py
|
||||
@@ -32,7 +29,7 @@ jobs:
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
@@ -55,18 +52,16 @@ jobs:
|
||||
CODEX_VERSION=0.125.0
|
||||
WORKFLOW_URL="https://github.com/openai/codex/actions/runs/24901475298"
|
||||
OUTPUT_DIR="${RUNNER_TEMP}"
|
||||
# This reused workflow predates the standalone bwrap artifact.
|
||||
python3 ./scripts/stage_npm_packages.py \
|
||||
--release-version "$CODEX_VERSION" \
|
||||
--workflow-url "$WORKFLOW_URL" \
|
||||
--package codex \
|
||||
--allow-missing-native-component bwrap \
|
||||
--output-dir "$OUTPUT_DIR"
|
||||
PACK_OUTPUT="${OUTPUT_DIR}/codex-npm-${CODEX_VERSION}.tgz"
|
||||
echo "pack_output=$PACK_OUTPUT" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload staged npm package artifact
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: codex-npm-staging
|
||||
path: ${{ steps.stage_npm_package.outputs.pack_output }}
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Close inactive PRs from contributors
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
||||
7
.github/workflows/codespell.yml
vendored
7
.github/workflows/codespell.yml
vendored
@@ -18,12 +18,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- name: Annotate locations with typos
|
||||
uses: codespell-project/codespell-problem-matcher@b80729f885d32f78a716c2f107b4db1025001c42 # v1.1.0
|
||||
uses: codespell-project/codespell-problem-matcher@b80729f885d32f78a716c2f107b4db1025001c42 # v1
|
||||
- name: Codespell
|
||||
uses: codespell-project/actions-codespell@8f01853be192eb0f849a5c7d721450e7a467c579 # v2.2
|
||||
with:
|
||||
|
||||
10
.github/workflows/issue-deduplicator.yml
vendored
10
.github/workflows/issue-deduplicator.yml
vendored
@@ -19,9 +19,7 @@ jobs:
|
||||
reason: ${{ steps.normalize-all.outputs.reason }}
|
||||
has_matches: ${{ steps.normalize-all.outputs.has_matches }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Prepare Codex inputs
|
||||
env:
|
||||
@@ -157,9 +155,7 @@ jobs:
|
||||
reason: ${{ steps.normalize-open.outputs.reason }}
|
||||
has_matches: ${{ steps.normalize-open.outputs.has_matches }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Prepare Codex inputs
|
||||
env:
|
||||
@@ -346,7 +342,7 @@ jobs:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Comment on issue
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
env:
|
||||
CODEX_OUTPUT: ${{ needs.select-final.outputs.codex_output }}
|
||||
with:
|
||||
|
||||
16
.github/workflows/issue-labeler.yml
vendored
16
.github/workflows/issue-labeler.yml
vendored
@@ -17,9 +17,7 @@ jobs:
|
||||
outputs:
|
||||
codex_output: ${{ steps.codex.outputs.final-message }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- id: codex
|
||||
uses: openai/codex-action@5c3f4ccdb2b8790f73d6b21751ac00e602aa0c02 # v1.7
|
||||
@@ -46,7 +44,7 @@ jobs:
|
||||
6. iOS — Issues with the Codex iOS app.
|
||||
|
||||
- Additionally add zero or more of the following labels that are relevant to the issue content. Prefer a small set of precise labels over many broad ones.
|
||||
- For agent-area issues, prefer the most specific applicable label. Use "agent" only as a fallback for agent-related issues that do not fit a more specific agent-area label. Prefer "app-server" over "session" or "config" when the issue is about app-server protocol, API, RPC, schema, launch, or bridge behavior. Use "memory" for agentic memory storage/retrieval and "performance" for high process memory utilization or memory leaks.
|
||||
- For agent-area issues, prefer the most specific applicable label. Use "agent" only as a fallback for agent-related issues that do not fit a more specific agent-area label. Prefer "app-server" over "session" or "config" when the issue is about app-server protocol, API, RPC, schema, launch, or bridge behavior.
|
||||
1. windows-os — Bugs or friction specific to Windows environments (always when PowerShell is mentioned, path handling, copy/paste, OS-specific auth or tooling failures).
|
||||
2. mcp — Topics involving Model Context Protocol servers/clients.
|
||||
3. mcp-server — Problems related to the codex mcp-server command, where codex runs as an MCP server.
|
||||
@@ -70,15 +68,7 @@ jobs:
|
||||
21. session - Issues involving session or thread management, including resume, fork, archive, rename/title, thread history, rollout persistence, compaction, checkpoints, retention, and cross-session state.
|
||||
22. config - Issues involving config.toml, config keys, config key merging, config updates, profiles, hooks config, project config, agent role TOMLs, instruction/personality config, and config schema behavior.
|
||||
23. plan - Issues involving plan mode, planning workflows, or plan-specific tools/behavior.
|
||||
24. computer-use - Issues involving agentic computer use or SkyComputerUseService.
|
||||
25. browser - Issues involving agentic browser use, IAB, or the built-in browser within the Codex app.
|
||||
26. memory - Issues involving agentic memory storage and retrieval.
|
||||
27. imagen - Issues involving image generation.
|
||||
28. remote - Issues involving remote access, remote control, or SSH.
|
||||
29. performance - Issues involving slow, laggy performance, high memory utilization, or memory leaks.
|
||||
30. automations - Issues involving scheduled automation tasks or heartbeats.
|
||||
31. pets - Issues involving pets avatars and animations.
|
||||
32. agent - Fallback only for core agent loop or agent-related issues that do not fit app-server, connectivity, subagent, session, config, plan, computer-use, browser, memory, imagen, remote, performance, automations, or pets.
|
||||
24. agent - Fallback only for core agent loop or agent-related issues that do not fit app-server, connectivity, subagent, session, config, or plan.
|
||||
|
||||
Issue number: ${{ github.event.issue.number }}
|
||||
|
||||
|
||||
78
.github/workflows/rust-ci-full.yml
vendored
78
.github/workflows/rust-ci-full.yml
vendored
@@ -7,11 +7,6 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
# CI builds in debug (dev) for faster signal.
|
||||
env:
|
||||
# Cargo's libgit2 transport has been flaky on macOS when fetching git
|
||||
# dependencies with nested submodules. Use the system git CLI, which has
|
||||
# better network/proxy behavior and matches Cargo's own suggested fallback.
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
|
||||
|
||||
jobs:
|
||||
# --- CI that doesn't need specific targets ---------------------------------
|
||||
@@ -22,9 +17,7 @@ jobs:
|
||||
run:
|
||||
working-directory: codex-rs
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
with:
|
||||
components: rustfmt
|
||||
@@ -38,15 +31,14 @@ jobs:
|
||||
run:
|
||||
working-directory: codex-rs
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
|
||||
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
|
||||
with:
|
||||
tool: cargo-shear@1.11.2
|
||||
tool: cargo-shear
|
||||
version: 1.5.1
|
||||
- name: cargo shear
|
||||
run: cargo shear --deny-warnings
|
||||
run: cargo shear
|
||||
|
||||
argument_comment_lint_package:
|
||||
name: Argument comment lint package
|
||||
@@ -55,16 +47,14 @@ jobs:
|
||||
CARGO_DYLINT_VERSION: 5.0.0
|
||||
DYLINT_LINK_VERSION: 5.0.0
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
with:
|
||||
toolchain: nightly-2025-09-18
|
||||
components: llvm-tools-preview, rustc-dev, rust-src
|
||||
- name: Cache cargo-dylint tooling
|
||||
id: cargo_dylint_cache
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/cargo-dylint
|
||||
@@ -107,9 +97,7 @@ jobs:
|
||||
group: codex-runners
|
||||
labels: codex-windows-x64
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: ./.github/actions/setup-bazel-ci
|
||||
with:
|
||||
target: ${{ runner.os }}
|
||||
@@ -245,9 +233,7 @@ jobs:
|
||||
labels: codex-windows-arm64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- name: Install Linux build dependencies
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
shell: bash
|
||||
@@ -290,7 +276,7 @@ jobs:
|
||||
# avoid caching the large target dir on the gnu-dev job.
|
||||
- name: Restore cargo home cache
|
||||
id: cache_cargo_home_restore
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
@@ -308,7 +294,7 @@ jobs:
|
||||
# Install and restore sccache cache
|
||||
- name: Install sccache
|
||||
if: ${{ env.USE_SCCACHE == 'true' }}
|
||||
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
|
||||
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
|
||||
with:
|
||||
tool: sccache
|
||||
version: 0.7.5
|
||||
@@ -335,7 +321,7 @@ jobs:
|
||||
- name: Restore sccache cache (fallback)
|
||||
if: ${{ env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' }}
|
||||
id: cache_sccache_restore
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: ${{ github.workspace }}/.sccache/
|
||||
key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }}
|
||||
@@ -362,7 +348,7 @@ jobs:
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Restore APT cache (musl)
|
||||
id: cache_apt_restore
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: |
|
||||
/var/cache/apt
|
||||
@@ -370,7 +356,7 @@ jobs:
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Install Zig
|
||||
uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2.2.1
|
||||
uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2
|
||||
with:
|
||||
version: 0.14.0
|
||||
|
||||
@@ -444,7 +430,7 @@ jobs:
|
||||
|
||||
- name: Install cargo-chef
|
||||
if: ${{ matrix.profile == 'release' }}
|
||||
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
|
||||
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
|
||||
with:
|
||||
tool: cargo-chef
|
||||
version: 0.1.71
|
||||
@@ -463,7 +449,7 @@ jobs:
|
||||
|
||||
- name: Upload Cargo timings (clippy)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: cargo-timings-rust-ci-clippy-${{ matrix.target }}-${{ matrix.profile }}
|
||||
path: codex-rs/target/**/cargo-timings/cargo-timing.html
|
||||
@@ -474,7 +460,7 @@ jobs:
|
||||
- name: Save cargo home cache
|
||||
if: always() && !cancelled() && steps.cache_cargo_home_restore.outputs.cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
@@ -490,7 +476,7 @@ jobs:
|
||||
- name: Save sccache cache (fallback)
|
||||
if: always() && !cancelled() && env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: ${{ github.workspace }}/.sccache/
|
||||
key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }}
|
||||
@@ -515,7 +501,7 @@ jobs:
|
||||
- name: Save APT cache (musl)
|
||||
if: always() && !cancelled() && (matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl') && steps.cache_apt_restore.outputs.cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: |
|
||||
/var/cache/apt
|
||||
@@ -573,9 +559,7 @@ jobs:
|
||||
labels: codex-windows-arm64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- name: Install Linux build dependencies
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
shell: bash
|
||||
@@ -583,7 +567,7 @@ jobs:
|
||||
set -euo pipefail
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
sudo apt-get update -y
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev bubblewrap
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev
|
||||
fi
|
||||
|
||||
# Some integration tests rely on DotSlash being installed.
|
||||
@@ -606,7 +590,7 @@ jobs:
|
||||
|
||||
- name: Restore cargo home cache
|
||||
id: cache_cargo_home_restore
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
@@ -619,7 +603,7 @@ jobs:
|
||||
|
||||
- name: Install sccache
|
||||
if: ${{ env.USE_SCCACHE == 'true' }}
|
||||
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
|
||||
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
|
||||
with:
|
||||
tool: sccache
|
||||
version: 0.7.5
|
||||
@@ -646,7 +630,7 @@ jobs:
|
||||
- name: Restore sccache cache (fallback)
|
||||
if: ${{ env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' }}
|
||||
id: cache_sccache_restore
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: ${{ github.workspace }}/.sccache/
|
||||
key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }}
|
||||
@@ -654,7 +638,7 @@ jobs:
|
||||
sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-
|
||||
sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-
|
||||
|
||||
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
|
||||
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
|
||||
with:
|
||||
tool: nextest
|
||||
version: 0.9.103
|
||||
@@ -690,7 +674,7 @@ jobs:
|
||||
|
||||
- name: Upload Cargo timings (nextest)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: cargo-timings-rust-ci-nextest-${{ matrix.target }}-${{ matrix.profile }}
|
||||
path: codex-rs/target/**/cargo-timings/cargo-timing.html
|
||||
@@ -699,7 +683,7 @@ jobs:
|
||||
- name: Save cargo home cache
|
||||
if: always() && !cancelled() && steps.cache_cargo_home_restore.outputs.cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
@@ -711,7 +695,7 @@ jobs:
|
||||
- name: Save sccache cache (fallback)
|
||||
if: always() && !cancelled() && env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: ${{ github.workspace }}/.sccache/
|
||||
key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }}
|
||||
@@ -738,12 +722,10 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
set +e
|
||||
if [[ "${STEPS_TEST_OUTCOME}" != "success" ]]; then
|
||||
if [[ "${{ steps.test.outcome }}" != "success" ]]; then
|
||||
docker logs codex-remote-test-env || true
|
||||
fi
|
||||
docker rm -f codex-remote-test-env >/dev/null 2>&1 || true
|
||||
env:
|
||||
STEPS_TEST_OUTCOME: ${{ steps.test.outcome }}
|
||||
|
||||
- name: verify tests passed
|
||||
if: steps.test.outcome == 'failure'
|
||||
|
||||
46
.github/workflows/rust-ci.yml
vendored
46
.github/workflows/rust-ci.yml
vendored
@@ -14,11 +14,9 @@ jobs:
|
||||
codex: ${{ steps.detect.outputs.codex }}
|
||||
workflows: ${{ steps.detect.outputs.workflows }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Detect changed paths (no external action)
|
||||
id: detect
|
||||
shell: bash
|
||||
@@ -63,10 +61,7 @@ jobs:
|
||||
run:
|
||||
working-directory: codex-rs
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
with:
|
||||
components: rustfmt
|
||||
@@ -82,16 +77,14 @@ jobs:
|
||||
run:
|
||||
working-directory: codex-rs
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
|
||||
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
|
||||
with:
|
||||
tool: cargo-shear@1.11.2
|
||||
tool: cargo-shear
|
||||
version: 1.5.1
|
||||
- name: cargo shear
|
||||
run: cargo shear --deny-warnings
|
||||
run: cargo shear
|
||||
|
||||
argument_comment_lint_package:
|
||||
name: Argument comment lint package
|
||||
@@ -102,10 +95,7 @@ jobs:
|
||||
CARGO_DYLINT_VERSION: 5.0.0
|
||||
DYLINT_LINK_VERSION: 5.0.0
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
- name: Install nightly argument-comment-lint toolchain
|
||||
shell: bash
|
||||
@@ -119,7 +109,7 @@ jobs:
|
||||
rustup default nightly-2025-09-18
|
||||
- name: Cache cargo-dylint tooling
|
||||
id: cargo_dylint_cache
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/cargo-dylint
|
||||
@@ -180,11 +170,8 @@ jobs:
|
||||
|
||||
echo "No argument-comment-lint relevant changes."
|
||||
echo "run=false" >> "$GITHUB_OUTPUT"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
if: ${{ steps.argument_comment_lint_gate.outputs.run == 'true' }}
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
- name: Run argument comment lint on codex-rs via Bazel
|
||||
if: ${{ steps.argument_comment_lint_gate.outputs.run == 'true' }}
|
||||
uses: ./.github/actions/run-argument-comment-lint
|
||||
@@ -216,25 +203,20 @@ jobs:
|
||||
|
||||
# If nothing relevant changed (PR touching only root README, etc.),
|
||||
# declare success regardless of other jobs.
|
||||
if [[ "${NEEDS_CHANGED_OUTPUTS_ARGUMENT_COMMENT_LINT}" != 'true' && "${NEEDS_CHANGED_OUTPUTS_CODEX}" != 'true' && "${NEEDS_CHANGED_OUTPUTS_WORKFLOWS}" != 'true' ]]; then
|
||||
if [[ '${{ needs.changed.outputs.argument_comment_lint }}' != 'true' && '${{ needs.changed.outputs.codex }}' != 'true' && '${{ needs.changed.outputs.workflows }}' != 'true' ]]; then
|
||||
echo 'No relevant changes -> CI not required.'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "${NEEDS_CHANGED_OUTPUTS_ARGUMENT_COMMENT_LINT_PACKAGE}" == 'true' ]]; then
|
||||
if [[ '${{ needs.changed.outputs.argument_comment_lint_package }}' == 'true' ]]; then
|
||||
[[ '${{ needs.argument_comment_lint_package.result }}' == 'success' ]] || { echo 'argument_comment_lint_package failed'; exit 1; }
|
||||
fi
|
||||
|
||||
if [[ "${NEEDS_CHANGED_OUTPUTS_ARGUMENT_COMMENT_LINT}" == 'true' || "${NEEDS_CHANGED_OUTPUTS_WORKFLOWS}" == 'true' ]]; then
|
||||
if [[ '${{ needs.changed.outputs.argument_comment_lint }}' == 'true' || '${{ needs.changed.outputs.workflows }}' == 'true' ]]; then
|
||||
[[ '${{ needs.argument_comment_lint_prebuilt.result }}' == 'success' ]] || { echo 'argument_comment_lint_prebuilt failed'; exit 1; }
|
||||
fi
|
||||
|
||||
if [[ "${NEEDS_CHANGED_OUTPUTS_CODEX}" == 'true' || "${NEEDS_CHANGED_OUTPUTS_WORKFLOWS}" == 'true' ]]; then
|
||||
if [[ '${{ needs.changed.outputs.codex }}' == 'true' || '${{ needs.changed.outputs.workflows }}' == 'true' ]]; then
|
||||
[[ '${{ needs.general.result }}' == 'success' ]] || { echo 'general failed'; exit 1; }
|
||||
[[ '${{ needs.cargo_shear.result }}' == 'success' ]] || { echo 'cargo_shear failed'; exit 1; }
|
||||
fi
|
||||
env:
|
||||
NEEDS_CHANGED_OUTPUTS_ARGUMENT_COMMENT_LINT: ${{ needs.changed.outputs.argument_comment_lint }}
|
||||
NEEDS_CHANGED_OUTPUTS_CODEX: ${{ needs.changed.outputs.codex }}
|
||||
NEEDS_CHANGED_OUTPUTS_WORKFLOWS: ${{ needs.changed.outputs.workflows }}
|
||||
NEEDS_CHANGED_OUTPUTS_ARGUMENT_COMMENT_LINT_PACKAGE: ${{ needs.changed.outputs.argument_comment_lint_package }}
|
||||
|
||||
@@ -56,9 +56,7 @@ jobs:
|
||||
labels: codex-windows-x64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
with:
|
||||
@@ -102,7 +100,7 @@ jobs:
|
||||
(cd "${RUNNER_TEMP}" && tar -czf "$GITHUB_WORKSPACE/$archive_path" argument-comment-lint)
|
||||
fi
|
||||
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: argument-comment-lint-${{ matrix.target }}
|
||||
path: dist/argument-comment-lint/${{ matrix.target }}/*
|
||||
|
||||
5
.github/workflows/rust-release-prepare.yml
vendored
5
.github/workflows/rust-release-prepare.yml
vendored
@@ -18,11 +18,10 @@ jobs:
|
||||
if: github.repository == 'openai/codex'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Update models.json
|
||||
env:
|
||||
@@ -44,7 +43,7 @@ jobs:
|
||||
curl --http1.1 --fail --show-error --location "${headers[@]}" "${url}" | jq '.' > codex-rs/models-manager/models.json
|
||||
|
||||
- name: Open pull request (if changed)
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8
|
||||
with:
|
||||
commit-message: "Update models.json"
|
||||
title: "Update models.json"
|
||||
|
||||
20
.github/workflows/rust-release-windows.yml
vendored
20
.github/workflows/rust-release-windows.yml
vendored
@@ -83,9 +83,7 @@ jobs:
|
||||
labels: codex-windows-arm64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- name: Print runner specs (Windows)
|
||||
shell: powershell
|
||||
run: |
|
||||
@@ -114,7 +112,7 @@ jobs:
|
||||
cargo build --target ${{ matrix.target }} --release --timings "${build_args[@]}"
|
||||
|
||||
- name: Upload Cargo timings
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: cargo-timings-rust-release-windows-${{ matrix.target }}-${{ matrix.bundle }}
|
||||
path: codex-rs/target/**/cargo-timings/cargo-timing.html
|
||||
@@ -130,7 +128,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Upload Windows binaries
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: windows-binaries-${{ matrix.target }}-${{ matrix.bundle }}
|
||||
path: |
|
||||
@@ -167,24 +165,22 @@ jobs:
|
||||
labels: codex-windows-arm64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Download prebuilt Windows primary binaries
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
name: windows-binaries-${{ matrix.target }}-primary
|
||||
path: codex-rs/target/${{ matrix.target }}/release
|
||||
|
||||
- name: Download prebuilt Windows helper binaries
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
name: windows-binaries-${{ matrix.target }}-helpers
|
||||
path: codex-rs/target/${{ matrix.target }}/release
|
||||
|
||||
- name: Download prebuilt Windows app-server binary
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
name: windows-binaries-${{ matrix.target }}-app-server
|
||||
path: codex-rs/target/${{ matrix.target }}/release
|
||||
@@ -285,7 +281,7 @@ jobs:
|
||||
"${GITHUB_WORKSPACE}/.github/workflows/zstd" -T0 -19 "$dest/$base"
|
||||
done
|
||||
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: |
|
||||
|
||||
12
.github/workflows/rust-release-zsh.yml
vendored
12
.github/workflows/rust-release-zsh.yml
vendored
@@ -45,9 +45,7 @@ jobs:
|
||||
git \
|
||||
libncursesw5-dev
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Build, smoke-test, and stage zsh artifact
|
||||
shell: bash
|
||||
@@ -55,7 +53,7 @@ jobs:
|
||||
"${GITHUB_WORKSPACE}/.github/scripts/build-zsh-release-artifact.sh" \
|
||||
"dist/zsh/${{ matrix.target }}/${{ matrix.archive_name }}"
|
||||
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: codex-zsh-${{ matrix.target }}
|
||||
path: dist/zsh/${{ matrix.target }}/*
|
||||
@@ -83,9 +81,7 @@ jobs:
|
||||
brew install autoconf
|
||||
fi
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Build, smoke-test, and stage zsh artifact
|
||||
shell: bash
|
||||
@@ -93,7 +89,7 @@ jobs:
|
||||
"${GITHUB_WORKSPACE}/.github/scripts/build-zsh-release-artifact.sh" \
|
||||
"dist/zsh/${{ matrix.target }}/${{ matrix.archive_name }}"
|
||||
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: codex-zsh-${{ matrix.target }}
|
||||
path: dist/zsh/${{ matrix.target }}/*
|
||||
|
||||
66
.github/workflows/rust-release.yml
vendored
66
.github/workflows/rust-release.yml
vendored
@@ -19,9 +19,7 @@ jobs:
|
||||
tag-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
- name: Validate tag matches Cargo.toml version
|
||||
shell: bash
|
||||
@@ -98,7 +96,7 @@ jobs:
|
||||
target: x86_64-unknown-linux-musl
|
||||
bundle: primary
|
||||
artifact_name: x86_64-unknown-linux-musl
|
||||
binaries: "codex codex-responses-api-proxy bwrap"
|
||||
binaries: "codex codex-responses-api-proxy"
|
||||
build_dmg: "false"
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-musl
|
||||
@@ -110,7 +108,7 @@ jobs:
|
||||
target: aarch64-unknown-linux-musl
|
||||
bundle: primary
|
||||
artifact_name: aarch64-unknown-linux-musl
|
||||
binaries: "codex codex-responses-api-proxy bwrap"
|
||||
binaries: "codex codex-responses-api-proxy"
|
||||
build_dmg: "false"
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
@@ -120,9 +118,7 @@ jobs:
|
||||
build_dmg: "false"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- name: Print runner specs (Linux)
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
shell: bash
|
||||
@@ -185,10 +181,9 @@ jobs:
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Install Zig
|
||||
uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2.2.1
|
||||
uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2
|
||||
with:
|
||||
version: 0.14.0
|
||||
use-cache: false
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Install musl build tools
|
||||
@@ -260,24 +255,6 @@ jobs:
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- if: ${{ contains(matrix.target, 'linux') && matrix.bundle == 'primary' }}
|
||||
name: Build bwrap and export digest
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
target="${{ matrix.target }}"
|
||||
cargo build --target "$target" --release --timings --bin bwrap
|
||||
|
||||
bwrap_path="target/${target}/release/bwrap"
|
||||
if [[ ! -f "$bwrap_path" ]]; then
|
||||
echo "bwrap binary ${bwrap_path} not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
digest="$(sha256sum "$bwrap_path" | awk '{print $1}')"
|
||||
echo "CODEX_BWRAP_SHA256=${digest}" >> "$GITHUB_ENV"
|
||||
echo "Built bwrap ${bwrap_path} with sha256:${digest}"
|
||||
|
||||
- name: Cargo build
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -289,7 +266,7 @@ jobs:
|
||||
cargo build --target ${{ matrix.target }} --release --timings "${build_args[@]}"
|
||||
|
||||
- name: Upload Cargo timings
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: cargo-timings-rust-release-${{ matrix.target }}-${{ matrix.bundle }}
|
||||
path: codex-rs/target/**/cargo-timings/cargo-timing.html
|
||||
@@ -384,17 +361,6 @@ jobs:
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "${{ matrix.target }}" == *linux* && "${{ matrix.bundle }}" == "primary" ]]; then
|
||||
bundle_root="${RUNNER_TEMP}/codex-${{ matrix.target }}-bundle"
|
||||
rm -rf "$bundle_root"
|
||||
mkdir -p "$bundle_root/codex-resources"
|
||||
cp "$dest/codex-${{ matrix.target }}" "$bundle_root/codex"
|
||||
cp "$dest/bwrap-${{ matrix.target }}" "$bundle_root/codex-resources/bwrap"
|
||||
chmod 0755 "$bundle_root/codex" "$bundle_root/codex-resources/bwrap"
|
||||
tar -C "$bundle_root" -cf - codex codex-resources/bwrap |
|
||||
zstd -T0 -19 -o "$dest/codex-${{ matrix.target }}-bundle.tar.zst"
|
||||
fi
|
||||
|
||||
if [[ "${{ matrix.build_dmg }}" == "true" ]]; then
|
||||
cp target/${{ matrix.target }}/release/codex-${{ matrix.target }}.dmg "$dest/codex-${{ matrix.target }}.dmg"
|
||||
fi
|
||||
@@ -418,7 +384,7 @@ jobs:
|
||||
base="$(basename "$f")"
|
||||
# Skip files that are already archives (shouldn't happen, but be
|
||||
# safe).
|
||||
if [[ "$base" == *.tar.gz || "$base" == *.tar.zst || "$base" == *.zip || "$base" == *.dmg ]]; then
|
||||
if [[ "$base" == *.tar.gz || "$base" == *.zip || "$base" == *.dmg ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
@@ -435,11 +401,11 @@ jobs:
|
||||
zstd -T0 -19 --rm "$dest/$base"
|
||||
done
|
||||
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
# Upload the per-binary .zst files, .tar.gz equivalents, and any
|
||||
# prebuilt archives staged above.
|
||||
# Upload the per-binary .zst files as well as the new .tar.gz
|
||||
# equivalents we generated in the previous step.
|
||||
path: |
|
||||
codex-rs/dist/${{ matrix.target }}/*
|
||||
|
||||
@@ -481,9 +447,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Generate release notes from tag commit message
|
||||
id: release_notes
|
||||
@@ -505,7 +469,7 @@ jobs:
|
||||
|
||||
echo "path=${notes_path}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
path: dist
|
||||
|
||||
@@ -560,7 +524,7 @@ jobs:
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node.js for npm packaging
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
@@ -586,7 +550,7 @@ jobs:
|
||||
cp scripts/install/install.ps1 dist/install.ps1
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
|
||||
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2
|
||||
with:
|
||||
name: ${{ steps.release_name.outputs.name }}
|
||||
tag_name: ${{ github.ref_name }}
|
||||
@@ -645,7 +609,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
# Node 24 bundles npm >= 11.5.1, which trusted publishing requires.
|
||||
node-version: 24
|
||||
|
||||
57
.github/workflows/rusty-v8-release.yml
vendored
57
.github/workflows/rusty-v8-release.yml
vendored
@@ -1,12 +1,20 @@
|
||||
name: rusty-v8-release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "rusty-v8-v*.*.*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: Optional release tag. Defaults to rusty-v8-v<resolved_v8_version>.
|
||||
required: false
|
||||
type: string
|
||||
publish:
|
||||
description: Publish the staged musl artifacts to a GitHub release.
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}::${{ github.ref_name }}
|
||||
group: ${{ github.workflow }}::${{ inputs.release_tag || github.run_id }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
@@ -17,12 +25,10 @@ jobs:
|
||||
v8_version: ${{ steps.v8_version.outputs.version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
@@ -37,17 +43,15 @@ jobs:
|
||||
- name: Resolve release tag
|
||||
id: release_tag
|
||||
env:
|
||||
GITHUB_REF_NAME: ${{ github.ref_name }}
|
||||
RELEASE_TAG_INPUT: ${{ inputs.release_tag }}
|
||||
V8_VERSION: ${{ steps.v8_version.outputs.version }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
expected_release_tag="rusty-v8-v${V8_VERSION}"
|
||||
release_tag="${GITHUB_REF_NAME}"
|
||||
if [[ "${release_tag}" != "${expected_release_tag}" ]]; then
|
||||
echo "Tag ${release_tag} does not match resolved v8 crate version ${V8_VERSION}." >&2
|
||||
exit 1
|
||||
release_tag="${RELEASE_TAG_INPUT}"
|
||||
if [[ -z "${release_tag}" ]]; then
|
||||
release_tag="rusty-v8-v${V8_VERSION}"
|
||||
fi
|
||||
|
||||
echo "release_tag=${release_tag}" >> "$GITHUB_OUTPUT"
|
||||
@@ -71,9 +75,7 @@ jobs:
|
||||
target: aarch64-unknown-linux-musl
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Set up Bazel
|
||||
uses: ./.github/actions/setup-bazel-ci
|
||||
@@ -81,7 +83,7 @@ jobs:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
@@ -109,7 +111,6 @@ jobs:
|
||||
-c
|
||||
opt
|
||||
"--platforms=@llvm//platforms:${PLATFORM}"
|
||||
--config=v8-release-compat
|
||||
"${pair_target}"
|
||||
"${extra_targets[@]}"
|
||||
--build_metadata=COMMIT_SHA=$(git rev-parse HEAD)
|
||||
@@ -133,16 +134,16 @@ jobs:
|
||||
--platform "${PLATFORM}" \
|
||||
--target "${TARGET}" \
|
||||
--compilation-mode opt \
|
||||
--bazel-config v8-release-compat \
|
||||
--output-dir "dist/${TARGET}"
|
||||
|
||||
- name: Upload staged musl artifacts
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: rusty-v8-${{ needs.metadata.outputs.v8_version }}-${{ matrix.target }}
|
||||
path: dist/${{ matrix.target }}/*
|
||||
|
||||
publish-release:
|
||||
if: ${{ inputs.publish }}
|
||||
needs:
|
||||
- metadata
|
||||
- build
|
||||
@@ -152,6 +153,16 @@ jobs:
|
||||
actions: read
|
||||
|
||||
steps:
|
||||
- name: Ensure publishing from default branch
|
||||
if: ${{ github.ref_name != github.event.repository.default_branch }}
|
||||
env:
|
||||
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "Publishing is only allowed from ${DEFAULT_BRANCH}; current ref is ${GITHUB_REF_NAME}." >&2
|
||||
exit 1
|
||||
|
||||
- name: Ensure release tag is new
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -165,12 +176,12 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
path: dist
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
|
||||
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2
|
||||
with:
|
||||
tag_name: ${{ needs.metadata.outputs.release_tag }}
|
||||
name: ${{ needs.metadata.outputs.release_tag }}
|
||||
|
||||
9
.github/workflows/sdk.yml
vendored
9
.github/workflows/sdk.yml
vendored
@@ -13,10 +13,7 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Install Linux bwrap build dependencies
|
||||
shell: bash
|
||||
@@ -31,7 +28,7 @@ jobs:
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
@@ -118,7 +115,7 @@ jobs:
|
||||
- name: Save bazel repository cache
|
||||
if: always() && !cancelled() && steps.setup_bazel.outputs.cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: |
|
||||
~/.cache/bazel-repo-cache
|
||||
|
||||
18
.github/workflows/v8-canary.yml
vendored
18
.github/workflows/v8-canary.yml
vendored
@@ -40,13 +40,10 @@ jobs:
|
||||
v8_version: ${{ steps.v8_version.outputs.version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
@@ -77,10 +74,7 @@ jobs:
|
||||
target: aarch64-unknown-linux-musl
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Set up Bazel
|
||||
uses: ./.github/actions/setup-bazel-ci
|
||||
@@ -88,7 +82,7 @@ jobs:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
@@ -111,7 +105,6 @@ jobs:
|
||||
bazel_args=(
|
||||
build
|
||||
"--platforms=@llvm//platforms:${PLATFORM}"
|
||||
--config=v8-release-compat
|
||||
"${pair_target}"
|
||||
"${extra_targets[@]}"
|
||||
--build_metadata=COMMIT_SHA=$(git rev-parse HEAD)
|
||||
@@ -134,11 +127,10 @@ jobs:
|
||||
python3 .github/scripts/rusty_v8_bazel.py stage-release-pair \
|
||||
--platform "${PLATFORM}" \
|
||||
--target "${TARGET}" \
|
||||
--bazel-config v8-release-compat \
|
||||
--output-dir "dist/${TARGET}"
|
||||
|
||||
- name: Upload staged musl artifacts
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: v8-canary-${{ needs.metadata.outputs.v8_version }}-${{ matrix.target }}
|
||||
path: dist/${{ matrix.target }}/*
|
||||
|
||||
@@ -26,7 +26,7 @@ In the codex-rs folder where the rust code lives:
|
||||
- Implementations may still use `async fn foo(&self, ...) -> T` when they satisfy that contract.
|
||||
- Do not use `#[allow(async_fn_in_trait)]` as a shortcut around spelling the future contract explicitly.
|
||||
- When writing tests, prefer comparing the equality of entire objects over fields one by one.
|
||||
- Do not add general product or user-facing documentation to the `docs/` folder. The official Codex documentation lives elsewhere. The exception is app-server API documentation, which is covered by the app-server guidance below.
|
||||
- When making a change that adds or changes an API, ensure that the documentation in the `docs/` folder is up to date if applicable.
|
||||
- Prefer private modules and explicitly exported public crate API.
|
||||
- If you change `ConfigToml` or nested config types, run `just write-config-schema` to update `codex-rs/core/config.schema.json`.
|
||||
- When working with MCP tool calls, prefer using `codex-rs/codex-mcp/src/mcp_connection_manager.rs` to handle mutation of tools and tool calls. Aim to minimize the footprint of changes and leverage existing abstractions rather than plumbing code through multiple levels of function calls.
|
||||
@@ -130,7 +130,7 @@ When UI or text output changes intentionally, update the snapshots as follows:
|
||||
|
||||
If you don’t have the tool:
|
||||
|
||||
- `cargo install --locked cargo-insta`
|
||||
- `cargo install cargo-insta`
|
||||
|
||||
### Test assertions
|
||||
|
||||
@@ -210,7 +210,7 @@ These guidelines apply to app-server protocol work in `codex-rs`, especially:
|
||||
|
||||
### Development Workflow
|
||||
|
||||
- Update app-server docs/examples when API behavior changes (at minimum `app-server/README.md`).
|
||||
- Update docs/examples when API behavior changes (at minimum `app-server/README.md`).
|
||||
- Regenerate schema fixtures when API shapes change:
|
||||
`just write-app-server-schema`
|
||||
(and `just write-app-server-schema --experimental` when experimental API fixtures are affected).
|
||||
|
||||
12
MODULE.bazel
12
MODULE.bazel
@@ -327,18 +327,6 @@ crate.annotation(
|
||||
"RUSTY_V8_SRC_BINDING_PATH": "$(execpath @v8_targets//:rusty_v8_binding_for_target)",
|
||||
},
|
||||
crate = "v8",
|
||||
# Keep the Rust feature aligned with the source-built Bazel artifacts.
|
||||
# Windows MSVC still consumes upstream non-sandboxed prebuilts.
|
||||
crate_features_select = {
|
||||
"aarch64-apple-darwin": ["v8_enable_sandbox"],
|
||||
"aarch64-pc-windows-gnullvm": ["v8_enable_sandbox"],
|
||||
"aarch64-unknown-linux-gnu": ["v8_enable_sandbox"],
|
||||
"aarch64-unknown-linux-musl": ["v8_enable_sandbox"],
|
||||
"x86_64-apple-darwin": ["v8_enable_sandbox"],
|
||||
"x86_64-pc-windows-gnullvm": ["v8_enable_sandbox"],
|
||||
"x86_64-unknown-linux-gnu": ["v8_enable_sandbox"],
|
||||
"x86_64-unknown-linux-musl": ["v8_enable_sandbox"],
|
||||
},
|
||||
gen_build_script = "on",
|
||||
patch_args = ["-p1"],
|
||||
patches = [
|
||||
|
||||
1
MODULE.bazel.lock
generated
1
MODULE.bazel.lock
generated
@@ -665,7 +665,6 @@
|
||||
"aws-lc-rs_1.16.2": "{\"dependencies\":[{\"name\":\"aws-lc-fips-sys\",\"optional\":true,\"req\":\"^0.13.1\"},{\"default_features\":false,\"name\":\"aws-lc-sys\",\"optional\":true,\"req\":\"^0.39.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"^4.4\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1.5.0\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1.0.15\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.11.1\"},{\"name\":\"untrusted\",\"optional\":true,\"req\":\"^0.7.1\"},{\"name\":\"zeroize\",\"req\":\"^1.8.1\"}],\"features\":{\"alloc\":[],\"asan\":[\"aws-lc-sys?/asan\",\"aws-lc-fips-sys?/asan\"],\"bindgen\":[\"aws-lc-sys?/bindgen\",\"aws-lc-fips-sys?/bindgen\"],\"default\":[\"aws-lc-sys\",\"alloc\",\"ring-io\",\"ring-sig-verify\"],\"dev-tests-only\":[],\"fips\":[\"dep:aws-lc-fips-sys\"],\"non-fips\":[\"aws-lc-sys\"],\"prebuilt-nasm\":[\"aws-lc-sys?/prebuilt-nasm\"],\"ring-io\":[\"dep:untrusted\"],\"ring-sig-verify\":[\"dep:untrusted\"],\"test_logging\":[],\"unstable\":[]}}",
|
||||
"aws-lc-sys_0.39.0": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"bindgen\",\"optional\":true,\"req\":\"^0.72.0\"},{\"features\":[\"parallel\"],\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.2.26\"},{\"kind\":\"build\",\"name\":\"cmake\",\"req\":\"^0.1.54\"},{\"kind\":\"build\",\"name\":\"dunce\",\"req\":\"^1.0.5\"},{\"kind\":\"build\",\"name\":\"fs_extra\",\"req\":\"^1.3.0\"}],\"features\":{\"all-bindings\":[],\"asan\":[],\"bindgen\":[\"dep:bindgen\"],\"default\":[\"all-bindings\"],\"disable-prebuilt-nasm\":[],\"fips\":[\"dep:bindgen\"],\"prebuilt-nasm\":[],\"ssl\":[\"bindgen\",\"all-bindings\"]}}",
|
||||
"aws-runtime_1.5.17": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"arbitrary\",\"req\":\"^1.3\"},{\"name\":\"aws-credential-types\",\"req\":\"^1.2.11\"},{\"features\":[\"test-util\"],\"kind\":\"dev\",\"name\":\"aws-credential-types\",\"req\":\"^1.2.11\"},{\"features\":[\"http0-compat\"],\"name\":\"aws-sigv4\",\"req\":\"^1.3.7\"},{\"name\":\"aws-smithy-async\",\"req\":\"^1.2.7\"},{\"features\":[\"test-util\"],\"kind\":\"dev\",\"name\":\"aws-smithy-async\",\"req\":\"^1.2.7\"},{\"name\":\"aws-smithy-eventstream\",\"optional\":true,\"req\":\"^0.60.14\"},{\"name\":\"aws-smithy-http\",\"req\":\"^0.62.6\"},{\"kind\":\"dev\",\"name\":\"aws-smithy-protocol-test\",\"req\":\"^0.63.7\"},{\"features\":[\"client\"],\"name\":\"aws-smithy-runtime\",\"req\":\"^1.9.5\"},{\"features\":[\"client\"],\"name\":\"aws-smithy-runtime-api\",\"req\":\"^1.9.3\"},{\"features\":[\"test-util\"],\"kind\":\"dev\",\"name\":\"aws-smithy-runtime-api\",\"req\":\"^1.9.3\"},{\"name\":\"aws-smithy-types\",\"req\":\"^1.3.5\"},{\"features\":[\"test-util\"],\"kind\":\"dev\",\"name\":\"aws-smithy-types\",\"req\":\"^1.3.5\"},{\"name\":\"aws-types\",\"req\":\"^1.3.11\"},{\"name\":\"bytes\",\"req\":\"^1.10.0\"},{\"kind\":\"dev\",\"name\":\"bytes-utils\",\"req\":\"^0.1.2\"},{\"kind\":\"dev\",\"name\":\"convert_case\",\"req\":\"^0.6.0\"},{\"name\":\"fastrand\",\"req\":\"^2.3.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.29\"},{\"name\":\"http-02x\",\"package\":\"http\",\"req\":\"^0.2.9\"},{\"name\":\"http-1x\",\"optional\":true,\"package\":\"http\",\"req\":\"^1.1.0\"},{\"name\":\"http-body-04x\",\"package\":\"http-body\",\"req\":\"^0.4.5\"},{\"name\":\"http-body-1x\",\"optional\":true,\"package\":\"http-body\",\"req\":\"^1.0.0\"},{\"name\":\"percent-encoding\",\"req\":\"^2.3.1\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.14\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.2\"},{\"name\":\"regex-lite\",\"optional\":true,\"req\":\"^0.1.5\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"},{\"features\":[\"macros\",\"rt\",\"time\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.23.1\"},{\"name\":\"tracing\",\"req\":\"^0.1.40\"},{\"features\":[\"env-filter\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3.17\"},{\"kind\":\"dev\",\"name\":\"tracing-test\",\"req\":\"^0.2.4\"},{\"name\":\"uuid\",\"req\":\"^1\"}],\"features\":{\"event-stream\":[\"dep:aws-smithy-eventstream\",\"aws-sigv4/sign-eventstream\"],\"http-02x\":[],\"http-1x\":[\"dep:http-1x\",\"dep:http-body-1x\"],\"sigv4a\":[\"aws-sigv4/sigv4a\"],\"test-util\":[\"dep:regex-lite\"]}}",
|
||||
"aws-sdk-signin_1.2.0": "{\"dependencies\":[{\"name\":\"aws-credential-types\",\"req\":\"^1.2.11\"},{\"features\":[\"test-util\"],\"kind\":\"dev\",\"name\":\"aws-credential-types\",\"req\":\"^1.2.11\"},{\"name\":\"aws-runtime\",\"req\":\"^1.5.17\"},{\"name\":\"aws-smithy-async\",\"req\":\"^1.2.7\"},{\"name\":\"aws-smithy-http\",\"req\":\"^0.62.6\"},{\"name\":\"aws-smithy-json\",\"req\":\"^0.61.8\"},{\"features\":[\"client\"],\"name\":\"aws-smithy-runtime\",\"req\":\"^1.9.5\"},{\"features\":[\"client\",\"http-02x\"],\"name\":\"aws-smithy-runtime-api\",\"req\":\"^1.9.3\"},{\"name\":\"aws-smithy-types\",\"req\":\"^1.3.5\"},{\"name\":\"aws-types\",\"req\":\"^1.3.11\"},{\"name\":\"bytes\",\"req\":\"^1.4.0\"},{\"name\":\"fastrand\",\"req\":\"^2.0.0\"},{\"name\":\"http\",\"req\":\"^0.2.9\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\"},{\"name\":\"regex-lite\",\"req\":\"^0.1.5\"},{\"features\":[\"macros\",\"test-util\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.23.1\"},{\"name\":\"tracing\",\"req\":\"^0.1\"}],\"features\":{\"behavior-version-latest\":[],\"default\":[\"rustls\",\"default-https-client\",\"rt-tokio\"],\"default-https-client\":[\"aws-smithy-runtime/default-https-client\"],\"gated-tests\":[],\"rt-tokio\":[\"aws-smithy-async/rt-tokio\",\"aws-smithy-types/rt-tokio\"],\"rustls\":[\"aws-smithy-runtime/tls-rustls\"],\"test-util\":[\"aws-credential-types/test-util\",\"aws-smithy-runtime/test-util\"]}}",
|
||||
"aws-sdk-sso_1.91.0": "{\"dependencies\":[{\"name\":\"aws-credential-types\",\"req\":\"^1.2.11\"},{\"features\":[\"test-util\"],\"kind\":\"dev\",\"name\":\"aws-credential-types\",\"req\":\"^1.2.11\"},{\"name\":\"aws-runtime\",\"req\":\"^1.5.17\"},{\"name\":\"aws-smithy-async\",\"req\":\"^1.2.7\"},{\"name\":\"aws-smithy-http\",\"req\":\"^0.62.6\"},{\"name\":\"aws-smithy-json\",\"req\":\"^0.61.8\"},{\"features\":[\"client\"],\"name\":\"aws-smithy-runtime\",\"req\":\"^1.9.5\"},{\"features\":[\"client\",\"http-02x\"],\"name\":\"aws-smithy-runtime-api\",\"req\":\"^1.9.3\"},{\"name\":\"aws-smithy-types\",\"req\":\"^1.3.5\"},{\"name\":\"aws-types\",\"req\":\"^1.3.11\"},{\"name\":\"bytes\",\"req\":\"^1.4.0\"},{\"name\":\"fastrand\",\"req\":\"^2.0.0\"},{\"name\":\"http\",\"req\":\"^0.2.9\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\"},{\"name\":\"regex-lite\",\"req\":\"^0.1.5\"},{\"features\":[\"macros\",\"test-util\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.23.1\"},{\"name\":\"tracing\",\"req\":\"^0.1\"}],\"features\":{\"behavior-version-latest\":[],\"default\":[\"rustls\",\"default-https-client\",\"rt-tokio\"],\"default-https-client\":[\"aws-smithy-runtime/default-https-client\"],\"gated-tests\":[],\"rt-tokio\":[\"aws-smithy-async/rt-tokio\",\"aws-smithy-types/rt-tokio\"],\"rustls\":[\"aws-smithy-runtime/tls-rustls\"],\"test-util\":[\"aws-credential-types/test-util\",\"aws-smithy-runtime/test-util\"]}}",
|
||||
"aws-sdk-ssooidc_1.93.0": "{\"dependencies\":[{\"name\":\"aws-credential-types\",\"req\":\"^1.2.11\"},{\"features\":[\"test-util\"],\"kind\":\"dev\",\"name\":\"aws-credential-types\",\"req\":\"^1.2.11\"},{\"name\":\"aws-runtime\",\"req\":\"^1.5.17\"},{\"name\":\"aws-smithy-async\",\"req\":\"^1.2.7\"},{\"name\":\"aws-smithy-http\",\"req\":\"^0.62.6\"},{\"name\":\"aws-smithy-json\",\"req\":\"^0.61.8\"},{\"features\":[\"client\"],\"name\":\"aws-smithy-runtime\",\"req\":\"^1.9.5\"},{\"features\":[\"client\",\"http-02x\"],\"name\":\"aws-smithy-runtime-api\",\"req\":\"^1.9.3\"},{\"name\":\"aws-smithy-types\",\"req\":\"^1.3.5\"},{\"name\":\"aws-types\",\"req\":\"^1.3.11\"},{\"name\":\"bytes\",\"req\":\"^1.4.0\"},{\"name\":\"fastrand\",\"req\":\"^2.0.0\"},{\"name\":\"http\",\"req\":\"^0.2.9\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\"},{\"name\":\"regex-lite\",\"req\":\"^0.1.5\"},{\"features\":[\"macros\",\"test-util\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.23.1\"},{\"name\":\"tracing\",\"req\":\"^0.1\"}],\"features\":{\"behavior-version-latest\":[],\"default\":[\"rustls\",\"default-https-client\",\"rt-tokio\"],\"default-https-client\":[\"aws-smithy-runtime/default-https-client\"],\"gated-tests\":[],\"rt-tokio\":[\"aws-smithy-async/rt-tokio\",\"aws-smithy-types/rt-tokio\"],\"rustls\":[\"aws-smithy-runtime/tls-rustls\"],\"test-util\":[\"aws-credential-types/test-util\",\"aws-smithy-runtime/test-util\"]}}",
|
||||
"aws-sdk-sts_1.95.0": "{\"dependencies\":[{\"name\":\"aws-credential-types\",\"req\":\"^1.2.11\"},{\"features\":[\"test-util\"],\"kind\":\"dev\",\"name\":\"aws-credential-types\",\"req\":\"^1.2.11\"},{\"name\":\"aws-runtime\",\"req\":\"^1.5.17\"},{\"features\":[\"test-util\"],\"kind\":\"dev\",\"name\":\"aws-runtime\",\"req\":\"^1.5.17\"},{\"name\":\"aws-smithy-async\",\"req\":\"^1.2.7\"},{\"features\":[\"test-util\"],\"kind\":\"dev\",\"name\":\"aws-smithy-async\",\"req\":\"^1.2.7\"},{\"name\":\"aws-smithy-http\",\"req\":\"^0.62.6\"},{\"features\":[\"test-util\",\"wire-mock\"],\"kind\":\"dev\",\"name\":\"aws-smithy-http-client\",\"req\":\"^1.1.5\"},{\"name\":\"aws-smithy-json\",\"req\":\"^0.61.8\"},{\"kind\":\"dev\",\"name\":\"aws-smithy-protocol-test\",\"req\":\"^0.63.7\"},{\"name\":\"aws-smithy-query\",\"req\":\"^0.60.9\"},{\"features\":[\"client\"],\"name\":\"aws-smithy-runtime\",\"req\":\"^1.9.5\"},{\"features\":[\"test-util\"],\"kind\":\"dev\",\"name\":\"aws-smithy-runtime\",\"req\":\"^1.9.5\"},{\"features\":[\"client\",\"http-02x\"],\"name\":\"aws-smithy-runtime-api\",\"req\":\"^1.9.3\"},{\"features\":[\"test-util\"],\"kind\":\"dev\",\"name\":\"aws-smithy-runtime-api\",\"req\":\"^1.9.3\"},{\"name\":\"aws-smithy-types\",\"req\":\"^1.3.5\"},{\"features\":[\"test-util\"],\"kind\":\"dev\",\"name\":\"aws-smithy-types\",\"req\":\"^1.3.5\"},{\"name\":\"aws-smithy-xml\",\"req\":\"^0.60.13\"},{\"name\":\"aws-types\",\"req\":\"^1.3.11\"},{\"name\":\"fastrand\",\"req\":\"^2.0.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.25\"},{\"name\":\"http\",\"req\":\"^0.2.9\"},{\"kind\":\"dev\",\"name\":\"http-1x\",\"package\":\"http\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\"},{\"name\":\"regex-lite\",\"req\":\"^0.1.5\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.0\"},{\"features\":[\"macros\",\"test-util\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.23.1\"},{\"name\":\"tracing\",\"req\":\"^0.1\"},{\"features\":[\"env-filter\",\"json\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3.16\"}],\"features\":{\"behavior-version-latest\":[],\"default\":[\"rustls\",\"default-https-client\",\"rt-tokio\"],\"default-https-client\":[\"aws-smithy-runtime/default-https-client\"],\"gated-tests\":[],\"rt-tokio\":[\"aws-smithy-async/rt-tokio\",\"aws-smithy-types/rt-tokio\"],\"rustls\":[\"aws-smithy-runtime/tls-rustls\"],\"test-util\":[\"aws-credential-types/test-util\",\"aws-smithy-runtime/test-util\"]}}",
|
||||
|
||||
@@ -69,8 +69,8 @@ PACKAGE_EXPANSIONS: dict[str, list[str]] = {
|
||||
|
||||
PACKAGE_NATIVE_COMPONENTS: dict[str, list[str]] = {
|
||||
"codex": [],
|
||||
"codex-linux-x64": ["bwrap", "codex", "rg"],
|
||||
"codex-linux-arm64": ["bwrap", "codex", "rg"],
|
||||
"codex-linux-x64": ["codex", "rg"],
|
||||
"codex-linux-arm64": ["codex", "rg"],
|
||||
"codex-darwin-x64": ["codex", "rg"],
|
||||
"codex-darwin-arm64": ["codex", "rg"],
|
||||
"codex-win32-x64": ["codex", "rg", "codex-windows-sandbox-setup", "codex-command-runner"],
|
||||
@@ -87,7 +87,6 @@ PACKAGE_TARGET_FILTERS: dict[str, str] = {
|
||||
PACKAGE_CHOICES = tuple(PACKAGE_NATIVE_COMPONENTS)
|
||||
|
||||
COMPONENT_DEST_DIR: dict[str, str] = {
|
||||
"bwrap": "codex-resources",
|
||||
"codex": "codex",
|
||||
"codex-responses-api-proxy": "codex-responses-api-proxy",
|
||||
"codex-windows-sandbox-setup": "codex",
|
||||
@@ -138,16 +137,6 @@ def parse_args() -> argparse.Namespace:
|
||||
type=Path,
|
||||
help="Directory containing pre-installed native binaries to bundle (vendor root).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--allow-missing-native-component",
|
||||
dest="allow_missing_native_components",
|
||||
action="append",
|
||||
default=[],
|
||||
help=(
|
||||
"Native component that may be absent from --vendor-src. Intended for CI "
|
||||
"compatibility with older artifact workflows; releases should not use this."
|
||||
),
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
@@ -188,7 +177,6 @@ def main() -> int:
|
||||
staging_dir,
|
||||
native_components,
|
||||
target_filter={target_filter} if target_filter else None,
|
||||
allow_missing_components=set(args.allow_missing_native_components),
|
||||
)
|
||||
|
||||
if release_version:
|
||||
@@ -377,14 +365,12 @@ def copy_native_binaries(
|
||||
staging_dir: Path,
|
||||
components: list[str],
|
||||
target_filter: set[str] | None = None,
|
||||
allow_missing_components: set[str] | None = None,
|
||||
) -> None:
|
||||
vendor_src = vendor_src.resolve()
|
||||
if not vendor_src.exists():
|
||||
raise RuntimeError(f"Vendor source directory not found: {vendor_src}")
|
||||
|
||||
components_set = {component for component in components if component in COMPONENT_DEST_DIR}
|
||||
allow_missing_components = allow_missing_components or set()
|
||||
if not components_set:
|
||||
return
|
||||
|
||||
@@ -413,8 +399,6 @@ def copy_native_binaries(
|
||||
|
||||
src_component_dir = target_dir / dest_dir_name
|
||||
if not src_component_dir.exists():
|
||||
if component in allow_missing_components:
|
||||
continue
|
||||
raise RuntimeError(
|
||||
f"Missing native component '{component}' in vendor source: {src_component_dir}"
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Install Codex native binaries (Rust CLI, bwrap, and ripgrep helpers)."""
|
||||
"""Install Codex native binaries (Rust CLI plus ripgrep helpers)."""
|
||||
|
||||
import argparse
|
||||
from contextlib import contextmanager
|
||||
@@ -42,15 +42,8 @@ class BinaryComponent:
|
||||
|
||||
|
||||
WINDOWS_TARGETS = tuple(target for target in BINARY_TARGETS if "windows" in target)
|
||||
LINUX_TARGETS = tuple(target for target in BINARY_TARGETS if "linux" in target)
|
||||
|
||||
BINARY_COMPONENTS = {
|
||||
"bwrap": BinaryComponent(
|
||||
artifact_prefix="bwrap",
|
||||
dest_dir="codex-resources",
|
||||
binary_basename="bwrap",
|
||||
targets=LINUX_TARGETS,
|
||||
),
|
||||
"codex": BinaryComponent(
|
||||
artifact_prefix="codex",
|
||||
dest_dir="codex",
|
||||
@@ -142,7 +135,7 @@ def parse_args() -> argparse.Namespace:
|
||||
choices=tuple(list(BINARY_COMPONENTS) + ["rg"]),
|
||||
help=(
|
||||
"Limit installation to the specified components."
|
||||
" May be repeated. Defaults to bwrap, codex, codex-windows-sandbox-setup,"
|
||||
" May be repeated. Defaults to codex, codex-windows-sandbox-setup,"
|
||||
" codex-command-runner, and rg."
|
||||
),
|
||||
)
|
||||
@@ -166,7 +159,6 @@ def main() -> int:
|
||||
vendor_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
components = args.components or [
|
||||
"bwrap",
|
||||
"codex",
|
||||
"codex-windows-sandbox-setup",
|
||||
"codex-command-runner",
|
||||
|
||||
198
codex-rs/Cargo.lock
generated
198
codex-rs/Cargo.lock
generated
@@ -757,7 +757,6 @@ checksum = "96571e6996817bf3d58f6b569e4b9fd2e9d2fcf9f7424eed07b2ce9bb87535e5"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
"aws-sdk-signin",
|
||||
"aws-sdk-sso",
|
||||
"aws-sdk-ssooidc",
|
||||
"aws-sdk-sts",
|
||||
@@ -768,20 +767,15 @@ dependencies = [
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types",
|
||||
"aws-types",
|
||||
"base64-simd",
|
||||
"bytes",
|
||||
"fastrand",
|
||||
"hex",
|
||||
"http 1.4.0",
|
||||
"p256",
|
||||
"rand 0.8.5",
|
||||
"ring",
|
||||
"sha2",
|
||||
"time",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -844,28 +838,6 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-signin"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c084bd63941916e1348cb8d9e05ac2e49bdd40a380e9167702683184c6c6be53"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-json",
|
||||
"aws-smithy-runtime",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types",
|
||||
"aws-types",
|
||||
"bytes",
|
||||
"fastrand",
|
||||
"http 0.2.12",
|
||||
"regex-lite",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sso"
|
||||
version = "1.91.0"
|
||||
@@ -1894,15 +1866,13 @@ dependencies = [
|
||||
"codex-config",
|
||||
"codex-core",
|
||||
"codex-core-plugins",
|
||||
"codex-device-key",
|
||||
"codex-exec-server",
|
||||
"codex-extension-api",
|
||||
"codex-external-agent-migration",
|
||||
"codex-external-agent-sessions",
|
||||
"codex-features",
|
||||
"codex-feedback",
|
||||
"codex-file-search",
|
||||
"codex-file-watcher",
|
||||
"codex-git-attribution",
|
||||
"codex-git-utils",
|
||||
"codex-hooks",
|
||||
"codex-login",
|
||||
@@ -1974,7 +1944,6 @@ dependencies = [
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"toml 0.9.11+spec-1.1.0",
|
||||
@@ -1982,26 +1951,6 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-app-server-daemon"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"codex-app-server-protocol",
|
||||
"codex-app-server-transport",
|
||||
"codex-core",
|
||||
"codex-uds",
|
||||
"futures",
|
||||
"libc",
|
||||
"pretty_assertions",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-app-server-protocol"
|
||||
version = "0.0.0"
|
||||
@@ -2176,26 +2125,6 @@ dependencies = [
|
||||
"serde_with",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-builtin-mcps"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"codex-memories-mcp",
|
||||
"codex-utils-absolute-path",
|
||||
"pretty_assertions",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-bwrap"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-chatgpt"
|
||||
version = "0.0.0"
|
||||
@@ -2229,7 +2158,6 @@ dependencies = [
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"codex-app-server",
|
||||
"codex-app-server-daemon",
|
||||
"codex-app-server-protocol",
|
||||
"codex-app-server-test-client",
|
||||
"codex-arg0",
|
||||
@@ -2260,6 +2188,7 @@ dependencies = [
|
||||
"codex-utils-cargo-bin",
|
||||
"codex-utils-cli",
|
||||
"codex-utils-path",
|
||||
"codex-web-server",
|
||||
"codex-windows-sandbox",
|
||||
"libc",
|
||||
"owo-colors",
|
||||
@@ -2465,11 +2394,7 @@ dependencies = [
|
||||
"codex-app-server-protocol",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha1",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"urlencoding",
|
||||
]
|
||||
|
||||
@@ -2499,7 +2424,6 @@ dependencies = [
|
||||
"codex-core-skills",
|
||||
"codex-exec-server",
|
||||
"codex-execpolicy",
|
||||
"codex-extension-api",
|
||||
"codex-features",
|
||||
"codex-feedback",
|
||||
"codex-git-utils",
|
||||
@@ -2555,6 +2479,7 @@ dependencies = [
|
||||
"insta",
|
||||
"libc",
|
||||
"maplit",
|
||||
"notify",
|
||||
"once_cell",
|
||||
"openssl-sys",
|
||||
"opentelemetry",
|
||||
@@ -2603,7 +2528,6 @@ dependencies = [
|
||||
"codex-config",
|
||||
"codex-core",
|
||||
"codex-exec-server",
|
||||
"codex-extension-api",
|
||||
"codex-features",
|
||||
"codex-login",
|
||||
"codex-model-provider-info",
|
||||
@@ -2624,7 +2548,6 @@ dependencies = [
|
||||
"codex-core-skills",
|
||||
"codex-exec-server",
|
||||
"codex-git-utils",
|
||||
"codex-hooks",
|
||||
"codex-login",
|
||||
"codex-model-provider",
|
||||
"codex-otel",
|
||||
@@ -2693,6 +2616,22 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-device-key"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"p256",
|
||||
"pretty_assertions",
|
||||
"rand 0.9.3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-exec"
|
||||
version = "0.0.0"
|
||||
@@ -2768,10 +2707,8 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"tokio-util",
|
||||
"toml 0.9.11+spec-1.1.0",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"wiremock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2820,16 +2757,6 @@ dependencies = [
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-extension-api"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-protocol",
|
||||
"codex-tools",
|
||||
"serde_json",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-external-agent-migration"
|
||||
version = "0.0.0"
|
||||
@@ -2908,27 +2835,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-file-watcher"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"notify",
|
||||
"pretty_assertions",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-git-attribution"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-core",
|
||||
"codex-extension-api",
|
||||
"codex-features",
|
||||
"pretty_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-git-utils"
|
||||
version = "0.0.0"
|
||||
@@ -2963,7 +2869,6 @@ dependencies = [
|
||||
"codex-plugin",
|
||||
"codex-protocol",
|
||||
"codex-utils-absolute-path",
|
||||
"codex-utils-output-truncation",
|
||||
"futures",
|
||||
"pretty_assertions",
|
||||
"regex",
|
||||
@@ -2972,8 +2877,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2997,6 +2900,7 @@ dependencies = [
|
||||
name = "codex-linux-sandbox"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"clap",
|
||||
"codex-core",
|
||||
"codex-process-hardening",
|
||||
@@ -3006,11 +2910,11 @@ dependencies = [
|
||||
"globset",
|
||||
"landlock",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"pretty_assertions",
|
||||
"seccompiler",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"url",
|
||||
@@ -3080,7 +2984,6 @@ dependencies = [
|
||||
"async-channel",
|
||||
"codex-api",
|
||||
"codex-async-utils",
|
||||
"codex-builtin-mcps",
|
||||
"codex-config",
|
||||
"codex-exec-server",
|
||||
"codex-login",
|
||||
@@ -3114,7 +3017,6 @@ dependencies = [
|
||||
"codex-config",
|
||||
"codex-core",
|
||||
"codex-exec-server",
|
||||
"codex-extension-api",
|
||||
"codex-login",
|
||||
"codex-protocol",
|
||||
"codex-shell-command",
|
||||
@@ -3137,23 +3039,6 @@ dependencies = [
|
||||
"wiremock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-memories-mcp"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"codex-utils-absolute-path",
|
||||
"codex-utils-output-truncation",
|
||||
"pretty_assertions",
|
||||
"rmcp",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-memories-read"
|
||||
version = "0.0.0"
|
||||
@@ -3203,19 +3088,6 @@ dependencies = [
|
||||
"wiremock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-message-history"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-config",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-model-provider"
|
||||
version = "0.0.0"
|
||||
@@ -3391,6 +3263,7 @@ dependencies = [
|
||||
"codex-utils-absolute-path",
|
||||
"codex-utils-image",
|
||||
"codex-utils-string",
|
||||
"codex-utils-template",
|
||||
"encoding_rs",
|
||||
"globset",
|
||||
"http 1.4.0",
|
||||
@@ -3697,11 +3570,16 @@ dependencies = [
|
||||
"codex-rollout",
|
||||
"codex-state",
|
||||
"pretty_assertions",
|
||||
"prost 0.14.3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tonic",
|
||||
"tonic-prost",
|
||||
"tonic-prost-build",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
@@ -3752,7 +3630,6 @@ dependencies = [
|
||||
"codex-install-context",
|
||||
"codex-login",
|
||||
"codex-mcp",
|
||||
"codex-message-history",
|
||||
"codex-model-provider",
|
||||
"codex-model-provider-info",
|
||||
"codex-models-manager",
|
||||
@@ -4060,6 +3937,24 @@ dependencies = [
|
||||
"v8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-web-server"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"clap",
|
||||
"codex-utils-pty",
|
||||
"futures",
|
||||
"include_dir",
|
||||
"pretty_assertions",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"tracing",
|
||||
"url",
|
||||
"webbrowser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-windows-sandbox"
|
||||
version = "0.0.0"
|
||||
@@ -4322,9 +4217,7 @@ dependencies = [
|
||||
"codex-config",
|
||||
"codex-core",
|
||||
"codex-exec-server",
|
||||
"codex-extension-api",
|
||||
"codex-features",
|
||||
"codex-hooks",
|
||||
"codex-login",
|
||||
"codex-model-provider-info",
|
||||
"codex-models-manager",
|
||||
@@ -4341,7 +4234,6 @@ dependencies = [
|
||||
"reqwest",
|
||||
"serde_json",
|
||||
"shlex",
|
||||
"similar",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
|
||||
@@ -5,13 +5,10 @@ members = [
|
||||
"agent-graph-store",
|
||||
"agent-identity",
|
||||
"backend-client",
|
||||
"builtin-mcps",
|
||||
"bwrap",
|
||||
"ansi-escape",
|
||||
"async-utils",
|
||||
"app-server",
|
||||
"app-server-transport",
|
||||
"app-server-daemon",
|
||||
"app-server-client",
|
||||
"app-server-protocol",
|
||||
"app-server-test-client",
|
||||
@@ -31,6 +28,7 @@ members = [
|
||||
"collaboration-mode-templates",
|
||||
"connectors",
|
||||
"config",
|
||||
"device-key",
|
||||
"shell-command",
|
||||
"shell-escalation",
|
||||
"skills",
|
||||
@@ -45,19 +43,15 @@ members = [
|
||||
"exec-server",
|
||||
"execpolicy",
|
||||
"execpolicy-legacy",
|
||||
"ext/extension-api",
|
||||
"ext/git-attribution",
|
||||
"external-agent-migration",
|
||||
"external-agent-sessions",
|
||||
"keyring-store",
|
||||
"file-search",
|
||||
"file-watcher",
|
||||
"linux-sandbox",
|
||||
"lmstudio",
|
||||
"login",
|
||||
"codex-mcp",
|
||||
"mcp-server",
|
||||
"memories/mcp",
|
||||
"memories/read",
|
||||
"memories/write",
|
||||
"model-provider-info",
|
||||
@@ -110,6 +104,7 @@ members = [
|
||||
"thread-store",
|
||||
"uds",
|
||||
"codex-experimental-api-macros",
|
||||
"web-server",
|
||||
"plugin",
|
||||
"model-provider",
|
||||
]
|
||||
@@ -135,7 +130,6 @@ codex-api = { path = "codex-api" }
|
||||
codex-aws-auth = { path = "aws-auth" }
|
||||
codex-app-server = { path = "app-server" }
|
||||
codex-app-server-transport = { path = "app-server-transport" }
|
||||
codex-app-server-daemon = { path = "app-server-daemon" }
|
||||
codex-app-server-client = { path = "app-server-client" }
|
||||
codex-app-server-protocol = { path = "app-server-protocol" }
|
||||
codex-app-server-test-client = { path = "app-server-test-client" }
|
||||
@@ -143,7 +137,6 @@ codex-apply-patch = { path = "apply-patch" }
|
||||
codex-arg0 = { path = "arg0" }
|
||||
codex-async-utils = { path = "async-utils" }
|
||||
codex-backend-client = { path = "backend-client" }
|
||||
codex-builtin-mcps = { path = "builtin-mcps" }
|
||||
codex-chatgpt = { path = "chatgpt" }
|
||||
codex-cli = { path = "cli" }
|
||||
codex-client = { path = "codex-client" }
|
||||
@@ -158,12 +151,11 @@ codex-core = { path = "core" }
|
||||
codex-core-api = { path = "core-api" }
|
||||
codex-core-plugins = { path = "core-plugins" }
|
||||
codex-core-skills = { path = "core-skills" }
|
||||
codex-device-key = { path = "device-key" }
|
||||
codex-exec = { path = "exec" }
|
||||
codex-file-system = { path = "file-system" }
|
||||
codex-exec-server = { path = "exec-server" }
|
||||
codex-execpolicy = { path = "execpolicy" }
|
||||
codex-extension-api = { path = "ext/extension-api" }
|
||||
codex-git-attribution = { path = "ext/git-attribution" }
|
||||
codex-external-agent-migration = { path = "external-agent-migration" }
|
||||
codex-external-agent-sessions = { path = "external-agent-sessions" }
|
||||
codex-experimental-api-macros = { path = "codex-experimental-api-macros" }
|
||||
@@ -171,15 +163,12 @@ codex-features = { path = "features" }
|
||||
codex-feedback = { path = "feedback" }
|
||||
codex-install-context = { path = "install-context" }
|
||||
codex-file-search = { path = "file-search" }
|
||||
codex-file-watcher = { path = "file-watcher" }
|
||||
codex-git-utils = { path = "git-utils" }
|
||||
codex-hooks = { path = "hooks" }
|
||||
codex-keyring-store = { path = "keyring-store" }
|
||||
codex-linux-sandbox = { path = "linux-sandbox" }
|
||||
codex-lmstudio = { path = "lmstudio" }
|
||||
codex-login = { path = "login" }
|
||||
codex-message-history = { path = "message-history" }
|
||||
codex-memories-mcp = { path = "memories/mcp" }
|
||||
codex-memories-read = { path = "memories/read" }
|
||||
codex-memories-write = { path = "memories/write" }
|
||||
codex-mcp = { path = "codex-mcp" }
|
||||
@@ -235,6 +224,7 @@ codex-utils-stream-parser = { path = "utils/stream-parser" }
|
||||
codex-utils-string = { path = "utils/string" }
|
||||
codex-utils-template = { path = "utils/template" }
|
||||
codex-v8-poc = { path = "v8-poc" }
|
||||
codex-web-server = { path = "web-server" }
|
||||
codex-windows-sandbox = { path = "windows-sandbox-rs" }
|
||||
core_test_support = { path = "core/tests/common" }
|
||||
mcp_test_support = { path = "mcp-server/tests/common" }
|
||||
@@ -325,6 +315,7 @@ os_info = "3.12.0"
|
||||
owo-colors = "4.3.0"
|
||||
path-absolutize = "3.1.1"
|
||||
pathdiff = "0.2"
|
||||
p256 = "0.13.2"
|
||||
portable-pty = "0.9.0"
|
||||
predicates = "3"
|
||||
pretty_assertions = "1.4.1"
|
||||
@@ -474,19 +465,21 @@ ignored = [
|
||||
"codex-agent-graph-store",
|
||||
"icu_provider",
|
||||
"openssl-sys",
|
||||
"codex-utils-readiness",
|
||||
"codex-utils-template",
|
||||
"codex-v8-poc",
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
# Keep line tables/backtraces while avoiding expensive full variable debug info
|
||||
# across local dev builds.
|
||||
debug = "limited"
|
||||
debug = 1
|
||||
|
||||
[profile.dev-small]
|
||||
inherits = "dev"
|
||||
opt-level = 0
|
||||
debug = "none"
|
||||
strip = "symbols"
|
||||
debug = 0
|
||||
strip = true
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
@@ -498,15 +491,8 @@ strip = "symbols"
|
||||
# See https://github.com/openai/codex/issues/1411 for details.
|
||||
codegen-units = 1
|
||||
|
||||
[profile.profiling]
|
||||
inherits = "release"
|
||||
debug = "full"
|
||||
lto = false
|
||||
strip = false
|
||||
|
||||
[profile.ci-test]
|
||||
# Reduce binary size to reduce disk pressure.
|
||||
debug = "limited"
|
||||
debug = 1 # Reduce debug symbol size
|
||||
inherits = "test"
|
||||
opt-level = 0
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ Use `codex mcp` to add/list/get/remove MCP server launchers defined in `config.t
|
||||
|
||||
### Notifications
|
||||
|
||||
You can enable notifications by configuring a script that is run whenever the agent finishes a turn. The [notify documentation](../docs/config.md#notify) includes a detailed example that explains how to get desktop notifications via [terminal-notifier](https://github.com/julienXX/terminal-notifier) on macOS. When Codex detects that it is running under WSL 2 inside Windows Terminal (`WT_SESSION` is set), the TUI automatically falls back to native Windows toast notifications so approval prompts and completed turns surface even though Windows Terminal does not implement OSC 9.
|
||||
The legacy `notify` setting is deprecated and will be removed in a future release. Existing configurations still work, but new automation should use lifecycle hooks instead. The [notify documentation](../docs/config.md#notify) explains the remaining compatibility behavior. When Codex detects that it is running under WSL 2 inside Windows Terminal (`WT_SESSION` is set), the TUI automatically falls back to native Windows toast notifications so approval prompts and completed turns surface even though Windows Terminal does not implement OSC 9.
|
||||
|
||||
### `codex exec` to run Codex programmatically/non-interactively
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ version.workspace = true
|
||||
[lib]
|
||||
name = "codex_agent_graph_store"
|
||||
path = "src/lib.rs"
|
||||
doctest = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,298 +0,0 @@
|
||||
use crate::events::CodexAcceptedLineFingerprintsEventParams;
|
||||
use crate::events::CodexAcceptedLineFingerprintsEventRequest;
|
||||
use crate::events::TrackEventRequest;
|
||||
use crate::facts::AcceptedLineFingerprint;
|
||||
use codex_git_utils::canonicalize_git_remote_url;
|
||||
use codex_git_utils::get_git_remote_urls_assume_git_repo;
|
||||
use sha1::Digest;
|
||||
use std::path::Path;
|
||||
|
||||
const ACCEPTED_LINE_FINGERPRINT_EVENT_TARGET_BYTES: usize = 2 * 1024 * 1024;
|
||||
const ACCEPTED_LINE_FINGERPRINT_EVENT_FIXED_BYTES: usize = 1024;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct AcceptedLineFingerprintSummary {
|
||||
pub accepted_added_lines: u64,
|
||||
pub accepted_deleted_lines: u64,
|
||||
pub line_fingerprints: Vec<AcceptedLineFingerprint>,
|
||||
}
|
||||
|
||||
pub(crate) struct AcceptedLineFingerprintEventInput {
|
||||
pub(crate) event_type: &'static str,
|
||||
pub(crate) turn_id: String,
|
||||
pub(crate) thread_id: String,
|
||||
pub(crate) product_surface: Option<String>,
|
||||
pub(crate) model_slug: Option<String>,
|
||||
pub(crate) completed_at: u64,
|
||||
pub(crate) repo_hash: Option<String>,
|
||||
pub(crate) accepted_added_lines: u64,
|
||||
pub(crate) accepted_deleted_lines: u64,
|
||||
pub(crate) line_fingerprints: Vec<AcceptedLineFingerprint>,
|
||||
}
|
||||
|
||||
pub fn accepted_line_fingerprints_from_unified_diff(
|
||||
unified_diff: &str,
|
||||
) -> AcceptedLineFingerprintSummary {
|
||||
let mut current_path: Option<String> = None;
|
||||
let mut in_hunk = false;
|
||||
let mut accepted_added_lines = 0;
|
||||
let mut accepted_deleted_lines = 0;
|
||||
let mut line_fingerprints = Vec::new();
|
||||
|
||||
for line in unified_diff.lines() {
|
||||
if line.starts_with("diff --git ") {
|
||||
current_path = None;
|
||||
in_hunk = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if line.starts_with("@@ ") {
|
||||
in_hunk = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if !in_hunk && let Some(path) = line.strip_prefix("+++ ") {
|
||||
current_path = normalize_diff_path(path);
|
||||
continue;
|
||||
}
|
||||
|
||||
if !in_hunk && line.starts_with("--- ") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(added_line) = line.strip_prefix('+') {
|
||||
accepted_added_lines += 1;
|
||||
if let Some(path) = current_path.as_deref()
|
||||
&& let Some(normalized_line) = normalize_effective_line(added_line)
|
||||
{
|
||||
line_fingerprints.push(AcceptedLineFingerprint {
|
||||
path_hash: fingerprint_hash("path", path),
|
||||
line_hash: fingerprint_hash("line", &normalized_line),
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if line.starts_with('-') {
|
||||
accepted_deleted_lines += 1;
|
||||
}
|
||||
}
|
||||
|
||||
AcceptedLineFingerprintSummary {
|
||||
accepted_added_lines,
|
||||
accepted_deleted_lines,
|
||||
line_fingerprints,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fingerprint_hash(domain: &str, value: &str) -> String {
|
||||
let mut hasher = sha1::Sha1::new();
|
||||
hasher.update(b"file-line-v1\0");
|
||||
hasher.update(domain.as_bytes());
|
||||
hasher.update(b"\0");
|
||||
hasher.update(value.as_bytes());
|
||||
format!("{:x}", hasher.finalize())
|
||||
}
|
||||
|
||||
pub(crate) fn accepted_line_fingerprint_event_requests(
|
||||
input: AcceptedLineFingerprintEventInput,
|
||||
) -> Vec<TrackEventRequest> {
|
||||
let chunks = accepted_line_fingerprint_chunks(input.line_fingerprints);
|
||||
chunks
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, line_fingerprints)| {
|
||||
let is_first_chunk = index == 0;
|
||||
TrackEventRequest::AcceptedLineFingerprints(Box::new(
|
||||
CodexAcceptedLineFingerprintsEventRequest {
|
||||
event_type: "codex_accepted_line_fingerprints",
|
||||
event_params: CodexAcceptedLineFingerprintsEventParams {
|
||||
event_type: input.event_type,
|
||||
turn_id: input.turn_id.clone(),
|
||||
thread_id: input.thread_id.clone(),
|
||||
product_surface: input.product_surface.clone(),
|
||||
model_slug: input.model_slug.clone(),
|
||||
completed_at: input.completed_at,
|
||||
repo_hash: input.repo_hash.clone(),
|
||||
accepted_added_lines: if is_first_chunk {
|
||||
input.accepted_added_lines
|
||||
} else {
|
||||
0
|
||||
},
|
||||
accepted_deleted_lines: if is_first_chunk {
|
||||
input.accepted_deleted_lines
|
||||
} else {
|
||||
0
|
||||
},
|
||||
line_fingerprints,
|
||||
},
|
||||
},
|
||||
))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub async fn accepted_line_repo_hash_for_cwd(cwd: &Path) -> Option<String> {
|
||||
let remotes = get_git_remote_urls_assume_git_repo(cwd).await?;
|
||||
remotes
|
||||
.get("origin")
|
||||
.or_else(|| remotes.values().next())
|
||||
.map(|remote_url| {
|
||||
let canonical_remote_url =
|
||||
canonicalize_git_remote_url(remote_url).unwrap_or_else(|| remote_url.to_string());
|
||||
fingerprint_hash("repo", &canonical_remote_url)
|
||||
})
|
||||
}
|
||||
|
||||
fn normalize_diff_path(path: &str) -> Option<String> {
|
||||
let path = path.trim();
|
||||
if path == "/dev/null" {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(
|
||||
path.strip_prefix("b/")
|
||||
.or_else(|| path.strip_prefix("a/"))
|
||||
.unwrap_or(path)
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
fn normalize_effective_line(line: &str) -> Option<String> {
|
||||
let normalized = line.split_whitespace().collect::<Vec<_>>().join(" ");
|
||||
if normalized.len() <= 3 {
|
||||
return None;
|
||||
}
|
||||
if !normalized
|
||||
.chars()
|
||||
.any(|ch| ch.is_alphanumeric() || ch == '_')
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(normalized)
|
||||
}
|
||||
|
||||
fn accepted_line_fingerprint_chunks(
|
||||
line_fingerprints: Vec<AcceptedLineFingerprint>,
|
||||
) -> Vec<Vec<AcceptedLineFingerprint>> {
|
||||
if line_fingerprints.is_empty() {
|
||||
return vec![Vec::new()];
|
||||
}
|
||||
|
||||
let mut chunks = Vec::new();
|
||||
let mut current = Vec::new();
|
||||
let mut current_bytes = ACCEPTED_LINE_FINGERPRINT_EVENT_FIXED_BYTES;
|
||||
|
||||
for fingerprint in line_fingerprints {
|
||||
let item_bytes = accepted_line_fingerprint_json_bytes(&fingerprint);
|
||||
let separator_bytes = usize::from(!current.is_empty());
|
||||
if !current.is_empty()
|
||||
&& current_bytes + separator_bytes + item_bytes
|
||||
> ACCEPTED_LINE_FINGERPRINT_EVENT_TARGET_BYTES
|
||||
{
|
||||
chunks.push(current);
|
||||
current = Vec::new();
|
||||
current_bytes = ACCEPTED_LINE_FINGERPRINT_EVENT_FIXED_BYTES;
|
||||
}
|
||||
current_bytes += usize::from(!current.is_empty()) + item_bytes;
|
||||
current.push(fingerprint);
|
||||
}
|
||||
|
||||
if !current.is_empty() {
|
||||
chunks.push(current);
|
||||
}
|
||||
chunks
|
||||
}
|
||||
|
||||
fn accepted_line_fingerprint_json_bytes(fingerprint: &AcceptedLineFingerprint) -> usize {
|
||||
// {"path_hash":"...","line_hash":"..."} plus one byte of array comma
|
||||
// accounted for by the caller when needed.
|
||||
32 + fingerprint.path_hash.len() + fingerprint.line_hash.len()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parses_counts_and_effective_added_fingerprints() {
|
||||
let diff = "\
|
||||
diff --git a/src/lib.rs b/src/lib.rs
|
||||
index 1111111..2222222
|
||||
--- a/src/lib.rs
|
||||
+++ b/src/lib.rs
|
||||
@@ -1,3 +1,5 @@
|
||||
-old line
|
||||
+fn useful() {
|
||||
+}
|
||||
+ return user.id;
|
||||
context
|
||||
";
|
||||
|
||||
let summary = accepted_line_fingerprints_from_unified_diff(diff);
|
||||
|
||||
assert_eq!(
|
||||
summary,
|
||||
AcceptedLineFingerprintSummary {
|
||||
accepted_added_lines: 3,
|
||||
accepted_deleted_lines: 1,
|
||||
line_fingerprints: vec![
|
||||
AcceptedLineFingerprint {
|
||||
path_hash: fingerprint_hash("path", "src/lib.rs"),
|
||||
line_hash: fingerprint_hash("line", "fn useful() {"),
|
||||
},
|
||||
AcceptedLineFingerprint {
|
||||
path_hash: fingerprint_hash("path", "src/lib.rs"),
|
||||
line_hash: fingerprint_hash("line", "return user.id;"),
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skips_added_file_metadata_headers() {
|
||||
let diff = "\
|
||||
diff --git a/new.py b/new.py
|
||||
new file mode 100644
|
||||
index 0000000..1111111
|
||||
--- /dev/null
|
||||
+++ b/new.py
|
||||
@@ -0,0 +1 @@
|
||||
+print('hello')
|
||||
";
|
||||
|
||||
let summary = accepted_line_fingerprints_from_unified_diff(diff);
|
||||
|
||||
assert_eq!(summary.accepted_added_lines, 1);
|
||||
assert_eq!(summary.accepted_deleted_lines, 0);
|
||||
assert_eq!(summary.line_fingerprints.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_hunk_lines_that_look_like_file_headers() {
|
||||
let diff = "\
|
||||
diff --git a/src/lib.rs b/src/lib.rs
|
||||
index 1111111..2222222
|
||||
--- a/src/lib.rs
|
||||
+++ b/src/lib.rs
|
||||
@@ -1,2 +1,2 @@
|
||||
--- old value
|
||||
+++ new value
|
||||
";
|
||||
|
||||
let summary = accepted_line_fingerprints_from_unified_diff(diff);
|
||||
|
||||
assert_eq!(
|
||||
summary,
|
||||
AcceptedLineFingerprintSummary {
|
||||
accepted_added_lines: 1,
|
||||
accepted_deleted_lines: 1,
|
||||
line_fingerprints: vec![AcceptedLineFingerprint {
|
||||
path_hash: fingerprint_hash("path", "src/lib.rs"),
|
||||
line_hash: fingerprint_hash("line", "++ new value"),
|
||||
}],
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,13 @@
|
||||
use crate::client::AnalyticsEventsQueue;
|
||||
use crate::events::AppServerRpcTransport;
|
||||
use crate::events::CodexAcceptedLineFingerprintsEventParams;
|
||||
use crate::events::CodexAcceptedLineFingerprintsEventRequest;
|
||||
use crate::events::CodexAppMentionedEventRequest;
|
||||
use crate::events::CodexAppServerClientMetadata;
|
||||
use crate::events::CodexAppUsedEventRequest;
|
||||
use crate::events::CodexCommandExecutionEventParams;
|
||||
use crate::events::CodexCommandExecutionEventRequest;
|
||||
use crate::events::CodexCompactionEventRequest;
|
||||
use crate::events::CodexHookRunEventRequest;
|
||||
use crate::events::CodexPluginEventRequest;
|
||||
use crate::events::CodexPluginUsedEventRequest;
|
||||
use crate::events::CodexRuntimeMetadata;
|
||||
use crate::events::CodexToolItemEventBase;
|
||||
use crate::events::CodexTurnEventRequest;
|
||||
use crate::events::GuardianApprovalRequestSource;
|
||||
use crate::events::GuardianReviewDecision;
|
||||
@@ -22,15 +17,12 @@ use crate::events::GuardianReviewTerminalStatus;
|
||||
use crate::events::GuardianReviewedAction;
|
||||
use crate::events::ThreadInitializedEvent;
|
||||
use crate::events::ThreadInitializedEventParams;
|
||||
use crate::events::ToolItemFinalApprovalOutcome;
|
||||
use crate::events::ToolItemTerminalStatus;
|
||||
use crate::events::TrackEventRequest;
|
||||
use crate::events::codex_app_metadata;
|
||||
use crate::events::codex_hook_run_metadata;
|
||||
use crate::events::codex_plugin_metadata;
|
||||
use crate::events::codex_plugin_used_metadata;
|
||||
use crate::events::subagent_thread_started_event_request;
|
||||
use crate::facts::AcceptedLineFingerprint;
|
||||
use crate::facts::AnalyticsFact;
|
||||
use crate::facts::AnalyticsJsonRpcError;
|
||||
use crate::facts::AppInvocation;
|
||||
@@ -69,13 +61,8 @@ use codex_app_server_protocol::ClientInfo;
|
||||
use codex_app_server_protocol::ClientRequest;
|
||||
use codex_app_server_protocol::ClientResponsePayload;
|
||||
use codex_app_server_protocol::CodexErrorInfo;
|
||||
use codex_app_server_protocol::CommandAction;
|
||||
use codex_app_server_protocol::CommandExecutionSource;
|
||||
use codex_app_server_protocol::CommandExecutionStatus;
|
||||
use codex_app_server_protocol::InitializeCapabilities;
|
||||
use codex_app_server_protocol::InitializeParams;
|
||||
use codex_app_server_protocol::ItemCompletedNotification;
|
||||
use codex_app_server_protocol::ItemStartedNotification;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_app_server_protocol::NonSteerableTurnKind;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
@@ -85,14 +72,11 @@ use codex_app_server_protocol::SessionSource as AppServerSessionSource;
|
||||
use codex_app_server_protocol::Thread;
|
||||
use codex_app_server_protocol::ThreadArchiveParams;
|
||||
use codex_app_server_protocol::ThreadArchiveResponse;
|
||||
use codex_app_server_protocol::ThreadItem;
|
||||
use codex_app_server_protocol::ThreadResumeResponse;
|
||||
use codex_app_server_protocol::ThreadSource as AppServerThreadSource;
|
||||
use codex_app_server_protocol::ThreadStartResponse;
|
||||
use codex_app_server_protocol::ThreadStatus as AppServerThreadStatus;
|
||||
use codex_app_server_protocol::Turn;
|
||||
use codex_app_server_protocol::TurnCompletedNotification;
|
||||
use codex_app_server_protocol::TurnDiffUpdatedNotification;
|
||||
use codex_app_server_protocol::TurnError as AppServerTurnError;
|
||||
use codex_app_server_protocol::TurnStartParams;
|
||||
use codex_app_server_protocol::TurnStartedNotification;
|
||||
@@ -117,7 +101,6 @@ use codex_protocol::protocol::HookSource;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
use codex_protocol::protocol::ThreadSource;
|
||||
use codex_protocol::protocol::TokenUsage;
|
||||
use codex_utils_absolute_path::test_support::PathBufExt;
|
||||
use codex_utils_absolute_path::test_support::test_path_buf;
|
||||
@@ -129,15 +112,17 @@ use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
fn sample_thread_with_metadata(
|
||||
fn sample_thread(thread_id: &str, ephemeral: bool) -> Thread {
|
||||
sample_thread_with_source(thread_id, ephemeral, AppServerSessionSource::Exec)
|
||||
}
|
||||
|
||||
fn sample_thread_with_source(
|
||||
thread_id: &str,
|
||||
ephemeral: bool,
|
||||
source: AppServerSessionSource,
|
||||
thread_source: Option<AppServerThreadSource>,
|
||||
) -> Thread {
|
||||
Thread {
|
||||
id: thread_id.to_string(),
|
||||
session_id: format!("session-{thread_id}"),
|
||||
forked_from_id: None,
|
||||
preview: "first prompt".to_string(),
|
||||
ephemeral,
|
||||
@@ -149,7 +134,6 @@ fn sample_thread_with_metadata(
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
cli_version: "0.0.0".to_string(),
|
||||
source,
|
||||
thread_source,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
git_info: None,
|
||||
@@ -164,12 +148,7 @@ fn sample_thread_start_response(
|
||||
model: &str,
|
||||
) -> ClientResponsePayload {
|
||||
ClientResponsePayload::ThreadStart(ThreadStartResponse {
|
||||
thread: sample_thread_with_metadata(
|
||||
thread_id,
|
||||
ephemeral,
|
||||
AppServerSessionSource::Exec,
|
||||
Some(AppServerThreadSource::User),
|
||||
),
|
||||
thread: sample_thread(thread_id, ephemeral),
|
||||
model: model.to_string(),
|
||||
model_provider: "openai".to_string(),
|
||||
service_tier: None,
|
||||
@@ -213,7 +192,6 @@ fn sample_thread_resume_response(
|
||||
ephemeral,
|
||||
model,
|
||||
AppServerSessionSource::Exec,
|
||||
Some(AppServerThreadSource::User),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -222,10 +200,9 @@ fn sample_thread_resume_response_with_source(
|
||||
ephemeral: bool,
|
||||
model: &str,
|
||||
source: AppServerSessionSource,
|
||||
thread_source: Option<AppServerThreadSource>,
|
||||
) -> ClientResponsePayload {
|
||||
ClientResponsePayload::ThreadResume(ThreadResumeResponse {
|
||||
thread: sample_thread_with_metadata(thread_id, ephemeral, source, thread_source),
|
||||
thread: sample_thread_with_source(thread_id, ephemeral, source),
|
||||
model: model.to_string(),
|
||||
model_provider: "openai".to_string(),
|
||||
service_tier: None,
|
||||
@@ -263,7 +240,6 @@ fn sample_turn_start_response(turn_id: &str) -> ClientResponsePayload {
|
||||
ClientResponsePayload::TurnStart(codex_app_server_protocol::TurnStartResponse {
|
||||
turn: Turn {
|
||||
id: turn_id.to_string(),
|
||||
items_view: codex_app_server_protocol::TurnItemsView::Full,
|
||||
items: vec![],
|
||||
status: AppServerTurnStatus::InProgress,
|
||||
error: None,
|
||||
@@ -279,7 +255,6 @@ fn sample_turn_started_notification(thread_id: &str, turn_id: &str) -> ServerNot
|
||||
thread_id: thread_id.to_string(),
|
||||
turn: Turn {
|
||||
id: turn_id.to_string(),
|
||||
items_view: codex_app_server_protocol::TurnItemsView::Full,
|
||||
items: vec![],
|
||||
status: AppServerTurnStatus::InProgress,
|
||||
error: None,
|
||||
@@ -314,7 +289,6 @@ fn sample_turn_completed_notification(
|
||||
thread_id: thread_id.to_string(),
|
||||
turn: Turn {
|
||||
id: turn_id.to_string(),
|
||||
items_view: codex_app_server_protocol::TurnItemsView::Full,
|
||||
items: vec![],
|
||||
status,
|
||||
error: codex_error_info.map(|codex_error_info| AppServerTurnError {
|
||||
@@ -607,91 +581,6 @@ async fn ingest_turn_prerequisites(
|
||||
}
|
||||
}
|
||||
|
||||
async fn ingest_tool_review_prerequisites(
|
||||
reducer: &mut AnalyticsReducer,
|
||||
events: &mut Vec<TrackEventRequest>,
|
||||
) {
|
||||
reducer
|
||||
.ingest(sample_initialize_fact(/*connection_id*/ 7), events)
|
||||
.await;
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::ClientResponse {
|
||||
connection_id: 7,
|
||||
request_id: RequestId::Integer(1),
|
||||
response: Box::new(sample_thread_start_response(
|
||||
"thread-1", /*ephemeral*/ false, "gpt-5",
|
||||
)),
|
||||
},
|
||||
events,
|
||||
)
|
||||
.await;
|
||||
events.clear();
|
||||
}
|
||||
|
||||
fn sample_initialize_fact(connection_id: u64) -> AnalyticsFact {
|
||||
AnalyticsFact::Initialize {
|
||||
connection_id,
|
||||
params: InitializeParams {
|
||||
client_info: ClientInfo {
|
||||
name: "codex-tui".to_string(),
|
||||
title: None,
|
||||
version: "1.0.0".to_string(),
|
||||
},
|
||||
capabilities: Some(InitializeCapabilities {
|
||||
experimental_api: false,
|
||||
request_attestation: false,
|
||||
opt_out_notification_methods: None,
|
||||
}),
|
||||
},
|
||||
product_client_id: DEFAULT_ORIGINATOR.to_string(),
|
||||
runtime: CodexRuntimeMetadata {
|
||||
codex_rs_version: "0.99.0".to_string(),
|
||||
runtime_os: "linux".to_string(),
|
||||
runtime_os_version: "24.04".to_string(),
|
||||
runtime_arch: "x86_64".to_string(),
|
||||
},
|
||||
rpc_transport: AppServerRpcTransport::Websocket,
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_command_execution_item(
|
||||
status: CommandExecutionStatus,
|
||||
exit_code: Option<i32>,
|
||||
duration_ms: Option<i64>,
|
||||
) -> ThreadItem {
|
||||
ThreadItem::CommandExecution {
|
||||
id: "item-1".to_string(),
|
||||
command: "echo hi".to_string(),
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
process_id: Some("pid-1".to_string()),
|
||||
source: CommandExecutionSource::Agent,
|
||||
status,
|
||||
command_actions: Vec::new(),
|
||||
aggregated_output: None,
|
||||
exit_code,
|
||||
duration_ms,
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_command_execution_item_with_actions(
|
||||
status: CommandExecutionStatus,
|
||||
exit_code: Option<i32>,
|
||||
duration_ms: Option<i64>,
|
||||
command_actions: Vec<CommandAction>,
|
||||
) -> ThreadItem {
|
||||
let mut item = sample_command_execution_item(status, exit_code, duration_ms);
|
||||
let ThreadItem::CommandExecution {
|
||||
command_actions: item_command_actions,
|
||||
..
|
||||
} = &mut item
|
||||
else {
|
||||
unreachable!("sample command execution item should be CommandExecution");
|
||||
};
|
||||
*item_command_actions = command_actions;
|
||||
item
|
||||
}
|
||||
|
||||
fn expected_absolute_path(path: &PathBuf) -> String {
|
||||
std::fs::canonicalize(path)
|
||||
.unwrap_or_else(|_| path.to_path_buf())
|
||||
@@ -832,206 +721,6 @@ fn app_used_event_serializes_expected_shape() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepted_line_fingerprints_event_serializes_expected_shape() {
|
||||
let event = TrackEventRequest::AcceptedLineFingerprints(Box::new(
|
||||
CodexAcceptedLineFingerprintsEventRequest {
|
||||
event_type: "codex_accepted_line_fingerprints",
|
||||
event_params: CodexAcceptedLineFingerprintsEventParams {
|
||||
event_type: "codex.accepted_line_fingerprints",
|
||||
turn_id: "turn-1".to_string(),
|
||||
thread_id: "thread-1".to_string(),
|
||||
product_surface: Some("codex".to_string()),
|
||||
model_slug: Some("gpt-5.1-codex".to_string()),
|
||||
completed_at: 1710000000,
|
||||
repo_hash: Some("repo-hash-1".to_string()),
|
||||
accepted_added_lines: 42,
|
||||
accepted_deleted_lines: 40,
|
||||
line_fingerprints: vec![AcceptedLineFingerprint {
|
||||
path_hash: "path-hash-1".to_string(),
|
||||
line_hash: "line-hash-1".to_string(),
|
||||
}],
|
||||
},
|
||||
},
|
||||
));
|
||||
|
||||
let payload = serde_json::to_value(&event).expect("serialize accepted line fingerprints event");
|
||||
|
||||
assert_eq!(
|
||||
payload,
|
||||
json!({
|
||||
"event_type": "codex_accepted_line_fingerprints",
|
||||
"event_params": {
|
||||
"event_type": "codex.accepted_line_fingerprints",
|
||||
"turn_id": "turn-1",
|
||||
"thread_id": "thread-1",
|
||||
"product_surface": "codex",
|
||||
"model_slug": "gpt-5.1-codex",
|
||||
"completed_at": 1710000000,
|
||||
"repo_hash": "repo-hash-1",
|
||||
"accepted_added_lines": 42,
|
||||
"accepted_deleted_lines": 40,
|
||||
"line_fingerprints": [
|
||||
{
|
||||
"path_hash": "path-hash-1",
|
||||
"line_hash": "line-hash-1"
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reducer_chunks_large_accepted_line_fingerprint_events_without_repeating_counts() {
|
||||
let mut reducer = AnalyticsReducer::default();
|
||||
let mut events = Vec::new();
|
||||
|
||||
ingest_turn_prerequisites(
|
||||
&mut reducer,
|
||||
&mut events,
|
||||
/*include_initialize*/ true,
|
||||
/*include_resolved_config*/ true,
|
||||
/*include_started*/ true,
|
||||
/*include_token_usage*/ true,
|
||||
)
|
||||
.await;
|
||||
events.clear();
|
||||
|
||||
let mut diff = "\
|
||||
diff --git a/src/lib.rs b/src/lib.rs
|
||||
index 1111111..2222222
|
||||
--- a/src/lib.rs
|
||||
+++ b/src/lib.rs
|
||||
@@ -0,0 +1,20000 @@
|
||||
"
|
||||
.to_string();
|
||||
for index in 0..20_000 {
|
||||
diff.push_str(&format!("+let value_{index} = {index};\n"));
|
||||
}
|
||||
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Notification(Box::new(ServerNotification::TurnDiffUpdated(
|
||||
TurnDiffUpdatedNotification {
|
||||
thread_id: "thread-2".to_string(),
|
||||
turn_id: "turn-2".to_string(),
|
||||
diff,
|
||||
},
|
||||
))),
|
||||
&mut events,
|
||||
)
|
||||
.await;
|
||||
assert!(events.is_empty());
|
||||
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Notification(Box::new(sample_turn_completed_notification(
|
||||
"thread-2",
|
||||
"turn-2",
|
||||
AppServerTurnStatus::Completed,
|
||||
/*codex_error_info*/ None,
|
||||
))),
|
||||
&mut events,
|
||||
)
|
||||
.await;
|
||||
|
||||
let accepted_line_events = events
|
||||
.iter()
|
||||
.filter_map(|event| match event {
|
||||
TrackEventRequest::AcceptedLineFingerprints(event) => Some(event),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert!(accepted_line_events.len() > 1);
|
||||
let mut total_fingerprints = 0;
|
||||
for (index, event) in accepted_line_events.iter().enumerate() {
|
||||
assert_eq!(event.event_params.turn_id, "turn-2");
|
||||
assert_eq!(event.event_params.thread_id, "thread-2");
|
||||
total_fingerprints += event.event_params.line_fingerprints.len();
|
||||
if index == 0 {
|
||||
assert_eq!(event.event_params.accepted_added_lines, 20_000);
|
||||
assert_eq!(event.event_params.accepted_deleted_lines, 0);
|
||||
} else {
|
||||
assert_eq!(event.event_params.accepted_added_lines, 0);
|
||||
assert_eq!(event.event_params.accepted_deleted_lines, 0);
|
||||
}
|
||||
assert!(serde_json::to_vec(event).expect("serialize chunk").len() < 2_100_000);
|
||||
}
|
||||
assert_eq!(total_fingerprints, 20_000);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reducer_emits_accepted_line_fingerprints_once_from_latest_turn_diff_on_completion() {
|
||||
let mut reducer = AnalyticsReducer::default();
|
||||
let mut events = Vec::new();
|
||||
|
||||
ingest_turn_prerequisites(
|
||||
&mut reducer,
|
||||
&mut events,
|
||||
/*include_initialize*/ true,
|
||||
/*include_resolved_config*/ true,
|
||||
/*include_started*/ true,
|
||||
/*include_token_usage*/ true,
|
||||
)
|
||||
.await;
|
||||
events.clear();
|
||||
|
||||
for line in ["let old_value = 1;", "let latest_value = 2;"] {
|
||||
let diff = format!(
|
||||
"\
|
||||
diff --git a/src/lib.rs b/src/lib.rs
|
||||
index 1111111..2222222
|
||||
--- a/src/lib.rs
|
||||
+++ b/src/lib.rs
|
||||
@@ -0,0 +1 @@
|
||||
+{line}
|
||||
"
|
||||
);
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Notification(Box::new(ServerNotification::TurnDiffUpdated(
|
||||
TurnDiffUpdatedNotification {
|
||||
thread_id: "thread-2".to_string(),
|
||||
turn_id: "turn-2".to_string(),
|
||||
diff,
|
||||
},
|
||||
))),
|
||||
&mut events,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
assert!(events.is_empty());
|
||||
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Notification(Box::new(sample_turn_completed_notification(
|
||||
"thread-2",
|
||||
"turn-2",
|
||||
AppServerTurnStatus::Completed,
|
||||
/*codex_error_info*/ None,
|
||||
))),
|
||||
&mut events,
|
||||
)
|
||||
.await;
|
||||
|
||||
let accepted_line_events = events
|
||||
.iter()
|
||||
.filter_map(|event| match event {
|
||||
TrackEventRequest::AcceptedLineFingerprints(event) => Some(event),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(accepted_line_events.len(), 1);
|
||||
let event = accepted_line_events[0];
|
||||
assert_eq!(event.event_params.accepted_added_lines, 1);
|
||||
assert_eq!(event.event_params.line_fingerprints.len(), 1);
|
||||
assert_eq!(
|
||||
event.event_params.line_fingerprints[0].line_hash,
|
||||
crate::fingerprint_hash("line", "let latest_value = 2;")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compaction_event_serializes_expected_shape() {
|
||||
let event = TrackEventRequest::Compaction(Box::new(CodexCompactionEventRequest {
|
||||
@@ -1055,7 +744,7 @@ fn compaction_event_serializes_expected_shape() {
|
||||
},
|
||||
sample_app_server_client_metadata(),
|
||||
sample_runtime_metadata(),
|
||||
Some(ThreadSource::User),
|
||||
Some("user"),
|
||||
/*subagent_source*/ None,
|
||||
/*parent_thread_id*/ None,
|
||||
),
|
||||
@@ -1154,7 +843,7 @@ fn thread_initialized_event_serializes_expected_shape() {
|
||||
},
|
||||
model: "gpt-5".to_string(),
|
||||
ephemeral: true,
|
||||
thread_source: Some(ThreadSource::User),
|
||||
thread_source: Some("user"),
|
||||
initialization_mode: ThreadInitializationMode::New,
|
||||
subagent_source: None,
|
||||
parent_thread_id: None,
|
||||
@@ -1195,105 +884,6 @@ fn thread_initialized_event_serializes_expected_shape() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_execution_event_serializes_expected_shape() {
|
||||
let event = TrackEventRequest::CommandExecution(CodexCommandExecutionEventRequest {
|
||||
event_type: "codex_command_execution_event",
|
||||
event_params: CodexCommandExecutionEventParams {
|
||||
base: CodexToolItemEventBase {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
item_id: "item-1".to_string(),
|
||||
app_server_client: CodexAppServerClientMetadata {
|
||||
product_client_id: "codex_tui".to_string(),
|
||||
client_name: Some("codex-tui".to_string()),
|
||||
client_version: Some("1.2.3".to_string()),
|
||||
rpc_transport: AppServerRpcTransport::Websocket,
|
||||
experimental_api_enabled: Some(true),
|
||||
},
|
||||
runtime: CodexRuntimeMetadata {
|
||||
codex_rs_version: "0.99.0".to_string(),
|
||||
runtime_os: "macos".to_string(),
|
||||
runtime_os_version: "15.3.1".to_string(),
|
||||
runtime_arch: "aarch64".to_string(),
|
||||
},
|
||||
thread_source: Some(ThreadSource::User),
|
||||
subagent_source: None,
|
||||
parent_thread_id: None,
|
||||
tool_name: "shell".to_string(),
|
||||
started_at_ms: 123_000,
|
||||
completed_at_ms: 125_000,
|
||||
duration_ms: Some(2000),
|
||||
execution_duration_ms: Some(1900),
|
||||
review_count: 0,
|
||||
guardian_review_count: 0,
|
||||
user_review_count: 0,
|
||||
final_approval_outcome: ToolItemFinalApprovalOutcome::NotNeeded,
|
||||
terminal_status: ToolItemTerminalStatus::Completed,
|
||||
failure_kind: None,
|
||||
requested_additional_permissions: false,
|
||||
requested_network_access: false,
|
||||
},
|
||||
command_execution_source: CommandExecutionSource::Agent,
|
||||
exit_code: Some(0),
|
||||
command_total_action_count: 4,
|
||||
command_read_action_count: 1,
|
||||
command_list_files_action_count: 1,
|
||||
command_search_action_count: 1,
|
||||
command_unknown_action_count: 1,
|
||||
},
|
||||
});
|
||||
|
||||
let payload = serde_json::to_value(&event).expect("serialize command execution event");
|
||||
assert_eq!(
|
||||
payload,
|
||||
json!({
|
||||
"event_type": "codex_command_execution_event",
|
||||
"event_params": {
|
||||
"thread_id": "thread-1",
|
||||
"turn_id": "turn-1",
|
||||
"item_id": "item-1",
|
||||
"app_server_client": {
|
||||
"product_client_id": "codex_tui",
|
||||
"client_name": "codex-tui",
|
||||
"client_version": "1.2.3",
|
||||
"rpc_transport": "websocket",
|
||||
"experimental_api_enabled": true
|
||||
},
|
||||
"runtime": {
|
||||
"codex_rs_version": "0.99.0",
|
||||
"runtime_os": "macos",
|
||||
"runtime_os_version": "15.3.1",
|
||||
"runtime_arch": "aarch64"
|
||||
},
|
||||
"thread_source": "user",
|
||||
"subagent_source": null,
|
||||
"parent_thread_id": null,
|
||||
"tool_name": "shell",
|
||||
"started_at_ms": 123000,
|
||||
"completed_at_ms": 125000,
|
||||
"duration_ms": 2000,
|
||||
"execution_duration_ms": 1900,
|
||||
"review_count": 0,
|
||||
"guardian_review_count": 0,
|
||||
"user_review_count": 0,
|
||||
"final_approval_outcome": "not_needed",
|
||||
"terminal_status": "completed",
|
||||
"failure_kind": null,
|
||||
"requested_additional_permissions": false,
|
||||
"requested_network_access": false,
|
||||
"command_execution_source": "agent",
|
||||
"exit_code": 0,
|
||||
"command_total_action_count": 4,
|
||||
"command_read_action_count": 1,
|
||||
"command_list_files_action_count": 1,
|
||||
"command_search_action_count": 1,
|
||||
"command_unknown_action_count": 1
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn initialize_caches_client_and_thread_lifecycle_publishes_once_initialized() {
|
||||
let mut reducer = AnalyticsReducer::default();
|
||||
@@ -1327,7 +917,6 @@ async fn initialize_caches_client_and_thread_lifecycle_publishes_once_initialize
|
||||
},
|
||||
capabilities: Some(InitializeCapabilities {
|
||||
experimental_api: false,
|
||||
request_attestation: false,
|
||||
opt_out_notification_methods: None,
|
||||
}),
|
||||
},
|
||||
@@ -1475,7 +1064,6 @@ async fn compaction_event_ingests_custom_fact() {
|
||||
},
|
||||
capabilities: Some(InitializeCapabilities {
|
||||
experimental_api: false,
|
||||
request_attestation: false,
|
||||
opt_out_notification_methods: None,
|
||||
}),
|
||||
},
|
||||
@@ -1502,7 +1090,6 @@ async fn compaction_event_ingests_custom_fact() {
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
}),
|
||||
Some(AppServerThreadSource::Subagent),
|
||||
)),
|
||||
},
|
||||
&mut events,
|
||||
@@ -1589,7 +1176,6 @@ async fn guardian_review_event_ingests_custom_fact_with_optional_target_item() {
|
||||
},
|
||||
capabilities: Some(InitializeCapabilities {
|
||||
experimental_api: false,
|
||||
request_attestation: false,
|
||||
opt_out_notification_methods: None,
|
||||
}),
|
||||
},
|
||||
@@ -1703,114 +1289,6 @@ async fn guardian_review_event_ingests_custom_fact_with_optional_target_item() {
|
||||
assert_eq!(payload[0]["event_params"]["review_timeout_ms"], 90_000);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn item_lifecycle_notifications_publish_command_execution_event() {
|
||||
let mut reducer = AnalyticsReducer::default();
|
||||
let mut events = Vec::new();
|
||||
|
||||
ingest_tool_review_prerequisites(&mut reducer, &mut events).await;
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Notification(Box::new(ServerNotification::ItemStarted(
|
||||
ItemStartedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
started_at_ms: 1_000,
|
||||
item: sample_command_execution_item(
|
||||
CommandExecutionStatus::InProgress,
|
||||
/*exit_code*/ None,
|
||||
/*duration_ms*/ None,
|
||||
),
|
||||
},
|
||||
))),
|
||||
&mut events,
|
||||
)
|
||||
.await;
|
||||
assert!(
|
||||
events.is_empty(),
|
||||
"tool item event should emit on completion"
|
||||
);
|
||||
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Notification(Box::new(ServerNotification::ItemCompleted(
|
||||
ItemCompletedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 1_045,
|
||||
item: sample_command_execution_item_with_actions(
|
||||
CommandExecutionStatus::Completed,
|
||||
Some(0),
|
||||
Some(42),
|
||||
vec![
|
||||
CommandAction::Read {
|
||||
command: "cat README.md".to_string(),
|
||||
name: "README.md".to_string(),
|
||||
path: test_path_buf("/tmp/README.md").abs(),
|
||||
},
|
||||
CommandAction::ListFiles {
|
||||
command: "ls".to_string(),
|
||||
path: None,
|
||||
},
|
||||
CommandAction::Search {
|
||||
command: "rg TODO".to_string(),
|
||||
query: Some("TODO".to_string()),
|
||||
path: None,
|
||||
},
|
||||
CommandAction::Unknown {
|
||||
command: "cargo test".to_string(),
|
||||
},
|
||||
],
|
||||
),
|
||||
},
|
||||
))),
|
||||
&mut events,
|
||||
)
|
||||
.await;
|
||||
|
||||
let payload = serde_json::to_value(&events).expect("serialize events");
|
||||
assert_eq!(payload.as_array().expect("events array").len(), 1);
|
||||
assert_eq!(payload[0]["event_type"], "codex_command_execution_event");
|
||||
assert_eq!(payload[0]["event_params"]["thread_id"], "thread-1");
|
||||
assert_eq!(payload[0]["event_params"]["turn_id"], "turn-1");
|
||||
assert_eq!(payload[0]["event_params"]["item_id"], "item-1");
|
||||
assert_eq!(payload[0]["event_params"]["tool_name"], "shell");
|
||||
assert_eq!(
|
||||
payload[0]["event_params"]["command_execution_source"],
|
||||
"agent"
|
||||
);
|
||||
assert_eq!(payload[0]["event_params"]["terminal_status"], "completed");
|
||||
assert_eq!(
|
||||
payload[0]["event_params"]["final_approval_outcome"],
|
||||
"unknown"
|
||||
);
|
||||
assert_eq!(
|
||||
payload[0]["event_params"]["failure_kind"],
|
||||
serde_json::Value::Null
|
||||
);
|
||||
assert_eq!(payload[0]["event_params"]["exit_code"], 0);
|
||||
assert_eq!(payload[0]["event_params"]["command_total_action_count"], 4);
|
||||
assert_eq!(payload[0]["event_params"]["command_read_action_count"], 1);
|
||||
assert_eq!(
|
||||
payload[0]["event_params"]["command_list_files_action_count"],
|
||||
1
|
||||
);
|
||||
assert_eq!(payload[0]["event_params"]["command_search_action_count"], 1);
|
||||
assert_eq!(
|
||||
payload[0]["event_params"]["command_unknown_action_count"],
|
||||
1
|
||||
);
|
||||
assert_eq!(payload[0]["event_params"]["started_at_ms"], 1_000);
|
||||
assert_eq!(payload[0]["event_params"]["completed_at_ms"], 1_045);
|
||||
assert_eq!(payload[0]["event_params"]["duration_ms"], 45);
|
||||
assert_eq!(payload[0]["event_params"]["execution_duration_ms"], 42);
|
||||
assert_eq!(
|
||||
payload[0]["event_params"]["app_server_client"]["client_name"],
|
||||
"codex-tui"
|
||||
);
|
||||
assert_eq!(payload[0]["event_params"]["thread_source"], "user");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subagent_thread_started_review_serializes_expected_shape() {
|
||||
let event = TrackEventRequest::ThreadInitialized(subagent_thread_started_event_request(
|
||||
@@ -2094,79 +1572,6 @@ async fn subagent_thread_started_inherits_parent_connection_for_new_thread() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn subagent_tool_items_inherit_parent_connection_metadata() {
|
||||
let mut reducer = AnalyticsReducer::default();
|
||||
let mut events = Vec::new();
|
||||
|
||||
ingest_tool_review_prerequisites(&mut reducer, &mut events).await;
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Custom(CustomAnalyticsFact::SubAgentThreadStarted(
|
||||
SubAgentThreadStartedInput {
|
||||
thread_id: "thread-subagent".to_string(),
|
||||
parent_thread_id: Some("thread-1".to_string()),
|
||||
product_client_id: "codex-tui".to_string(),
|
||||
client_name: "codex-tui".to_string(),
|
||||
client_version: "1.0.0".to_string(),
|
||||
model: "gpt-5".to_string(),
|
||||
ephemeral: false,
|
||||
subagent_source: SubAgentSource::Review,
|
||||
created_at: 128,
|
||||
},
|
||||
)),
|
||||
&mut events,
|
||||
)
|
||||
.await;
|
||||
events.clear();
|
||||
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Notification(Box::new(ServerNotification::ItemStarted(
|
||||
ItemStartedNotification {
|
||||
thread_id: "thread-subagent".to_string(),
|
||||
turn_id: "turn-subagent".to_string(),
|
||||
started_at_ms: 1_000,
|
||||
item: sample_command_execution_item(
|
||||
CommandExecutionStatus::InProgress,
|
||||
/*exit_code*/ None,
|
||||
/*duration_ms*/ None,
|
||||
),
|
||||
},
|
||||
))),
|
||||
&mut events,
|
||||
)
|
||||
.await;
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Notification(Box::new(ServerNotification::ItemCompleted(
|
||||
ItemCompletedNotification {
|
||||
thread_id: "thread-subagent".to_string(),
|
||||
turn_id: "turn-subagent".to_string(),
|
||||
completed_at_ms: 1_042,
|
||||
item: sample_command_execution_item(
|
||||
CommandExecutionStatus::Completed,
|
||||
Some(0),
|
||||
Some(42),
|
||||
),
|
||||
},
|
||||
))),
|
||||
&mut events,
|
||||
)
|
||||
.await;
|
||||
|
||||
let payload = serde_json::to_value(&events).expect("serialize events");
|
||||
assert_eq!(payload.as_array().expect("events array").len(), 1);
|
||||
assert_eq!(payload[0]["event_type"], "codex_command_execution_event");
|
||||
assert_eq!(payload[0]["event_params"]["thread_source"], "subagent");
|
||||
assert_eq!(payload[0]["event_params"]["subagent_source"], "review");
|
||||
assert_eq!(payload[0]["event_params"]["parent_thread_id"], "thread-1");
|
||||
assert_eq!(
|
||||
payload[0]["event_params"]["app_server_client"]["client_name"],
|
||||
"codex-tui"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plugin_used_event_serializes_expected_shape() {
|
||||
let tracking = TrackEventsContext {
|
||||
@@ -2411,7 +1816,6 @@ async fn reducer_ingests_skill_invoked_fact() {
|
||||
skill_name: "doc".to_string(),
|
||||
skill_scope: codex_protocol::protocol::SkillScope::User,
|
||||
skill_path,
|
||||
plugin_id: None,
|
||||
invocation_type: InvocationType::Explicit,
|
||||
}],
|
||||
})),
|
||||
@@ -2429,10 +1833,8 @@ async fn reducer_ingests_skill_invoked_fact() {
|
||||
"event_params": {
|
||||
"product_client_id": originator().value,
|
||||
"skill_scope": "user",
|
||||
"plugin_id": null,
|
||||
"repo_url": null,
|
||||
"thread_id": "thread-1",
|
||||
"turn_id": "turn-1",
|
||||
"invoke_type": "explicit",
|
||||
"model_slug": "gpt-5"
|
||||
}
|
||||
@@ -2440,41 +1842,6 @@ async fn reducer_ingests_skill_invoked_fact() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reducer_includes_plugin_id_for_plugin_skill_invocations() {
|
||||
let mut reducer = AnalyticsReducer::default();
|
||||
let mut events = Vec::new();
|
||||
let tracking = TrackEventsContext {
|
||||
model_slug: "gpt-5".to_string(),
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
};
|
||||
let skill_path =
|
||||
PathBuf::from("/Users/abc/.codex/plugins/cache/test/sample/skills/doc/SKILL.md");
|
||||
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Custom(CustomAnalyticsFact::SkillInvoked(SkillInvokedInput {
|
||||
tracking,
|
||||
invocations: vec![SkillInvocation {
|
||||
skill_name: "sample:doc".to_string(),
|
||||
skill_scope: codex_protocol::protocol::SkillScope::User,
|
||||
skill_path,
|
||||
plugin_id: Some("sample@test".to_string()),
|
||||
invocation_type: InvocationType::Explicit,
|
||||
}],
|
||||
})),
|
||||
&mut events,
|
||||
)
|
||||
.await;
|
||||
|
||||
let payload = serde_json::to_value(&events).expect("serialize events");
|
||||
assert_eq!(
|
||||
payload[0]["event_params"]["plugin_id"],
|
||||
json!("sample@test")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reducer_ingests_hook_run_fact() {
|
||||
let mut reducer = AnalyticsReducer::default();
|
||||
@@ -2605,7 +1972,7 @@ fn turn_event_serializes_expected_shape() {
|
||||
runtime: sample_runtime_metadata(),
|
||||
submission_type: None,
|
||||
ephemeral: false,
|
||||
thread_source: Some(ThreadSource::User),
|
||||
thread_source: Some("user".to_string()),
|
||||
initialization_mode: ThreadInitializationMode::New,
|
||||
subagent_source: None,
|
||||
parent_thread_id: None,
|
||||
|
||||
@@ -30,7 +30,6 @@ use codex_app_server_protocol::ServerNotification;
|
||||
use codex_app_server_protocol::ServerRequest;
|
||||
use codex_app_server_protocol::ServerResponse;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_login::default_client::create_client;
|
||||
use codex_plugin::PluginTelemetryMetadata;
|
||||
use std::collections::HashSet;
|
||||
@@ -334,6 +333,10 @@ impl AnalyticsEventsClient {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn track_notification(&self, notification: ServerNotification) {
|
||||
self.record_fact(AnalyticsFact::Notification(Box::new(notification)));
|
||||
}
|
||||
|
||||
pub fn track_server_request(&self, connection_id: u64, request: ServerRequest) {
|
||||
self.record_fact(AnalyticsFact::ServerRequest {
|
||||
connection_id,
|
||||
@@ -341,28 +344,11 @@ impl AnalyticsEventsClient {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn track_server_response(&self, completed_at_ms: u64, response: ServerResponse) {
|
||||
pub fn track_server_response(&self, response: ServerResponse) {
|
||||
self.record_fact(AnalyticsFact::ServerResponse {
|
||||
completed_at_ms,
|
||||
response: Box::new(response),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn track_notification(&self, notification: ServerNotification) {
|
||||
if !matches!(
|
||||
notification,
|
||||
ServerNotification::TurnStarted(_)
|
||||
| ServerNotification::TurnCompleted(_)
|
||||
| ServerNotification::TurnDiffUpdated(_)
|
||||
| ServerNotification::ItemStarted(_)
|
||||
| ServerNotification::ItemCompleted(_)
|
||||
| ServerNotification::ItemGuardianApprovalReviewStarted(_)
|
||||
| ServerNotification::ItemGuardianApprovalReviewCompleted(_)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
self.record_fact(AnalyticsFact::Notification(Box::new(notification)));
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_track_events(
|
||||
@@ -373,7 +359,6 @@ async fn send_track_events(
|
||||
if events.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(auth) = auth_manager.auth().await else {
|
||||
return;
|
||||
};
|
||||
@@ -383,45 +368,12 @@ async fn send_track_events(
|
||||
|
||||
let base_url = base_url.trim_end_matches('/');
|
||||
let url = format!("{base_url}/codex/analytics-events/events");
|
||||
for events in track_event_request_batches(events) {
|
||||
send_track_events_request(&auth, &url, events).await;
|
||||
}
|
||||
}
|
||||
|
||||
fn track_event_request_batches(events: Vec<TrackEventRequest>) -> Vec<Vec<TrackEventRequest>> {
|
||||
let mut batches = Vec::new();
|
||||
let mut current_batch = Vec::new();
|
||||
|
||||
for event in events {
|
||||
if event.should_send_in_isolated_request() {
|
||||
if !current_batch.is_empty() {
|
||||
batches.push(current_batch);
|
||||
current_batch = Vec::new();
|
||||
}
|
||||
batches.push(vec![event]);
|
||||
} else {
|
||||
current_batch.push(event);
|
||||
}
|
||||
}
|
||||
|
||||
if !current_batch.is_empty() {
|
||||
batches.push(current_batch);
|
||||
}
|
||||
|
||||
batches
|
||||
}
|
||||
|
||||
async fn send_track_events_request(auth: &CodexAuth, url: &str, events: Vec<TrackEventRequest>) {
|
||||
if events.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let payload = TrackEventsRequest { events };
|
||||
|
||||
let response = create_client()
|
||||
.post(url)
|
||||
.post(&url)
|
||||
.timeout(ANALYTICS_EVENTS_TIMEOUT)
|
||||
.headers(codex_model_provider::auth_provider_from_auth(auth).to_auth_headers())
|
||||
.headers(codex_model_provider::auth_provider_from_auth(&auth).to_auth_headers())
|
||||
.header("Content-Type", "application/json")
|
||||
.json(&payload)
|
||||
.send()
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
use super::AnalyticsEventsClient;
|
||||
use super::AnalyticsEventsQueue;
|
||||
use super::track_event_request_batches;
|
||||
use crate::events::CodexAcceptedLineFingerprintsEventParams;
|
||||
use crate::events::CodexAcceptedLineFingerprintsEventRequest;
|
||||
use crate::events::SkillInvocationEventParams;
|
||||
use crate::events::SkillInvocationEventRequest;
|
||||
use crate::events::TrackEventRequest;
|
||||
use crate::facts::AcceptedLineFingerprint;
|
||||
use crate::facts::AnalyticsFact;
|
||||
use crate::facts::InvocationType;
|
||||
use codex_app_server_protocol::ApprovalsReviewer as AppServerApprovalsReviewer;
|
||||
use codex_app_server_protocol::AskForApproval as AppServerAskForApproval;
|
||||
use codex_app_server_protocol::ClientRequest;
|
||||
@@ -39,47 +31,6 @@ use std::sync::Mutex;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::mpsc::error::TryRecvError;
|
||||
|
||||
fn sample_accepted_line_fingerprint_event(thread_id: &str) -> TrackEventRequest {
|
||||
TrackEventRequest::AcceptedLineFingerprints(Box::new(
|
||||
CodexAcceptedLineFingerprintsEventRequest {
|
||||
event_type: "codex_accepted_line_fingerprints",
|
||||
event_params: CodexAcceptedLineFingerprintsEventParams {
|
||||
event_type: "codex.accepted_line_fingerprints",
|
||||
turn_id: "turn-1".to_string(),
|
||||
thread_id: thread_id.to_string(),
|
||||
product_surface: Some("codex".to_string()),
|
||||
model_slug: Some("gpt-5.1-codex".to_string()),
|
||||
completed_at: 1,
|
||||
repo_hash: None,
|
||||
accepted_added_lines: 1,
|
||||
accepted_deleted_lines: 0,
|
||||
line_fingerprints: vec![AcceptedLineFingerprint {
|
||||
path_hash: "path-hash".to_string(),
|
||||
line_hash: "line-hash".to_string(),
|
||||
}],
|
||||
},
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn sample_regular_track_event(thread_id: &str) -> TrackEventRequest {
|
||||
TrackEventRequest::SkillInvocation(SkillInvocationEventRequest {
|
||||
event_type: "skill_invocation",
|
||||
skill_id: format!("skill-{thread_id}"),
|
||||
skill_name: "doc".to_string(),
|
||||
event_params: SkillInvocationEventParams {
|
||||
product_client_id: None,
|
||||
skill_scope: None,
|
||||
plugin_id: None,
|
||||
repo_url: None,
|
||||
thread_id: Some(thread_id.to_string()),
|
||||
turn_id: Some("turn-1".to_string()),
|
||||
invoke_type: Some(InvocationType::Explicit),
|
||||
model_slug: Some("gpt-5.1-codex".to_string()),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn client_with_receiver() -> (AnalyticsEventsClient, mpsc::Receiver<AnalyticsFact>) {
|
||||
let (sender, receiver) = mpsc::channel(8);
|
||||
let queue = AnalyticsEventsQueue {
|
||||
@@ -125,7 +76,6 @@ fn sample_thread_archive_request() -> ClientRequest {
|
||||
fn sample_thread(thread_id: &str) -> Thread {
|
||||
Thread {
|
||||
id: thread_id.to_string(),
|
||||
session_id: format!("session-{thread_id}"),
|
||||
forked_from_id: None,
|
||||
preview: "first prompt".to_string(),
|
||||
ephemeral: false,
|
||||
@@ -137,7 +87,6 @@ fn sample_thread(thread_id: &str) -> Thread {
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
cli_version: "0.0.0".to_string(),
|
||||
source: AppServerSessionSource::Exec,
|
||||
thread_source: None,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
git_info: None,
|
||||
@@ -205,7 +154,6 @@ fn sample_turn_start_response() -> ClientResponsePayload {
|
||||
ClientResponsePayload::TurnStart(TurnStartResponse {
|
||||
turn: Turn {
|
||||
id: "turn-1".to_string(),
|
||||
items_view: codex_app_server_protocol::TurnItemsView::Full,
|
||||
items: Vec::new(),
|
||||
status: AppServerTurnStatus::InProgress,
|
||||
error: None,
|
||||
@@ -271,23 +219,3 @@ fn track_response_only_enqueues_analytics_relevant_responses() {
|
||||
);
|
||||
assert!(matches!(receiver.try_recv(), Err(TryRecvError::Empty)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn track_event_request_batches_only_isolates_accepted_line_fingerprint_events() {
|
||||
let batches = track_event_request_batches(vec![
|
||||
sample_regular_track_event("thread-1"),
|
||||
sample_regular_track_event("thread-2"),
|
||||
sample_accepted_line_fingerprint_event("thread-3"),
|
||||
sample_accepted_line_fingerprint_event("thread-4"),
|
||||
sample_regular_track_event("thread-5"),
|
||||
sample_regular_track_event("thread-6"),
|
||||
]);
|
||||
|
||||
assert_eq!(batches.len(), 4);
|
||||
assert_eq!(batches[0].len(), 2);
|
||||
assert_eq!(batches[1].len(), 1);
|
||||
assert_eq!(batches[2].len(), 1);
|
||||
assert_eq!(batches[3].len(), 2);
|
||||
assert!(batches[1][0].should_send_in_isolated_request());
|
||||
assert!(batches[2][0].should_send_in_isolated_request());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::facts::AcceptedLineFingerprint;
|
||||
use crate::facts::AppInvocation;
|
||||
use crate::facts::CodexCompactionEvent;
|
||||
use crate::facts::CompactionImplementation;
|
||||
@@ -19,10 +18,8 @@ use crate::facts::TurnStatus;
|
||||
use crate::facts::TurnSteerRejectionReason;
|
||||
use crate::facts::TurnSteerResult;
|
||||
use crate::facts::TurnSubmissionType;
|
||||
use crate::now_unix_millis;
|
||||
use crate::now_unix_seconds;
|
||||
use codex_app_server_protocol::CodexErrorInfo;
|
||||
use codex_app_server_protocol::CommandExecutionSource;
|
||||
use codex_login::default_client::originator;
|
||||
use codex_plugin::PluginTelemetryMetadata;
|
||||
use codex_protocol::approvals::NetworkApprovalProtocol;
|
||||
@@ -36,7 +33,6 @@ use codex_protocol::protocol::HookEventName;
|
||||
use codex_protocol::protocol::HookRunStatus;
|
||||
use codex_protocol::protocol::HookSource;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
use codex_protocol::protocol::ThreadSource;
|
||||
use codex_protocol::protocol::TokenUsage;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -65,16 +61,6 @@ pub(crate) enum TrackEventRequest {
|
||||
Compaction(Box<CodexCompactionEventRequest>),
|
||||
TurnEvent(Box<CodexTurnEventRequest>),
|
||||
TurnSteer(CodexTurnSteerEventRequest),
|
||||
CommandExecution(CodexCommandExecutionEventRequest),
|
||||
FileChange(CodexFileChangeEventRequest),
|
||||
McpToolCall(CodexMcpToolCallEventRequest),
|
||||
DynamicToolCall(CodexDynamicToolCallEventRequest),
|
||||
CollabAgentToolCall(CodexCollabAgentToolCallEventRequest),
|
||||
WebSearch(CodexWebSearchEventRequest),
|
||||
ImageGeneration(CodexImageGenerationEventRequest),
|
||||
AcceptedLineFingerprints(Box<CodexAcceptedLineFingerprintsEventRequest>),
|
||||
#[allow(dead_code)]
|
||||
ReviewEvent(CodexReviewEventRequest),
|
||||
PluginUsed(CodexPluginUsedEventRequest),
|
||||
PluginInstalled(CodexPluginEventRequest),
|
||||
PluginUninstalled(CodexPluginEventRequest),
|
||||
@@ -82,32 +68,6 @@ pub(crate) enum TrackEventRequest {
|
||||
PluginDisabled(CodexPluginEventRequest),
|
||||
}
|
||||
|
||||
impl TrackEventRequest {
|
||||
pub(crate) fn should_send_in_isolated_request(&self) -> bool {
|
||||
matches!(self, Self::AcceptedLineFingerprints(_))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexAcceptedLineFingerprintsEventParams {
|
||||
pub(crate) event_type: &'static str,
|
||||
pub(crate) turn_id: String,
|
||||
pub(crate) thread_id: String,
|
||||
pub(crate) product_surface: Option<String>,
|
||||
pub(crate) model_slug: Option<String>,
|
||||
pub(crate) completed_at: u64,
|
||||
pub(crate) repo_hash: Option<String>,
|
||||
pub(crate) accepted_added_lines: u64,
|
||||
pub(crate) accepted_deleted_lines: u64,
|
||||
pub(crate) line_fingerprints: Vec<AcceptedLineFingerprint>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexAcceptedLineFingerprintsEventRequest {
|
||||
pub(crate) event_type: &'static str,
|
||||
pub(crate) event_params: CodexAcceptedLineFingerprintsEventParams,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct SkillInvocationEventRequest {
|
||||
pub(crate) event_type: &'static str,
|
||||
@@ -120,10 +80,8 @@ pub(crate) struct SkillInvocationEventRequest {
|
||||
pub(crate) struct SkillInvocationEventParams {
|
||||
pub(crate) product_client_id: Option<String>,
|
||||
pub(crate) skill_scope: Option<String>,
|
||||
pub(crate) plugin_id: Option<String>,
|
||||
pub(crate) repo_url: Option<String>,
|
||||
pub(crate) thread_id: Option<String>,
|
||||
pub(crate) turn_id: Option<String>,
|
||||
pub(crate) invoke_type: Option<InvocationType>,
|
||||
pub(crate) model_slug: Option<String>,
|
||||
}
|
||||
@@ -152,7 +110,7 @@ pub(crate) struct ThreadInitializedEventParams {
|
||||
pub(crate) runtime: CodexRuntimeMetadata,
|
||||
pub(crate) model: String,
|
||||
pub(crate) ephemeral: bool,
|
||||
pub(crate) thread_source: Option<ThreadSource>,
|
||||
pub(crate) thread_source: Option<&'static str>,
|
||||
pub(crate) initialization_mode: ThreadInitializationMode,
|
||||
pub(crate) subagent_source: Option<String>,
|
||||
pub(crate) parent_thread_id: Option<String>,
|
||||
@@ -290,7 +248,7 @@ pub struct GuardianReviewTrackContext {
|
||||
approval_request_source: GuardianApprovalRequestSource,
|
||||
reviewed_action: GuardianReviewedAction,
|
||||
review_timeout_ms: u64,
|
||||
pub started_at_ms: u64,
|
||||
started_at: u64,
|
||||
started_instant: Instant,
|
||||
}
|
||||
|
||||
@@ -312,7 +270,7 @@ impl GuardianReviewTrackContext {
|
||||
approval_request_source,
|
||||
reviewed_action,
|
||||
review_timeout_ms,
|
||||
started_at_ms: now_unix_millis(),
|
||||
started_at: now_unix_seconds(),
|
||||
started_instant: Instant::now(),
|
||||
}
|
||||
}
|
||||
@@ -345,7 +303,7 @@ impl GuardianReviewTrackContext {
|
||||
tool_call_count: None,
|
||||
time_to_first_token_ms: result.time_to_first_token_ms,
|
||||
completion_latency_ms: Some(self.started_instant.elapsed().as_millis() as u64),
|
||||
started_at: self.started_at_ms / 1_000,
|
||||
started_at: self.started_at,
|
||||
completed_at: Some(now_unix_seconds()),
|
||||
input_tokens: result.token_usage.as_ref().map(|usage| usage.input_tokens),
|
||||
cached_input_tokens: result
|
||||
@@ -426,276 +384,6 @@ pub(crate) struct GuardianReviewEventPayload {
|
||||
pub(crate) guardian_review: GuardianReviewEventParams,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub(crate) enum ToolItemFinalApprovalOutcome {
|
||||
Unknown,
|
||||
NotNeeded,
|
||||
ConfigAllowed,
|
||||
PolicyForbidden,
|
||||
GuardianApproved,
|
||||
GuardianDenied,
|
||||
GuardianAborted,
|
||||
UserApproved,
|
||||
UserApprovedForSession,
|
||||
UserDenied,
|
||||
UserAborted,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub(crate) enum ToolItemTerminalStatus {
|
||||
Completed,
|
||||
Failed,
|
||||
Rejected,
|
||||
Interrupted,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub(crate) enum ToolItemFailureKind {
|
||||
ToolError,
|
||||
ApprovalDenied,
|
||||
ApprovalAborted,
|
||||
SandboxDenied,
|
||||
PolicyForbidden,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexToolItemEventBase {
|
||||
pub(crate) thread_id: String,
|
||||
pub(crate) turn_id: String,
|
||||
/// App-server ThreadItem.id. For tool-originated items this generally
|
||||
/// corresponds to the originating core call_id.
|
||||
pub(crate) item_id: String,
|
||||
pub(crate) app_server_client: CodexAppServerClientMetadata,
|
||||
pub(crate) runtime: CodexRuntimeMetadata,
|
||||
pub(crate) thread_source: Option<ThreadSource>,
|
||||
pub(crate) subagent_source: Option<String>,
|
||||
pub(crate) parent_thread_id: Option<String>,
|
||||
pub(crate) tool_name: String,
|
||||
pub(crate) started_at_ms: u64,
|
||||
pub(crate) completed_at_ms: u64,
|
||||
// Observed item lifecycle duration. This may undercount end-to-end execution
|
||||
// for tools where app-server only sees part of the upstream flow.
|
||||
pub(crate) duration_ms: Option<u64>,
|
||||
pub(crate) execution_duration_ms: Option<u64>,
|
||||
pub(crate) review_count: u64,
|
||||
pub(crate) guardian_review_count: u64,
|
||||
pub(crate) user_review_count: u64,
|
||||
pub(crate) final_approval_outcome: ToolItemFinalApprovalOutcome,
|
||||
pub(crate) terminal_status: ToolItemTerminalStatus,
|
||||
pub(crate) failure_kind: Option<ToolItemFailureKind>,
|
||||
pub(crate) requested_additional_permissions: bool,
|
||||
pub(crate) requested_network_access: bool,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub(crate) enum ReviewSubjectKind {
|
||||
CommandExecution,
|
||||
FileChange,
|
||||
McpToolCall,
|
||||
Permissions,
|
||||
NetworkAccess,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub(crate) enum Reviewer {
|
||||
Guardian,
|
||||
User,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub(crate) enum ReviewTrigger {
|
||||
Initial,
|
||||
SandboxDenial,
|
||||
NetworkPolicyDenial,
|
||||
ExecveIntercept,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub(crate) enum ReviewStatus {
|
||||
Approved,
|
||||
Denied,
|
||||
Aborted,
|
||||
TimedOut,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub(crate) enum ReviewResolution {
|
||||
None,
|
||||
SessionApproval,
|
||||
ExecPolicyAmendment,
|
||||
NetworkPolicyAmendment,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexReviewEventParams {
|
||||
pub(crate) thread_id: String,
|
||||
pub(crate) turn_id: String,
|
||||
pub(crate) item_id: Option<String>,
|
||||
pub(crate) review_id: String,
|
||||
pub(crate) app_server_client: CodexAppServerClientMetadata,
|
||||
pub(crate) runtime: CodexRuntimeMetadata,
|
||||
pub(crate) thread_source: Option<ThreadSource>,
|
||||
pub(crate) subagent_source: Option<String>,
|
||||
pub(crate) parent_thread_id: Option<String>,
|
||||
pub(crate) tool_kind: ReviewSubjectKind,
|
||||
pub(crate) tool_name: String,
|
||||
pub(crate) reviewer: Reviewer,
|
||||
pub(crate) trigger: ReviewTrigger,
|
||||
pub(crate) status: ReviewStatus,
|
||||
pub(crate) resolution: ReviewResolution,
|
||||
pub(crate) started_at_ms: u64,
|
||||
pub(crate) completed_at_ms: u64,
|
||||
pub(crate) duration_ms: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexReviewEventRequest {
|
||||
pub(crate) event_type: &'static str,
|
||||
pub(crate) event_params: CodexReviewEventParams,
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub(crate) enum WebSearchActionKind {
|
||||
Search,
|
||||
OpenPage,
|
||||
FindInPage,
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexCommandExecutionEventParams {
|
||||
#[serde(flatten)]
|
||||
pub(crate) base: CodexToolItemEventBase,
|
||||
pub(crate) command_execution_source: CommandExecutionSource,
|
||||
pub(crate) exit_code: Option<i32>,
|
||||
pub(crate) command_total_action_count: u64,
|
||||
pub(crate) command_read_action_count: u64,
|
||||
pub(crate) command_list_files_action_count: u64,
|
||||
pub(crate) command_search_action_count: u64,
|
||||
pub(crate) command_unknown_action_count: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexCommandExecutionEventRequest {
|
||||
pub(crate) event_type: &'static str,
|
||||
pub(crate) event_params: CodexCommandExecutionEventParams,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexFileChangeEventParams {
|
||||
#[serde(flatten)]
|
||||
pub(crate) base: CodexToolItemEventBase,
|
||||
pub(crate) file_change_count: u64,
|
||||
pub(crate) file_add_count: u64,
|
||||
pub(crate) file_update_count: u64,
|
||||
pub(crate) file_delete_count: u64,
|
||||
pub(crate) file_move_count: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexFileChangeEventRequest {
|
||||
pub(crate) event_type: &'static str,
|
||||
pub(crate) event_params: CodexFileChangeEventParams,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexMcpToolCallEventParams {
|
||||
#[serde(flatten)]
|
||||
pub(crate) base: CodexToolItemEventBase,
|
||||
pub(crate) mcp_server_name: String,
|
||||
pub(crate) mcp_tool_name: String,
|
||||
pub(crate) mcp_error_present: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexMcpToolCallEventRequest {
|
||||
pub(crate) event_type: &'static str,
|
||||
pub(crate) event_params: CodexMcpToolCallEventParams,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexDynamicToolCallEventParams {
|
||||
#[serde(flatten)]
|
||||
pub(crate) base: CodexToolItemEventBase,
|
||||
pub(crate) dynamic_tool_name: String,
|
||||
pub(crate) success: Option<bool>,
|
||||
pub(crate) output_content_item_count: Option<u64>,
|
||||
pub(crate) output_text_item_count: Option<u64>,
|
||||
pub(crate) output_image_item_count: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexDynamicToolCallEventRequest {
|
||||
pub(crate) event_type: &'static str,
|
||||
pub(crate) event_params: CodexDynamicToolCallEventParams,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexCollabAgentToolCallEventParams {
|
||||
#[serde(flatten)]
|
||||
pub(crate) base: CodexToolItemEventBase,
|
||||
pub(crate) sender_thread_id: String,
|
||||
pub(crate) receiver_thread_count: u64,
|
||||
pub(crate) receiver_thread_ids: Option<Vec<String>>,
|
||||
pub(crate) requested_model: Option<String>,
|
||||
pub(crate) requested_reasoning_effort: Option<String>,
|
||||
pub(crate) agent_state_count: Option<u64>,
|
||||
pub(crate) completed_agent_count: Option<u64>,
|
||||
pub(crate) failed_agent_count: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexCollabAgentToolCallEventRequest {
|
||||
pub(crate) event_type: &'static str,
|
||||
pub(crate) event_params: CodexCollabAgentToolCallEventParams,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexWebSearchEventParams {
|
||||
#[serde(flatten)]
|
||||
pub(crate) base: CodexToolItemEventBase,
|
||||
pub(crate) web_search_action: Option<WebSearchActionKind>,
|
||||
pub(crate) query_present: bool,
|
||||
pub(crate) query_count: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexWebSearchEventRequest {
|
||||
pub(crate) event_type: &'static str,
|
||||
pub(crate) event_params: CodexWebSearchEventParams,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexImageGenerationEventParams {
|
||||
#[serde(flatten)]
|
||||
pub(crate) base: CodexToolItemEventBase,
|
||||
pub(crate) revised_prompt_present: bool,
|
||||
pub(crate) saved_path_present: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexImageGenerationEventRequest {
|
||||
pub(crate) event_type: &'static str,
|
||||
pub(crate) event_params: CodexImageGenerationEventParams,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexAppMetadata {
|
||||
pub(crate) connector_id: Option<String>,
|
||||
@@ -741,7 +429,7 @@ pub(crate) struct CodexCompactionEventParams {
|
||||
pub(crate) turn_id: String,
|
||||
pub(crate) app_server_client: CodexAppServerClientMetadata,
|
||||
pub(crate) runtime: CodexRuntimeMetadata,
|
||||
pub(crate) thread_source: Option<ThreadSource>,
|
||||
pub(crate) thread_source: Option<&'static str>,
|
||||
pub(crate) subagent_source: Option<String>,
|
||||
pub(crate) parent_thread_id: Option<String>,
|
||||
pub(crate) trigger: CompactionTrigger,
|
||||
@@ -774,7 +462,7 @@ pub(crate) struct CodexTurnEventParams {
|
||||
pub(crate) app_server_client: CodexAppServerClientMetadata,
|
||||
pub(crate) runtime: CodexRuntimeMetadata,
|
||||
pub(crate) ephemeral: bool,
|
||||
pub(crate) thread_source: Option<ThreadSource>,
|
||||
pub(crate) thread_source: Option<String>,
|
||||
pub(crate) initialization_mode: ThreadInitializationMode,
|
||||
pub(crate) subagent_source: Option<String>,
|
||||
pub(crate) parent_thread_id: Option<String>,
|
||||
@@ -827,7 +515,7 @@ pub(crate) struct CodexTurnSteerEventParams {
|
||||
pub(crate) accepted_turn_id: Option<String>,
|
||||
pub(crate) app_server_client: CodexAppServerClientMetadata,
|
||||
pub(crate) runtime: CodexRuntimeMetadata,
|
||||
pub(crate) thread_source: Option<ThreadSource>,
|
||||
pub(crate) thread_source: Option<String>,
|
||||
pub(crate) subagent_source: Option<String>,
|
||||
pub(crate) parent_thread_id: Option<String>,
|
||||
pub(crate) num_input_images: usize,
|
||||
@@ -930,7 +618,7 @@ pub(crate) fn codex_compaction_event_params(
|
||||
input: CodexCompactionEvent,
|
||||
app_server_client: CodexAppServerClientMetadata,
|
||||
runtime: CodexRuntimeMetadata,
|
||||
thread_source: Option<ThreadSource>,
|
||||
thread_source: Option<&'static str>,
|
||||
subagent_source: Option<String>,
|
||||
parent_thread_id: Option<String>,
|
||||
) -> CodexCompactionEventParams {
|
||||
@@ -988,8 +676,6 @@ fn analytics_hook_event_name(event_name: HookEventName) -> &'static str {
|
||||
HookEventName::PreToolUse => "PreToolUse",
|
||||
HookEventName::PermissionRequest => "PermissionRequest",
|
||||
HookEventName::PostToolUse => "PostToolUse",
|
||||
HookEventName::PreCompact => "PreCompact",
|
||||
HookEventName::PostCompact => "PostCompact",
|
||||
HookEventName::SessionStart => "SessionStart",
|
||||
HookEventName::UserPromptSubmit => "UserPromptSubmit",
|
||||
HookEventName::Stop => "Stop",
|
||||
@@ -1036,7 +722,7 @@ pub(crate) fn subagent_thread_started_event_request(
|
||||
runtime: current_runtime_metadata(),
|
||||
model: input.model,
|
||||
ephemeral: input.ephemeral,
|
||||
thread_source: Some(ThreadSource::Subagent),
|
||||
thread_source: Some("subagent"),
|
||||
initialization_mode: ThreadInitializationMode::New,
|
||||
subagent_source: Some(subagent_source_name(&input.subagent_source)),
|
||||
parent_thread_id: input
|
||||
|
||||
@@ -28,12 +28,6 @@ use codex_protocol::protocol::TokenUsage;
|
||||
use serde::Serialize;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||
pub struct AcceptedLineFingerprint {
|
||||
pub path_hash: String,
|
||||
pub line_hash: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TrackEventsContext {
|
||||
pub model_slug: String,
|
||||
@@ -179,7 +173,6 @@ pub struct SkillInvocation {
|
||||
pub skill_name: String,
|
||||
pub skill_scope: SkillScope,
|
||||
pub skill_path: PathBuf,
|
||||
pub plugin_id: Option<String>,
|
||||
pub invocation_type: InvocationType,
|
||||
}
|
||||
|
||||
@@ -302,7 +295,6 @@ pub(crate) enum AnalyticsFact {
|
||||
request: Box<ServerRequest>,
|
||||
},
|
||||
ServerResponse {
|
||||
completed_at_ms: u64,
|
||||
response: Box<ServerResponse>,
|
||||
},
|
||||
Notification(Box<ServerNotification>),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
mod accepted_lines;
|
||||
mod client;
|
||||
mod events;
|
||||
mod facts;
|
||||
@@ -7,8 +6,6 @@ mod reducer;
|
||||
use std::time::SystemTime;
|
||||
use std::time::UNIX_EPOCH;
|
||||
|
||||
pub use accepted_lines::accepted_line_fingerprints_from_unified_diff;
|
||||
pub use accepted_lines::fingerprint_hash;
|
||||
pub use client::AnalyticsEventsClient;
|
||||
pub use events::AppServerRpcTransport;
|
||||
pub use events::GuardianApprovalRequestSource;
|
||||
@@ -20,7 +17,6 @@ pub use events::GuardianReviewSessionKind;
|
||||
pub use events::GuardianReviewTerminalStatus;
|
||||
pub use events::GuardianReviewTrackContext;
|
||||
pub use events::GuardianReviewedAction;
|
||||
pub use facts::AcceptedLineFingerprint;
|
||||
pub use facts::AnalyticsJsonRpcError;
|
||||
pub use facts::AppInvocation;
|
||||
pub use facts::CodexCompactionEvent;
|
||||
@@ -55,27 +51,3 @@ pub fn now_unix_seconds() -> u64 {
|
||||
.unwrap_or_default()
|
||||
.as_secs()
|
||||
}
|
||||
|
||||
pub fn now_unix_millis() -> u64 {
|
||||
u64::try_from(
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis(),
|
||||
)
|
||||
.unwrap_or(u64::MAX)
|
||||
}
|
||||
|
||||
pub(crate) fn serialize_enum_as_string<T: serde::Serialize>(value: &T) -> Option<String> {
|
||||
serde_json::to_value(value)
|
||||
.ok()
|
||||
.and_then(|value| value.as_str().map(str::to_string))
|
||||
}
|
||||
|
||||
pub(crate) fn usize_to_u64(value: usize) -> u64 {
|
||||
u64::try_from(value).unwrap_or(u64::MAX)
|
||||
}
|
||||
|
||||
pub(crate) fn option_i64_to_u64(value: Option<i64>) -> Option<u64> {
|
||||
value.and_then(|value| u64::try_from(value).ok())
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,8 +7,6 @@ license.workspace = true
|
||||
[lib]
|
||||
name = "codex_ansi_escape"
|
||||
path = "src/lib.rs"
|
||||
test = false
|
||||
doctest = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -7,7 +7,6 @@ license.workspace = true
|
||||
[lib]
|
||||
name = "codex_app_server_client"
|
||||
path = "src/lib.rs"
|
||||
doctest = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -34,5 +33,4 @@ url = { workspace = true }
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
|
||||
|
||||
@@ -29,7 +29,6 @@ pub use codex_app_server::in_process::DEFAULT_IN_PROCESS_CHANNEL_CAPACITY;
|
||||
pub use codex_app_server::in_process::InProcessServerEvent;
|
||||
use codex_app_server::in_process::InProcessStartArgs;
|
||||
use codex_app_server::in_process::LogDbLayer;
|
||||
pub use codex_app_server::in_process::StateDbHandle;
|
||||
use codex_app_server_protocol::ClientInfo;
|
||||
use codex_app_server_protocol::ClientNotification;
|
||||
use codex_app_server_protocol::ClientRequest;
|
||||
@@ -49,6 +48,7 @@ use codex_config::RemoteThreadConfigLoader;
|
||||
use codex_config::ThreadConfigLoader;
|
||||
use codex_core::config::Config;
|
||||
pub use codex_exec_server::EnvironmentManager;
|
||||
pub use codex_exec_server::EnvironmentManagerArgs;
|
||||
pub use codex_exec_server::ExecServerRuntimePaths;
|
||||
use codex_feedback::CodexFeedback;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
@@ -71,9 +71,12 @@ pub mod legacy_core {
|
||||
pub use codex_core::DEFAULT_AGENTS_MD_FILENAME;
|
||||
pub use codex_core::LOCAL_AGENTS_MD_FILENAME;
|
||||
pub use codex_core::McpManager;
|
||||
pub use codex_core::append_message_history_entry;
|
||||
pub use codex_core::check_execpolicy_for_warnings;
|
||||
pub use codex_core::format_exec_policy_error_with_source;
|
||||
pub use codex_core::grant_read_root_non_elevated;
|
||||
pub use codex_core::lookup_message_history_entry;
|
||||
pub use codex_core::message_history_metadata;
|
||||
pub use codex_core::web_search_detail;
|
||||
|
||||
pub mod config {
|
||||
@@ -340,8 +343,6 @@ pub struct InProcessClientStartArgs {
|
||||
pub feedback: CodexFeedback,
|
||||
/// SQLite tracing layer used to flush recently emitted logs before feedback upload.
|
||||
pub log_db: Option<LogDbLayer>,
|
||||
/// Process-wide SQLite state handle shared with the embedded app-server.
|
||||
pub state_db: Option<StateDbHandle>,
|
||||
/// Environment manager used by core execution and filesystem operations.
|
||||
pub environment_manager: Arc<EnvironmentManager>,
|
||||
/// Startup warnings emitted after initialize succeeds.
|
||||
@@ -374,7 +375,6 @@ impl InProcessClientStartArgs {
|
||||
pub fn initialize_params(&self) -> InitializeParams {
|
||||
let capabilities = InitializeCapabilities {
|
||||
experimental_api: self.experimental_api,
|
||||
request_attestation: false,
|
||||
opt_out_notification_methods: if self.opt_out_notification_methods.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@@ -404,7 +404,6 @@ impl InProcessClientStartArgs {
|
||||
thread_config_loader,
|
||||
feedback: self.feedback,
|
||||
log_db: self.log_db,
|
||||
state_db: self.state_db,
|
||||
environment_manager: self.environment_manager,
|
||||
config_warnings: self.config_warnings,
|
||||
session_source: self.session_source,
|
||||
@@ -951,13 +950,9 @@ mod tests {
|
||||
use codex_app_server_protocol::ToolRequestUserInputParams;
|
||||
use codex_app_server_protocol::ToolRequestUserInputQuestion;
|
||||
use codex_core::config::ConfigBuilder;
|
||||
use codex_core::init_state_db;
|
||||
use futures::SinkExt;
|
||||
use futures::StreamExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
use tempfile::TempDir;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::time::Duration;
|
||||
use tokio::time::timeout;
|
||||
@@ -976,59 +971,18 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
async fn build_test_config_for_codex_home(codex_home: &Path) -> Config {
|
||||
match ConfigBuilder::default()
|
||||
.codex_home(codex_home.to_path_buf())
|
||||
.build()
|
||||
.await
|
||||
{
|
||||
Ok(config) => config,
|
||||
Err(_) => Config::load_default_with_cli_overrides_for_codex_home(
|
||||
codex_home.to_path_buf(),
|
||||
Vec::new(),
|
||||
)
|
||||
.await
|
||||
.expect("default config should load"),
|
||||
}
|
||||
}
|
||||
|
||||
struct TestClient {
|
||||
_codex_home: TempDir,
|
||||
client: InProcessAppServerClient,
|
||||
}
|
||||
|
||||
impl Deref for TestClient {
|
||||
type Target = InProcessAppServerClient;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.client
|
||||
}
|
||||
}
|
||||
|
||||
impl TestClient {
|
||||
async fn shutdown(self) -> IoResult<()> {
|
||||
self.client.shutdown().await
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_test_client_with_capacity(
|
||||
session_source: SessionSource,
|
||||
channel_capacity: usize,
|
||||
) -> TestClient {
|
||||
let codex_home = TempDir::new().expect("temp dir");
|
||||
let config = Arc::new(build_test_config_for_codex_home(codex_home.path()).await);
|
||||
let state_db = init_state_db(config.as_ref())
|
||||
.await
|
||||
.expect("state db should initialize for in-process test");
|
||||
let client = InProcessAppServerClient::start(InProcessClientStartArgs {
|
||||
) -> InProcessAppServerClient {
|
||||
InProcessAppServerClient::start(InProcessClientStartArgs {
|
||||
arg0_paths: Arg0DispatchPaths::default(),
|
||||
config,
|
||||
config: Arc::new(build_test_config().await),
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides: LoaderOverrides::default(),
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
feedback: CodexFeedback::new(),
|
||||
log_db: None,
|
||||
state_db: Some(state_db),
|
||||
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
|
||||
config_warnings: Vec::new(),
|
||||
session_source,
|
||||
@@ -1040,15 +994,10 @@ mod tests {
|
||||
channel_capacity,
|
||||
})
|
||||
.await
|
||||
.expect("in-process app-server client should start");
|
||||
|
||||
TestClient {
|
||||
_codex_home: codex_home,
|
||||
client,
|
||||
}
|
||||
.expect("in-process app-server client should start")
|
||||
}
|
||||
|
||||
async fn start_test_client(session_source: SessionSource) -> TestClient {
|
||||
async fn start_test_client(session_source: SessionSource) -> InProcessAppServerClient {
|
||||
start_test_client_with_capacity(session_source, DEFAULT_IN_PROCESS_CHANNEL_CAPACITY).await
|
||||
}
|
||||
|
||||
@@ -1185,7 +1134,6 @@ mod tests {
|
||||
ServerNotification::ItemCompleted(codex_app_server_protocol::ItemCompletedNotification {
|
||||
thread_id: "thread".to_string(),
|
||||
turn_id: "turn".to_string(),
|
||||
completed_at_ms: 0,
|
||||
item: codex_app_server_protocol::ThreadItem::AgentMessage {
|
||||
id: "item".to_string(),
|
||||
text: text.to_string(),
|
||||
@@ -1200,7 +1148,6 @@ mod tests {
|
||||
thread_id: "thread".to_string(),
|
||||
turn: codex_app_server_protocol::Turn {
|
||||
id: "turn".to_string(),
|
||||
items_view: codex_app_server_protocol::TurnItemsView::Full,
|
||||
items: Vec::new(),
|
||||
status: codex_app_server_protocol::TurnStatus::Completed,
|
||||
error: None,
|
||||
@@ -2031,7 +1978,6 @@ mod tests {
|
||||
thread_id: "thread".to_string(),
|
||||
turn: codex_app_server_protocol::Turn {
|
||||
id: "turn".to_string(),
|
||||
items_view: codex_app_server_protocol::TurnItemsView::Full,
|
||||
items: Vec::new(),
|
||||
status: codex_app_server_protocol::TurnStatus::Completed,
|
||||
error: None,
|
||||
@@ -2061,7 +2007,6 @@ mod tests {
|
||||
codex_app_server_protocol::ItemCompletedNotification {
|
||||
thread_id: "thread".to_string(),
|
||||
turn_id: "turn".to_string(),
|
||||
completed_at_ms: 0,
|
||||
item: codex_app_server_protocol::ThreadItem::AgentMessage {
|
||||
id: "item".to_string(),
|
||||
text: "hello".to_string(),
|
||||
@@ -2112,7 +2057,6 @@ mod tests {
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
feedback: CodexFeedback::new(),
|
||||
log_db: None,
|
||||
state_db: None,
|
||||
environment_manager: environment_manager.clone(),
|
||||
config_warnings: Vec::new(),
|
||||
session_source: SessionSource::Exec,
|
||||
@@ -2152,7 +2096,6 @@ mod tests {
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
feedback: CodexFeedback::new(),
|
||||
log_db: None,
|
||||
state_db: None,
|
||||
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
|
||||
config_warnings: Vec::new(),
|
||||
session_source: SessionSource::Exec,
|
||||
|
||||
@@ -73,7 +73,6 @@ impl RemoteAppServerConnectArgs {
|
||||
fn initialize_params(&self) -> InitializeParams {
|
||||
let capabilities = InitializeCapabilities {
|
||||
experimental_api: self.experimental_api,
|
||||
request_attestation: false,
|
||||
opt_out_notification_methods: if self.opt_out_notification_methods.is_empty() {
|
||||
None
|
||||
} else {
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
codex_rust_crate(
|
||||
name = "app-server-daemon",
|
||||
crate_name = "codex_app_server_daemon",
|
||||
)
|
||||
@@ -1,39 +0,0 @@
|
||||
[package]
|
||||
name = "codex-app-server-daemon"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "codex_app_server_daemon"
|
||||
path = "src/lib.rs"
|
||||
doctest = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
codex-app-server-transport = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-uds = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["rustls-tls"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
tokio = { workspace = true, features = [
|
||||
"fs",
|
||||
"io-util",
|
||||
"macros",
|
||||
"process",
|
||||
"rt-multi-thread",
|
||||
"signal",
|
||||
"time",
|
||||
] }
|
||||
tokio-tungstenite = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
@@ -1,104 +0,0 @@
|
||||
# codex-app-server-daemon
|
||||
|
||||
> `codex-app-server-daemon` is experimental and its lifecycle contract may
|
||||
> change while the remote-management flow is still being developed.
|
||||
|
||||
`codex-app-server-daemon` backs the machine-readable `codex app-server`
|
||||
lifecycle commands used by remote clients such as the desktop and mobile apps.
|
||||
It is intended for Codex instances launched over SSH, including fresh developer
|
||||
machines that should expose app-server with `remote_control` enabled.
|
||||
|
||||
## Platform support
|
||||
|
||||
The current daemon implementation is Unix-only. It uses pidfile-backed
|
||||
daemonization plus Unix process and file-locking primitives, and does not yet
|
||||
support Windows lifecycle management.
|
||||
|
||||
## Commands
|
||||
|
||||
```sh
|
||||
codex app-server daemon start
|
||||
codex app-server daemon restart
|
||||
codex app-server daemon enable-remote-control
|
||||
codex app-server daemon disable-remote-control
|
||||
codex app-server daemon stop
|
||||
codex app-server daemon version
|
||||
codex app-server daemon bootstrap --remote-control
|
||||
```
|
||||
|
||||
On success, every command writes exactly one JSON object to stdout. Consumers
|
||||
should parse that JSON rather than relying on human-readable text. Lifecycle
|
||||
responses report the resolved backend, socket path, local CLI version, and
|
||||
running app-server version when applicable.
|
||||
|
||||
## Bootstrap flow
|
||||
|
||||
For a new remote machine:
|
||||
|
||||
```sh
|
||||
curl -fsSL https://chatgpt.com/codex/install.sh | sh
|
||||
$HOME/.codex/packages/standalone/current/codex app-server daemon bootstrap --remote-control
|
||||
```
|
||||
|
||||
`bootstrap` requires the standalone managed install. It records the daemon
|
||||
settings under `CODEX_HOME/app-server-daemon/`, starts app-server as a
|
||||
pidfile-backed detached process, and launches a detached updater loop.
|
||||
|
||||
## Installation and update cases
|
||||
|
||||
The daemon assumes Codex is installed through `install.sh` and always launches
|
||||
the standalone managed binary under `CODEX_HOME`.
|
||||
|
||||
| Situation | What starts | Does this daemon fetch new binaries? | Does a running app-server eventually move to a newer binary on its own? |
|
||||
| --- | --- | --- | --- |
|
||||
| `install.sh` has run, but only `start` is used | `start` uses `CODEX_HOME/packages/standalone/current/codex` | No | No. The managed path is used when starting or restarting, but no updater is installed. |
|
||||
| `install.sh` has run, then `bootstrap` is used | The pidfile backend uses `CODEX_HOME/packages/standalone/current/codex` | Yes. Bootstrap launches a detached updater loop that runs `install.sh` hourly. | Yes, while that updater process is alive. After a successful fetch, it restarts a currently running app-server only when the managed binary reports a different version. |
|
||||
| Some other tool updates the managed binary path | The next fresh start or restart uses the updated file at that path | No | Not automatically. The existing process keeps the old executable image until an explicit `restart`. |
|
||||
|
||||
### Standalone installs
|
||||
|
||||
For installs created by `install.sh`:
|
||||
|
||||
- lifecycle commands always use the standalone managed binary path
|
||||
- `bootstrap` is supported
|
||||
- `bootstrap` starts a detached pid-backed updater loop that fetches via
|
||||
`install.sh`, then restarts app-server if it is running on a different version
|
||||
- the updater loop is not reboot-persistent; it must be started again by
|
||||
rerunning `bootstrap` after a reboot
|
||||
|
||||
### Out-of-band updates
|
||||
|
||||
This daemon does not watch arbitrary executable files for replacement. If some
|
||||
other tool updates a binary that the daemon would use on its next launch:
|
||||
|
||||
- a currently running app-server remains on the old executable image
|
||||
- `restart` will launch the updated binary
|
||||
- for bootstrapped daemons, the detached updater loop only reacts to updates it
|
||||
fetched itself; it does not watch arbitrary file replacement
|
||||
|
||||
## Lifecycle semantics
|
||||
|
||||
`start` is idempotent and returns after app-server is ready to answer the normal
|
||||
JSON-RPC initialize handshake on the Unix control socket.
|
||||
|
||||
`restart` stops any managed daemon and starts it again.
|
||||
|
||||
`enable-remote-control` and `disable-remote-control` persist the launch setting
|
||||
for future starts. If a managed app-server is already running, they restart it
|
||||
so the new setting takes effect immediately.
|
||||
|
||||
`stop` sends a graceful termination request first, then sends a second
|
||||
termination signal after the grace window if the process is still alive.
|
||||
|
||||
All mutating lifecycle commands are serialized per `CODEX_HOME`, so a concurrent
|
||||
`start`, `restart`, `enable-remote-control`, `disable-remote-control`, `stop`,
|
||||
or `bootstrap` does not race another in-flight lifecycle operation.
|
||||
|
||||
## State
|
||||
|
||||
The daemon stores its local state under `CODEX_HOME/app-server-daemon/`:
|
||||
|
||||
- `settings.json` for persisted launch settings
|
||||
- `app-server.pid` for the app-server process record
|
||||
- `app-server-updater.pid` for the pid-backed standalone updater loop
|
||||
- `daemon.lock` for daemon-wide lifecycle serialization
|
||||
@@ -1,33 +0,0 @@
|
||||
mod pid;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
pub(crate) use pid::PidBackend;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum BackendKind {
|
||||
Pid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct BackendPaths {
|
||||
pub(crate) codex_bin: PathBuf,
|
||||
pub(crate) pid_file: PathBuf,
|
||||
pub(crate) update_pid_file: PathBuf,
|
||||
pub(crate) remote_control_enabled: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn pid_backend(paths: BackendPaths) -> PidBackend {
|
||||
PidBackend::new(
|
||||
paths.codex_bin,
|
||||
paths.pid_file,
|
||||
paths.remote_control_enabled,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn pid_update_loop_backend(paths: BackendPaths) -> PidBackend {
|
||||
PidBackend::new_update_loop(paths.codex_bin, paths.update_pid_file)
|
||||
}
|
||||
@@ -1,600 +0,0 @@
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
#[cfg(unix)]
|
||||
use std::process::Stdio;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use anyhow::bail;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use tokio::fs;
|
||||
#[cfg(unix)]
|
||||
use tokio::process::Command;
|
||||
use tokio::time::sleep;
|
||||
|
||||
const STOP_POLL_INTERVAL: Duration = Duration::from_millis(50);
|
||||
const STOP_GRACE_PERIOD: Duration = Duration::from_secs(60);
|
||||
const STOP_TIMEOUT: Duration = Duration::from_secs(70);
|
||||
const START_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(not(unix), allow(dead_code))]
|
||||
pub(crate) struct PidBackend {
|
||||
codex_bin: PathBuf,
|
||||
pid_file: PathBuf,
|
||||
lock_file: PathBuf,
|
||||
command_kind: PidCommandKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct PidRecord {
|
||||
pid: u32,
|
||||
process_start_time: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum PidFileState {
|
||||
Missing,
|
||||
Starting,
|
||||
Running(PidRecord),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[cfg_attr(not(unix), allow(dead_code))]
|
||||
enum PidCommandKind {
|
||||
AppServer { remote_control_enabled: bool },
|
||||
UpdateLoop,
|
||||
}
|
||||
|
||||
impl PidBackend {
|
||||
pub(crate) fn new(codex_bin: PathBuf, pid_file: PathBuf, remote_control_enabled: bool) -> Self {
|
||||
let lock_file = pid_file.with_extension("pid.lock");
|
||||
Self {
|
||||
codex_bin,
|
||||
pid_file,
|
||||
lock_file,
|
||||
command_kind: PidCommandKind::AppServer {
|
||||
remote_control_enabled,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_update_loop(codex_bin: PathBuf, pid_file: PathBuf) -> Self {
|
||||
let lock_file = pid_file.with_extension("pid.lock");
|
||||
Self {
|
||||
codex_bin,
|
||||
pid_file,
|
||||
lock_file,
|
||||
command_kind: PidCommandKind::UpdateLoop,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn is_starting_or_running(&self) -> Result<bool> {
|
||||
loop {
|
||||
match self.read_pid_file_state().await? {
|
||||
PidFileState::Missing => return Ok(false),
|
||||
PidFileState::Starting => return Ok(true),
|
||||
PidFileState::Running(record) => {
|
||||
if self.record_is_active(&record).await? {
|
||||
return Ok(true);
|
||||
}
|
||||
match self.refresh_after_stale_record(&record).await? {
|
||||
PidFileState::Missing => return Ok(false),
|
||||
PidFileState::Starting | PidFileState::Running(_) => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(crate) async fn start(&self) -> Result<Option<u32>> {
|
||||
if let Some(parent) = self.pid_file.parent() {
|
||||
fs::create_dir_all(parent)
|
||||
.await
|
||||
.with_context(|| format!("failed to create pid directory {}", parent.display()))?;
|
||||
}
|
||||
let reservation_lock = self.acquire_reservation_lock().await?;
|
||||
let _pid_file = loop {
|
||||
match fs::OpenOptions::new()
|
||||
.create_new(true)
|
||||
.write(true)
|
||||
.open(&self.pid_file)
|
||||
.await
|
||||
{
|
||||
Ok(pid_file) => break pid_file,
|
||||
Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {
|
||||
match self.read_pid_file_state_with_lock_held().await? {
|
||||
PidFileState::Missing => continue,
|
||||
PidFileState::Running(record) => {
|
||||
if self.record_is_active(&record).await? {
|
||||
return Ok(None);
|
||||
}
|
||||
let _ = fs::remove_file(&self.pid_file).await;
|
||||
continue;
|
||||
}
|
||||
PidFileState::Starting => {
|
||||
unreachable!("lock holder cannot observe starting")
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err).with_context(|| {
|
||||
format!("failed to reserve pid file {}", self.pid_file.display())
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
let mut command = Command::new(&self.codex_bin);
|
||||
command
|
||||
.args(self.command_args())
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null());
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
unsafe {
|
||||
command.pre_exec(|| {
|
||||
if libc::setsid() == -1 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let child = match command.spawn() {
|
||||
Ok(child) => child,
|
||||
Err(err) => {
|
||||
let _ = fs::remove_file(&self.pid_file).await;
|
||||
return Err(err).with_context(|| {
|
||||
format!(
|
||||
"failed to spawn detached app-server process using {}",
|
||||
self.codex_bin.display()
|
||||
)
|
||||
});
|
||||
}
|
||||
};
|
||||
let pid = child
|
||||
.id()
|
||||
.context("spawned app-server process has no pid")?;
|
||||
let record = match read_process_start_time(pid).await {
|
||||
Ok(process_start_time) => PidRecord {
|
||||
pid,
|
||||
process_start_time,
|
||||
},
|
||||
Err(err) => {
|
||||
let _ = self.terminate_process(pid);
|
||||
let _ = fs::remove_file(&self.pid_file).await;
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
let contents = serde_json::to_vec(&record).context("failed to serialize pid record")?;
|
||||
let temp_pid_file = self.pid_file.with_extension("pid.tmp");
|
||||
if let Err(err) = fs::write(&temp_pid_file, &contents).await {
|
||||
let _ = self.terminate_process(pid);
|
||||
let _ = fs::remove_file(&self.pid_file).await;
|
||||
return Err(err).with_context(|| {
|
||||
format!("failed to write pid temp file {}", temp_pid_file.display())
|
||||
});
|
||||
}
|
||||
if let Err(err) = fs::rename(&temp_pid_file, &self.pid_file).await {
|
||||
let _ = self.terminate_process(pid);
|
||||
let _ = fs::remove_file(&temp_pid_file).await;
|
||||
let _ = fs::remove_file(&self.pid_file).await;
|
||||
return Err(err).with_context(|| {
|
||||
format!("failed to publish pid file {}", self.pid_file.display())
|
||||
});
|
||||
}
|
||||
drop(reservation_lock);
|
||||
Ok(Some(pid))
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
pub(crate) async fn start(&self) -> Result<Option<u32>> {
|
||||
bail!("pid-managed app-server startup is unsupported on this platform")
|
||||
}
|
||||
|
||||
pub(crate) async fn stop(&self) -> Result<()> {
|
||||
loop {
|
||||
let Some(record) = self.wait_for_pid_start().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
if !self.record_is_active(&record).await? {
|
||||
match self.refresh_after_stale_record(&record).await? {
|
||||
PidFileState::Missing => return Ok(()),
|
||||
PidFileState::Starting | PidFileState::Running(_) => continue,
|
||||
}
|
||||
}
|
||||
|
||||
let pid = record.pid;
|
||||
self.terminate_process(pid)?;
|
||||
let started_at = tokio::time::Instant::now();
|
||||
let deadline = tokio::time::Instant::now() + STOP_TIMEOUT;
|
||||
let mut forced = false;
|
||||
while tokio::time::Instant::now() < deadline {
|
||||
if !self.record_is_active(&record).await? {
|
||||
match self.refresh_after_stale_record(&record).await? {
|
||||
PidFileState::Missing => return Ok(()),
|
||||
PidFileState::Starting | PidFileState::Running(_) => break,
|
||||
}
|
||||
}
|
||||
if !forced && started_at.elapsed() >= STOP_GRACE_PERIOD {
|
||||
self.force_terminate_process(pid)?;
|
||||
forced = true;
|
||||
}
|
||||
sleep(STOP_POLL_INTERVAL).await;
|
||||
}
|
||||
|
||||
if self.record_is_active(&record).await? {
|
||||
bail!("timed out waiting for pid-managed app server {pid} to stop");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_for_pid_start(&self) -> Result<Option<PidRecord>> {
|
||||
let deadline = tokio::time::Instant::now() + START_TIMEOUT;
|
||||
loop {
|
||||
match self.read_pid_file_state().await? {
|
||||
PidFileState::Missing => return Ok(None),
|
||||
PidFileState::Running(record) => return Ok(Some(record)),
|
||||
PidFileState::Starting if tokio::time::Instant::now() < deadline => {
|
||||
sleep(STOP_POLL_INTERVAL).await;
|
||||
}
|
||||
PidFileState::Starting => {
|
||||
bail!(
|
||||
"timed out waiting for pid reservation in {} to finish initializing",
|
||||
self.pid_file.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn read_pid_file_state(&self) -> Result<PidFileState> {
|
||||
let contents = match fs::read_to_string(&self.pid_file).await {
|
||||
Ok(contents) => contents,
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
return if reservation_lock_is_active(&self.lock_file).await? {
|
||||
Ok(PidFileState::Starting)
|
||||
} else {
|
||||
Ok(PidFileState::Missing)
|
||||
};
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err).with_context(|| {
|
||||
format!("failed to read pid file {}", self.pid_file.display())
|
||||
});
|
||||
}
|
||||
};
|
||||
if contents.trim().is_empty() {
|
||||
match inspect_empty_pid_reservation(&self.pid_file, &self.lock_file).await? {
|
||||
EmptyPidReservation::Active => {
|
||||
return Ok(PidFileState::Starting);
|
||||
}
|
||||
EmptyPidReservation::Stale => {
|
||||
return Ok(PidFileState::Missing);
|
||||
}
|
||||
EmptyPidReservation::Record(record) => return Ok(PidFileState::Running(record)),
|
||||
}
|
||||
}
|
||||
let record = serde_json::from_str(&contents)
|
||||
.with_context(|| format!("invalid pid file contents in {}", self.pid_file.display()))?;
|
||||
Ok(PidFileState::Running(record))
|
||||
}
|
||||
|
||||
async fn read_pid_file_state_with_lock_held(&self) -> Result<PidFileState> {
|
||||
let contents = match fs::read_to_string(&self.pid_file).await {
|
||||
Ok(contents) => contents,
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
return Ok(PidFileState::Missing);
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err).with_context(|| {
|
||||
format!("failed to read pid file {}", self.pid_file.display())
|
||||
});
|
||||
}
|
||||
};
|
||||
if contents.trim().is_empty() {
|
||||
let _ = fs::remove_file(&self.pid_file).await;
|
||||
return Ok(PidFileState::Missing);
|
||||
}
|
||||
let record = serde_json::from_str(&contents)
|
||||
.with_context(|| format!("invalid pid file contents in {}", self.pid_file.display()))?;
|
||||
Ok(PidFileState::Running(record))
|
||||
}
|
||||
|
||||
async fn refresh_after_stale_record(&self, expected: &PidRecord) -> Result<PidFileState> {
|
||||
let reservation_lock = self.acquire_reservation_lock().await?;
|
||||
let state = match self.read_pid_file_state_with_lock_held().await? {
|
||||
PidFileState::Running(record) if record == *expected => {
|
||||
let _ = fs::remove_file(&self.pid_file).await;
|
||||
PidFileState::Missing
|
||||
}
|
||||
state => state,
|
||||
};
|
||||
drop(reservation_lock);
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
async fn acquire_reservation_lock(&self) -> Result<fs::File> {
|
||||
let reservation_lock = fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(false)
|
||||
.write(true)
|
||||
.open(&self.lock_file)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("failed to open pid lock file {}", self.lock_file.display())
|
||||
})?;
|
||||
let lock_deadline = tokio::time::Instant::now() + START_TIMEOUT;
|
||||
while !try_lock_file(&reservation_lock)? {
|
||||
if tokio::time::Instant::now() >= lock_deadline {
|
||||
bail!(
|
||||
"timed out waiting for pid lock {}",
|
||||
self.lock_file.display()
|
||||
);
|
||||
}
|
||||
sleep(STOP_POLL_INTERVAL).await;
|
||||
}
|
||||
Ok(reservation_lock)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn command_args(&self) -> Vec<&'static str> {
|
||||
match self.command_kind {
|
||||
PidCommandKind::AppServer {
|
||||
remote_control_enabled: true,
|
||||
} => vec![
|
||||
"--enable",
|
||||
"remote_control",
|
||||
"app-server",
|
||||
"--listen",
|
||||
"unix://",
|
||||
],
|
||||
PidCommandKind::AppServer {
|
||||
remote_control_enabled: false,
|
||||
} => vec!["app-server", "--listen", "unix://"],
|
||||
PidCommandKind::UpdateLoop => vec!["app-server", "daemon", "pid-update-loop"],
|
||||
}
|
||||
}
|
||||
|
||||
fn terminate_process(&self, pid: u32) -> Result<()> {
|
||||
match self.command_kind {
|
||||
PidCommandKind::AppServer { .. } => terminate_process(pid),
|
||||
PidCommandKind::UpdateLoop => terminate_process(pid),
|
||||
}
|
||||
}
|
||||
|
||||
fn force_terminate_process(&self, pid: u32) -> Result<()> {
|
||||
match self.command_kind {
|
||||
PidCommandKind::AppServer { .. } => force_terminate_process(pid),
|
||||
PidCommandKind::UpdateLoop => force_terminate_process_group(pid),
|
||||
}
|
||||
}
|
||||
|
||||
async fn record_is_active(&self, record: &PidRecord) -> Result<bool> {
|
||||
process_matches_record(record).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn process_exists(pid: u32) -> bool {
|
||||
let Ok(pid) = libc::pid_t::try_from(pid) else {
|
||||
return false;
|
||||
};
|
||||
let result = unsafe { libc::kill(pid, 0) };
|
||||
result == 0 || std::io::Error::last_os_error().raw_os_error() == Some(libc::EPERM)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn terminate_process(pid: u32) -> Result<()> {
|
||||
let raw_pid = libc::pid_t::try_from(pid)
|
||||
.with_context(|| format!("pid-managed app server pid {pid} is out of range"))?;
|
||||
let result = unsafe { libc::kill(raw_pid, libc::SIGTERM) };
|
||||
if result == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
let err = std::io::Error::last_os_error();
|
||||
if err.raw_os_error() == Some(libc::ESRCH) {
|
||||
return Ok(());
|
||||
}
|
||||
Err(err).with_context(|| format!("failed to terminate pid-managed app server {pid}"))
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn force_terminate_process(pid: u32) -> Result<()> {
|
||||
let raw_pid = libc::pid_t::try_from(pid)
|
||||
.with_context(|| format!("pid-managed app server pid {pid} is out of range"))?;
|
||||
let result = unsafe { libc::kill(raw_pid, libc::SIGKILL) };
|
||||
if result == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
let err = std::io::Error::last_os_error();
|
||||
if err.raw_os_error() == Some(libc::ESRCH) {
|
||||
return Ok(());
|
||||
}
|
||||
Err(err).with_context(|| format!("failed to force terminate pid-managed app server {pid}"))
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn force_terminate_process_group(pid: u32) -> Result<()> {
|
||||
let raw_pid = libc::pid_t::try_from(pid)
|
||||
.with_context(|| format!("pid-managed updater pid {pid} is out of range"))?;
|
||||
let result = unsafe { libc::kill(-raw_pid, libc::SIGKILL) };
|
||||
if result == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
let err = std::io::Error::last_os_error();
|
||||
if err.raw_os_error() == Some(libc::ESRCH) {
|
||||
return Ok(());
|
||||
}
|
||||
Err(err).with_context(|| format!("failed to force terminate pid-managed updater group {pid}"))
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn terminate_process(_pid: u32) -> Result<()> {
|
||||
bail!("pid-managed app-server shutdown is unsupported on this platform")
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn force_terminate_process(_pid: u32) -> Result<()> {
|
||||
bail!("pid-managed app-server shutdown is unsupported on this platform")
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn force_terminate_process_group(_pid: u32) -> Result<()> {
|
||||
bail!("pid-managed updater shutdown is unsupported on this platform")
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
async fn process_matches_record(record: &PidRecord) -> Result<bool> {
|
||||
if !process_exists(record.pid) {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
match read_process_start_time(record.pid).await {
|
||||
Ok(start_time) => Ok(start_time == record.process_start_time),
|
||||
Err(_err) if !process_exists(record.pid) => Ok(false),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
async fn process_matches_record(_record: &PidRecord) -> Result<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(not(unix), allow(dead_code))]
|
||||
enum EmptyPidReservation {
|
||||
Active,
|
||||
Stale,
|
||||
Record(PidRecord),
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn try_lock_file(file: &fs::File) -> Result<bool> {
|
||||
use std::os::fd::AsRawFd;
|
||||
|
||||
let result = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) };
|
||||
if result == 0 {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let err = std::io::Error::last_os_error();
|
||||
if err.raw_os_error() == Some(libc::EWOULDBLOCK) {
|
||||
return Ok(false);
|
||||
}
|
||||
Err(err).context("failed to lock pid reservation")
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn try_lock_file(_file: &fs::File) -> Result<bool> {
|
||||
bail!("pid-managed app-server startup is unsupported on this platform")
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
async fn reservation_lock_is_active(path: &Path) -> Result<bool> {
|
||||
let file = match fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(false)
|
||||
.open(path)
|
||||
.await
|
||||
{
|
||||
Ok(file) => file,
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
return Ok(false);
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err)
|
||||
.with_context(|| format!("failed to inspect pid lock file {}", path.display()));
|
||||
}
|
||||
};
|
||||
Ok(!try_lock_file(&file)?)
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
async fn reservation_lock_is_active(_path: &Path) -> Result<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
async fn inspect_empty_pid_reservation(
|
||||
pid_path: &Path,
|
||||
lock_path: &Path,
|
||||
) -> Result<EmptyPidReservation> {
|
||||
let file = match fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(false)
|
||||
.open(lock_path)
|
||||
.await
|
||||
{
|
||||
Ok(file) => file,
|
||||
Err(err) => {
|
||||
return Err(err).with_context(|| {
|
||||
format!("failed to inspect pid lock file {}", lock_path.display())
|
||||
});
|
||||
}
|
||||
};
|
||||
if !try_lock_file(&file)? {
|
||||
return Ok(EmptyPidReservation::Active);
|
||||
}
|
||||
|
||||
let contents = match fs::read_to_string(pid_path).await {
|
||||
Ok(contents) => contents,
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
return Ok(EmptyPidReservation::Stale);
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err)
|
||||
.with_context(|| format!("failed to reread pid file {}", pid_path.display()));
|
||||
}
|
||||
};
|
||||
if contents.trim().is_empty() {
|
||||
let _ = fs::remove_file(pid_path).await;
|
||||
return Ok(EmptyPidReservation::Stale);
|
||||
}
|
||||
|
||||
let record = serde_json::from_str(&contents)
|
||||
.with_context(|| format!("invalid pid file contents in {}", pid_path.display()))?;
|
||||
Ok(EmptyPidReservation::Record(record))
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
async fn inspect_empty_pid_reservation(
|
||||
_pid_path: &Path,
|
||||
_lock_path: &Path,
|
||||
) -> Result<EmptyPidReservation> {
|
||||
Ok(EmptyPidReservation::Stale)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
async fn read_process_start_time(pid: u32) -> Result<String> {
|
||||
let output = Command::new("ps")
|
||||
.args(["-p", &pid.to_string(), "-o", "lstart="])
|
||||
.output()
|
||||
.await
|
||||
.context("failed to invoke ps for pid-managed app server")?;
|
||||
if !output.status.success() {
|
||||
bail!("failed to read start time for pid-managed app server {pid}");
|
||||
}
|
||||
|
||||
let start_time = String::from_utf8(output.stdout)
|
||||
.context("pid-managed app server start time was not utf-8")?;
|
||||
let start_time = start_time.trim();
|
||||
if start_time.is_empty() {
|
||||
bail!("pid-managed app server {pid} has no recorded start time");
|
||||
}
|
||||
Ok(start_time.to_string())
|
||||
}
|
||||
|
||||
#[cfg(all(test, unix))]
|
||||
#[path = "pid_tests.rs"]
|
||||
mod tests;
|
||||
@@ -1,158 +0,0 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
|
||||
use super::PidBackend;
|
||||
use super::PidCommandKind;
|
||||
use super::PidFileState;
|
||||
use super::PidRecord;
|
||||
use super::try_lock_file;
|
||||
|
||||
#[tokio::test]
|
||||
async fn locked_empty_pid_file_is_treated_as_active_reservation() {
|
||||
let temp_dir = TempDir::new().expect("temp dir");
|
||||
let pid_file = temp_dir.path().join("app-server.pid");
|
||||
tokio::fs::write(&pid_file, "")
|
||||
.await
|
||||
.expect("write pid file");
|
||||
let backend = PidBackend::new(
|
||||
temp_dir.path().join("codex"),
|
||||
pid_file.clone(),
|
||||
/*remote_control_enabled*/ false,
|
||||
);
|
||||
let reservation = tokio::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(false)
|
||||
.write(true)
|
||||
.open(&backend.lock_file)
|
||||
.await
|
||||
.expect("open pid lock file");
|
||||
assert!(try_lock_file(&reservation).expect("lock reservation"));
|
||||
|
||||
assert_eq!(
|
||||
backend.read_pid_file_state().await.expect("read pid"),
|
||||
PidFileState::Starting
|
||||
);
|
||||
assert!(pid_file.exists());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn unlocked_empty_pid_file_is_treated_as_stale_reservation() {
|
||||
let temp_dir = TempDir::new().expect("temp dir");
|
||||
let pid_file = temp_dir.path().join("app-server.pid");
|
||||
tokio::fs::write(&pid_file, "")
|
||||
.await
|
||||
.expect("write pid file");
|
||||
let backend = PidBackend::new(
|
||||
temp_dir.path().join("codex"),
|
||||
pid_file.clone(),
|
||||
/*remote_control_enabled*/ false,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
backend.read_pid_file_state().await.expect("read pid"),
|
||||
PidFileState::Missing
|
||||
);
|
||||
assert!(!pid_file.exists());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stop_waits_for_live_reservation_to_resolve() {
|
||||
let temp_dir = TempDir::new().expect("temp dir");
|
||||
let pid_file = temp_dir.path().join("app-server.pid");
|
||||
tokio::fs::write(&pid_file, "")
|
||||
.await
|
||||
.expect("write pid file");
|
||||
let backend = PidBackend::new(
|
||||
temp_dir.path().join("codex"),
|
||||
pid_file.clone(),
|
||||
/*remote_control_enabled*/ false,
|
||||
);
|
||||
let reservation = tokio::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(false)
|
||||
.write(true)
|
||||
.open(&backend.lock_file)
|
||||
.await
|
||||
.expect("open pid lock file");
|
||||
assert!(try_lock_file(&reservation).expect("lock reservation"));
|
||||
let cleanup = tokio::spawn(async move {
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
drop(reservation);
|
||||
tokio::fs::remove_file(pid_file)
|
||||
.await
|
||||
.expect("remove pid file");
|
||||
});
|
||||
|
||||
backend.stop().await.expect("stop");
|
||||
cleanup.await.expect("cleanup task");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn start_retries_stale_empty_pid_file_under_its_own_lock() {
|
||||
let temp_dir = TempDir::new().expect("temp dir");
|
||||
let pid_file = temp_dir.path().join("app-server.pid");
|
||||
tokio::fs::write(&pid_file, "")
|
||||
.await
|
||||
.expect("write pid file");
|
||||
let backend = PidBackend::new(
|
||||
temp_dir.path().join("missing-codex"),
|
||||
pid_file,
|
||||
/*remote_control_enabled*/ false,
|
||||
);
|
||||
|
||||
let err = backend.start().await.expect_err("start");
|
||||
assert!(
|
||||
err.to_string()
|
||||
.starts_with("failed to spawn detached app-server process using ")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stale_record_cleanup_preserves_replacement_record() {
|
||||
let temp_dir = TempDir::new().expect("temp dir");
|
||||
let pid_file = temp_dir.path().join("app-server.pid");
|
||||
let backend = PidBackend::new(
|
||||
temp_dir.path().join("codex"),
|
||||
pid_file.clone(),
|
||||
/*remote_control_enabled*/ false,
|
||||
);
|
||||
let stale = PidRecord {
|
||||
pid: 1,
|
||||
process_start_time: "old".to_string(),
|
||||
};
|
||||
let replacement = PidRecord {
|
||||
pid: 2,
|
||||
process_start_time: "new".to_string(),
|
||||
};
|
||||
tokio::fs::write(
|
||||
&pid_file,
|
||||
serde_json::to_vec(&replacement).expect("serialize replacement"),
|
||||
)
|
||||
.await
|
||||
.expect("write replacement pid file");
|
||||
|
||||
assert_eq!(
|
||||
backend
|
||||
.refresh_after_stale_record(&stale)
|
||||
.await
|
||||
.expect("cleanup"),
|
||||
PidFileState::Running(replacement)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_loop_uses_hidden_app_server_subcommand() {
|
||||
let backend = PidBackend {
|
||||
codex_bin: "codex".into(),
|
||||
pid_file: "updater.pid".into(),
|
||||
lock_file: "updater.pid.lock".into(),
|
||||
command_kind: PidCommandKind::UpdateLoop,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
backend.command_args(),
|
||||
vec!["app-server", "daemon", "pid-update-loop"]
|
||||
);
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use anyhow::anyhow;
|
||||
use codex_app_server_protocol::ClientInfo;
|
||||
use codex_app_server_protocol::InitializeParams;
|
||||
use codex_app_server_protocol::InitializeResponse;
|
||||
use codex_app_server_protocol::JSONRPCMessage;
|
||||
use codex_app_server_protocol::JSONRPCNotification;
|
||||
use codex_app_server_protocol::JSONRPCRequest;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_uds::UnixStream;
|
||||
use futures::SinkExt;
|
||||
use futures::StreamExt;
|
||||
use tokio::time::timeout;
|
||||
use tokio_tungstenite::client_async;
|
||||
use tokio_tungstenite::tungstenite::Message;
|
||||
|
||||
const PROBE_TIMEOUT: Duration = Duration::from_secs(2);
|
||||
const CLIENT_NAME: &str = "codex_app_server_daemon";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct ProbeInfo {
|
||||
pub(crate) app_server_version: String,
|
||||
}
|
||||
|
||||
pub(crate) async fn probe(socket_path: &Path) -> Result<ProbeInfo> {
|
||||
timeout(PROBE_TIMEOUT, probe_inner(socket_path))
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"timed out probing app-server control socket {}",
|
||||
socket_path.display()
|
||||
)
|
||||
})?
|
||||
}
|
||||
|
||||
async fn probe_inner(socket_path: &Path) -> Result<ProbeInfo> {
|
||||
let stream = UnixStream::connect(socket_path)
|
||||
.await
|
||||
.with_context(|| format!("failed to connect to {}", socket_path.display()))?;
|
||||
let (mut websocket, _response) = client_async("ws://localhost/", stream)
|
||||
.await
|
||||
.with_context(|| format!("failed to upgrade {}", socket_path.display()))?;
|
||||
|
||||
let initialize = JSONRPCMessage::Request(JSONRPCRequest {
|
||||
id: RequestId::Integer(1),
|
||||
method: "initialize".to_string(),
|
||||
params: Some(serde_json::to_value(InitializeParams {
|
||||
client_info: ClientInfo {
|
||||
name: CLIENT_NAME.to_string(),
|
||||
title: Some("Codex App Server Daemon".to_string()),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
},
|
||||
capabilities: None,
|
||||
})?),
|
||||
trace: None,
|
||||
});
|
||||
websocket
|
||||
.send(Message::Text(serde_json::to_string(&initialize)?.into()))
|
||||
.await
|
||||
.context("failed to send initialize request")?;
|
||||
|
||||
let response = loop {
|
||||
let frame = websocket
|
||||
.next()
|
||||
.await
|
||||
.ok_or_else(|| anyhow!("app-server closed before initialize response"))??;
|
||||
let Message::Text(payload) = frame else {
|
||||
continue;
|
||||
};
|
||||
let message = serde_json::from_str::<JSONRPCMessage>(&payload)?;
|
||||
if let JSONRPCMessage::Response(response) = message
|
||||
&& response.id == RequestId::Integer(1)
|
||||
{
|
||||
break response;
|
||||
}
|
||||
};
|
||||
let initialize_response = serde_json::from_value::<InitializeResponse>(response.result)?;
|
||||
|
||||
let initialized = JSONRPCMessage::Notification(JSONRPCNotification {
|
||||
method: "initialized".to_string(),
|
||||
params: None,
|
||||
});
|
||||
websocket
|
||||
.send(Message::Text(serde_json::to_string(&initialized)?.into()))
|
||||
.await
|
||||
.context("failed to send initialized notification")?;
|
||||
websocket.close(None).await.ok();
|
||||
|
||||
Ok(ProbeInfo {
|
||||
app_server_version: parse_version_from_user_agent(&initialize_response.user_agent)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_version_from_user_agent(user_agent: &str) -> Result<String> {
|
||||
let (_originator, rest) = user_agent
|
||||
.split_once('/')
|
||||
.ok_or_else(|| anyhow!("app-server user-agent omitted version separator"))?;
|
||||
let version = rest
|
||||
.split_whitespace()
|
||||
.next()
|
||||
.filter(|version| !version.is_empty())
|
||||
.ok_or_else(|| anyhow!("app-server user-agent omitted version"))?;
|
||||
Ok(version.to_string())
|
||||
}
|
||||
|
||||
#[cfg(all(test, unix))]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::parse_version_from_user_agent;
|
||||
|
||||
#[test]
|
||||
fn parses_version_from_codex_user_agent() {
|
||||
assert_eq!(
|
||||
parse_version_from_user_agent(
|
||||
"codex_app_server_daemon/1.2.3 (Linux 6.8.0; x86_64) codex_cli_rs/1.2.3",
|
||||
)
|
||||
.expect("version"),
|
||||
"1.2.3"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_user_agent_without_version() {
|
||||
assert!(parse_version_from_user_agent("codex_app_server_daemon").is_err());
|
||||
}
|
||||
}
|
||||
@@ -1,630 +0,0 @@
|
||||
mod backend;
|
||||
mod client;
|
||||
mod managed_install;
|
||||
mod settings;
|
||||
mod update_loop;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use anyhow::anyhow;
|
||||
pub use backend::BackendKind;
|
||||
use backend::BackendPaths;
|
||||
use codex_app_server_transport::app_server_control_socket_path;
|
||||
use codex_core::config::find_codex_home;
|
||||
use managed_install::managed_codex_bin;
|
||||
#[cfg(unix)]
|
||||
use managed_install::managed_codex_version;
|
||||
use serde::Serialize;
|
||||
use settings::DaemonSettings;
|
||||
use tokio::time::sleep;
|
||||
|
||||
const START_POLL_INTERVAL: Duration = Duration::from_millis(50);
|
||||
const START_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
const OPERATION_LOCK_TIMEOUT: Duration = Duration::from_secs(75);
|
||||
const PID_FILE_NAME: &str = "app-server.pid";
|
||||
const UPDATE_PID_FILE_NAME: &str = "app-server-updater.pid";
|
||||
const OPERATION_LOCK_FILE_NAME: &str = "daemon.lock";
|
||||
const SETTINGS_FILE_NAME: &str = "settings.json";
|
||||
const STATE_DIR_NAME: &str = "app-server-daemon";
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LifecycleCommand {
|
||||
Start,
|
||||
Restart,
|
||||
Stop,
|
||||
Version,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum LifecycleStatus {
|
||||
AlreadyRunning,
|
||||
Started,
|
||||
Restarted,
|
||||
Stopped,
|
||||
NotRunning,
|
||||
Running,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LifecycleOutput {
|
||||
pub status: LifecycleStatus,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub backend: Option<BackendKind>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub pid: Option<u32>,
|
||||
pub socket_path: PathBuf,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cli_version: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub app_server_version: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct BootstrapOptions {
|
||||
pub remote_control_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum BootstrapStatus {
|
||||
Bootstrapped,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BootstrapOutput {
|
||||
pub status: BootstrapStatus,
|
||||
pub backend: BackendKind,
|
||||
pub auto_update_enabled: bool,
|
||||
pub remote_control_enabled: bool,
|
||||
pub managed_codex_path: PathBuf,
|
||||
pub socket_path: PathBuf,
|
||||
pub cli_version: String,
|
||||
pub app_server_version: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RemoteControlMode {
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl RemoteControlMode {
|
||||
fn is_enabled(self) -> bool {
|
||||
matches!(self, Self::Enabled)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum RemoteControlStatus {
|
||||
Enabled,
|
||||
Disabled,
|
||||
AlreadyEnabled,
|
||||
AlreadyDisabled,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RemoteControlOutput {
|
||||
pub status: RemoteControlStatus,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub backend: Option<BackendKind>,
|
||||
pub remote_control_enabled: bool,
|
||||
pub socket_path: PathBuf,
|
||||
pub cli_version: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub app_server_version: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(crate) enum RestartIfRunningOutcome {
|
||||
Completed,
|
||||
Busy,
|
||||
}
|
||||
|
||||
pub async fn run(command: LifecycleCommand) -> Result<LifecycleOutput> {
|
||||
ensure_supported_platform()?;
|
||||
Daemon::from_environment()?.run(command).await
|
||||
}
|
||||
|
||||
pub async fn bootstrap(options: BootstrapOptions) -> Result<BootstrapOutput> {
|
||||
ensure_supported_platform()?;
|
||||
Daemon::from_environment()?.bootstrap(options).await
|
||||
}
|
||||
|
||||
pub async fn set_remote_control(mode: RemoteControlMode) -> Result<RemoteControlOutput> {
|
||||
ensure_supported_platform()?;
|
||||
Daemon::from_environment()?.set_remote_control(mode).await
|
||||
}
|
||||
|
||||
pub async fn run_pid_update_loop() -> Result<()> {
|
||||
ensure_supported_platform()?;
|
||||
update_loop::run().await
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn ensure_supported_platform() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn ensure_supported_platform() -> Result<()> {
|
||||
Err(anyhow!(
|
||||
"codex app-server daemon lifecycle is only supported on Unix platforms"
|
||||
))
|
||||
}
|
||||
|
||||
struct Daemon {
|
||||
socket_path: PathBuf,
|
||||
pid_file: PathBuf,
|
||||
update_pid_file: PathBuf,
|
||||
operation_lock_file: PathBuf,
|
||||
settings_file: PathBuf,
|
||||
managed_codex_bin: PathBuf,
|
||||
}
|
||||
|
||||
impl Daemon {
|
||||
fn from_environment() -> Result<Self> {
|
||||
let codex_home = find_codex_home().context("failed to resolve CODEX_HOME")?;
|
||||
let socket_path = app_server_control_socket_path(codex_home.as_path())?
|
||||
.as_path()
|
||||
.to_path_buf();
|
||||
let state_dir = codex_home.as_path().join(STATE_DIR_NAME);
|
||||
Ok(Self {
|
||||
socket_path,
|
||||
pid_file: state_dir.join(PID_FILE_NAME),
|
||||
update_pid_file: state_dir.join(UPDATE_PID_FILE_NAME),
|
||||
operation_lock_file: state_dir.join(OPERATION_LOCK_FILE_NAME),
|
||||
settings_file: state_dir.join(SETTINGS_FILE_NAME),
|
||||
managed_codex_bin: managed_codex_bin(codex_home.as_path()),
|
||||
})
|
||||
}
|
||||
|
||||
async fn run(&self, command: LifecycleCommand) -> Result<LifecycleOutput> {
|
||||
match command {
|
||||
LifecycleCommand::Start => {
|
||||
let _operation_lock = self.acquire_operation_lock().await?;
|
||||
self.start().await
|
||||
}
|
||||
LifecycleCommand::Restart => {
|
||||
let _operation_lock = self.acquire_operation_lock().await?;
|
||||
self.restart().await
|
||||
}
|
||||
LifecycleCommand::Stop => {
|
||||
let _operation_lock = self.acquire_operation_lock().await?;
|
||||
self.stop().await
|
||||
}
|
||||
LifecycleCommand::Version => self.version().await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn start(&self) -> Result<LifecycleOutput> {
|
||||
let settings = self.load_settings().await?;
|
||||
if let Ok(info) = client::probe(&self.socket_path).await {
|
||||
return Ok(self.output(
|
||||
LifecycleStatus::AlreadyRunning,
|
||||
self.running_backend(&settings).await?,
|
||||
/*pid*/ None,
|
||||
Some(info.app_server_version),
|
||||
));
|
||||
}
|
||||
|
||||
if self.running_backend_instance(&settings).await?.is_some() {
|
||||
let info = self.wait_until_ready().await?;
|
||||
return Ok(self.output(
|
||||
LifecycleStatus::AlreadyRunning,
|
||||
Some(BackendKind::Pid),
|
||||
/*pid*/ None,
|
||||
Some(info.app_server_version),
|
||||
));
|
||||
}
|
||||
|
||||
self.ensure_managed_codex_bin()?;
|
||||
let pid = self.start_managed_backend(&settings).await?;
|
||||
let info = self.wait_until_ready().await?;
|
||||
Ok(self.output(
|
||||
LifecycleStatus::Started,
|
||||
Some(BackendKind::Pid),
|
||||
pid,
|
||||
Some(info.app_server_version),
|
||||
))
|
||||
}
|
||||
|
||||
async fn restart(&self) -> Result<LifecycleOutput> {
|
||||
let settings = self.load_settings().await?;
|
||||
if client::probe(&self.socket_path).await.is_ok()
|
||||
&& self.running_backend(&settings).await?.is_none()
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"app server is running but is not managed by codex app-server daemon"
|
||||
));
|
||||
}
|
||||
|
||||
self.ensure_managed_codex_bin()?;
|
||||
if let Some(backend) = self.running_backend_instance(&settings).await? {
|
||||
backend.stop().await?;
|
||||
}
|
||||
|
||||
let pid = self.start_managed_backend(&settings).await?;
|
||||
let info = self.wait_until_ready().await?;
|
||||
Ok(self.output(
|
||||
LifecycleStatus::Restarted,
|
||||
Some(BackendKind::Pid),
|
||||
pid,
|
||||
Some(info.app_server_version),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(crate) async fn try_restart_if_running(&self) -> Result<RestartIfRunningOutcome> {
|
||||
let operation_lock = self.open_operation_lock_file().await?;
|
||||
if !try_lock_file(&operation_lock)? {
|
||||
return Ok(RestartIfRunningOutcome::Busy);
|
||||
}
|
||||
let settings = self.load_settings().await?;
|
||||
if let Some(backend) = self.running_backend_instance(&settings).await? {
|
||||
let Ok(info) = client::probe(&self.socket_path).await else {
|
||||
return Ok(RestartIfRunningOutcome::Completed);
|
||||
};
|
||||
let managed_version = managed_codex_version(&self.managed_codex_bin).await?;
|
||||
if info.app_server_version == managed_version {
|
||||
return Ok(RestartIfRunningOutcome::Completed);
|
||||
}
|
||||
backend.stop().await?;
|
||||
let _ = self.start_managed_backend(&settings).await?;
|
||||
self.wait_until_ready().await?;
|
||||
return Ok(RestartIfRunningOutcome::Completed);
|
||||
}
|
||||
|
||||
if client::probe(&self.socket_path).await.is_ok() {
|
||||
return Err(anyhow!(
|
||||
"app server is running but is not managed by codex app-server daemon"
|
||||
));
|
||||
}
|
||||
|
||||
Ok(RestartIfRunningOutcome::Completed)
|
||||
}
|
||||
|
||||
async fn stop(&self) -> Result<LifecycleOutput> {
|
||||
let settings = self.load_settings().await?;
|
||||
if let Some(backend) = self.running_backend_instance(&settings).await? {
|
||||
backend.stop().await?;
|
||||
return Ok(self.output(
|
||||
LifecycleStatus::Stopped,
|
||||
Some(BackendKind::Pid),
|
||||
/*pid*/ None,
|
||||
/*app_server_version*/ None,
|
||||
));
|
||||
}
|
||||
|
||||
if client::probe(&self.socket_path).await.is_ok() {
|
||||
return Err(anyhow!(
|
||||
"app server is running but is not managed by codex app-server daemon"
|
||||
));
|
||||
}
|
||||
|
||||
Ok(self.output(
|
||||
LifecycleStatus::NotRunning,
|
||||
/*backend*/ None,
|
||||
/*pid*/ None,
|
||||
/*app_server_version*/ None,
|
||||
))
|
||||
}
|
||||
|
||||
async fn version(&self) -> Result<LifecycleOutput> {
|
||||
let settings = self.load_settings().await?;
|
||||
let info = client::probe(&self.socket_path).await?;
|
||||
Ok(self.output(
|
||||
LifecycleStatus::Running,
|
||||
self.running_backend(&settings).await?,
|
||||
/*pid*/ None,
|
||||
Some(info.app_server_version),
|
||||
))
|
||||
}
|
||||
|
||||
async fn wait_until_ready(&self) -> Result<client::ProbeInfo> {
|
||||
let deadline = tokio::time::Instant::now() + START_TIMEOUT;
|
||||
loop {
|
||||
match client::probe(&self.socket_path).await {
|
||||
Ok(info) => return Ok(info),
|
||||
Err(err) if tokio::time::Instant::now() < deadline => {
|
||||
let _ = err;
|
||||
sleep(START_POLL_INTERVAL).await;
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err).with_context(|| {
|
||||
format!(
|
||||
"app server did not become ready on {}",
|
||||
self.socket_path.display()
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn bootstrap(&self, options: BootstrapOptions) -> Result<BootstrapOutput> {
|
||||
let _operation_lock = self.acquire_operation_lock().await?;
|
||||
self.bootstrap_locked(options).await
|
||||
}
|
||||
|
||||
async fn set_remote_control(&self, mode: RemoteControlMode) -> Result<RemoteControlOutput> {
|
||||
let _operation_lock = self.acquire_operation_lock().await?;
|
||||
let previous_settings = self.load_settings().await?;
|
||||
let mut settings = previous_settings.clone();
|
||||
let remote_control_enabled = mode.is_enabled();
|
||||
let backend = self.running_backend_instance(&previous_settings).await?;
|
||||
|
||||
if backend.is_none() && client::probe(&self.socket_path).await.is_ok() {
|
||||
return Err(anyhow!(
|
||||
"app server is running but is not managed by codex app-server daemon"
|
||||
));
|
||||
}
|
||||
|
||||
if settings.remote_control_enabled == remote_control_enabled {
|
||||
let info = if backend.is_some() {
|
||||
Some(self.wait_until_ready().await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
return Ok(self.remote_control_output(
|
||||
already_remote_control_status(mode),
|
||||
backend.map(|_| BackendKind::Pid),
|
||||
remote_control_enabled,
|
||||
info.map(|info| info.app_server_version),
|
||||
));
|
||||
}
|
||||
|
||||
settings.remote_control_enabled = remote_control_enabled;
|
||||
settings.save(&self.settings_file).await?;
|
||||
|
||||
let app_server_version = if let Some(backend) = backend {
|
||||
self.ensure_managed_codex_bin()?;
|
||||
backend.stop().await?;
|
||||
let _ = self.start_managed_backend(&settings).await?;
|
||||
Some(self.wait_until_ready().await?.app_server_version)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(self.remote_control_output(
|
||||
remote_control_status(mode),
|
||||
app_server_version.as_ref().map(|_| BackendKind::Pid),
|
||||
remote_control_enabled,
|
||||
app_server_version,
|
||||
))
|
||||
}
|
||||
|
||||
async fn bootstrap_locked(&self, options: BootstrapOptions) -> Result<BootstrapOutput> {
|
||||
self.ensure_managed_codex_bin()?;
|
||||
|
||||
let settings = DaemonSettings {
|
||||
remote_control_enabled: options.remote_control_enabled,
|
||||
};
|
||||
if client::probe(&self.socket_path).await.is_ok()
|
||||
&& self.running_backend(&settings).await?.is_none()
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"app server is running but is not managed by codex app-server daemon"
|
||||
));
|
||||
}
|
||||
settings.save(&self.settings_file).await?;
|
||||
|
||||
if let Some(backend) = self.running_backend_instance(&settings).await? {
|
||||
backend.stop().await?;
|
||||
}
|
||||
|
||||
let backend = backend::pid_backend(self.backend_paths(&settings));
|
||||
backend.start().await?;
|
||||
let updater = backend::pid_update_loop_backend(self.backend_paths(&settings));
|
||||
if updater.is_starting_or_running().await? {
|
||||
updater.stop().await?;
|
||||
}
|
||||
updater.start().await?;
|
||||
|
||||
let info = self.wait_until_ready().await?;
|
||||
Ok(BootstrapOutput {
|
||||
status: BootstrapStatus::Bootstrapped,
|
||||
backend: BackendKind::Pid,
|
||||
auto_update_enabled: true,
|
||||
remote_control_enabled: settings.remote_control_enabled,
|
||||
managed_codex_path: self.managed_codex_bin.clone(),
|
||||
socket_path: self.socket_path.clone(),
|
||||
cli_version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
app_server_version: info.app_server_version,
|
||||
})
|
||||
}
|
||||
|
||||
async fn running_backend(&self, settings: &DaemonSettings) -> Result<Option<BackendKind>> {
|
||||
Ok(self
|
||||
.running_backend_instance(settings)
|
||||
.await?
|
||||
.map(|_| BackendKind::Pid))
|
||||
}
|
||||
|
||||
async fn running_backend_instance(
|
||||
&self,
|
||||
settings: &DaemonSettings,
|
||||
) -> Result<Option<backend::PidBackend>> {
|
||||
let backend = backend::pid_backend(self.backend_paths(settings));
|
||||
if backend.is_starting_or_running().await? {
|
||||
return Ok(Some(backend));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
async fn start_managed_backend(&self, settings: &DaemonSettings) -> Result<Option<u32>> {
|
||||
let backend = backend::pid_backend(self.backend_paths(settings));
|
||||
backend.start().await
|
||||
}
|
||||
|
||||
fn ensure_managed_codex_bin(&self) -> Result<()> {
|
||||
if self.managed_codex_bin.is_file() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(anyhow!(
|
||||
"managed standalone Codex install not found at {}; install Codex first",
|
||||
self.managed_codex_bin.display()
|
||||
))
|
||||
}
|
||||
|
||||
fn backend_paths(&self, settings: &DaemonSettings) -> BackendPaths {
|
||||
BackendPaths {
|
||||
codex_bin: self.managed_codex_bin.clone(),
|
||||
pid_file: self.pid_file.clone(),
|
||||
update_pid_file: self.update_pid_file.clone(),
|
||||
remote_control_enabled: settings.remote_control_enabled,
|
||||
}
|
||||
}
|
||||
|
||||
async fn load_settings(&self) -> Result<DaemonSettings> {
|
||||
DaemonSettings::load(&self.settings_file).await
|
||||
}
|
||||
|
||||
async fn acquire_operation_lock(&self) -> Result<tokio::fs::File> {
|
||||
let operation_lock = self.open_operation_lock_file().await?;
|
||||
let deadline = tokio::time::Instant::now() + OPERATION_LOCK_TIMEOUT;
|
||||
while !try_lock_file(&operation_lock)? {
|
||||
if tokio::time::Instant::now() >= deadline {
|
||||
return Err(anyhow!(
|
||||
"timed out waiting for daemon operation lock {}",
|
||||
self.operation_lock_file.display()
|
||||
));
|
||||
}
|
||||
sleep(START_POLL_INTERVAL).await;
|
||||
}
|
||||
Ok(operation_lock)
|
||||
}
|
||||
|
||||
async fn open_operation_lock_file(&self) -> Result<tokio::fs::File> {
|
||||
if let Some(parent) = self.operation_lock_file.parent() {
|
||||
tokio::fs::create_dir_all(parent).await.with_context(|| {
|
||||
format!(
|
||||
"failed to create daemon state directory {}",
|
||||
parent.display()
|
||||
)
|
||||
})?;
|
||||
}
|
||||
tokio::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(false)
|
||||
.write(true)
|
||||
.open(&self.operation_lock_file)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to open daemon operation lock {}",
|
||||
self.operation_lock_file.display()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn output(
|
||||
&self,
|
||||
status: LifecycleStatus,
|
||||
backend: Option<BackendKind>,
|
||||
pid: Option<u32>,
|
||||
app_server_version: Option<String>,
|
||||
) -> LifecycleOutput {
|
||||
LifecycleOutput {
|
||||
status,
|
||||
backend,
|
||||
pid,
|
||||
socket_path: self.socket_path.clone(),
|
||||
cli_version: Some(env!("CARGO_PKG_VERSION").to_string()),
|
||||
app_server_version,
|
||||
}
|
||||
}
|
||||
|
||||
fn remote_control_output(
|
||||
&self,
|
||||
status: RemoteControlStatus,
|
||||
backend: Option<BackendKind>,
|
||||
remote_control_enabled: bool,
|
||||
app_server_version: Option<String>,
|
||||
) -> RemoteControlOutput {
|
||||
RemoteControlOutput {
|
||||
status,
|
||||
backend,
|
||||
remote_control_enabled,
|
||||
socket_path: self.socket_path.clone(),
|
||||
cli_version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
app_server_version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remote_control_status(mode: RemoteControlMode) -> RemoteControlStatus {
|
||||
match mode {
|
||||
RemoteControlMode::Enabled => RemoteControlStatus::Enabled,
|
||||
RemoteControlMode::Disabled => RemoteControlStatus::Disabled,
|
||||
}
|
||||
}
|
||||
|
||||
fn already_remote_control_status(mode: RemoteControlMode) -> RemoteControlStatus {
|
||||
match mode {
|
||||
RemoteControlMode::Enabled => RemoteControlStatus::AlreadyEnabled,
|
||||
RemoteControlMode::Disabled => RemoteControlStatus::AlreadyDisabled,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn try_lock_file(file: &tokio::fs::File) -> Result<bool> {
|
||||
use std::os::fd::AsRawFd;
|
||||
|
||||
let result = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) };
|
||||
if result == 0 {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let err = std::io::Error::last_os_error();
|
||||
if err.raw_os_error() == Some(libc::EWOULDBLOCK) {
|
||||
return Ok(false);
|
||||
}
|
||||
Err(err).context("failed to lock daemon operation")
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn try_lock_file(_file: &tokio::fs::File) -> Result<bool> {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
#[cfg(all(test, unix))]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::BootstrapStatus;
|
||||
use super::LifecycleStatus;
|
||||
use super::RemoteControlStatus;
|
||||
|
||||
#[test]
|
||||
fn lifecycle_status_uses_camel_case_json() {
|
||||
assert_eq!(
|
||||
serde_json::to_string(&LifecycleStatus::AlreadyRunning).expect("serialize"),
|
||||
"\"alreadyRunning\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bootstrap_status_uses_camel_case_json() {
|
||||
assert_eq!(
|
||||
serde_json::to_string(&BootstrapStatus::Bootstrapped).expect("serialize"),
|
||||
"\"bootstrapped\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_control_status_uses_camel_case_json() {
|
||||
assert_eq!(
|
||||
serde_json::to_string(&RemoteControlStatus::AlreadyEnabled).expect("serialize"),
|
||||
"\"alreadyEnabled\""
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(unix)]
|
||||
use anyhow::Context;
|
||||
#[cfg(unix)]
|
||||
use anyhow::Result;
|
||||
#[cfg(unix)]
|
||||
use anyhow::anyhow;
|
||||
#[cfg(unix)]
|
||||
use tokio::process::Command;
|
||||
|
||||
pub(crate) fn managed_codex_bin(codex_home: &Path) -> PathBuf {
|
||||
codex_home
|
||||
.join("packages")
|
||||
.join("standalone")
|
||||
.join("current")
|
||||
.join(managed_codex_file_name())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(crate) async fn managed_codex_version(codex_bin: &Path) -> Result<String> {
|
||||
let output = Command::new(codex_bin)
|
||||
.arg("--version")
|
||||
.output()
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to invoke managed Codex binary {}",
|
||||
codex_bin.display()
|
||||
)
|
||||
})?;
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"managed Codex binary {} exited with status {}",
|
||||
codex_bin.display(),
|
||||
output.status
|
||||
));
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8(output.stdout).with_context(|| {
|
||||
format!(
|
||||
"managed Codex version was not utf-8: {}",
|
||||
codex_bin.display()
|
||||
)
|
||||
})?;
|
||||
parse_codex_version(&stdout)
|
||||
}
|
||||
|
||||
fn managed_codex_file_name() -> &'static str {
|
||||
if cfg!(windows) { "codex.exe" } else { "codex" }
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn parse_codex_version(output: &str) -> Result<String> {
|
||||
let version = output
|
||||
.split_whitespace()
|
||||
.nth(1)
|
||||
.filter(|version| !version.is_empty())
|
||||
.ok_or_else(|| anyhow!("managed Codex version output was malformed"))?;
|
||||
Ok(version.to_string())
|
||||
}
|
||||
|
||||
#[cfg(all(test, unix))]
|
||||
#[path = "managed_install_tests.rs"]
|
||||
mod tests;
|
||||
@@ -1,16 +0,0 @@
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::parse_codex_version;
|
||||
|
||||
#[test]
|
||||
fn parses_codex_cli_version_output() {
|
||||
assert_eq!(
|
||||
parse_codex_version("codex 1.2.3\n").expect("version"),
|
||||
"1.2.3"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_malformed_codex_cli_version_output() {
|
||||
assert!(parse_codex_version("codex\n").is_err());
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use tokio::fs;
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct DaemonSettings {
|
||||
pub(crate) remote_control_enabled: bool,
|
||||
}
|
||||
|
||||
impl DaemonSettings {
|
||||
pub(crate) async fn load(path: &Path) -> Result<Self> {
|
||||
let contents = match fs::read_to_string(path).await {
|
||||
Ok(contents) => contents,
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(Self::default()),
|
||||
Err(err) => {
|
||||
return Err(err)
|
||||
.with_context(|| format!("failed to read daemon settings {}", path.display()));
|
||||
}
|
||||
};
|
||||
|
||||
serde_json::from_str(&contents)
|
||||
.with_context(|| format!("failed to parse daemon settings {}", path.display()))
|
||||
}
|
||||
|
||||
pub(crate) async fn save(&self, path: &Path) -> Result<()> {
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent).await.with_context(|| {
|
||||
format!(
|
||||
"failed to create daemon settings directory {}",
|
||||
parent.display()
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
let contents = serde_json::to_vec_pretty(self).context("failed to serialize settings")?;
|
||||
fs::write(path, contents)
|
||||
.await
|
||||
.with_context(|| format!("failed to write daemon settings {}", path.display()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, unix))]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::DaemonSettings;
|
||||
|
||||
#[test]
|
||||
fn daemon_settings_use_camel_case_json() {
|
||||
assert_eq!(
|
||||
serde_json::to_string(&DaemonSettings {
|
||||
remote_control_enabled: true,
|
||||
})
|
||||
.expect("serialize"),
|
||||
r#"{"remoteControlEnabled":true}"#
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
#[cfg(unix)]
|
||||
use std::process::Stdio;
|
||||
#[cfg(unix)]
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(unix)]
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
#[cfg(not(unix))]
|
||||
use anyhow::bail;
|
||||
#[cfg(unix)]
|
||||
use futures::FutureExt;
|
||||
#[cfg(unix)]
|
||||
use tokio::io::AsyncWriteExt;
|
||||
#[cfg(unix)]
|
||||
use tokio::process::Command;
|
||||
#[cfg(unix)]
|
||||
use tokio::signal::unix::Signal;
|
||||
#[cfg(unix)]
|
||||
use tokio::signal::unix::SignalKind;
|
||||
#[cfg(unix)]
|
||||
use tokio::signal::unix::signal;
|
||||
#[cfg(unix)]
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[cfg(unix)]
|
||||
use crate::Daemon;
|
||||
#[cfg(unix)]
|
||||
use crate::RestartIfRunningOutcome;
|
||||
|
||||
#[cfg(unix)]
|
||||
const INITIAL_UPDATE_DELAY: Duration = Duration::from_secs(5 * 60);
|
||||
#[cfg(unix)]
|
||||
const RESTART_RETRY_INTERVAL: Duration = Duration::from_millis(50);
|
||||
#[cfg(unix)]
|
||||
const UPDATE_INTERVAL: Duration = Duration::from_secs(60 * 60);
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(crate) async fn run() -> Result<()> {
|
||||
let mut terminate =
|
||||
signal(SignalKind::terminate()).context("failed to install updater shutdown handler")?;
|
||||
if sleep_or_terminate(INITIAL_UPDATE_DELAY, &mut terminate).await {
|
||||
return Ok(());
|
||||
}
|
||||
loop {
|
||||
match update_once(&mut terminate).await {
|
||||
Ok(UpdateLoopControl::Continue) | Err(_) => {}
|
||||
Ok(UpdateLoopControl::Stop) => return Ok(()),
|
||||
}
|
||||
if sleep_or_terminate(UPDATE_INTERVAL, &mut terminate).await {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
pub(crate) async fn run() -> Result<()> {
|
||||
bail!("pid-managed updater loop is unsupported on this platform")
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
async fn sleep_or_terminate(duration: Duration, terminate: &mut Signal) -> bool {
|
||||
tokio::select! {
|
||||
_ = sleep(duration) => false,
|
||||
_ = terminate.recv() => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
enum UpdateLoopControl {
|
||||
Continue,
|
||||
Stop,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
async fn update_once(terminate: &mut Signal) -> Result<UpdateLoopControl> {
|
||||
install_latest_standalone().await?;
|
||||
|
||||
let daemon = Daemon::from_environment()?;
|
||||
loop {
|
||||
if terminate.recv().now_or_never().flatten().is_some() {
|
||||
return Ok(UpdateLoopControl::Stop);
|
||||
}
|
||||
match daemon.try_restart_if_running().await? {
|
||||
RestartIfRunningOutcome::Completed => return Ok(UpdateLoopControl::Continue),
|
||||
RestartIfRunningOutcome::Busy => {
|
||||
if sleep_or_terminate(RESTART_RETRY_INTERVAL, terminate).await {
|
||||
return Ok(UpdateLoopControl::Stop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
async fn install_latest_standalone() -> Result<()> {
|
||||
let script = reqwest::get("https://chatgpt.com/codex/install.sh")
|
||||
.await
|
||||
.context("failed to fetch standalone Codex updater")?
|
||||
.error_for_status()
|
||||
.context("standalone Codex updater request failed")?
|
||||
.bytes()
|
||||
.await
|
||||
.context("failed to read standalone Codex updater")?;
|
||||
|
||||
let mut child = Command::new("/bin/sh")
|
||||
.arg("-s")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.context("failed to invoke standalone Codex updater")?;
|
||||
let mut stdin = child
|
||||
.stdin
|
||||
.take()
|
||||
.context("standalone Codex updater stdin was unavailable")?;
|
||||
stdin
|
||||
.write_all(&script)
|
||||
.await
|
||||
.context("failed to pass standalone Codex updater to shell")?;
|
||||
drop(stdin);
|
||||
let status = child
|
||||
.wait()
|
||||
.await
|
||||
.context("failed to wait for standalone Codex updater")?;
|
||||
|
||||
if status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
anyhow::bail!("standalone Codex updater exited with status {status}")
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ license.workspace = true
|
||||
[lib]
|
||||
name = "codex_app_server_protocol"
|
||||
path = "src/lib.rs"
|
||||
doctest = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "AttestationGenerateParams",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -533,6 +533,200 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"DeviceKeyCreateParams": {
|
||||
"description": "Create a controller-local device key with a random key id.",
|
||||
"properties": {
|
||||
"accountUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"clientId": {
|
||||
"type": "string"
|
||||
},
|
||||
"protectionPolicy": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/DeviceKeyProtectionPolicy"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Defaults to `hardware_only` when omitted."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accountUserId",
|
||||
"clientId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"DeviceKeyProtectionPolicy": {
|
||||
"description": "Protection policy for creating or loading a controller-local device key.",
|
||||
"enum": [
|
||||
"hardware_only",
|
||||
"allow_os_protected_nonextractable"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"DeviceKeyPublicParams": {
|
||||
"description": "Fetch a controller-local device key public key by id.",
|
||||
"properties": {
|
||||
"keyId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"keyId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"DeviceKeySignParams": {
|
||||
"description": "Sign an accepted structured payload with a controller-local device key.",
|
||||
"properties": {
|
||||
"keyId": {
|
||||
"type": "string"
|
||||
},
|
||||
"payload": {
|
||||
"$ref": "#/definitions/DeviceKeySignPayload"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"keyId",
|
||||
"payload"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"DeviceKeySignPayload": {
|
||||
"description": "Structured payloads accepted by `device/key/sign`.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Payload bound to one remote-control controller websocket `/client` connection challenge.",
|
||||
"properties": {
|
||||
"accountUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"audience": {
|
||||
"$ref": "#/definitions/RemoteControlClientConnectionAudience"
|
||||
},
|
||||
"clientId": {
|
||||
"type": "string"
|
||||
},
|
||||
"nonce": {
|
||||
"type": "string"
|
||||
},
|
||||
"scopes": {
|
||||
"description": "Must contain exactly `remote_control_controller_websocket`.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"sessionId": {
|
||||
"description": "Backend-issued websocket session id that this proof authorizes.",
|
||||
"type": "string"
|
||||
},
|
||||
"targetOrigin": {
|
||||
"description": "Origin of the backend endpoint that issued the challenge and will verify this proof.",
|
||||
"type": "string"
|
||||
},
|
||||
"targetPath": {
|
||||
"description": "Websocket route path that this proof authorizes.",
|
||||
"type": "string"
|
||||
},
|
||||
"tokenExpiresAt": {
|
||||
"description": "Remote-control token expiration as Unix seconds.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"tokenSha256Base64url": {
|
||||
"description": "SHA-256 of the controller-scoped remote-control token, encoded as unpadded base64url.",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"remoteControlClientConnection"
|
||||
],
|
||||
"title": "RemoteControlClientConnectionDeviceKeySignPayloadType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accountUserId",
|
||||
"audience",
|
||||
"clientId",
|
||||
"nonce",
|
||||
"scopes",
|
||||
"sessionId",
|
||||
"targetOrigin",
|
||||
"targetPath",
|
||||
"tokenExpiresAt",
|
||||
"tokenSha256Base64url",
|
||||
"type"
|
||||
],
|
||||
"title": "RemoteControlClientConnectionDeviceKeySignPayload",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Payload bound to a remote-control client `/client/enroll` ownership challenge.",
|
||||
"properties": {
|
||||
"accountUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"audience": {
|
||||
"$ref": "#/definitions/RemoteControlClientEnrollmentAudience"
|
||||
},
|
||||
"challengeExpiresAt": {
|
||||
"description": "Enrollment challenge expiration as Unix seconds.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"challengeId": {
|
||||
"description": "Backend-issued enrollment challenge id that this proof authorizes.",
|
||||
"type": "string"
|
||||
},
|
||||
"clientId": {
|
||||
"type": "string"
|
||||
},
|
||||
"deviceIdentitySha256Base64url": {
|
||||
"description": "SHA-256 of the requested device identity operation, encoded as unpadded base64url.",
|
||||
"type": "string"
|
||||
},
|
||||
"nonce": {
|
||||
"type": "string"
|
||||
},
|
||||
"targetOrigin": {
|
||||
"description": "Origin of the backend endpoint that issued the challenge and will verify this proof.",
|
||||
"type": "string"
|
||||
},
|
||||
"targetPath": {
|
||||
"description": "HTTP route path that this proof authorizes.",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"remoteControlClientEnrollment"
|
||||
],
|
||||
"title": "RemoteControlClientEnrollmentDeviceKeySignPayloadType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accountUserId",
|
||||
"audience",
|
||||
"challengeExpiresAt",
|
||||
"challengeId",
|
||||
"clientId",
|
||||
"deviceIdentitySha256Base64url",
|
||||
"nonce",
|
||||
"targetOrigin",
|
||||
"targetPath",
|
||||
"type"
|
||||
],
|
||||
"title": "RemoteControlClientEnrollmentDeviceKeySignPayload",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"DynamicToolSpec": {
|
||||
"properties": {
|
||||
"deferLoading": {
|
||||
@@ -1259,11 +1453,6 @@
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"requestAttestation": {
|
||||
"default": false,
|
||||
"description": "Opt into `attestation/generate` requests for upstream `x-oai-attestation`.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -1955,14 +2144,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginListMarketplaceKind": {
|
||||
"enum": [
|
||||
"local",
|
||||
"workspace-directory",
|
||||
"shared-with-me"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginListParams": {
|
||||
"properties": {
|
||||
"cwds": {
|
||||
@@ -1974,16 +2155,6 @@
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"marketplaceKinds": {
|
||||
"description": "Optional marketplace kind filter. When omitted, only local marketplaces are queried, plus the default remote catalog when enabled by feature flag.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginListMarketplaceKind"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -2026,37 +2197,11 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareDiscoverability": {
|
||||
"enum": [
|
||||
"LISTED",
|
||||
"UNLISTED",
|
||||
"PRIVATE"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginShareListParams": {
|
||||
"type": "object"
|
||||
},
|
||||
"PluginSharePrincipalType": {
|
||||
"enum": [
|
||||
"user",
|
||||
"group",
|
||||
"workspace"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginShareSaveParams": {
|
||||
"properties": {
|
||||
"discoverability": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/PluginShareDiscoverability"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"pluginPath": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
@@ -2065,15 +2210,6 @@
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"shareTargets": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginShareTarget"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -2081,61 +2217,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareTarget": {
|
||||
"properties": {
|
||||
"principalId": {
|
||||
"type": "string"
|
||||
},
|
||||
"principalType": {
|
||||
"$ref": "#/definitions/PluginSharePrincipalType"
|
||||
},
|
||||
"role": {
|
||||
"$ref": "#/definitions/PluginShareTargetRole"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"principalId",
|
||||
"principalType",
|
||||
"role"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareTargetRole": {
|
||||
"enum": [
|
||||
"reader",
|
||||
"editor"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginShareUpdateDiscoverability": {
|
||||
"enum": [
|
||||
"UNLISTED",
|
||||
"PRIVATE"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginShareUpdateTargetsParams": {
|
||||
"properties": {
|
||||
"discoverability": {
|
||||
"$ref": "#/definitions/PluginShareUpdateDiscoverability"
|
||||
},
|
||||
"remotePluginId": {
|
||||
"type": "string"
|
||||
},
|
||||
"shareTargets": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginShareTarget"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"discoverability",
|
||||
"remotePluginId",
|
||||
"shareTargets"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginSkillReadParams": {
|
||||
"properties": {
|
||||
"remoteMarketplaceName": {
|
||||
@@ -2184,28 +2265,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ProcessTerminalSize": {
|
||||
"description": "PTY size in character cells for `process/spawn` PTY sessions.",
|
||||
"properties": {
|
||||
"cols": {
|
||||
"description": "Terminal width in character cells.",
|
||||
"format": "uint16",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"rows": {
|
||||
"description": "Terminal height in character cells.",
|
||||
"format": "uint16",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"cols",
|
||||
"rows"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"RealtimeOutputModality": {
|
||||
"enum": [
|
||||
"text",
|
||||
@@ -2337,6 +2396,20 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"RemoteControlClientConnectionAudience": {
|
||||
"description": "Audience for a remote-control client connection device-key proof.",
|
||||
"enum": [
|
||||
"remote_control_client_websocket"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RemoteControlClientEnrollmentAudience": {
|
||||
"description": "Audience for a remote-control client enrollment device-key proof.",
|
||||
"enum": [
|
||||
"remote_control_client_enrollment"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RequestId": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -2777,28 +2850,6 @@
|
||||
"title": "CompactionResponseItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"encrypted_content": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"context_compaction"
|
||||
],
|
||||
"title": "ContextCompactionResponseItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "ContextCompactionResponseItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
@@ -3161,6 +3212,13 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ServiceTier": {
|
||||
"enum": [
|
||||
"fast",
|
||||
"flex"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"SessionMigration": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
@@ -3239,6 +3297,24 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SkillsListExtraRootsForCwd": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"type": "string"
|
||||
},
|
||||
"extraUserRoots": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"cwd",
|
||||
"extraUserRoots"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SkillsListParams": {
|
||||
"properties": {
|
||||
"cwds": {
|
||||
@@ -3251,6 +3327,17 @@
|
||||
"forceReload": {
|
||||
"description": "When true, bypass the skills cache and re-scan skills from disk.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"perCwdExtraUserRoots": {
|
||||
"default": null,
|
||||
"description": "Optional per-cwd extra roots to scan as user-scoped skills.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/SkillsListExtraRootsForCwd"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -3409,24 +3496,24 @@
|
||||
]
|
||||
},
|
||||
"serviceTier": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadSource"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ServiceTier"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional client-supplied analytics source classification for this forked thread."
|
||||
]
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -3820,9 +3907,20 @@
|
||||
]
|
||||
},
|
||||
"serviceTier": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
"anyOf": [
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ServiceTier"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"threadId": {
|
||||
@@ -3890,14 +3988,6 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadSource": {
|
||||
"enum": [
|
||||
"user",
|
||||
"subagent",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadSourceKind": {
|
||||
"enum": [
|
||||
"cli",
|
||||
@@ -4006,9 +4096,20 @@
|
||||
]
|
||||
},
|
||||
"serviceTier": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
"anyOf": [
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ServiceTier"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessionStartSource": {
|
||||
@@ -4020,17 +4121,6 @@
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadSource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional client-supplied analytics source classification for this thread."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -4094,31 +4184,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"TurnItemsView": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "`items` was not loaded for this turn. The field is intentionally empty.",
|
||||
"enum": [
|
||||
"notLoaded"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "`items` contains only a display summary for this turn.",
|
||||
"enum": [
|
||||
"summary"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "`items` contains every ThreadItem available from persisted app-server history for this turn.",
|
||||
"enum": [
|
||||
"full"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"TurnStartParams": {
|
||||
"properties": {
|
||||
"approvalPolicy": {
|
||||
@@ -4200,11 +4265,22 @@
|
||||
"description": "Override the sandbox policy for this turn and subsequent turns."
|
||||
},
|
||||
"serviceTier": {
|
||||
"description": "Override the service tier for this turn and subsequent turns.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
"anyOf": [
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ServiceTier"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Override the service tier for this turn and subsequent turns."
|
||||
},
|
||||
"summary": {
|
||||
"anyOf": [
|
||||
@@ -5027,30 +5103,6 @@
|
||||
"title": "Plugin/share/saveRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"plugin/share/updateTargets"
|
||||
],
|
||||
"title": "Plugin/share/updateTargetsRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/PluginShareUpdateTargetsParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Plugin/share/updateTargetsRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -5123,6 +5175,78 @@
|
||||
"title": "App/listRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"device/key/create"
|
||||
],
|
||||
"title": "Device/key/createRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/DeviceKeyCreateParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Device/key/createRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"device/key/public"
|
||||
],
|
||||
"title": "Device/key/publicRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/DeviceKeyPublicParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Device/key/publicRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"device/key/sign"
|
||||
],
|
||||
"title": "Device/key/signRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/DeviceKeySignParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Device/key/signRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -5746,29 +5870,6 @@
|
||||
"title": "WindowsSandbox/setupStartRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"windowsSandbox/readiness"
|
||||
],
|
||||
"title": "WindowsSandbox/readinessRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method"
|
||||
],
|
||||
"title": "WindowsSandbox/readinessRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -593,11 +593,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"startedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this approval request started.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -607,7 +602,6 @@
|
||||
},
|
||||
"required": [
|
||||
"itemId",
|
||||
"startedAtMs",
|
||||
"threadId",
|
||||
"turnId"
|
||||
],
|
||||
|
||||
@@ -18,11 +18,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"startedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this approval request started.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -32,7 +27,6 @@
|
||||
},
|
||||
"required": [
|
||||
"itemId",
|
||||
"startedAtMs",
|
||||
"threadId",
|
||||
"turnId"
|
||||
],
|
||||
|
||||
@@ -297,11 +297,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"startedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this approval request started.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -313,7 +308,6 @@
|
||||
"cwd",
|
||||
"itemId",
|
||||
"permissions",
|
||||
"startedAtMs",
|
||||
"threadId",
|
||||
"turnId"
|
||||
],
|
||||
|
||||
@@ -1736,8 +1736,6 @@
|
||||
"preToolUse",
|
||||
"permissionRequest",
|
||||
"postToolUse",
|
||||
"preCompact",
|
||||
"postCompact",
|
||||
"sessionStart",
|
||||
"userPromptSubmit",
|
||||
"stop"
|
||||
@@ -1934,11 +1932,6 @@
|
||||
},
|
||||
"ItemCompletedNotification": {
|
||||
"properties": {
|
||||
"completedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this item lifecycle completed.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"item": {
|
||||
"$ref": "#/definitions/ThreadItem"
|
||||
},
|
||||
@@ -1950,7 +1943,6 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"completedAtMs",
|
||||
"item",
|
||||
"threadId",
|
||||
"turnId"
|
||||
@@ -1963,11 +1955,6 @@
|
||||
"action": {
|
||||
"$ref": "#/definitions/GuardianApprovalReviewAction"
|
||||
},
|
||||
"completedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this review completed.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"decisionSource": {
|
||||
"$ref": "#/definitions/AutoReviewDecisionSource"
|
||||
},
|
||||
@@ -1978,11 +1965,6 @@
|
||||
"description": "Stable identifier for this review.",
|
||||
"type": "string"
|
||||
},
|
||||
"startedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this review started.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"targetItemId": {
|
||||
"description": "Identifier for the reviewed item or tool call when one exists.\n\nIn most cases, one review maps to one target item. The exceptions are - execve reviews, where a single command may contain multiple execve calls to review (only possible when using the shell_zsh_fork feature) - network policy reviews, where there is no target item\n\nA network call is triggered by a CommandExecution item, so having a target_item_id set to the CommandExecution item would be misleading because the review is about the network call, not the command execution. Therefore, target_item_id is set to None for network policy reviews.",
|
||||
"type": [
|
||||
@@ -1999,11 +1981,9 @@
|
||||
},
|
||||
"required": [
|
||||
"action",
|
||||
"completedAtMs",
|
||||
"decisionSource",
|
||||
"review",
|
||||
"reviewId",
|
||||
"startedAtMs",
|
||||
"threadId",
|
||||
"turnId"
|
||||
],
|
||||
@@ -2022,11 +2002,6 @@
|
||||
"description": "Stable identifier for this review.",
|
||||
"type": "string"
|
||||
},
|
||||
"startedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this review started.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"targetItemId": {
|
||||
"description": "Identifier for the reviewed item or tool call when one exists.\n\nIn most cases, one review maps to one target item. The exceptions are - execve reviews, where a single command may contain multiple execve calls to review (only possible when using the shell_zsh_fork feature) - network policy reviews, where there is no target item\n\nA network call is triggered by a CommandExecution item, so having a target_item_id set to the CommandExecution item would be misleading because the review is about the network call, not the command execution. Therefore, target_item_id is set to None for network policy reviews.",
|
||||
"type": [
|
||||
@@ -2045,7 +2020,6 @@
|
||||
"action",
|
||||
"review",
|
||||
"reviewId",
|
||||
"startedAtMs",
|
||||
"threadId",
|
||||
"turnId"
|
||||
],
|
||||
@@ -2056,11 +2030,6 @@
|
||||
"item": {
|
||||
"$ref": "#/definitions/ThreadItem"
|
||||
},
|
||||
"startedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this item lifecycle started.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -2070,7 +2039,6 @@
|
||||
},
|
||||
"required": [
|
||||
"item",
|
||||
"startedAtMs",
|
||||
"threadId",
|
||||
"turnId"
|
||||
],
|
||||
@@ -2435,96 +2403,6 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ProcessExitedNotification": {
|
||||
"description": "Final process exit notification for `process/spawn`.",
|
||||
"properties": {
|
||||
"exitCode": {
|
||||
"description": "Process exit code.",
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
"processHandle": {
|
||||
"description": "Client-supplied, connection-scoped `processHandle` from `process/spawn`.",
|
||||
"type": "string"
|
||||
},
|
||||
"stderr": {
|
||||
"description": "Buffered stderr capture.\n\nEmpty when stderr was streamed via `process/outputDelta`.",
|
||||
"type": "string"
|
||||
},
|
||||
"stderrCapReached": {
|
||||
"description": "Whether stderr reached `outputBytesCap`.\n\nIn streaming mode, stderr is empty and cap state is also reported on the final stderr `process/outputDelta` notification.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"stdout": {
|
||||
"description": "Buffered stdout capture.\n\nEmpty when stdout was streamed via `process/outputDelta`.",
|
||||
"type": "string"
|
||||
},
|
||||
"stdoutCapReached": {
|
||||
"description": "Whether stdout reached `outputBytesCap`.\n\nIn streaming mode, stdout is empty and cap state is also reported on the final stdout `process/outputDelta` notification.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"exitCode",
|
||||
"processHandle",
|
||||
"stderr",
|
||||
"stderrCapReached",
|
||||
"stdout",
|
||||
"stdoutCapReached"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ProcessOutputDeltaNotification": {
|
||||
"description": "Base64-encoded output chunk emitted for a streaming `process/spawn` request.",
|
||||
"properties": {
|
||||
"capReached": {
|
||||
"description": "True on the final streamed chunk for this stream when output was truncated by `outputBytesCap`.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"deltaBase64": {
|
||||
"description": "Base64-encoded output bytes.",
|
||||
"type": "string"
|
||||
},
|
||||
"processHandle": {
|
||||
"description": "Client-supplied, connection-scoped `processHandle` from `process/spawn`.",
|
||||
"type": "string"
|
||||
},
|
||||
"stream": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ProcessOutputStream"
|
||||
}
|
||||
],
|
||||
"description": "Output stream this chunk belongs to."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"capReached",
|
||||
"deltaBase64",
|
||||
"processHandle",
|
||||
"stream"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ProcessOutputStream": {
|
||||
"description": "Stream label for `process/outputDelta` notifications.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "stdout stream. PTY mode multiplexes terminal output here.",
|
||||
"enum": [
|
||||
"stdout"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "stderr stream.",
|
||||
"enum": [
|
||||
"stderr"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"RateLimitReachedType": {
|
||||
"enum": [
|
||||
"rate_limit_reached",
|
||||
@@ -2737,7 +2615,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"RemoteControlStatusChangedNotification": {
|
||||
"description": "Current remote-control connection status and remote identity exposed to clients.",
|
||||
"description": "Current remote-control connection status and environment id exposed to clients.",
|
||||
"properties": {
|
||||
"environmentId": {
|
||||
"type": [
|
||||
@@ -2745,15 +2623,11 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"installationId": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/RemoteControlConnectionStatus"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"installationId",
|
||||
"status"
|
||||
],
|
||||
"type": "object"
|
||||
@@ -3096,10 +2970,6 @@
|
||||
"description": "Usually the first user message in the thread, if available.",
|
||||
"type": "string"
|
||||
},
|
||||
"sessionId": {
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -3116,17 +2986,6 @@
|
||||
],
|
||||
"description": "Current runtime status for the thread."
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadSource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional analytics source classification for this thread."
|
||||
},
|
||||
"turns": {
|
||||
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
|
||||
"items": {
|
||||
@@ -3148,7 +3007,6 @@
|
||||
"id",
|
||||
"modelProvider",
|
||||
"preview",
|
||||
"sessionId",
|
||||
"source",
|
||||
"status",
|
||||
"turns",
|
||||
@@ -4134,14 +3992,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadSource": {
|
||||
"enum": [
|
||||
"user",
|
||||
"subagent",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadStartedNotification": {
|
||||
"properties": {
|
||||
"thread": {
|
||||
@@ -4360,21 +4210,12 @@
|
||||
"type": "string"
|
||||
},
|
||||
"items": {
|
||||
"description": "Thread items currently included in this turn payload.",
|
||||
"description": "Only populated on a `thread/resume` or `thread/fork` response. For all other responses and notifications returning a Turn, the items field will be an empty list.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ThreadItem"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"itemsView": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/TurnItemsView"
|
||||
}
|
||||
],
|
||||
"default": "full",
|
||||
"description": "Describes how much of `items` has been loaded for this turn."
|
||||
},
|
||||
"startedAt": {
|
||||
"description": "Unix timestamp (in seconds) when the turn started.",
|
||||
"format": "int64",
|
||||
@@ -4457,31 +4298,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"TurnItemsView": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "`items` was not loaded for this turn. The field is intentionally empty.",
|
||||
"enum": [
|
||||
"notLoaded"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "`items` contains only a display summary for this turn.",
|
||||
"enum": [
|
||||
"summary"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "`items` contains every ThreadItem available from persisted app-server history for this turn.",
|
||||
"enum": [
|
||||
"full"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"TurnPlanStep": {
|
||||
"properties": {
|
||||
"status": {
|
||||
@@ -5335,48 +5151,6 @@
|
||||
"title": "Command/exec/outputDeltaNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Stream base64-encoded stdout/stderr chunks for a running `process/spawn` session.",
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"process/outputDelta"
|
||||
],
|
||||
"title": "Process/outputDeltaNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ProcessOutputDeltaNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Process/outputDeltaNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Final exit notification for a `process/spawn` session.",
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"process/exited"
|
||||
],
|
||||
"title": "Process/exitedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ProcessExitedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Process/exitedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
|
||||
@@ -121,9 +121,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AttestationGenerateParams": {
|
||||
"type": "object"
|
||||
},
|
||||
"ChatgptAuthTokensRefreshParams": {
|
||||
"properties": {
|
||||
"previousAccountId": {
|
||||
@@ -420,11 +417,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"startedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this approval request started.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -434,7 +426,6 @@
|
||||
},
|
||||
"required": [
|
||||
"itemId",
|
||||
"startedAtMs",
|
||||
"threadId",
|
||||
"turnId"
|
||||
],
|
||||
@@ -607,11 +598,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"startedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this approval request started.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -621,7 +607,6 @@
|
||||
},
|
||||
"required": [
|
||||
"itemId",
|
||||
"startedAtMs",
|
||||
"threadId",
|
||||
"turnId"
|
||||
],
|
||||
@@ -1602,11 +1587,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"startedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this approval request started.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -1618,7 +1598,6 @@
|
||||
"cwd",
|
||||
"itemId",
|
||||
"permissions",
|
||||
"startedAtMs",
|
||||
"threadId",
|
||||
"turnId"
|
||||
],
|
||||
@@ -1921,31 +1900,6 @@
|
||||
"title": "Account/chatgptAuthTokens/refreshRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Generate a fresh upstream attestation result on demand.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"attestation/generate"
|
||||
],
|
||||
"title": "Attestation/generateRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/AttestationGenerateParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Attestation/generateRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "DEPRECATED APIs below Request to approve a patch. This request is used for Turns started via the legacy APIs (i.e. SendUserTurn, SendUserMessage).",
|
||||
"properties": {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -39,11 +39,6 @@
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"requestAttestation": {
|
||||
"default": false,
|
||||
"description": "Opt into `attestation/generate` requests for upstream `x-oai-attestation`.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
||||
@@ -352,9 +352,13 @@
|
||||
]
|
||||
},
|
||||
"service_tier": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ServiceTier"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tools": {
|
||||
@@ -654,9 +658,13 @@
|
||||
]
|
||||
},
|
||||
"service_tier": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ServiceTier"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tools": {
|
||||
@@ -746,6 +754,13 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ServiceTier": {
|
||||
"enum": [
|
||||
"fast",
|
||||
"flex"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ToolsV2": {
|
||||
"properties": {
|
||||
"view_image": {
|
||||
|
||||
@@ -213,24 +213,12 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"PostCompact": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/ConfiguredHookMatcherGroup"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"PostToolUse": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/ConfiguredHookMatcherGroup"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"PreCompact": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/ConfiguredHookMatcherGroup"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"PreToolUse": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/ConfiguredHookMatcherGroup"
|
||||
@@ -270,9 +258,7 @@
|
||||
},
|
||||
"required": [
|
||||
"PermissionRequest",
|
||||
"PostCompact",
|
||||
"PostToolUse",
|
||||
"PreCompact",
|
||||
"PreToolUse",
|
||||
"SessionStart",
|
||||
"Stop",
|
||||
|
||||
39
codex-rs/app-server-protocol/schema/json/v2/DeviceKeyCreateParams.json
generated
Normal file
39
codex-rs/app-server-protocol/schema/json/v2/DeviceKeyCreateParams.json
generated
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"DeviceKeyProtectionPolicy": {
|
||||
"description": "Protection policy for creating or loading a controller-local device key.",
|
||||
"enum": [
|
||||
"hardware_only",
|
||||
"allow_os_protected_nonextractable"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "Create a controller-local device key with a random key id.",
|
||||
"properties": {
|
||||
"accountUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"clientId": {
|
||||
"type": "string"
|
||||
},
|
||||
"protectionPolicy": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/DeviceKeyProtectionPolicy"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Defaults to `hardware_only` when omitted."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accountUserId",
|
||||
"clientId"
|
||||
],
|
||||
"title": "DeviceKeyCreateParams",
|
||||
"type": "object"
|
||||
}
|
||||
45
codex-rs/app-server-protocol/schema/json/v2/DeviceKeyCreateResponse.json
generated
Normal file
45
codex-rs/app-server-protocol/schema/json/v2/DeviceKeyCreateResponse.json
generated
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"DeviceKeyAlgorithm": {
|
||||
"description": "Device-key algorithm reported at enrollment and signing boundaries.",
|
||||
"enum": [
|
||||
"ecdsa_p256_sha256"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"DeviceKeyProtectionClass": {
|
||||
"description": "Platform protection class for a controller-local device key.",
|
||||
"enum": [
|
||||
"hardware_secure_enclave",
|
||||
"hardware_tpm",
|
||||
"os_protected_nonextractable"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "Device-key metadata and public key returned by create/public APIs.",
|
||||
"properties": {
|
||||
"algorithm": {
|
||||
"$ref": "#/definitions/DeviceKeyAlgorithm"
|
||||
},
|
||||
"keyId": {
|
||||
"type": "string"
|
||||
},
|
||||
"protectionClass": {
|
||||
"$ref": "#/definitions/DeviceKeyProtectionClass"
|
||||
},
|
||||
"publicKeySpkiDerBase64": {
|
||||
"description": "SubjectPublicKeyInfo DER encoded as base64.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"algorithm",
|
||||
"keyId",
|
||||
"protectionClass",
|
||||
"publicKeySpkiDerBase64"
|
||||
],
|
||||
"title": "DeviceKeyCreateResponse",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Fetch a controller-local device key public key by id.",
|
||||
"properties": {
|
||||
"token": {
|
||||
"description": "Opaque client attestation token.",
|
||||
"keyId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"token"
|
||||
"keyId"
|
||||
],
|
||||
"title": "AttestationGenerateResponse",
|
||||
"title": "DeviceKeyPublicParams",
|
||||
"type": "object"
|
||||
}
|
||||
45
codex-rs/app-server-protocol/schema/json/v2/DeviceKeyPublicResponse.json
generated
Normal file
45
codex-rs/app-server-protocol/schema/json/v2/DeviceKeyPublicResponse.json
generated
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"DeviceKeyAlgorithm": {
|
||||
"description": "Device-key algorithm reported at enrollment and signing boundaries.",
|
||||
"enum": [
|
||||
"ecdsa_p256_sha256"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"DeviceKeyProtectionClass": {
|
||||
"description": "Platform protection class for a controller-local device key.",
|
||||
"enum": [
|
||||
"hardware_secure_enclave",
|
||||
"hardware_tpm",
|
||||
"os_protected_nonextractable"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "Device-key public metadata returned by `device/key/public`.",
|
||||
"properties": {
|
||||
"algorithm": {
|
||||
"$ref": "#/definitions/DeviceKeyAlgorithm"
|
||||
},
|
||||
"keyId": {
|
||||
"type": "string"
|
||||
},
|
||||
"protectionClass": {
|
||||
"$ref": "#/definitions/DeviceKeyProtectionClass"
|
||||
},
|
||||
"publicKeySpkiDerBase64": {
|
||||
"description": "SubjectPublicKeyInfo DER encoded as base64.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"algorithm",
|
||||
"keyId",
|
||||
"protectionClass",
|
||||
"publicKeySpkiDerBase64"
|
||||
],
|
||||
"title": "DeviceKeyPublicResponse",
|
||||
"type": "object"
|
||||
}
|
||||
165
codex-rs/app-server-protocol/schema/json/v2/DeviceKeySignParams.json
generated
Normal file
165
codex-rs/app-server-protocol/schema/json/v2/DeviceKeySignParams.json
generated
Normal file
@@ -0,0 +1,165 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"DeviceKeySignPayload": {
|
||||
"description": "Structured payloads accepted by `device/key/sign`.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Payload bound to one remote-control controller websocket `/client` connection challenge.",
|
||||
"properties": {
|
||||
"accountUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"audience": {
|
||||
"$ref": "#/definitions/RemoteControlClientConnectionAudience"
|
||||
},
|
||||
"clientId": {
|
||||
"type": "string"
|
||||
},
|
||||
"nonce": {
|
||||
"type": "string"
|
||||
},
|
||||
"scopes": {
|
||||
"description": "Must contain exactly `remote_control_controller_websocket`.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"sessionId": {
|
||||
"description": "Backend-issued websocket session id that this proof authorizes.",
|
||||
"type": "string"
|
||||
},
|
||||
"targetOrigin": {
|
||||
"description": "Origin of the backend endpoint that issued the challenge and will verify this proof.",
|
||||
"type": "string"
|
||||
},
|
||||
"targetPath": {
|
||||
"description": "Websocket route path that this proof authorizes.",
|
||||
"type": "string"
|
||||
},
|
||||
"tokenExpiresAt": {
|
||||
"description": "Remote-control token expiration as Unix seconds.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"tokenSha256Base64url": {
|
||||
"description": "SHA-256 of the controller-scoped remote-control token, encoded as unpadded base64url.",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"remoteControlClientConnection"
|
||||
],
|
||||
"title": "RemoteControlClientConnectionDeviceKeySignPayloadType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accountUserId",
|
||||
"audience",
|
||||
"clientId",
|
||||
"nonce",
|
||||
"scopes",
|
||||
"sessionId",
|
||||
"targetOrigin",
|
||||
"targetPath",
|
||||
"tokenExpiresAt",
|
||||
"tokenSha256Base64url",
|
||||
"type"
|
||||
],
|
||||
"title": "RemoteControlClientConnectionDeviceKeySignPayload",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Payload bound to a remote-control client `/client/enroll` ownership challenge.",
|
||||
"properties": {
|
||||
"accountUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"audience": {
|
||||
"$ref": "#/definitions/RemoteControlClientEnrollmentAudience"
|
||||
},
|
||||
"challengeExpiresAt": {
|
||||
"description": "Enrollment challenge expiration as Unix seconds.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"challengeId": {
|
||||
"description": "Backend-issued enrollment challenge id that this proof authorizes.",
|
||||
"type": "string"
|
||||
},
|
||||
"clientId": {
|
||||
"type": "string"
|
||||
},
|
||||
"deviceIdentitySha256Base64url": {
|
||||
"description": "SHA-256 of the requested device identity operation, encoded as unpadded base64url.",
|
||||
"type": "string"
|
||||
},
|
||||
"nonce": {
|
||||
"type": "string"
|
||||
},
|
||||
"targetOrigin": {
|
||||
"description": "Origin of the backend endpoint that issued the challenge and will verify this proof.",
|
||||
"type": "string"
|
||||
},
|
||||
"targetPath": {
|
||||
"description": "HTTP route path that this proof authorizes.",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"remoteControlClientEnrollment"
|
||||
],
|
||||
"title": "RemoteControlClientEnrollmentDeviceKeySignPayloadType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accountUserId",
|
||||
"audience",
|
||||
"challengeExpiresAt",
|
||||
"challengeId",
|
||||
"clientId",
|
||||
"deviceIdentitySha256Base64url",
|
||||
"nonce",
|
||||
"targetOrigin",
|
||||
"targetPath",
|
||||
"type"
|
||||
],
|
||||
"title": "RemoteControlClientEnrollmentDeviceKeySignPayload",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"RemoteControlClientConnectionAudience": {
|
||||
"description": "Audience for a remote-control client connection device-key proof.",
|
||||
"enum": [
|
||||
"remote_control_client_websocket"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RemoteControlClientEnrollmentAudience": {
|
||||
"description": "Audience for a remote-control client enrollment device-key proof.",
|
||||
"enum": [
|
||||
"remote_control_client_enrollment"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "Sign an accepted structured payload with a controller-local device key.",
|
||||
"properties": {
|
||||
"keyId": {
|
||||
"type": "string"
|
||||
},
|
||||
"payload": {
|
||||
"$ref": "#/definitions/DeviceKeySignPayload"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"keyId",
|
||||
"payload"
|
||||
],
|
||||
"title": "DeviceKeySignParams",
|
||||
"type": "object"
|
||||
}
|
||||
33
codex-rs/app-server-protocol/schema/json/v2/DeviceKeySignResponse.json
generated
Normal file
33
codex-rs/app-server-protocol/schema/json/v2/DeviceKeySignResponse.json
generated
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"DeviceKeyAlgorithm": {
|
||||
"description": "Device-key algorithm reported at enrollment and signing boundaries.",
|
||||
"enum": [
|
||||
"ecdsa_p256_sha256"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "ASN.1 DER signature returned by `device/key/sign`.",
|
||||
"properties": {
|
||||
"algorithm": {
|
||||
"$ref": "#/definitions/DeviceKeyAlgorithm"
|
||||
},
|
||||
"signatureDerBase64": {
|
||||
"description": "ECDSA signature DER encoded as base64.",
|
||||
"type": "string"
|
||||
},
|
||||
"signedPayloadBase64": {
|
||||
"description": "Exact bytes signed by the device key, encoded as base64. Verifiers must verify this byte string directly and must not reserialize `payload`.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"algorithm",
|
||||
"signatureDerBase64",
|
||||
"signedPayloadBase64"
|
||||
],
|
||||
"title": "DeviceKeySignResponse",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -10,8 +10,6 @@
|
||||
"preToolUse",
|
||||
"permissionRequest",
|
||||
"postToolUse",
|
||||
"preCompact",
|
||||
"postCompact",
|
||||
"sessionStart",
|
||||
"userPromptSubmit",
|
||||
"stop"
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
"preToolUse",
|
||||
"permissionRequest",
|
||||
"postToolUse",
|
||||
"preCompact",
|
||||
"postCompact",
|
||||
"sessionStart",
|
||||
"userPromptSubmit",
|
||||
"stop"
|
||||
|
||||
@@ -25,8 +25,6 @@
|
||||
"preToolUse",
|
||||
"permissionRequest",
|
||||
"postToolUse",
|
||||
"preCompact",
|
||||
"postCompact",
|
||||
"sessionStart",
|
||||
"userPromptSubmit",
|
||||
"stop"
|
||||
@@ -49,9 +47,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"currentHash": {
|
||||
"type": "string"
|
||||
},
|
||||
"displayOrder": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
@@ -99,13 +94,9 @@
|
||||
"format": "uint64",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"trustStatus": {
|
||||
"$ref": "#/definitions/HookTrustStatus"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"currentHash",
|
||||
"displayOrder",
|
||||
"enabled",
|
||||
"eventName",
|
||||
@@ -114,8 +105,7 @@
|
||||
"key",
|
||||
"source",
|
||||
"sourcePath",
|
||||
"timeoutSec",
|
||||
"trustStatus"
|
||||
"timeoutSec"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
@@ -134,15 +124,6 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"HookTrustStatus": {
|
||||
"enum": [
|
||||
"managed",
|
||||
"untrusted",
|
||||
"trusted",
|
||||
"modified"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"HooksListEntry": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
|
||||
@@ -1370,11 +1370,6 @@
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"completedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this item lifecycle completed.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"item": {
|
||||
"$ref": "#/definitions/ThreadItem"
|
||||
},
|
||||
@@ -1386,7 +1381,6 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"completedAtMs",
|
||||
"item",
|
||||
"threadId",
|
||||
"turnId"
|
||||
|
||||
@@ -574,11 +574,6 @@
|
||||
"action": {
|
||||
"$ref": "#/definitions/GuardianApprovalReviewAction"
|
||||
},
|
||||
"completedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this review completed.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"decisionSource": {
|
||||
"$ref": "#/definitions/AutoReviewDecisionSource"
|
||||
},
|
||||
@@ -589,11 +584,6 @@
|
||||
"description": "Stable identifier for this review.",
|
||||
"type": "string"
|
||||
},
|
||||
"startedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this review started.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"targetItemId": {
|
||||
"description": "Identifier for the reviewed item or tool call when one exists.\n\nIn most cases, one review maps to one target item. The exceptions are - execve reviews, where a single command may contain multiple execve calls to review (only possible when using the shell_zsh_fork feature) - network policy reviews, where there is no target item\n\nA network call is triggered by a CommandExecution item, so having a target_item_id set to the CommandExecution item would be misleading because the review is about the network call, not the command execution. Therefore, target_item_id is set to None for network policy reviews.",
|
||||
"type": [
|
||||
@@ -610,11 +600,9 @@
|
||||
},
|
||||
"required": [
|
||||
"action",
|
||||
"completedAtMs",
|
||||
"decisionSource",
|
||||
"review",
|
||||
"reviewId",
|
||||
"startedAtMs",
|
||||
"threadId",
|
||||
"turnId"
|
||||
],
|
||||
|
||||
@@ -574,11 +574,6 @@
|
||||
"description": "Stable identifier for this review.",
|
||||
"type": "string"
|
||||
},
|
||||
"startedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this review started.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"targetItemId": {
|
||||
"description": "Identifier for the reviewed item or tool call when one exists.\n\nIn most cases, one review maps to one target item. The exceptions are - execve reviews, where a single command may contain multiple execve calls to review (only possible when using the shell_zsh_fork feature) - network policy reviews, where there is no target item\n\nA network call is triggered by a CommandExecution item, so having a target_item_id set to the CommandExecution item would be misleading because the review is about the network call, not the command execution. Therefore, target_item_id is set to None for network policy reviews.",
|
||||
"type": [
|
||||
@@ -597,7 +592,6 @@
|
||||
"action",
|
||||
"review",
|
||||
"reviewId",
|
||||
"startedAtMs",
|
||||
"threadId",
|
||||
"turnId"
|
||||
],
|
||||
|
||||
@@ -1373,11 +1373,6 @@
|
||||
"item": {
|
||||
"$ref": "#/definitions/ThreadItem"
|
||||
},
|
||||
"startedAtMs": {
|
||||
"description": "Unix timestamp (in milliseconds) when this item lifecycle started.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -1387,7 +1382,6 @@
|
||||
},
|
||||
"required": [
|
||||
"item",
|
||||
"startedAtMs",
|
||||
"threadId",
|
||||
"turnId"
|
||||
],
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
"properties": {
|
||||
"additionalSpeedTiers": {
|
||||
"default": [],
|
||||
"description": "Deprecated: use `serviceTiers` instead.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -71,13 +70,6 @@
|
||||
"model": {
|
||||
"type": "string"
|
||||
},
|
||||
"serviceTiers": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/ModelServiceTier"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supportedReasoningEfforts": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/ReasoningEffortOption"
|
||||
@@ -128,25 +120,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ModelServiceTier": {
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"description",
|
||||
"id",
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ModelUpgradeInfo": {
|
||||
"properties": {
|
||||
"migrationMarkdown": {
|
||||
|
||||
@@ -4,14 +4,6 @@
|
||||
"AbsolutePathBuf": {
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
},
|
||||
"PluginListMarketplaceKind": {
|
||||
"enum": [
|
||||
"local",
|
||||
"workspace-directory",
|
||||
"shared-with-me"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
@@ -24,16 +16,6 @@
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"marketplaceKinds": {
|
||||
"description": "Optional marketplace kind filter. When omitted, only local marketplaces are queried, plus the default remote catalog when enabled by feature flag.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginListMarketplaceKind"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"title": "PluginListParams",
|
||||
|
||||
@@ -232,101 +232,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareContext": {
|
||||
"properties": {
|
||||
"creatorAccountUserId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"creatorName": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"discoverability": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/PluginShareDiscoverability"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"remotePluginId": {
|
||||
"type": "string"
|
||||
},
|
||||
"sharePrincipals": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginSharePrincipal"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"shareUrl": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"remotePluginId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareDiscoverability": {
|
||||
"enum": [
|
||||
"LISTED",
|
||||
"UNLISTED",
|
||||
"PRIVATE"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginSharePrincipal": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"principalId": {
|
||||
"type": "string"
|
||||
},
|
||||
"principalType": {
|
||||
"$ref": "#/definitions/PluginSharePrincipalType"
|
||||
},
|
||||
"role": {
|
||||
"$ref": "#/definitions/PluginSharePrincipalRole"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"principalId",
|
||||
"principalType",
|
||||
"role"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginSharePrincipalRole": {
|
||||
"enum": [
|
||||
"reader",
|
||||
"editor",
|
||||
"owner"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginSharePrincipalType": {
|
||||
"enum": [
|
||||
"user",
|
||||
"group",
|
||||
"workspace"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginSource": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -442,27 +347,9 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"keywords": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"shareContext": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/PluginShareContext"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Remote sharing context associated with this plugin when available."
|
||||
},
|
||||
"source": {
|
||||
"$ref": "#/definitions/PluginSource"
|
||||
}
|
||||
|
||||
@@ -37,19 +37,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"HookEventName": {
|
||||
"enum": [
|
||||
"preToolUse",
|
||||
"permissionRequest",
|
||||
"postToolUse",
|
||||
"preCompact",
|
||||
"postCompact",
|
||||
"sessionStart",
|
||||
"userPromptSubmit",
|
||||
"stop"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginAuthPolicy": {
|
||||
"enum": [
|
||||
"ON_INSTALL",
|
||||
@@ -88,12 +75,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"hooks": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginHookSummary"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"marketplaceName": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -125,7 +106,6 @@
|
||||
},
|
||||
"required": [
|
||||
"apps",
|
||||
"hooks",
|
||||
"marketplaceName",
|
||||
"mcpServers",
|
||||
"skills",
|
||||
@@ -133,21 +113,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginHookSummary": {
|
||||
"properties": {
|
||||
"eventName": {
|
||||
"$ref": "#/definitions/HookEventName"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"eventName",
|
||||
"key"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginInstallPolicy": {
|
||||
"enum": [
|
||||
"NOT_AVAILABLE",
|
||||
@@ -286,101 +251,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareContext": {
|
||||
"properties": {
|
||||
"creatorAccountUserId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"creatorName": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"discoverability": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/PluginShareDiscoverability"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"remotePluginId": {
|
||||
"type": "string"
|
||||
},
|
||||
"sharePrincipals": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginSharePrincipal"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"shareUrl": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"remotePluginId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareDiscoverability": {
|
||||
"enum": [
|
||||
"LISTED",
|
||||
"UNLISTED",
|
||||
"PRIVATE"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginSharePrincipal": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"principalId": {
|
||||
"type": "string"
|
||||
},
|
||||
"principalType": {
|
||||
"$ref": "#/definitions/PluginSharePrincipalType"
|
||||
},
|
||||
"role": {
|
||||
"$ref": "#/definitions/PluginSharePrincipalRole"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"principalId",
|
||||
"principalType",
|
||||
"role"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginSharePrincipalRole": {
|
||||
"enum": [
|
||||
"reader",
|
||||
"editor",
|
||||
"owner"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginSharePrincipalType": {
|
||||
"enum": [
|
||||
"user",
|
||||
"group",
|
||||
"workspace"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginSource": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -496,27 +366,9 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"keywords": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"shareContext": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/PluginShareContext"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Remote sharing context associated with this plugin when available."
|
||||
},
|
||||
"source": {
|
||||
"$ref": "#/definitions/PluginSource"
|
||||
}
|
||||
|
||||
@@ -167,62 +167,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareContext": {
|
||||
"properties": {
|
||||
"creatorAccountUserId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"creatorName": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"discoverability": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/PluginShareDiscoverability"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"remotePluginId": {
|
||||
"type": "string"
|
||||
},
|
||||
"sharePrincipals": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginSharePrincipal"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"shareUrl": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"remotePluginId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareDiscoverability": {
|
||||
"enum": [
|
||||
"LISTED",
|
||||
"UNLISTED",
|
||||
"PRIVATE"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginShareListItem": {
|
||||
"properties": {
|
||||
"localPluginPath": {
|
||||
@@ -237,52 +181,17 @@
|
||||
},
|
||||
"plugin": {
|
||||
"$ref": "#/definitions/PluginSummary"
|
||||
},
|
||||
"shareUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"plugin"
|
||||
"plugin",
|
||||
"shareUrl"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginSharePrincipal": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"principalId": {
|
||||
"type": "string"
|
||||
},
|
||||
"principalType": {
|
||||
"$ref": "#/definitions/PluginSharePrincipalType"
|
||||
},
|
||||
"role": {
|
||||
"$ref": "#/definitions/PluginSharePrincipalRole"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"principalId",
|
||||
"principalType",
|
||||
"role"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginSharePrincipalRole": {
|
||||
"enum": [
|
||||
"reader",
|
||||
"editor",
|
||||
"owner"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginSharePrincipalType": {
|
||||
"enum": [
|
||||
"user",
|
||||
"group",
|
||||
"workspace"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginSource": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -398,27 +307,9 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"keywords": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"shareContext": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/PluginShareContext"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Remote sharing context associated with this plugin when available."
|
||||
},
|
||||
"source": {
|
||||
"$ref": "#/definitions/PluginSource"
|
||||
}
|
||||
|
||||
@@ -4,61 +4,9 @@
|
||||
"AbsolutePathBuf": {
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
},
|
||||
"PluginShareDiscoverability": {
|
||||
"enum": [
|
||||
"LISTED",
|
||||
"UNLISTED",
|
||||
"PRIVATE"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginSharePrincipalType": {
|
||||
"enum": [
|
||||
"user",
|
||||
"group",
|
||||
"workspace"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginShareTarget": {
|
||||
"properties": {
|
||||
"principalId": {
|
||||
"type": "string"
|
||||
},
|
||||
"principalType": {
|
||||
"$ref": "#/definitions/PluginSharePrincipalType"
|
||||
},
|
||||
"role": {
|
||||
"$ref": "#/definitions/PluginShareTargetRole"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"principalId",
|
||||
"principalType",
|
||||
"role"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareTargetRole": {
|
||||
"enum": [
|
||||
"reader",
|
||||
"editor"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"discoverability": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/PluginShareDiscoverability"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"pluginPath": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
@@ -67,15 +15,6 @@
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"shareTargets": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginShareTarget"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"PluginSharePrincipalType": {
|
||||
"enum": [
|
||||
"user",
|
||||
"group",
|
||||
"workspace"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginShareTarget": {
|
||||
"properties": {
|
||||
"principalId": {
|
||||
"type": "string"
|
||||
},
|
||||
"principalType": {
|
||||
"$ref": "#/definitions/PluginSharePrincipalType"
|
||||
},
|
||||
"role": {
|
||||
"$ref": "#/definitions/PluginShareTargetRole"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"principalId",
|
||||
"principalType",
|
||||
"role"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareTargetRole": {
|
||||
"enum": [
|
||||
"reader",
|
||||
"editor"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginShareUpdateDiscoverability": {
|
||||
"enum": [
|
||||
"UNLISTED",
|
||||
"PRIVATE"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"discoverability": {
|
||||
"$ref": "#/definitions/PluginShareUpdateDiscoverability"
|
||||
},
|
||||
"remotePluginId": {
|
||||
"type": "string"
|
||||
},
|
||||
"shareTargets": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginShareTarget"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"discoverability",
|
||||
"remotePluginId",
|
||||
"shareTargets"
|
||||
],
|
||||
"title": "PluginShareUpdateTargetsParams",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"PluginShareDiscoverability": {
|
||||
"enum": [
|
||||
"LISTED",
|
||||
"UNLISTED",
|
||||
"PRIVATE"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginSharePrincipal": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"principalId": {
|
||||
"type": "string"
|
||||
},
|
||||
"principalType": {
|
||||
"$ref": "#/definitions/PluginSharePrincipalType"
|
||||
},
|
||||
"role": {
|
||||
"$ref": "#/definitions/PluginSharePrincipalRole"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"principalId",
|
||||
"principalType",
|
||||
"role"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginSharePrincipalRole": {
|
||||
"enum": [
|
||||
"reader",
|
||||
"editor",
|
||||
"owner"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginSharePrincipalType": {
|
||||
"enum": [
|
||||
"user",
|
||||
"group",
|
||||
"workspace"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"discoverability": {
|
||||
"$ref": "#/definitions/PluginShareDiscoverability"
|
||||
},
|
||||
"principals": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginSharePrincipal"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"discoverability",
|
||||
"principals"
|
||||
],
|
||||
"title": "PluginShareUpdateTargetsResponse",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Final process exit notification for `process/spawn`.",
|
||||
"properties": {
|
||||
"exitCode": {
|
||||
"description": "Process exit code.",
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
"processHandle": {
|
||||
"description": "Client-supplied, connection-scoped `processHandle` from `process/spawn`.",
|
||||
"type": "string"
|
||||
},
|
||||
"stderr": {
|
||||
"description": "Buffered stderr capture.\n\nEmpty when stderr was streamed via `process/outputDelta`.",
|
||||
"type": "string"
|
||||
},
|
||||
"stderrCapReached": {
|
||||
"description": "Whether stderr reached `outputBytesCap`.\n\nIn streaming mode, stderr is empty and cap state is also reported on the final stderr `process/outputDelta` notification.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"stdout": {
|
||||
"description": "Buffered stdout capture.\n\nEmpty when stdout was streamed via `process/outputDelta`.",
|
||||
"type": "string"
|
||||
},
|
||||
"stdoutCapReached": {
|
||||
"description": "Whether stdout reached `outputBytesCap`.\n\nIn streaming mode, stdout is empty and cap state is also reported on the final stdout `process/outputDelta` notification.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"exitCode",
|
||||
"processHandle",
|
||||
"stderr",
|
||||
"stderrCapReached",
|
||||
"stdout",
|
||||
"stdoutCapReached"
|
||||
],
|
||||
"title": "ProcessExitedNotification",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"ProcessOutputStream": {
|
||||
"description": "Stream label for `process/outputDelta` notifications.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "stdout stream. PTY mode multiplexes terminal output here.",
|
||||
"enum": [
|
||||
"stdout"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "stderr stream.",
|
||||
"enum": [
|
||||
"stderr"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"description": "Base64-encoded output chunk emitted for a streaming `process/spawn` request.",
|
||||
"properties": {
|
||||
"capReached": {
|
||||
"description": "True on the final streamed chunk for this stream when output was truncated by `outputBytesCap`.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"deltaBase64": {
|
||||
"description": "Base64-encoded output bytes.",
|
||||
"type": "string"
|
||||
},
|
||||
"processHandle": {
|
||||
"description": "Client-supplied, connection-scoped `processHandle` from `process/spawn`.",
|
||||
"type": "string"
|
||||
},
|
||||
"stream": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ProcessOutputStream"
|
||||
}
|
||||
],
|
||||
"description": "Output stream this chunk belongs to."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"capReached",
|
||||
"deltaBase64",
|
||||
"processHandle",
|
||||
"stream"
|
||||
],
|
||||
"title": "ProcessOutputDeltaNotification",
|
||||
"type": "object"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user