mirror of
https://github.com/openai/codex.git
synced 2026-10-28 18:58:46 +00:00
Merge branch 'main' into canvrno/delegated-mcp-elicitation-routing
This commit is contained in:
@@ -1,153 +0,0 @@
|
||||
name: Issue Labeler
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
- labeled
|
||||
|
||||
jobs:
|
||||
gather-labels:
|
||||
name: Generate label suggestions
|
||||
# Prevent runs on forks (requires OpenAI API key, wastes Actions minutes)
|
||||
if: github.repository == 'openai/codex' && (github.event.action == 'opened' || (github.event.action == 'labeled' && github.event.label.name == 'codex-label'))
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
codex_output: ${{ steps.codex.outputs.final-message }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- id: codex
|
||||
uses: openai/codex-action@5c3f4ccdb2b8790f73d6b21751ac00e602aa0c02 # v1.7
|
||||
with:
|
||||
openai-api-key: ${{ secrets.CODEX_OPENAI_API_KEY }}
|
||||
allow-users: "*"
|
||||
prompt: |
|
||||
You are an assistant that reviews GitHub issues for the repository.
|
||||
|
||||
Your job is to choose the most appropriate labels for the issue described later in this prompt.
|
||||
Follow these rules:
|
||||
|
||||
- Add one (and only one) of the following three labels to distinguish the type of issue. Default to "bug" if unsure.
|
||||
1. bug — Reproducible defects in Codex products (CLI, VS Code extension, web, auth).
|
||||
2. enhancement — Feature requests or usability improvements that ask for new capabilities, better ergonomics, or quality-of-life tweaks.
|
||||
3. documentation — Updates or corrections needed in docs/README/config references (broken links, missing examples, outdated keys, clarification requests).
|
||||
|
||||
- If applicable, add one of the following labels to specify which sub-product or product surface the issue relates to.
|
||||
1. CLI — the Codex command line interface.
|
||||
2. extension — VS Code (or other IDE) extension-specific issues.
|
||||
3. app - Issues related to the Codex desktop application.
|
||||
4. codex-web — Issues targeting the Codex web UI/Cloud experience.
|
||||
5. github-action — Issues with the Codex GitHub action.
|
||||
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.
|
||||
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.
|
||||
4. azure — Problems or requests tied to Azure OpenAI deployments.
|
||||
5. model-behavior — Undesirable LLM behavior: forgetting goals, refusing work, hallucinating environment details, quota misreports, or other reasoning/performance anomalies.
|
||||
6. code-review — Issues related to the code review feature or functionality.
|
||||
7. safety-check - Issues related to cyber risk detection or trusted access verification.
|
||||
8. auth - Problems related to authentication, login, or access tokens.
|
||||
9. exec - Problems related to the "codex exec" command or functionality.
|
||||
10. hooks - Problems related to event hooks
|
||||
11. context - Problems related to compaction, context windows, or available context reporting.
|
||||
12. skills - Problems related to skills or plugins
|
||||
13. custom-model - Problems that involve using custom model providers, local models, or OSS models.
|
||||
14. rate-limits - Problems related to token limits, rate limits, or token usage reporting.
|
||||
15. sandbox - Issues related to local sandbox environments or tool call approvals to override sandbox restrictions.
|
||||
16. tool-calls - Problems related to specific tool call invocations including unexpected errors, failures, or hangs.
|
||||
17. TUI - Problems with the terminal user interface (TUI) including keyboard shortcuts, copy & pasting, menus, or screen update issues.
|
||||
18. app-server - Issues involving the app-server protocol or interfaces, including SDK/API payloads, thread/* and turn/* RPCs, app-server launch behavior, external app/controller bridges, and app-server protocol/schema behavior.
|
||||
19. connectivity - Network connectivity or endpoint issues, including reconnecting messages, stream dropped/disconnected errors, websocket/SSE/transport failures, timeout/network/VPN/proxy/API endpoint failures, and related retry behavior.
|
||||
20. subagent - Issues involving subagents, sub-agents, or multi-agent behavior, including spawn_agent, wait_agent, close_agent, worker/explorer roles, delegation, agent teams, lifecycle, model/config inheritance, quotas, and orchestration.
|
||||
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.
|
||||
|
||||
Issue number: ${{ github.event.issue.number }}
|
||||
|
||||
Issue title:
|
||||
${{ github.event.issue.title }}
|
||||
|
||||
Issue body:
|
||||
${{ github.event.issue.body }}
|
||||
|
||||
Repository full name:
|
||||
${{ github.repository }}
|
||||
|
||||
output-schema: |
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["labels"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
||||
apply-labels:
|
||||
name: Apply labels from Codex output
|
||||
needs: gather-labels
|
||||
if: ${{ needs.gather-labels.result != 'skipped' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
||||
CODEX_OUTPUT: ${{ needs.gather-labels.outputs.codex_output }}
|
||||
steps:
|
||||
- name: Apply labels
|
||||
run: |
|
||||
json=${CODEX_OUTPUT//$'\r'/}
|
||||
if [ -z "$json" ]; then
|
||||
echo "Codex produced no output. Skipping label application."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! printf '%s' "$json" | jq -e 'type == "object" and (.labels | type == "array")' >/dev/null 2>&1; then
|
||||
echo "Codex output did not include a labels array. Raw output: $json"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
labels=$(printf '%s' "$json" | jq -r '.labels[] | tostring')
|
||||
if [ -z "$labels" ]; then
|
||||
echo "Codex returned an empty array. Nothing to do."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cmd=(gh issue edit "$ISSUE_NUMBER")
|
||||
while IFS= read -r label; do
|
||||
cmd+=(--add-label "$label")
|
||||
done <<< "$labels"
|
||||
|
||||
"${cmd[@]}" || true
|
||||
|
||||
- name: Remove codex-label trigger
|
||||
if: ${{ always() && github.event.action == 'labeled' && github.event.label.name == 'codex-label' }}
|
||||
run: |
|
||||
gh issue edit "$ISSUE_NUMBER" --remove-label codex-label || true
|
||||
echo "Attempted to remove label: codex-label"
|
||||
@@ -10,6 +10,13 @@ on:
|
||||
push:
|
||||
tags:
|
||||
- "rust-v*.*.*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
sign_macos:
|
||||
description: "Sign and notarize macOS release artifacts."
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
@@ -25,10 +32,17 @@ jobs:
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
- name: Validate tag matches Cargo.toml version
|
||||
shell: bash
|
||||
env:
|
||||
SIGN_MACOS: ${{ github.event_name != 'workflow_dispatch' || inputs.sign_macos }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "::group::Tag validation"
|
||||
|
||||
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" && "${SIGN_MACOS}" == "true" ]]; then
|
||||
echo "❌ Manual rust-release runs must set sign_macos=false"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 1. Must be a tag and match the regex
|
||||
[[ "${GITHUB_REF_TYPE}" == "tag" ]] \
|
||||
|| { echo "❌ Not a tag push"; exit 1; }
|
||||
@@ -64,6 +78,7 @@ jobs:
|
||||
# 2026-03-04: temporarily change releases to use thin LTO because
|
||||
# Ubuntu ARM is timing out at 60 minutes.
|
||||
CARGO_PROFILE_RELEASE_LTO: ${{ contains(github.ref_name, '-alpha') && 'thin' || 'thin' }}
|
||||
SIGN_MACOS: ${{ github.event_name != 'workflow_dispatch' || inputs.sign_macos }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -295,6 +310,39 @@ jobs:
|
||||
path: codex-rs/target/**/cargo-timings/cargo-timing.html
|
||||
if-no-files-found: warn
|
||||
|
||||
- if: ${{ runner.os == 'macOS' }}
|
||||
name: Stage unsigned macOS artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
target="${{ matrix.target }}"
|
||||
release_dir="target/${target}/release"
|
||||
dest="unsigned-dist/${target}"
|
||||
mkdir -p "$dest"
|
||||
|
||||
for binary in ${{ matrix.binaries }}; do
|
||||
binary_path="${release_dir}/${binary}"
|
||||
unsigned_name="${binary}-${target}-unsigned"
|
||||
unsigned_path="${dest}/${unsigned_name}"
|
||||
if [[ ! -f "${binary_path}" ]]; then
|
||||
echo "Binary ${binary_path} not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp "${binary_path}" "${unsigned_path}"
|
||||
tar -C "$dest" -czf "${unsigned_path}.tar.gz" "${unsigned_name}"
|
||||
zstd -T0 -19 --rm "${unsigned_path}"
|
||||
done
|
||||
|
||||
- if: ${{ runner.os == 'macOS' }}
|
||||
name: Upload unsigned macOS artifacts
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}-unsigned
|
||||
path: codex-rs/unsigned-dist/${{ matrix.target }}/*
|
||||
if-no-files-found: error
|
||||
|
||||
- if: ${{ contains(matrix.target, 'linux') }}
|
||||
name: Cosign Linux artifacts
|
||||
uses: ./.github/actions/linux-code-sign
|
||||
@@ -303,7 +351,7 @@ jobs:
|
||||
artifacts-dir: ${{ github.workspace }}/codex-rs/target/${{ matrix.target }}/release
|
||||
binaries: ${{ matrix.binaries }}
|
||||
|
||||
- if: ${{ runner.os == 'macOS' }}
|
||||
- if: ${{ runner.os == 'macOS' && env.SIGN_MACOS == 'true' }}
|
||||
name: MacOS code signing (binaries)
|
||||
uses: ./.github/actions/macos-code-sign
|
||||
with:
|
||||
@@ -317,7 +365,7 @@ jobs:
|
||||
apple-notarization-key-id: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
|
||||
apple-notarization-issuer-id: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
|
||||
|
||||
- if: ${{ runner.os == 'macOS' && matrix.build_dmg == 'true' }}
|
||||
- if: ${{ runner.os == 'macOS' && matrix.build_dmg == 'true' && env.SIGN_MACOS == 'true' }}
|
||||
name: Build macOS dmg
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -357,7 +405,7 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- if: ${{ runner.os == 'macOS' && matrix.build_dmg == 'true' }}
|
||||
- if: ${{ runner.os == 'macOS' && matrix.build_dmg == 'true' && env.SIGN_MACOS == 'true' }}
|
||||
name: MacOS code signing (dmg)
|
||||
uses: ./.github/actions/macos-code-sign
|
||||
with:
|
||||
@@ -371,6 +419,7 @@ jobs:
|
||||
apple-notarization-issuer-id: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
|
||||
|
||||
- name: Stage artifacts
|
||||
if: ${{ runner.os != 'macOS' || env.SIGN_MACOS == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
dest="dist/${{ matrix.target }}"
|
||||
@@ -400,7 +449,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Build Python runtime wheel
|
||||
if: ${{ matrix.bundle == 'primary' }}
|
||||
if: ${{ matrix.bundle == 'primary' && (runner.os != 'macOS' || env.SIGN_MACOS == 'true') }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -451,7 +500,7 @@ jobs:
|
||||
"${RUNNER_TEMP}/python-runtime-build-venv/bin/python" -m build --wheel --outdir "$wheel_dir" "$stage_dir"
|
||||
|
||||
- name: Upload Python runtime wheel
|
||||
if: ${{ matrix.bundle == 'primary' }}
|
||||
if: ${{ matrix.bundle == 'primary' && (runner.os != 'macOS' || env.SIGN_MACOS == 'true') }}
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: python-runtime-wheel-${{ matrix.target }}
|
||||
@@ -459,6 +508,7 @@ jobs:
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Compress artifacts
|
||||
if: ${{ runner.os != 'macOS' || env.SIGN_MACOS == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
# Path that contains the uncompressed binaries for the current
|
||||
@@ -495,6 +545,7 @@ jobs:
|
||||
done
|
||||
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
if: ${{ runner.os != 'macOS' || env.SIGN_MACOS == 'true' }}
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
# Upload the per-binary .zst files, .tar.gz equivalents, and any
|
||||
@@ -522,6 +573,7 @@ jobs:
|
||||
uses: ./.github/workflows/rust-release-zsh.yml
|
||||
|
||||
release:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.sign_macos }}
|
||||
needs:
|
||||
- build
|
||||
- build-windows
|
||||
|
||||
@@ -514,9 +514,16 @@ pub async fn run_main_with_transport_options(
|
||||
})?;
|
||||
codex_core::otel_init::record_process_start(otel.as_ref(), OTEL_SERVICE_NAME);
|
||||
codex_core::otel_init::install_sqlite_telemetry(otel.as_ref(), OTEL_SERVICE_NAME);
|
||||
let state_db_result = rollout_state_db::try_init(&config).await;
|
||||
let state_db_init_error = state_db_result.as_ref().err().map(ToString::to_string);
|
||||
let state_db = state_db_result.ok();
|
||||
let state_db = match rollout_state_db::try_init(&config).await {
|
||||
Ok(state_db) => Some(state_db),
|
||||
Err(err) => {
|
||||
let state_db_path = codex_state::state_db_path(config.sqlite_home.as_path());
|
||||
return Err(std::io::Error::other(format!(
|
||||
"failed to initialize sqlite state db at {}: {err}",
|
||||
state_db_path.display()
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
if should_run_personality_migration {
|
||||
let effective_toml = config.config_layer_stack.effective_config();
|
||||
@@ -633,10 +640,6 @@ pub async fn run_main_with_transport_options(
|
||||
}
|
||||
}
|
||||
let installation_id = resolve_installation_id(&config.codex_home).await?;
|
||||
if let Some(err) = &state_db_init_error {
|
||||
error!("failed to initialize sqlite state db: {err}");
|
||||
}
|
||||
|
||||
let transport_shutdown_token = CancellationToken::new();
|
||||
let mut transport_accept_handles = Vec::<JoinHandle<()>>::new();
|
||||
|
||||
|
||||
@@ -1823,6 +1823,18 @@ async fn plugin_list_fetches_shared_with_me_kind() -> Result<()> {
|
||||
/*enabled*/ None,
|
||||
))?;
|
||||
shared_plugin_body["plugins"][0]["share_principals"] = serde_json::Value::Null;
|
||||
let shared_unlisted_body: serde_json::Value =
|
||||
serde_json::from_str(&workspace_remote_plugin_page_body(
|
||||
"plugins~Plugin_44444444444444444444444444444444",
|
||||
"shared-unlisted-linear",
|
||||
"Shared Unlisted Linear",
|
||||
"UNLISTED",
|
||||
/*enabled*/ None,
|
||||
))?;
|
||||
shared_plugin_body["plugins"]
|
||||
.as_array_mut()
|
||||
.expect("shared plugins should be an array")
|
||||
.push(shared_unlisted_body["plugins"][0].clone());
|
||||
let shared_plugin_body = serde_json::to_string(&shared_plugin_body)?;
|
||||
let mut workspace_installed_body: serde_json::Value =
|
||||
serde_json::from_str(&workspace_remote_plugin_page_body(
|
||||
@@ -1878,10 +1890,10 @@ async fn plugin_list_fetches_shared_with_me_kind() -> Result<()> {
|
||||
.and_then(|interface| interface.display_name.as_deref()),
|
||||
Some("Shared with me")
|
||||
);
|
||||
assert_eq!(marketplace.plugins.len(), 1);
|
||||
assert_eq!(marketplace.plugins.len(), 2);
|
||||
assert_eq!(
|
||||
marketplace.plugins[0].id,
|
||||
"shared-linear@workspace-shared-with-me-private"
|
||||
"shared-linear@workspace-shared-with-me"
|
||||
);
|
||||
assert_eq!(
|
||||
marketplace.plugins[0].remote_plugin_id.as_deref(),
|
||||
@@ -1913,6 +1925,29 @@ async fn plugin_list_fetches_shared_with_me_kind() -> Result<()> {
|
||||
Some("https://chatgpt.example/plugins/share/share-key-1")
|
||||
);
|
||||
assert_eq!(share_context.share_principals, None);
|
||||
assert_eq!(
|
||||
marketplace.plugins[1].id,
|
||||
"shared-unlisted-linear@workspace-shared-with-me"
|
||||
);
|
||||
assert_eq!(
|
||||
marketplace.plugins[1].remote_plugin_id.as_deref(),
|
||||
Some("plugins~Plugin_44444444444444444444444444444444")
|
||||
);
|
||||
assert_eq!(marketplace.plugins[1].name, "shared-unlisted-linear");
|
||||
assert_eq!(marketplace.plugins[1].installed, false);
|
||||
assert_eq!(marketplace.plugins[1].enabled, false);
|
||||
let share_context = marketplace.plugins[1]
|
||||
.share_context
|
||||
.as_ref()
|
||||
.expect("expected share context");
|
||||
assert_eq!(
|
||||
share_context.remote_plugin_id,
|
||||
"plugins~Plugin_44444444444444444444444444444444"
|
||||
);
|
||||
assert_eq!(
|
||||
share_context.discoverability,
|
||||
Some(PluginShareDiscoverability::Unlisted)
|
||||
);
|
||||
|
||||
let marketplace = response
|
||||
.marketplaces
|
||||
@@ -1929,7 +1964,7 @@ async fn plugin_list_fetches_shared_with_me_kind() -> Result<()> {
|
||||
assert_eq!(marketplace.plugins.len(), 1);
|
||||
assert_eq!(
|
||||
marketplace.plugins[0].id,
|
||||
"unlisted-linear@workspace-shared-with-me-unlisted"
|
||||
"unlisted-linear@workspace-shared-with-me"
|
||||
);
|
||||
assert_eq!(
|
||||
marketplace.plugins[0].remote_plugin_id.as_deref(),
|
||||
|
||||
@@ -300,74 +300,76 @@ async fn plugin_read_returns_share_context_for_shared_remote_plugin() -> Result<
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_plugin_read_request(PluginReadParams {
|
||||
marketplace_path: None,
|
||||
remote_marketplace_name: Some("workspace-shared-with-me-private".to_string()),
|
||||
plugin_name: "plugins~Plugin_11111111111111111111111111111111".to_string(),
|
||||
})
|
||||
.await?;
|
||||
for remote_marketplace_name in [
|
||||
"workspace-shared-with-me-private",
|
||||
"workspace-shared-with-me",
|
||||
] {
|
||||
let request_id = mcp
|
||||
.send_plugin_read_request(PluginReadParams {
|
||||
marketplace_path: None,
|
||||
remote_marketplace_name: Some(remote_marketplace_name.to_string()),
|
||||
plugin_name: "plugins~Plugin_11111111111111111111111111111111".to_string(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let response: PluginReadResponse = to_response(response)?;
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let response: PluginReadResponse = to_response(response)?;
|
||||
|
||||
assert_eq!(
|
||||
response.plugin.marketplace_name,
|
||||
"workspace-shared-with-me-private"
|
||||
);
|
||||
assert_eq!(
|
||||
response.plugin.summary.id,
|
||||
"shared-linear@workspace-shared-with-me-private"
|
||||
);
|
||||
assert_eq!(
|
||||
response.plugin.summary.remote_plugin_id.as_deref(),
|
||||
Some("plugins~Plugin_11111111111111111111111111111111")
|
||||
);
|
||||
let share_context = response
|
||||
.plugin
|
||||
.summary
|
||||
.share_context
|
||||
.as_ref()
|
||||
.expect("expected share context");
|
||||
assert_eq!(
|
||||
share_context.remote_plugin_id,
|
||||
"plugins~Plugin_11111111111111111111111111111111"
|
||||
);
|
||||
assert_eq!(share_context.remote_version.as_deref(), Some("2.3.4"));
|
||||
assert_eq!(
|
||||
share_context.discoverability,
|
||||
Some(PluginShareDiscoverability::Private)
|
||||
);
|
||||
assert_eq!(
|
||||
share_context.creator_account_user_id.as_deref(),
|
||||
Some("user-gavin__account-123")
|
||||
);
|
||||
assert_eq!(share_context.creator_name.as_deref(), Some("Gavin"));
|
||||
assert_eq!(
|
||||
share_context.share_url.as_deref(),
|
||||
Some("https://chatgpt.example/plugins/share/share-key-1")
|
||||
);
|
||||
assert_eq!(
|
||||
share_context.share_principals,
|
||||
Some(vec![
|
||||
PluginSharePrincipal {
|
||||
principal_type: PluginSharePrincipalType::User,
|
||||
principal_id: "user-gavin__account-123".to_string(),
|
||||
role: PluginSharePrincipalRole::Owner,
|
||||
name: "Gavin".to_string(),
|
||||
},
|
||||
PluginSharePrincipal {
|
||||
principal_type: PluginSharePrincipalType::User,
|
||||
principal_id: "user-ada__account-123".to_string(),
|
||||
role: PluginSharePrincipalRole::Reader,
|
||||
name: "Ada".to_string(),
|
||||
},
|
||||
])
|
||||
);
|
||||
assert_eq!(response.plugin.marketplace_name, "workspace-shared-with-me");
|
||||
assert_eq!(
|
||||
response.plugin.summary.id,
|
||||
"shared-linear@workspace-shared-with-me"
|
||||
);
|
||||
assert_eq!(
|
||||
response.plugin.summary.remote_plugin_id.as_deref(),
|
||||
Some("plugins~Plugin_11111111111111111111111111111111")
|
||||
);
|
||||
let share_context = response
|
||||
.plugin
|
||||
.summary
|
||||
.share_context
|
||||
.as_ref()
|
||||
.expect("expected share context");
|
||||
assert_eq!(
|
||||
share_context.remote_plugin_id,
|
||||
"plugins~Plugin_11111111111111111111111111111111"
|
||||
);
|
||||
assert_eq!(share_context.remote_version.as_deref(), Some("2.3.4"));
|
||||
assert_eq!(
|
||||
share_context.discoverability,
|
||||
Some(PluginShareDiscoverability::Private)
|
||||
);
|
||||
assert_eq!(
|
||||
share_context.creator_account_user_id.as_deref(),
|
||||
Some("user-gavin__account-123")
|
||||
);
|
||||
assert_eq!(share_context.creator_name.as_deref(), Some("Gavin"));
|
||||
assert_eq!(
|
||||
share_context.share_url.as_deref(),
|
||||
Some("https://chatgpt.example/plugins/share/share-key-1")
|
||||
);
|
||||
assert_eq!(
|
||||
share_context.share_principals,
|
||||
Some(vec![
|
||||
PluginSharePrincipal {
|
||||
principal_type: PluginSharePrincipalType::User,
|
||||
principal_id: "user-gavin__account-123".to_string(),
|
||||
role: PluginSharePrincipalRole::Owner,
|
||||
name: "Gavin".to_string(),
|
||||
},
|
||||
PluginSharePrincipal {
|
||||
principal_type: PluginSharePrincipalType::User,
|
||||
principal_id: "user-ada__account-123".to_string(),
|
||||
role: PluginSharePrincipalRole::Reader,
|
||||
name: "Ada".to_string(),
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ async fn plugin_share_save_uploads_local_plugin() -> Result<()> {
|
||||
PluginShareListResponse {
|
||||
data: vec![PluginShareListItem {
|
||||
plugin: PluginSummary {
|
||||
id: "demo-plugin@workspace-shared-with-me-private".to_string(),
|
||||
id: "demo-plugin@workspace-shared-with-me".to_string(),
|
||||
remote_plugin_id: Some("plugins_123".to_string()),
|
||||
local_version: None,
|
||||
name: "demo-plugin".to_string(),
|
||||
@@ -573,7 +573,7 @@ async fn plugin_share_list_returns_created_workspace_plugins() -> Result<()> {
|
||||
PluginShareListResponse {
|
||||
data: vec![PluginShareListItem {
|
||||
plugin: PluginSummary {
|
||||
id: "demo-plugin@workspace-shared-with-me-private".to_string(),
|
||||
id: "demo-plugin@workspace-shared-with-me".to_string(),
|
||||
remote_plugin_id: Some("plugins_123".to_string()),
|
||||
local_version: None,
|
||||
name: "demo-plugin".to_string(),
|
||||
@@ -1123,7 +1123,7 @@ async fn plugin_share_delete_removes_created_workspace_plugin() -> Result<()> {
|
||||
PluginShareListResponse {
|
||||
data: vec![PluginShareListItem {
|
||||
plugin: PluginSummary {
|
||||
id: "demo-plugin@workspace-shared-with-me-private".to_string(),
|
||||
id: "demo-plugin@workspace-shared-with-me".to_string(),
|
||||
remote_plugin_id: Some("plugins_123".to_string()),
|
||||
local_version: None,
|
||||
name: "demo-plugin".to_string(),
|
||||
|
||||
@@ -596,8 +596,8 @@ fn codex_apps_tools_cache_filters_disallowed_connectors() {
|
||||
create_test_tool_with_connector(
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
"blocked_tool",
|
||||
"connector_openai_hidden",
|
||||
Some("Hidden"),
|
||||
"connector_2b0a9009c9c64bf9933a3dae3f2b1254",
|
||||
Some("Blocked"),
|
||||
),
|
||||
create_test_tool_with_connector(
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
|
||||
@@ -443,7 +443,6 @@ pub struct ConfigToml {
|
||||
pub experimental_instructions_file: Option<AbsolutePathBuf>,
|
||||
pub experimental_compact_prompt_file: Option<AbsolutePathBuf>,
|
||||
pub experimental_use_unified_exec_tool: Option<bool>,
|
||||
pub experimental_use_freeform_apply_patch: Option<bool>,
|
||||
/// Preferred OSS provider for local models, e.g. "lmstudio" or "ollama".
|
||||
pub oss_provider: Option<String>,
|
||||
}
|
||||
|
||||
@@ -54,13 +54,11 @@ pub struct ConfigProfile {
|
||||
#[schemars(skip)]
|
||||
pub experimental_instructions_file: Option<AbsolutePathBuf>,
|
||||
pub experimental_compact_prompt_file: Option<AbsolutePathBuf>,
|
||||
pub include_apply_patch_tool: Option<bool>,
|
||||
pub include_permissions_instructions: Option<bool>,
|
||||
pub include_apps_instructions: Option<bool>,
|
||||
pub include_collaboration_mode_instructions: Option<bool>,
|
||||
pub include_environment_context: Option<bool>,
|
||||
pub experimental_use_unified_exec_tool: Option<bool>,
|
||||
pub experimental_use_freeform_apply_patch: Option<bool>,
|
||||
pub tools: Option<ToolsToml>,
|
||||
pub web_search: Option<WebSearchMode>,
|
||||
pub analytics: Option<AnalyticsConfigToml>,
|
||||
|
||||
@@ -37,7 +37,6 @@ const DISALLOWED_CONNECTOR_IDS: &[&str] = &[
|
||||
];
|
||||
const FIRST_PARTY_CHAT_DISALLOWED_CONNECTOR_IDS: &[&str] =
|
||||
&["connector_0f9c9d4592e54d0a9a12b3f44a1e2010"];
|
||||
const DISALLOWED_CONNECTOR_PREFIX: &str = "connector_openai_";
|
||||
|
||||
pub fn filter_disallowed_connectors(
|
||||
connectors: Vec<AppInfo>,
|
||||
@@ -63,6 +62,166 @@ fn is_connector_id_allowed(connector_id: &str, first_party_chat_originator: bool
|
||||
DISALLOWED_CONNECTOR_IDS
|
||||
};
|
||||
|
||||
!connector_id.starts_with(DISALLOWED_CONNECTOR_PREFIX)
|
||||
&& !disallowed_connector_ids.contains(&connector_id)
|
||||
!disallowed_connector_ids.contains(&connector_id)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::metadata::connector_install_url;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
fn app(id: &str) -> AppInfo {
|
||||
AppInfo {
|
||||
id: id.to_string(),
|
||||
name: id.to_string(),
|
||||
description: None,
|
||||
logo_url: None,
|
||||
logo_url_dark: None,
|
||||
distribution_channel: None,
|
||||
install_url: None,
|
||||
branding: None,
|
||||
app_metadata: None,
|
||||
labels: None,
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn named_app(id: &str, name: &str) -> AppInfo {
|
||||
AppInfo {
|
||||
id: id.to_string(),
|
||||
name: name.to_string(),
|
||||
install_url: Some(connector_install_url(name, id)),
|
||||
..app(id)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_disallowed_connectors_allows_non_disallowed_connectors() {
|
||||
let filtered =
|
||||
filter_disallowed_connectors(vec![app("asdk_app_hidden"), app("alpha")], "codex_cli");
|
||||
assert_eq!(filtered, vec![app("asdk_app_hidden"), app("alpha")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_disallowed_connectors_allows_openai_prefix() {
|
||||
let filtered = filter_disallowed_connectors(
|
||||
vec![
|
||||
app("connector_openai_foo"),
|
||||
app("connector_openai_bar"),
|
||||
app("gamma"),
|
||||
],
|
||||
"codex_cli",
|
||||
);
|
||||
assert_eq!(
|
||||
filtered,
|
||||
vec![
|
||||
app("connector_openai_foo"),
|
||||
app("connector_openai_bar"),
|
||||
app("gamma")
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_disallowed_connectors_filters_disallowed_connector_ids() {
|
||||
let filtered = filter_disallowed_connectors(
|
||||
vec![
|
||||
app("asdk_app_6938a94a61d881918ef32cb999ff937c"),
|
||||
app("connector_3f8d1a79f27c4c7ba1a897ab13bf37dc"),
|
||||
app("delta"),
|
||||
],
|
||||
"codex_cli",
|
||||
);
|
||||
assert_eq!(filtered, vec![app("delta")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_party_chat_originator_filters_target_connector_ids() {
|
||||
let filtered = filter_disallowed_connectors(
|
||||
vec![
|
||||
app("connector_openai_foo"),
|
||||
app("asdk_app_6938a94a61d881918ef32cb999ff937c"),
|
||||
app("connector_0f9c9d4592e54d0a9a12b3f44a1e2010"),
|
||||
],
|
||||
"codex_atlas",
|
||||
);
|
||||
assert_eq!(
|
||||
filtered,
|
||||
vec![
|
||||
app("connector_openai_foo"),
|
||||
app("asdk_app_6938a94a61d881918ef32cb999ff937c")
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_tool_suggest_discoverable_connectors_keeps_only_plugin_backed_uninstalled_apps() {
|
||||
let filtered = filter_tool_suggest_discoverable_connectors(
|
||||
vec![
|
||||
named_app(
|
||||
"connector_2128aebfecb84f64a069897515042a44",
|
||||
"Google Calendar",
|
||||
),
|
||||
named_app("connector_68df038e0ba48191908c8434991bbac2", "Gmail"),
|
||||
named_app("connector_other", "Other"),
|
||||
],
|
||||
&[AppInfo {
|
||||
is_accessible: true,
|
||||
..named_app(
|
||||
"connector_2128aebfecb84f64a069897515042a44",
|
||||
"Google Calendar",
|
||||
)
|
||||
}],
|
||||
&HashSet::from([
|
||||
"connector_2128aebfecb84f64a069897515042a44".to_string(),
|
||||
"connector_68df038e0ba48191908c8434991bbac2".to_string(),
|
||||
]),
|
||||
"codex_cli",
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
filtered,
|
||||
vec![named_app(
|
||||
"connector_68df038e0ba48191908c8434991bbac2",
|
||||
"Gmail",
|
||||
)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_tool_suggest_discoverable_connectors_excludes_accessible_apps_even_when_disabled() {
|
||||
let filtered = filter_tool_suggest_discoverable_connectors(
|
||||
vec![
|
||||
named_app(
|
||||
"connector_2128aebfecb84f64a069897515042a44",
|
||||
"Google Calendar",
|
||||
),
|
||||
named_app("connector_68df038e0ba48191908c8434991bbac2", "Gmail"),
|
||||
],
|
||||
&[
|
||||
AppInfo {
|
||||
is_accessible: true,
|
||||
..named_app(
|
||||
"connector_2128aebfecb84f64a069897515042a44",
|
||||
"Google Calendar",
|
||||
)
|
||||
},
|
||||
AppInfo {
|
||||
is_accessible: true,
|
||||
is_enabled: false,
|
||||
..named_app("connector_68df038e0ba48191908c8434991bbac2", "Gmail")
|
||||
},
|
||||
],
|
||||
&HashSet::from([
|
||||
"connector_2128aebfecb84f64a069897515042a44".to_string(),
|
||||
"connector_68df038e0ba48191908c8434991bbac2".to_string(),
|
||||
]),
|
||||
"codex_cli",
|
||||
);
|
||||
|
||||
assert_eq!(filtered, Vec::<AppInfo>::new());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,3 +117,90 @@ pub fn plugin_connector_to_app_info(connector_id: String) -> AppInfo {
|
||||
plugin_display_names: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::metadata::connector_install_url;
|
||||
use crate::metadata::connector_mention_slug;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
fn plugin_names(names: &[&str]) -> Vec<String> {
|
||||
names.iter().map(ToString::to_string).collect()
|
||||
}
|
||||
|
||||
fn google_calendar_accessible_connector(plugin_display_names: &[&str]) -> AppInfo {
|
||||
AppInfo {
|
||||
id: "calendar".to_string(),
|
||||
name: "Google Calendar".to_string(),
|
||||
description: Some("Plan events".to_string()),
|
||||
logo_url: Some("https://example.com/logo.png".to_string()),
|
||||
logo_url_dark: Some("https://example.com/logo-dark.png".to_string()),
|
||||
distribution_channel: Some("workspace".to_string()),
|
||||
branding: None,
|
||||
app_metadata: None,
|
||||
labels: None,
|
||||
install_url: None,
|
||||
is_accessible: true,
|
||||
is_enabled: true,
|
||||
plugin_display_names: plugin_names(plugin_display_names),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_connectors_replaces_plugin_placeholder_name_with_accessible_name() {
|
||||
let plugin = plugin_connector_to_app_info("calendar".to_string());
|
||||
let accessible = google_calendar_accessible_connector(&[]);
|
||||
|
||||
let merged = merge_connectors(vec![plugin], vec![accessible]);
|
||||
|
||||
assert_eq!(
|
||||
merged,
|
||||
vec![AppInfo {
|
||||
id: "calendar".to_string(),
|
||||
name: "Google Calendar".to_string(),
|
||||
description: Some("Plan events".to_string()),
|
||||
logo_url: Some("https://example.com/logo.png".to_string()),
|
||||
logo_url_dark: Some("https://example.com/logo-dark.png".to_string()),
|
||||
distribution_channel: Some("workspace".to_string()),
|
||||
branding: None,
|
||||
app_metadata: None,
|
||||
labels: None,
|
||||
install_url: Some(connector_install_url("calendar", "calendar")),
|
||||
is_accessible: true,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
}]
|
||||
);
|
||||
assert_eq!(connector_mention_slug(&merged[0]), "google-calendar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_connectors_unions_and_dedupes_plugin_display_names() {
|
||||
let mut plugin = plugin_connector_to_app_info("calendar".to_string());
|
||||
plugin.plugin_display_names = plugin_names(&["sample", "alpha", "sample"]);
|
||||
|
||||
let accessible = google_calendar_accessible_connector(&["beta", "alpha"]);
|
||||
|
||||
let merged = merge_connectors(vec![plugin], vec![accessible]);
|
||||
|
||||
assert_eq!(
|
||||
merged,
|
||||
vec![AppInfo {
|
||||
id: "calendar".to_string(),
|
||||
name: "Google Calendar".to_string(),
|
||||
description: Some("Plan events".to_string()),
|
||||
logo_url: Some("https://example.com/logo.png".to_string()),
|
||||
logo_url_dark: Some("https://example.com/logo-dark.png".to_string()),
|
||||
distribution_channel: Some("workspace".to_string()),
|
||||
branding: None,
|
||||
app_metadata: None,
|
||||
labels: None,
|
||||
install_url: Some(connector_install_url("calendar", "calendar")),
|
||||
is_accessible: true,
|
||||
is_enabled: true,
|
||||
plugin_display_names: plugin_names(&["alpha", "beta", "sample"]),
|
||||
}]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ pub use share::update_remote_plugin_share_targets;
|
||||
|
||||
pub const REMOTE_GLOBAL_MARKETPLACE_NAME: &str = "chatgpt-global";
|
||||
pub const REMOTE_WORKSPACE_MARKETPLACE_NAME: &str = "workspace-directory";
|
||||
pub const REMOTE_WORKSPACE_SHARED_WITH_ME_MARKETPLACE_NAME: &str = "workspace-shared-with-me";
|
||||
pub const REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME: &str =
|
||||
"workspace-shared-with-me-private";
|
||||
pub const REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME: &str =
|
||||
@@ -296,6 +297,7 @@ impl RemotePluginScope {
|
||||
match name {
|
||||
REMOTE_GLOBAL_MARKETPLACE_NAME => Some(Self::Global),
|
||||
REMOTE_WORKSPACE_MARKETPLACE_NAME
|
||||
| REMOTE_WORKSPACE_SHARED_WITH_ME_MARKETPLACE_NAME
|
||||
| REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME
|
||||
| REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME => Some(Self::Workspace),
|
||||
_ => None,
|
||||
@@ -397,11 +399,9 @@ fn remote_plugin_canonical_marketplace_name(
|
||||
RemotePluginScope::Global => Ok(REMOTE_GLOBAL_MARKETPLACE_NAME),
|
||||
RemotePluginScope::Workspace => match workspace_plugin_discoverability(plugin)? {
|
||||
RemotePluginShareDiscoverability::Listed => Ok(REMOTE_WORKSPACE_MARKETPLACE_NAME),
|
||||
RemotePluginShareDiscoverability::Unlisted => {
|
||||
Ok(REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME)
|
||||
}
|
||||
RemotePluginShareDiscoverability::Private => {
|
||||
Ok(REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME)
|
||||
RemotePluginShareDiscoverability::Private
|
||||
| RemotePluginShareDiscoverability::Unlisted => {
|
||||
Ok(REMOTE_WORKSPACE_SHARED_WITH_ME_MARKETPLACE_NAME)
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -505,20 +505,29 @@ pub async fn fetch_remote_marketplaces(
|
||||
}
|
||||
}
|
||||
RemoteMarketplaceSource::SharedWithMe => {
|
||||
let private_plugins = fetch_shared_workspace_plugins(config, auth)
|
||||
.await?
|
||||
// The shared endpoint is the source of truth for plugins explicitly shared
|
||||
// with the user. Installed unlisted plugins that are not returned there are
|
||||
// link-installed and stay in the separate unlisted bucket.
|
||||
let shared_plugins = fetch_shared_workspace_plugins(config, auth).await?;
|
||||
let shared_plugin_ids = shared_plugins
|
||||
.iter()
|
||||
.map(|plugin| plugin.id.clone())
|
||||
.collect::<BTreeSet<_>>();
|
||||
let directly_shared_plugins = shared_plugins
|
||||
.into_iter()
|
||||
.filter_map(|plugin| match workspace_plugin_discoverability(&plugin) {
|
||||
Ok(RemotePluginShareDiscoverability::Private) => Some(Ok(plugin)),
|
||||
Ok(RemotePluginShareDiscoverability::Listed)
|
||||
| Ok(RemotePluginShareDiscoverability::Unlisted) => None,
|
||||
Ok(
|
||||
RemotePluginShareDiscoverability::Private
|
||||
| RemotePluginShareDiscoverability::Unlisted,
|
||||
) => Some(Ok(plugin)),
|
||||
Ok(RemotePluginShareDiscoverability::Listed) => None,
|
||||
Err(err) => Some(Err(err)),
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
if let Some(marketplace) = build_remote_marketplace(
|
||||
REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME,
|
||||
REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_DISPLAY_NAME,
|
||||
private_plugins,
|
||||
directly_shared_plugins,
|
||||
workspace_installed_plugins.clone().unwrap_or_default(),
|
||||
/*include_installed_only*/ false,
|
||||
)? {
|
||||
@@ -531,7 +540,12 @@ pub async fn fetch_remote_marketplaces(
|
||||
.into_iter()
|
||||
.filter_map(
|
||||
|plugin| match workspace_plugin_discoverability(&plugin.plugin) {
|
||||
Ok(RemotePluginShareDiscoverability::Unlisted) => Some(Ok(plugin)),
|
||||
Ok(RemotePluginShareDiscoverability::Unlisted)
|
||||
if !shared_plugin_ids.contains(&plugin.plugin.id) =>
|
||||
{
|
||||
Some(Ok(plugin))
|
||||
}
|
||||
Ok(RemotePluginShareDiscoverability::Unlisted) => None,
|
||||
Ok(RemotePluginShareDiscoverability::Listed)
|
||||
| Ok(RemotePluginShareDiscoverability::Private) => None,
|
||||
Err(err) => Some(Err(err)),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::REMOTE_GLOBAL_MARKETPLACE_NAME;
|
||||
use super::REMOTE_WORKSPACE_MARKETPLACE_NAME;
|
||||
use super::REMOTE_WORKSPACE_SHARED_WITH_ME_MARKETPLACE_NAME;
|
||||
use super::REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME;
|
||||
use super::REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME;
|
||||
use super::RemotePluginCatalogError;
|
||||
@@ -153,6 +154,10 @@ pub async fn sync_remote_installed_plugin_bundles_once(
|
||||
REMOTE_WORKSPACE_MARKETPLACE_NAME.to_string(),
|
||||
BTreeSet::new(),
|
||||
),
|
||||
(
|
||||
REMOTE_WORKSPACE_SHARED_WITH_ME_MARKETPLACE_NAME.to_string(),
|
||||
BTreeSet::new(),
|
||||
),
|
||||
(
|
||||
REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME.to_string(),
|
||||
BTreeSet::new(),
|
||||
@@ -303,6 +308,7 @@ fn remove_stale_remote_plugin_caches(
|
||||
for marketplace_name in [
|
||||
REMOTE_GLOBAL_MARKETPLACE_NAME,
|
||||
REMOTE_WORKSPACE_MARKETPLACE_NAME,
|
||||
REMOTE_WORKSPACE_SHARED_WITH_ME_MARKETPLACE_NAME,
|
||||
REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME,
|
||||
REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME,
|
||||
] {
|
||||
@@ -510,7 +516,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stale_remote_plugin_cleanup_removes_private_shared_with_me_cache() {
|
||||
fn stale_remote_plugin_cleanup_removes_old_shared_with_me_cache_and_keeps_canonical_cache() {
|
||||
let codex_home = tempfile::tempdir().expect("create codex home");
|
||||
let cached_manifest = codex_home
|
||||
.path()
|
||||
@@ -524,6 +530,18 @@ mod tests {
|
||||
.expect("create cached plugin manifest parent");
|
||||
std::fs::write(&cached_manifest, r#"{"name":"private-plugin"}"#)
|
||||
.expect("write cached plugin manifest");
|
||||
let canonical_cached_manifest = codex_home
|
||||
.path()
|
||||
.join(PLUGINS_CACHE_DIR)
|
||||
.join(REMOTE_WORKSPACE_SHARED_WITH_ME_MARKETPLACE_NAME)
|
||||
.join("shared-plugin")
|
||||
.join("1.2.3")
|
||||
.join(".codex-plugin")
|
||||
.join("plugin.json");
|
||||
std::fs::create_dir_all(canonical_cached_manifest.parent().expect("manifest parent"))
|
||||
.expect("create canonical cached plugin manifest parent");
|
||||
std::fs::write(&canonical_cached_manifest, r#"{"name":"shared-plugin"}"#)
|
||||
.expect("write canonical cached plugin manifest");
|
||||
let installed_plugin_names_by_marketplace =
|
||||
BTreeMap::<String, BTreeSet<String>>::from_iter([
|
||||
(REMOTE_GLOBAL_MARKETPLACE_NAME.to_string(), BTreeSet::new()),
|
||||
@@ -531,6 +549,10 @@ mod tests {
|
||||
REMOTE_WORKSPACE_MARKETPLACE_NAME.to_string(),
|
||||
BTreeSet::new(),
|
||||
),
|
||||
(
|
||||
REMOTE_WORKSPACE_SHARED_WITH_ME_MARKETPLACE_NAME.to_string(),
|
||||
BTreeSet::from(["shared-plugin".to_string()]),
|
||||
),
|
||||
(
|
||||
REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME.to_string(),
|
||||
BTreeSet::new(),
|
||||
@@ -552,5 +574,6 @@ mod tests {
|
||||
vec!["private-plugin@workspace-shared-with-me-private".to_string()]
|
||||
);
|
||||
assert!(!cached_manifest.exists());
|
||||
assert!(canonical_cached_manifest.is_file());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use super::super::REMOTE_WORKSPACE_SHARED_WITH_ME_MARKETPLACE_NAME;
|
||||
use super::super::REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME;
|
||||
use super::super::REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME;
|
||||
use super::super::RemotePluginCatalogError;
|
||||
@@ -162,7 +163,8 @@ pub async fn checkout_remote_plugin_share(
|
||||
fn is_checkout_supported_share_marketplace(marketplace_name: &str) -> bool {
|
||||
matches!(
|
||||
marketplace_name,
|
||||
REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME
|
||||
REMOTE_WORKSPACE_SHARED_WITH_ME_MARKETPLACE_NAME
|
||||
| REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME
|
||||
| REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME
|
||||
)
|
||||
}
|
||||
|
||||
@@ -586,7 +586,7 @@ async fn list_remote_plugin_shares_fetches_created_workspace_plugins() {
|
||||
vec![
|
||||
RemotePluginShareSummary {
|
||||
summary: RemotePluginSummary {
|
||||
id: "demo-plugin@workspace-shared-with-me-private".to_string(),
|
||||
id: "demo-plugin@workspace-shared-with-me".to_string(),
|
||||
remote_plugin_id: "plugins_123".to_string(),
|
||||
name: "demo-plugin".to_string(),
|
||||
share_context: Some(RemotePluginShareContext {
|
||||
@@ -625,7 +625,7 @@ async fn list_remote_plugin_shares_fetches_created_workspace_plugins() {
|
||||
},
|
||||
RemotePluginShareSummary {
|
||||
summary: RemotePluginSummary {
|
||||
id: "demo-plugin@workspace-shared-with-me-private".to_string(),
|
||||
id: "demo-plugin@workspace-shared-with-me".to_string(),
|
||||
remote_plugin_id: "plugins_456".to_string(),
|
||||
name: "demo-plugin".to_string(),
|
||||
share_context: Some(RemotePluginShareContext {
|
||||
|
||||
@@ -347,9 +347,6 @@
|
||||
"experimental_compact_prompt_file": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"experimental_use_freeform_apply_patch": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"experimental_use_unified_exec_tool": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -430,9 +427,6 @@
|
||||
"exec_permission_approvals": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"experimental_use_freeform_apply_patch": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"experimental_use_unified_exec_tool": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -619,9 +613,6 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"include_apply_patch_tool": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"include_apps_instructions": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -4090,9 +4081,6 @@
|
||||
],
|
||||
"description": "Experimental / do not use. Selects the thread store implementation."
|
||||
},
|
||||
"experimental_use_freeform_apply_patch": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"experimental_use_unified_exec_tool": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -4173,9 +4161,6 @@
|
||||
"exec_permission_approvals": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"experimental_use_freeform_apply_patch": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"experimental_use_unified_exec_tool": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
@@ -4237,7 +4237,6 @@ async fn legacy_toggles_map_to_features() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let cfg = ConfigToml {
|
||||
experimental_use_unified_exec_tool: Some(true),
|
||||
experimental_use_freeform_apply_patch: Some(true),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -4248,11 +4247,8 @@ async fn legacy_toggles_map_to_features() -> std::io::Result<()> {
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert!(config.features.enabled(Feature::ApplyPatchFreeform));
|
||||
assert!(config.features.enabled(Feature::UnifiedExec));
|
||||
|
||||
assert!(config.include_apply_patch_tool);
|
||||
|
||||
assert!(config.use_experimental_unified_exec_tool);
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -269,13 +269,6 @@ fn explicit_feature_settings_in_config(cfg: &ConfigToml) -> Vec<(String, Feature
|
||||
enabled,
|
||||
));
|
||||
}
|
||||
if let Some(enabled) = cfg.experimental_use_freeform_apply_patch {
|
||||
explicit_settings.push((
|
||||
"experimental_use_freeform_apply_patch".to_string(),
|
||||
Feature::ApplyPatchFreeform,
|
||||
enabled,
|
||||
));
|
||||
}
|
||||
for (profile_name, profile) in &cfg.profiles {
|
||||
if let Some(features) = profile.features.as_ref() {
|
||||
for (key, enabled) in features.entries() {
|
||||
@@ -288,13 +281,6 @@ fn explicit_feature_settings_in_config(cfg: &ConfigToml) -> Vec<(String, Feature
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(enabled) = profile.include_apply_patch_tool {
|
||||
explicit_settings.push((
|
||||
format!("profiles.{profile_name}.include_apply_patch_tool"),
|
||||
Feature::ApplyPatchFreeform,
|
||||
enabled,
|
||||
));
|
||||
}
|
||||
if let Some(enabled) = profile.experimental_use_unified_exec_tool {
|
||||
explicit_settings.push((
|
||||
format!("profiles.{profile_name}.experimental_use_unified_exec_tool"),
|
||||
@@ -302,13 +288,6 @@ fn explicit_feature_settings_in_config(cfg: &ConfigToml) -> Vec<(String, Feature
|
||||
enabled,
|
||||
));
|
||||
}
|
||||
if let Some(enabled) = profile.experimental_use_freeform_apply_patch {
|
||||
explicit_settings.push((
|
||||
format!("profiles.{profile_name}.experimental_use_freeform_apply_patch"),
|
||||
Feature::ApplyPatchFreeform,
|
||||
enabled,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
explicit_settings
|
||||
@@ -370,14 +349,11 @@ pub(crate) fn validate_feature_requirements_in_config_toml(
|
||||
FeatureConfigSource {
|
||||
features: cfg.features.as_ref(),
|
||||
include_apply_patch_tool: None,
|
||||
experimental_use_freeform_apply_patch: cfg.experimental_use_freeform_apply_patch,
|
||||
experimental_use_unified_exec_tool: cfg.experimental_use_unified_exec_tool,
|
||||
},
|
||||
FeatureConfigSource {
|
||||
features: profile.features.as_ref(),
|
||||
include_apply_patch_tool: profile.include_apply_patch_tool,
|
||||
experimental_use_freeform_apply_patch: profile
|
||||
.experimental_use_freeform_apply_patch,
|
||||
include_apply_patch_tool: None,
|
||||
experimental_use_unified_exec_tool: profile.experimental_use_unified_exec_tool,
|
||||
},
|
||||
FeatureOverrides::default(),
|
||||
|
||||
@@ -1881,7 +1881,6 @@ pub struct ConfigOverrides {
|
||||
pub developer_instructions: Option<String>,
|
||||
pub personality: Option<Personality>,
|
||||
pub compact_prompt: Option<String>,
|
||||
pub include_apply_patch_tool: Option<bool>,
|
||||
pub show_raw_agent_reasoning: Option<bool>,
|
||||
pub tools_web_search_request: Option<bool>,
|
||||
pub ephemeral: Option<bool>,
|
||||
@@ -2196,7 +2195,6 @@ impl Config {
|
||||
developer_instructions,
|
||||
personality,
|
||||
compact_prompt,
|
||||
include_apply_patch_tool: include_apply_patch_tool_override,
|
||||
show_raw_agent_reasoning,
|
||||
tools_web_search_request: override_tools_web_search_request,
|
||||
ephemeral,
|
||||
@@ -2250,7 +2248,6 @@ impl Config {
|
||||
};
|
||||
let tool_suggest = resolve_tool_suggest_config(&cfg, &config_layer_stack);
|
||||
let feature_overrides = FeatureOverrides {
|
||||
include_apply_patch_tool: include_apply_patch_tool_override,
|
||||
web_search_request: override_tools_web_search_request,
|
||||
};
|
||||
|
||||
@@ -2258,14 +2255,11 @@ impl Config {
|
||||
FeatureConfigSource {
|
||||
features: cfg.features.as_ref(),
|
||||
include_apply_patch_tool: None,
|
||||
experimental_use_freeform_apply_patch: cfg.experimental_use_freeform_apply_patch,
|
||||
experimental_use_unified_exec_tool: cfg.experimental_use_unified_exec_tool,
|
||||
},
|
||||
FeatureConfigSource {
|
||||
features: config_profile.features.as_ref(),
|
||||
include_apply_patch_tool: config_profile.include_apply_patch_tool,
|
||||
experimental_use_freeform_apply_patch: config_profile
|
||||
.experimental_use_freeform_apply_patch,
|
||||
include_apply_patch_tool: None,
|
||||
experimental_use_unified_exec_tool: config_profile
|
||||
.experimental_use_unified_exec_tool,
|
||||
},
|
||||
|
||||
@@ -13,12 +13,8 @@ use codex_config::types::AppConfig;
|
||||
use codex_config::types::AppToolConfig;
|
||||
use codex_config::types::AppToolsConfig;
|
||||
use codex_config::types::AppsDefaultConfig;
|
||||
use codex_connectors::filter::filter_disallowed_connectors;
|
||||
use codex_connectors::filter::filter_tool_suggest_discoverable_connectors;
|
||||
use codex_connectors::merge::merge_connectors;
|
||||
use codex_connectors::merge::plugin_connector_to_app_info;
|
||||
use codex_connectors::metadata::connector_install_url;
|
||||
use codex_connectors::metadata::connector_mention_slug;
|
||||
use codex_connectors::metadata::sanitize_name;
|
||||
use codex_features::Feature;
|
||||
use codex_login::CodexAuth;
|
||||
@@ -62,15 +58,6 @@ fn app(id: &str) -> AppInfo {
|
||||
}
|
||||
}
|
||||
|
||||
fn named_app(id: &str, name: &str) -> AppInfo {
|
||||
AppInfo {
|
||||
id: id.to_string(),
|
||||
name: name.to_string(),
|
||||
install_url: Some(connector_install_url(name, id)),
|
||||
..app(id)
|
||||
}
|
||||
}
|
||||
|
||||
fn plugin_names(names: &[&str]) -> Vec<String> {
|
||||
names.iter().map(ToString::to_string).collect()
|
||||
}
|
||||
@@ -89,24 +76,6 @@ fn test_tool_definition(tool_name: &str) -> Tool {
|
||||
}
|
||||
}
|
||||
|
||||
fn google_calendar_accessible_connector(plugin_display_names: &[&str]) -> AppInfo {
|
||||
AppInfo {
|
||||
id: "calendar".to_string(),
|
||||
name: "Google Calendar".to_string(),
|
||||
description: Some("Plan events".to_string()),
|
||||
logo_url: Some("https://example.com/logo.png".to_string()),
|
||||
logo_url_dark: Some("https://example.com/logo-dark.png".to_string()),
|
||||
distribution_channel: Some("workspace".to_string()),
|
||||
branding: None,
|
||||
app_metadata: None,
|
||||
labels: None,
|
||||
install_url: None,
|
||||
is_accessible: true,
|
||||
is_enabled: true,
|
||||
plugin_display_names: plugin_names(plugin_display_names),
|
||||
}
|
||||
}
|
||||
|
||||
fn codex_app_tool(
|
||||
tool_name: &str,
|
||||
connector_id: &str,
|
||||
@@ -147,34 +116,6 @@ fn with_accessible_connectors_cache_cleared<R>(f: impl FnOnce() -> R) -> R {
|
||||
result
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_connectors_replaces_plugin_placeholder_name_with_accessible_name() {
|
||||
let plugin = plugin_connector_to_app_info("calendar".to_string());
|
||||
let accessible = google_calendar_accessible_connector(&[]);
|
||||
|
||||
let merged = merge_connectors(vec![plugin], vec![accessible]);
|
||||
|
||||
assert_eq!(
|
||||
merged,
|
||||
vec![AppInfo {
|
||||
id: "calendar".to_string(),
|
||||
name: "Google Calendar".to_string(),
|
||||
description: Some("Plan events".to_string()),
|
||||
logo_url: Some("https://example.com/logo.png".to_string()),
|
||||
logo_url_dark: Some("https://example.com/logo-dark.png".to_string()),
|
||||
distribution_channel: Some("workspace".to_string()),
|
||||
branding: None,
|
||||
app_metadata: None,
|
||||
labels: None,
|
||||
install_url: Some(connector_install_url("calendar", "calendar")),
|
||||
is_accessible: true,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
}]
|
||||
);
|
||||
assert_eq!(connector_mention_slug(&merged[0]), "google-calendar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accessible_connectors_from_mcp_tools_carries_plugin_display_names() {
|
||||
let tools = vec![
|
||||
@@ -258,50 +199,38 @@ async fn refresh_accessible_connectors_cache_from_mcp_tools_writes_latest_instal
|
||||
|
||||
assert_eq!(
|
||||
cached,
|
||||
vec![AppInfo {
|
||||
id: "calendar".to_string(),
|
||||
name: "Google Calendar".to_string(),
|
||||
description: None,
|
||||
logo_url: None,
|
||||
logo_url_dark: None,
|
||||
distribution_channel: None,
|
||||
install_url: Some(connector_install_url("Google Calendar", "calendar")),
|
||||
branding: None,
|
||||
app_metadata: None,
|
||||
labels: None,
|
||||
is_accessible: true,
|
||||
is_enabled: true,
|
||||
plugin_display_names: plugin_names(&["calendar-plugin"]),
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_connectors_unions_and_dedupes_plugin_display_names() {
|
||||
let mut plugin = plugin_connector_to_app_info("calendar".to_string());
|
||||
plugin.plugin_display_names = plugin_names(&["sample", "alpha", "sample"]);
|
||||
|
||||
let accessible = google_calendar_accessible_connector(&["beta", "alpha"]);
|
||||
|
||||
let merged = merge_connectors(vec![plugin], vec![accessible]);
|
||||
|
||||
assert_eq!(
|
||||
merged,
|
||||
vec![AppInfo {
|
||||
id: "calendar".to_string(),
|
||||
name: "Google Calendar".to_string(),
|
||||
description: Some("Plan events".to_string()),
|
||||
logo_url: Some("https://example.com/logo.png".to_string()),
|
||||
logo_url_dark: Some("https://example.com/logo-dark.png".to_string()),
|
||||
distribution_channel: Some("workspace".to_string()),
|
||||
branding: None,
|
||||
app_metadata: None,
|
||||
labels: None,
|
||||
install_url: Some(connector_install_url("calendar", "calendar")),
|
||||
is_accessible: true,
|
||||
is_enabled: true,
|
||||
plugin_display_names: plugin_names(&["alpha", "beta", "sample"]),
|
||||
}]
|
||||
vec![
|
||||
AppInfo {
|
||||
id: "calendar".to_string(),
|
||||
name: "Google Calendar".to_string(),
|
||||
description: None,
|
||||
logo_url: None,
|
||||
logo_url_dark: None,
|
||||
distribution_channel: None,
|
||||
install_url: Some(connector_install_url("Google Calendar", "calendar")),
|
||||
branding: None,
|
||||
app_metadata: None,
|
||||
labels: None,
|
||||
is_accessible: true,
|
||||
is_enabled: true,
|
||||
plugin_display_names: plugin_names(&["calendar-plugin"]),
|
||||
},
|
||||
AppInfo {
|
||||
id: "connector_openai_hidden".to_string(),
|
||||
name: "Hidden".to_string(),
|
||||
description: None,
|
||||
logo_url: None,
|
||||
logo_url_dark: None,
|
||||
distribution_channel: None,
|
||||
install_url: Some(connector_install_url("Hidden", "connector_openai_hidden")),
|
||||
branding: None,
|
||||
app_metadata: None,
|
||||
labels: None,
|
||||
is_accessible: true,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1245,55 +1174,6 @@ fn app_tool_policy_matches_prefix_stripped_tool_name_for_tool_config() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_disallowed_connectors_allows_non_disallowed_connectors() {
|
||||
let filtered =
|
||||
filter_disallowed_connectors(vec![app("asdk_app_hidden"), app("alpha")], "codex_cli");
|
||||
assert_eq!(filtered, vec![app("asdk_app_hidden"), app("alpha")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_disallowed_connectors_filters_openai_prefix() {
|
||||
let filtered = filter_disallowed_connectors(
|
||||
vec![
|
||||
app("connector_openai_foo"),
|
||||
app("connector_openai_bar"),
|
||||
app("gamma"),
|
||||
],
|
||||
"codex_cli",
|
||||
);
|
||||
assert_eq!(filtered, vec![app("gamma")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_disallowed_connectors_filters_disallowed_connector_ids() {
|
||||
let filtered = filter_disallowed_connectors(
|
||||
vec![
|
||||
app("asdk_app_6938a94a61d881918ef32cb999ff937c"),
|
||||
app("connector_3f8d1a79f27c4c7ba1a897ab13bf37dc"),
|
||||
app("delta"),
|
||||
],
|
||||
"codex_cli",
|
||||
);
|
||||
assert_eq!(filtered, vec![app("delta")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_party_chat_originator_filters_target_and_openai_prefixed_connectors() {
|
||||
let filtered = filter_disallowed_connectors(
|
||||
vec![
|
||||
app("connector_openai_foo"),
|
||||
app("asdk_app_6938a94a61d881918ef32cb999ff937c"),
|
||||
app("connector_0f9c9d4592e54d0a9a12b3f44a1e2010"),
|
||||
],
|
||||
"codex_atlas",
|
||||
);
|
||||
assert_eq!(
|
||||
filtered,
|
||||
vec![app("asdk_app_6938a94a61d881918ef32cb999ff937c")]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn tool_suggest_connector_ids_include_configured_tool_suggest_discoverables() {
|
||||
let codex_home = tempdir().expect("tempdir should succeed");
|
||||
@@ -1385,71 +1265,3 @@ discoverables = [
|
||||
))]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_tool_suggest_discoverable_connectors_keeps_only_plugin_backed_uninstalled_apps() {
|
||||
let filtered = filter_tool_suggest_discoverable_connectors(
|
||||
vec![
|
||||
named_app(
|
||||
"connector_2128aebfecb84f64a069897515042a44",
|
||||
"Google Calendar",
|
||||
),
|
||||
named_app("connector_68df038e0ba48191908c8434991bbac2", "Gmail"),
|
||||
named_app("connector_other", "Other"),
|
||||
],
|
||||
&[AppInfo {
|
||||
is_accessible: true,
|
||||
..named_app(
|
||||
"connector_2128aebfecb84f64a069897515042a44",
|
||||
"Google Calendar",
|
||||
)
|
||||
}],
|
||||
&HashSet::from([
|
||||
"connector_2128aebfecb84f64a069897515042a44".to_string(),
|
||||
"connector_68df038e0ba48191908c8434991bbac2".to_string(),
|
||||
]),
|
||||
"codex_cli",
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
filtered,
|
||||
vec![named_app(
|
||||
"connector_68df038e0ba48191908c8434991bbac2",
|
||||
"Gmail",
|
||||
)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_tool_suggest_discoverable_connectors_excludes_accessible_apps_even_when_disabled() {
|
||||
let filtered = filter_tool_suggest_discoverable_connectors(
|
||||
vec![
|
||||
named_app(
|
||||
"connector_2128aebfecb84f64a069897515042a44",
|
||||
"Google Calendar",
|
||||
),
|
||||
named_app("connector_68df038e0ba48191908c8434991bbac2", "Gmail"),
|
||||
],
|
||||
&[
|
||||
AppInfo {
|
||||
is_accessible: true,
|
||||
..named_app(
|
||||
"connector_2128aebfecb84f64a069897515042a44",
|
||||
"Google Calendar",
|
||||
)
|
||||
},
|
||||
AppInfo {
|
||||
is_accessible: true,
|
||||
is_enabled: false,
|
||||
..named_app("connector_68df038e0ba48191908c8434991bbac2", "Gmail")
|
||||
},
|
||||
],
|
||||
&HashSet::from([
|
||||
"connector_2128aebfecb84f64a069897515042a44".to_string(),
|
||||
"connector_68df038e0ba48191908c8434991bbac2".to_string(),
|
||||
]),
|
||||
"codex_cli",
|
||||
);
|
||||
|
||||
assert_eq!(filtered, Vec::<AppInfo>::new());
|
||||
}
|
||||
|
||||
@@ -194,7 +194,6 @@ fn drop_lockfile_inputs(lock_config: &mut ConfigToml) {
|
||||
lock_config.default_permissions = None;
|
||||
lock_config.permissions = None;
|
||||
lock_config.experimental_use_unified_exec_tool = None;
|
||||
lock_config.experimental_use_freeform_apply_patch = None;
|
||||
}
|
||||
|
||||
fn resolved_config_to_toml<Toml>(
|
||||
|
||||
@@ -423,7 +423,6 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
|
||||
developer_instructions: None,
|
||||
personality: None,
|
||||
compact_prompt: None,
|
||||
include_apply_patch_tool: None,
|
||||
show_raw_agent_reasoning: oss.then_some(true),
|
||||
tools_web_search_request: None,
|
||||
ephemeral: ephemeral.then_some(true),
|
||||
|
||||
@@ -21,10 +21,6 @@ const ALIASES: &[Alias] = &[
|
||||
legacy_key: "experimental_use_unified_exec_tool",
|
||||
feature: Feature::UnifiedExec,
|
||||
},
|
||||
Alias {
|
||||
legacy_key: "experimental_use_freeform_apply_patch",
|
||||
feature: Feature::ApplyPatchFreeform,
|
||||
},
|
||||
Alias {
|
||||
legacy_key: "include_apply_patch_tool",
|
||||
feature: Feature::ApplyPatchFreeform,
|
||||
@@ -72,7 +68,6 @@ pub(crate) fn feature_for_key(key: &str) -> Option<Feature> {
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct LegacyFeatureToggles {
|
||||
pub include_apply_patch_tool: Option<bool>,
|
||||
pub experimental_use_freeform_apply_patch: Option<bool>,
|
||||
pub experimental_use_unified_exec_tool: Option<bool>,
|
||||
}
|
||||
|
||||
@@ -84,12 +79,6 @@ impl LegacyFeatureToggles {
|
||||
self.include_apply_patch_tool,
|
||||
"include_apply_patch_tool",
|
||||
);
|
||||
set_if_some(
|
||||
features,
|
||||
Feature::ApplyPatchFreeform,
|
||||
self.experimental_use_freeform_apply_patch,
|
||||
"experimental_use_freeform_apply_patch",
|
||||
);
|
||||
set_if_some(
|
||||
features,
|
||||
Feature::UnifiedExec,
|
||||
|
||||
@@ -286,7 +286,6 @@ pub struct Features {
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FeatureOverrides {
|
||||
pub include_apply_patch_tool: Option<bool>,
|
||||
pub web_search_request: Option<bool>,
|
||||
}
|
||||
|
||||
@@ -294,17 +293,11 @@ pub struct FeatureOverrides {
|
||||
pub struct FeatureConfigSource<'a> {
|
||||
pub features: Option<&'a FeaturesToml>,
|
||||
pub include_apply_patch_tool: Option<bool>,
|
||||
pub experimental_use_freeform_apply_patch: Option<bool>,
|
||||
pub experimental_use_unified_exec_tool: Option<bool>,
|
||||
}
|
||||
|
||||
impl FeatureOverrides {
|
||||
fn apply(self, features: &mut Features) {
|
||||
LegacyFeatureToggles {
|
||||
include_apply_patch_tool: self.include_apply_patch_tool,
|
||||
..Default::default()
|
||||
}
|
||||
.apply(features);
|
||||
if let Some(enabled) = self.web_search_request {
|
||||
if enabled {
|
||||
features.enable(Feature::WebSearchRequest);
|
||||
@@ -470,7 +463,6 @@ impl Features {
|
||||
for source in [base, profile] {
|
||||
LegacyFeatureToggles {
|
||||
include_apply_patch_tool: source.include_apply_patch_tool,
|
||||
experimental_use_freeform_apply_patch: source.experimental_use_freeform_apply_patch,
|
||||
experimental_use_unified_exec_tool: source.experimental_use_unified_exec_tool,
|
||||
}
|
||||
.apply(&mut features);
|
||||
|
||||
@@ -393,7 +393,6 @@ fn from_sources_applies_base_profile_and_overrides() {
|
||||
},
|
||||
FeatureOverrides {
|
||||
web_search_request: Some(false),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@ const DISALLOWED_CONNECTOR_IDS: &[&str] = &[
|
||||
];
|
||||
const FIRST_PARTY_CHAT_DISALLOWED_CONNECTOR_IDS: &[&str] =
|
||||
&["connector_0f9c9d4592e54d0a9a12b3f44a1e2010"];
|
||||
const ALLOWED_OPENAI_CONNECTOR_IDS: &[&str] = &["connector_openai_library"];
|
||||
const DISALLOWED_CONNECTOR_PREFIX: &str = "connector_openai_";
|
||||
|
||||
pub fn is_connector_id_allowed(connector_id: &str) -> bool {
|
||||
is_connector_id_allowed_for_originator(connector_id, originator().value.as_str())
|
||||
@@ -25,12 +23,7 @@ fn is_connector_id_allowed_for_originator(connector_id: &str, originator_value:
|
||||
DISALLOWED_CONNECTOR_IDS
|
||||
};
|
||||
|
||||
if ALLOWED_OPENAI_CONNECTOR_IDS.contains(&connector_id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
!connector_id.starts_with(DISALLOWED_CONNECTOR_PREFIX)
|
||||
&& !disallowed_connector_ids.contains(&connector_id)
|
||||
!disallowed_connector_ids.contains(&connector_id)
|
||||
}
|
||||
|
||||
pub fn sanitize_name(name: &str) -> String {
|
||||
|
||||
Reference in New Issue
Block a user