Skip to content

Commit 274debd

Browse files
committed
feat(ecosystem): discover and import bundled Codex pets
1 parent cb21e9f commit 274debd

25 files changed

Lines changed: 1131 additions & 133 deletions

File tree

‎scripts/core-boundaries/rules/feature-rules.mjs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export const optionalDependencyFeatureOwnerRules = [
9393
},
9494
{ depName: 'which', ownerFeatures: ['process-runtime'] },
9595
{ depName: 'win32job', ownerFeatures: ['process-runtime'] },
96-
{ depName: 'windows', ownerFeatures: ['json-io', 'local-storage', 'process-runtime'] },
96+
{ depName: 'windows', ownerFeatures: ['installed-apps', 'json-io', 'local-storage', 'process-runtime'] },
9797
{
9898
depName: 'tokio',
9999
ownerFeatures: [

‎scripts/core-boundaries/rules/source/public-api-rules.mjs‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -541,17 +541,17 @@ export const codexAdapterPublicApiEntries = [
541541
))).concat([
542542
'load_codex_user_instructions',
543543
'CodexInstructionSourceOptions',
544-
].map((symbol) => userInstructionSourceAdapterEntry(symbol, 'Codex'))).concat([{
545-
symbol: 'pet_source_root',
544+
].map((symbol) => userInstructionSourceAdapterEntry(symbol, 'Codex'))).concat(['pet_source_root', 'builtin_pet_sources', 'BuiltinPetCatalog', 'BuiltinPetSource'].map((symbol) => ({
545+
symbol,
546546
owner: 'Codex adapter static pet source owner',
547547
consumer: 'openbitfun-core external source composition facade',
548-
verification: 'Codex configured root test and services-core pet package fixtures',
549-
p0: 'local custom pet source root discovery without execution or installation',
548+
verification: 'Codex pet source fixtures, installed bundle smoke test, and services-core pet package tests',
549+
p0: 'local custom and bundled pet source discovery without execution or installation',
550550
contractSlice: contractSlices.externalSourcePetContract,
551551
wireImpact: false,
552-
rationale: 'the adapter resolves CODEX_HOME; the host supplies the native destination to the portable pet package service',
552+
rationale: 'the adapter translates Codex package metadata; portable services own bounded resource IO and native copying',
553553
exit: 'remove only when an equivalent typed pet source provider owns this root',
554-
}]);
554+
})));
555555

556556
export const staticHookSupportPublicApiEntries = [
557557
// Shared by DSH/PI only for validated, redacted source/event construction.
@@ -1224,6 +1224,8 @@ export const externalSourceCorePublicApiEntries = [
12241224
'native_prompt_command_conflicts',
12251225
'instruction_source_catalog',
12261226
'external_pet_source_root',
1227+
'external_builtin_pet_sources',
1228+
'ExternalBuiltinPetCatalog',
12271229
'InstructionSourceCatalog',
12281230
'set_native_prompt_command_conflict_choice',
12291231
'external_source_conflict_choices',

‎src/apps/desktop/src/api/commands.rs‎

Lines changed: 117 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,8 @@ pub struct ImportAgentCompanionPetPackageRequest {
482482
pub path: String,
483483
#[serde(default)]
484484
pub expected_fingerprint: Option<String>,
485+
#[serde(default)]
486+
pub builtin_id: Option<String>,
485487
}
486488

487489
#[derive(Debug, Deserialize)]
@@ -497,13 +499,16 @@ pub use openbitfun_services_core::pet_packages::PetPackage as AgentCompanionPetP
497499
pub struct ListAgentCompanionPetsRequest {
498500
#[serde(default)]
499501
pub include_external: bool,
502+
#[serde(default)]
503+
pub builtin_import_version: Option<u32>,
500504
}
501505

502506
#[derive(Debug, Serialize)]
503507
#[serde(rename_all = "camelCase")]
504508
pub struct ListAgentCompanionPetsResponse {
505509
pub pets: Vec<AgentCompanionPetPackageDto>,
506510
pub import_operations_version: u32,
511+
pub builtin_import_version: u32,
507512
#[serde(skip_serializing_if = "Option::is_none")]
508513
pub external: Option<openbitfun_services_core::pet_packages::PetCatalog>,
509514
}
@@ -2525,11 +2530,57 @@ mod pet_package_tests {
25252530
let old: ImportAgentCompanionPetPackageRequest =
25262531
serde_json::from_value(serde_json::json!({"path": "legacy-pet"})).unwrap();
25272532
assert!(old.expected_fingerprint.is_none());
2533+
assert!(old.builtin_id.is_none());
25282534
let request: ListAgentCompanionPetsRequest =
25292535
serde_json::from_value(serde_json::json!({})).unwrap();
25302536
assert!(!request.include_external);
2537+
assert!(request.builtin_import_version.is_none());
2538+
}
2539+
2540+
#[test]
2541+
fn bundled_pet_request_requires_review() {
2542+
let temp = tempfile::tempdir().unwrap();
2543+
let request =
2544+
serde_json::from_value(serde_json::json!({"path":"app.asar", "builtinId":"codex"}))
2545+
.unwrap();
2546+
assert!(import_pet_request(temp.path(), request)
2547+
.unwrap_err()
2548+
.contains("reviewed fingerprint"));
25312549
}
25322550

2551+
#[test]
2552+
#[ignore = "Imports installed Codex pets into an isolated temporary directory"]
2553+
fn installed_builtin_pets_can_be_previewed_and_imported() {
2554+
let temp = tempfile::tempdir().unwrap();
2555+
let sources =
2556+
openbitfun_core::external_sources::external_builtin_pet_sources("codex", None);
2557+
assert!(sources.diagnostics.is_empty(), "{:?}", sources.diagnostics);
2558+
assert!(!sources.pets.is_empty());
2559+
for source in sources.pets {
2560+
let identity = format!("codex:builtin:{}", source.id);
2561+
let candidate = openbitfun_services_core::pet_packages::candidate_from_bytes(
2562+
temp.path(),
2563+
&identity,
2564+
&source.archive_path,
2565+
&source.manifest,
2566+
source.image,
2567+
)
2568+
.unwrap();
2569+
assert!(candidate
2570+
.preview_data_url
2571+
.starts_with("data:image/png;base64,"));
2572+
let request = serde_json::from_value(serde_json::json!({
2573+
"path": source.archive_path, "builtinId":source.id, "expectedFingerprint":candidate.fingerprint,
2574+
})).unwrap();
2575+
let imported = import_pet_request(temp.path(), request).unwrap();
2576+
assert!(Path::new(&imported.spritesheet_path).is_file());
2577+
assert_eq!(
2578+
imported.sprite_version_number,
2579+
candidate.pet.sprite_version_number
2580+
);
2581+
println!("Validated bundled pet: {}", imported.display_name);
2582+
}
2583+
}
25332584
#[test]
25342585
fn pet_manifest_versions_are_validated_and_exported() {
25352586
for version in [None, Some(1), Some(2)] {
@@ -2656,14 +2707,43 @@ pub async fn list_agent_companion_pets(
26562707
state: State<'_, AppState>,
26572708
request: Option<ListAgentCompanionPetsRequest>,
26582709
) -> Result<ListAgentCompanionPetsResponse, String> {
2710+
let include_builtins = request
2711+
.as_ref()
2712+
.is_some_and(|r| r.builtin_import_version == Some(1));
26592713
let mut response = list_agent_companion_pets_impl(&state).await?;
26602714
if request.is_some_and(|r| r.include_external) {
26612715
let source_root = openbitfun_core::external_sources::external_pet_source_root("codex")
26622716
.ok_or("Pet source is unavailable")?;
26632717
let installed_root = companion_user_packages_dir(&state);
26642718
response.external = Some(
26652719
tokio::task::spawn_blocking(move || {
2666-
openbitfun_services_core::pet_packages::catalog(&source_root, &installed_root)
2720+
let mut catalog =
2721+
openbitfun_services_core::pet_packages::catalog(&source_root, &installed_root);
2722+
if !include_builtins {
2723+
return catalog;
2724+
}
2725+
let bundled =
2726+
openbitfun_core::external_sources::external_builtin_pet_sources("codex", None);
2727+
catalog.diagnostics.extend(bundled.diagnostics);
2728+
for source in bundled.pets {
2729+
let identity = format!("codex:builtin:{}", source.id);
2730+
match openbitfun_services_core::pet_packages::candidate_from_bytes(
2731+
&installed_root,
2732+
&identity,
2733+
&source.archive_path,
2734+
&source.manifest,
2735+
source.image,
2736+
) {
2737+
Ok(mut candidate) => {
2738+
candidate.builtin_id = Some(source.id);
2739+
catalog.candidates.push(candidate);
2740+
}
2741+
Err(error) => catalog
2742+
.diagnostics
2743+
.push(format!("Codex built-in pet {}: {error}", source.id)),
2744+
}
2745+
}
2746+
catalog
26672747
})
26682748
.await
26692749
.map_err(|e| e.to_string())?,
@@ -2679,6 +2759,7 @@ pub(crate) async fn list_agent_companion_pets_impl(
26792759
Ok(ListAgentCompanionPetsResponse {
26802760
pets,
26812761
import_operations_version: 1,
2762+
builtin_import_version: 1,
26822763
external: None,
26832764
})
26842765
}
@@ -2689,15 +2770,42 @@ pub async fn import_agent_companion_pet_package(
26892770
request: ImportAgentCompanionPetPackageRequest,
26902771
) -> Result<AgentCompanionPetPackageDto, String> {
26912772
let root = companion_user_packages_dir(&state);
2692-
tokio::task::spawn_blocking(move || {
2693-
openbitfun_services_core::pet_packages::import(
2773+
tokio::task::spawn_blocking(move || import_pet_request(&root, request))
2774+
.await
2775+
.map_err(|e| e.to_string())?
2776+
}
2777+
2778+
fn import_pet_request(
2779+
root: &Path,
2780+
request: ImportAgentCompanionPetPackageRequest,
2781+
) -> Result<AgentCompanionPetPackageDto, String> {
2782+
if let Some(id) = request.builtin_id {
2783+
let expected = request
2784+
.expected_fingerprint
2785+
.as_deref()
2786+
.ok_or("Built-in pet import requires a reviewed fingerprint")?;
2787+
let mut catalog =
2788+
openbitfun_core::external_sources::external_builtin_pet_sources("codex", Some(&id));
2789+
let source = catalog.pets.pop().ok_or_else(|| {
2790+
if catalog.diagnostics.is_empty() {
2791+
"Built-in pet is unavailable".to_string()
2792+
} else {
2793+
catalog.diagnostics.join("; ")
2794+
}
2795+
})?;
2796+
return openbitfun_services_core::pet_packages::import_bytes(
26942797
&root,
2695-
Path::new(&request.path),
2696-
request.expected_fingerprint.as_deref(),
2697-
)
2698-
})
2699-
.await
2700-
.map_err(|e| e.to_string())?
2798+
&format!("codex:builtin:{id}"),
2799+
&source.manifest,
2800+
source.image,
2801+
expected,
2802+
);
2803+
}
2804+
openbitfun_services_core::pet_packages::import(
2805+
&root,
2806+
Path::new(&request.path),
2807+
request.expected_fingerprint.as_deref(),
2808+
)
27012809
}
27022810

27032811
pub(crate) async fn import_agent_companion_pet_package_impl(

‎src/crates/adapters/codex-adapter/AGENTS.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,11 @@
55
- Preserve native Codex event and handler names. Map only reviewed OpenBitFun Hook points; report every other event as native-only.
66
- Preserve Codex configuration-layer and field-overlay semantics inside each typed provider. Unknown behavior fields must block explicitly; do not invent Codex Command or standalone Tool sources.
77
- Do not depend on another ecosystem adapter. Shared product behavior belongs in contracts or assembly.
8+
9+
## Pet source verification
10+
11+
```bash
12+
cargo test -p openbitfun-codex-adapter --lib pet_source
13+
# Optional read-only verification on a host with the Codex desktop app installed:
14+
cargo test -p openbitfun-codex-adapter --lib installed_builtin_pet_catalog -- --ignored --nocapture
15+
```

‎src/crates/adapters/codex-adapter/Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ crate-type = ["rlib"]
1212

1313
[dependencies]
1414
openbitfun-product-domains = { path = "../../contracts/product-domains", features = ["external-sources"] }
15-
openbitfun-services-core = { path = "../../services/services-core" }
15+
openbitfun-services-core = { path = "../../services/services-core", features = ["asar", "installed-apps"] }
1616
openbitfun-static-hook-support = { path = "../static-hook-support" }
1717
dirs = { workspace = true }
1818
dunce = { workspace = true }

‎src/crates/adapters/codex-adapter/src/lib.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ mod agent_source;
44
mod hook_source;
55
mod instruction_source;
66
mod mcp_source;
7+
mod pet_source;
8+
9+
pub use pet_source::{builtin_pet_sources, BuiltinPetCatalog, BuiltinPetSource};
710

811
pub use agent_source::{CodexSubagentProvider, CodexSubagentProviderOptions};
912
pub use hook_source::{CodexHookProvider, CodexHookProviderOptions};

0 commit comments

Comments
 (0)