Skip to content

Commit e512c80

Browse files
authored
Merge pull request DeusData#1408 from dergachoff/fix/codex-agent-toml-transport
fix(install): declare a transport in generated Codex agent role TOMLs
2 parents b6fe834 + abee454 commit e512c80

5 files changed

Lines changed: 172 additions & 41 deletions

File tree

‎src/cli/agent_profiles.c‎

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88
#include "cli/agent_profiles.h"
99

10+
#include "cli/config_toml_edit.h"
1011
#include "yyjson/yyjson.h"
1112

1213
#include <stdbool.h>
@@ -451,9 +452,51 @@ static char *render_kiro_profile(cbm_graph_tier_t tier, cbm_graph_access_t acces
451452
return result;
452453
}
453454

455+
/* Codex deserializes role files standalone: a server table without command/url
456+
* is rejected as "invalid transport" and the whole role is dropped, so direct
457+
* profiles must declare the transport. rc1_transportless reproduces the
458+
* v0.9.1-rc.1 rendering so installs can migrate those files. */
459+
static bool append_codex_profile(profile_buffer_t *buffer, cbm_graph_tier_t tier,
460+
cbm_graph_access_t access, const char *binary_path,
461+
const char *prompt, bool rc1_transportless) {
462+
if (!profile_buffer_append(buffer, "name = \"") ||
463+
!profile_buffer_append(buffer, cbm_graph_tier_slug(tier)) ||
464+
!profile_buffer_append(buffer, "\"\ndescription = \"") ||
465+
!profile_buffer_append(buffer, profile_description(tier, access)) ||
466+
!profile_buffer_append(buffer, "\"\nsandbox_mode = \"read-only\"\ndeveloper_instructions = "
467+
"\"\"\"\n") ||
468+
!profile_buffer_append(buffer, prompt) || !profile_buffer_append(buffer, "\"\"\"\n")) {
469+
return false;
470+
}
471+
if (access != CBM_GRAPH_ACCESS_DIRECT) {
472+
return true;
473+
}
474+
if (!profile_buffer_append(buffer, "\n[mcp_servers.codebase-memory-mcp]\n")) {
475+
return false;
476+
}
477+
if (!rc1_transportless) {
478+
char escaped_binary[8192];
479+
if (!binary_path || !binary_path[0] ||
480+
cbm_toml_escape_basic_string(binary_path, escaped_binary, sizeof(escaped_binary)) !=
481+
0) {
482+
return false;
483+
}
484+
if (!profile_buffer_append(buffer, "command = \"") ||
485+
!profile_buffer_append(buffer, escaped_binary) ||
486+
!profile_buffer_append(buffer, "\"\nargs = [\"--tool-profile=") ||
487+
!profile_buffer_append(buffer, tier_server_profile(tier)) ||
488+
!profile_buffer_append(buffer, "\"]\n")) {
489+
return false;
490+
}
491+
}
492+
return profile_buffer_append(buffer, "enabled_tools = [") &&
493+
append_toml_mcp_tools(buffer, CBM_GRAPH_DIALECT_CODEX, tier, false) &&
494+
profile_buffer_append(buffer, "]\n");
495+
}
496+
454497
static bool render_profile_text(profile_buffer_t *buffer, cbm_graph_profile_dialect_t dialect,
455498
cbm_graph_tier_t tier, cbm_graph_access_t access,
456-
const char *prompt) {
499+
const char *binary_path, const char *prompt) {
457500
const char *slug = cbm_graph_tier_slug(tier);
458501
const char *display = cbm_graph_tier_display_name(tier);
459502
const char *description = profile_description(tier, access);
@@ -471,21 +514,7 @@ static bool render_profile_text(profile_buffer_t *buffer, cbm_graph_profile_dial
471514
}
472515
return true;
473516
case CBM_GRAPH_DIALECT_CODEX:
474-
if (!profile_buffer_append(buffer, "name = \"") || !profile_buffer_append(buffer, slug) ||
475-
!profile_buffer_append(buffer, "\"\ndescription = \"") ||
476-
!profile_buffer_append(buffer, description) ||
477-
!profile_buffer_append(
478-
buffer, "\"\nsandbox_mode = \"read-only\"\ndeveloper_instructions = \"\"\"\n") ||
479-
!profile_buffer_append(buffer, prompt) || !profile_buffer_append(buffer, "\"\"\"\n")) {
480-
return false;
481-
}
482-
if (direct && (!profile_buffer_append(
483-
buffer, "\n[mcp_servers.codebase-memory-mcp]\nenabled_tools = [") ||
484-
!append_toml_mcp_tools(buffer, dialect, tier, false) ||
485-
!profile_buffer_append(buffer, "]\n"))) {
486-
return false;
487-
}
488-
return true;
517+
return append_codex_profile(buffer, tier, access, binary_path, prompt, false);
489518
case CBM_GRAPH_DIALECT_GEMINI:
490519
if (!append_yaml_identity(buffer, slug, description) ||
491520
!profile_buffer_append(buffer,
@@ -627,7 +656,26 @@ char *cbm_render_graph_profile(cbm_graph_profile_dialect_t dialect, cbm_graph_ti
627656
}
628657
profile_buffer_t buffer;
629658
profile_buffer_init(&buffer);
630-
bool ok = render_profile_text(&buffer, dialect, tier, access, prompt);
659+
bool ok = render_profile_text(&buffer, dialect, tier, access, binary_path, prompt);
660+
free(prompt);
661+
if (!ok) {
662+
profile_buffer_discard(&buffer);
663+
return NULL;
664+
}
665+
return profile_buffer_finish(&buffer);
666+
}
667+
668+
char *cbm_render_graph_profile_codex_rc1(cbm_graph_tier_t tier) {
669+
if (!tier_valid(tier)) {
670+
return NULL;
671+
}
672+
char *prompt = cbm_render_graph_prompt(tier, CBM_GRAPH_ACCESS_DIRECT);
673+
if (!prompt) {
674+
return NULL;
675+
}
676+
profile_buffer_t buffer;
677+
profile_buffer_init(&buffer);
678+
bool ok = append_codex_profile(&buffer, tier, CBM_GRAPH_ACCESS_DIRECT, NULL, prompt, true);
631679
free(prompt);
632680
if (!ok) {
633681
profile_buffer_discard(&buffer);

‎src/cli/agent_profiles.h‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,14 @@ const char *cbm_graph_tier_display_name(cbm_graph_tier_t tier);
5050
bool cbm_graph_dialect_direct_capable(cbm_graph_profile_dialect_t dialect);
5151

5252
/* Returns malloc-owned profile content, or NULL for invalid/unsafe combinations.
53-
* binary_path is required for a direct Kiro profile and ignored otherwise. */
53+
* binary_path is required for direct Kiro and Codex profiles and ignored otherwise. */
5454
char *cbm_render_graph_profile(cbm_graph_profile_dialect_t dialect, cbm_graph_tier_t tier,
5555
cbm_graph_access_t access, const char *binary_path);
5656

57+
/* v0.9.1-rc.1 direct Codex rendering (server table without a transport), kept
58+
* so install/uninstall can recognize and migrate those files. */
59+
char *cbm_render_graph_profile_codex_rc1(cbm_graph_tier_t tier);
60+
5761
/* Vibe stores the behavioral prompt separately from its TOML agent definition.
5862
* Other integrations may also use this as the canonical contract text. */
5963
char *cbm_render_graph_prompt(cbm_graph_tier_t tier, cbm_graph_access_t access);

‎src/cli/cli.c‎

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7394,17 +7394,25 @@ static void install_tiered_agent_profiles(cbm_tiered_profile_set_t profiles, boo
73947394
access == CBM_GRAPH_ACCESS_DIRECT ? CBM_GRAPH_ACCESS_HANDOFF : CBM_GRAPH_ACCESS_DIRECT;
73957395
char *alternate = cbm_render_graph_profile(profiles.dialect, tier, alternate_access,
73967396
profiles.binary_path);
7397-
const char *released[2];
7397+
char *codex_rc1 =
7398+
profiles.dialect == CBM_GRAPH_DIALECT_CODEX && access == CBM_GRAPH_ACCESS_DIRECT
7399+
? cbm_render_graph_profile_codex_rc1(tier)
7400+
: NULL;
7401+
const char *released[3];
73987402
size_t released_count = 0U;
73997403
if (alternate) {
74007404
released[released_count++] = alternate;
74017405
}
7406+
if (codex_rc1) {
7407+
released[released_count++] = codex_rc1;
7408+
}
74027409
if (tier == CBM_GRAPH_TIER_VERIFY && profiles.legacy_verify_content) {
74037410
released[released_count++] = profiles.legacy_verify_content;
74047411
}
74057412
int result = prepare_config_parent(path)
74067413
? cbm_text_migrate_owned_document(path, current, released, released_count)
74077414
: CLI_ERR;
7415+
free(codex_rc1);
74087416
free(alternate);
74097417
free(current);
74107418
if (result != CLI_OK) {
@@ -7441,15 +7449,23 @@ static void uninstall_tiered_agent_profiles(cbm_tiered_profile_set_t profiles, b
74417449
access == CBM_GRAPH_ACCESS_DIRECT ? CBM_GRAPH_ACCESS_HANDOFF : CBM_GRAPH_ACCESS_DIRECT;
74427450
char *alternate = cbm_render_graph_profile(profiles.dialect, tier, alternate_access,
74437451
profiles.binary_path);
7444-
const char *released[2];
7452+
char *codex_rc1 =
7453+
profiles.dialect == CBM_GRAPH_DIALECT_CODEX && access == CBM_GRAPH_ACCESS_DIRECT
7454+
? cbm_render_graph_profile_codex_rc1(tier)
7455+
: NULL;
7456+
const char *released[3];
74457457
size_t released_count = 0U;
74467458
if (alternate) {
74477459
released[released_count++] = alternate;
74487460
}
7461+
if (codex_rc1) {
7462+
released[released_count++] = codex_rc1;
7463+
}
74497464
if (tier == CBM_GRAPH_TIER_VERIFY && profiles.legacy_verify_content) {
74507465
released[released_count++] = profiles.legacy_verify_content;
74517466
}
74527467
int result = cbm_text_remove_owned_document_any(path, current, released, released_count);
7468+
free(codex_rc1);
74537469
free(alternate);
74547470
free(current);
74557471
if (result < CLI_OK) {
@@ -10123,18 +10139,21 @@ static void uninstall_cli_agents(const cbm_detected_agents_t *agents, const char
1012310139
char ip[CLI_BUF_1K];
1012410140
char skills_dir[CLI_BUF_1K];
1012510141
char ap[CLI_BUF_1K];
10142+
char installed_binary[CLI_BUF_1K];
1012610143
cbm_codex_config_dir(home, config_dir, sizeof(config_dir));
1012710144
snprintf(cp, sizeof(cp), "%s/config.toml", config_dir);
1012810145
snprintf(ip, sizeof(ip), "%s/AGENTS.md", config_dir);
1012910146
snprintf(skills_dir, sizeof(skills_dir), "%s/skills", config_dir);
1013010147
snprintf(ap, sizeof(ap), "%s/agents/codebase-memory.toml", config_dir);
10148+
cbm_agent_installed_binary_path(home, installed_binary, sizeof(installed_binary));
1013110149
uninstall_agent_mcp_instr((mcp_uninstall_args_t){"Codex CLI", cp, ip}, dry_run,
1013210150
cbm_remove_codex_mcp_owned);
1013310151
uninstall_agent_skill("Codex CLI", skills_dir, dry_run);
1013410152
uninstall_tiered_agent_profiles(
1013510153
(cbm_tiered_profile_set_t){
1013610154
.label = "Codex CLI",
1013710155
.verify_path = ap,
10156+
.binary_path = installed_binary,
1013810157
.legacy_verify_content = legacy_codex_verify_agent_content,
1013910158
.dialect = CBM_GRAPH_DIALECT_CODEX,
1014010159
},
@@ -10144,10 +10163,8 @@ static void uninstall_cli_agents(const cbm_detected_agents_t *agents, const char
1014410163
record_agent_config_error(true, "Codex CLI", "hook_uninstall", cp);
1014510164
}
1014610165
char hooks_json[CLI_BUF_1K];
10147-
char installed_binary[CLI_BUF_1K];
1014810166
char hook_command[CLI_BUF_8K];
1014910167
snprintf(hooks_json, sizeof(hooks_json), "%s/hooks.json", config_dir);
10150-
cbm_agent_installed_binary_path(home, installed_binary, sizeof(installed_binary));
1015110168
if (cbm_file_exists(hooks_json) &&
1015210169
(cbm_build_augment_command(installed_binary, hook_command, sizeof(hook_command)) !=
1015310170
CLI_OK ||

‎tests/test_agent_profiles.c‎

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,10 @@ TEST(agent_profiles_direct_dialects_are_coverage_aware_and_read_only) {
7878
for (size_t i = 0U; i < sizeof(direct_dialects) / sizeof(direct_dialects[0]); i++) {
7979
const direct_dialect_expectation_t *expectation = &direct_dialects[i];
8080
for (int tier = 0; tier < (int)CBM_GRAPH_TIER_COUNT; tier++) {
81-
const char *binary =
82-
expectation->dialect == CBM_GRAPH_DIALECT_KIRO ? "/opt/codebase memory/cbm" : NULL;
81+
const char *binary = expectation->dialect == CBM_GRAPH_DIALECT_KIRO ||
82+
expectation->dialect == CBM_GRAPH_DIALECT_CODEX
83+
? "/opt/codebase memory/cbm"
84+
: NULL;
8385
char *profile = cbm_render_graph_profile(expectation->dialect, (cbm_graph_tier_t)tier,
8486
CBM_GRAPH_ACCESS_DIRECT, binary);
8587
if (!profile) {
@@ -225,6 +227,40 @@ TEST(agent_profiles_kiro_is_valid_json_and_escapes_binary_path) {
225227
PASS();
226228
}
227229

230+
TEST(agent_profiles_codex_declares_transport_and_escapes_binary_path) {
231+
const char *binary = "C:\\cbm bin\\codebase-memory-mcp.exe";
232+
char *scout = cbm_render_graph_profile(CBM_GRAPH_DIALECT_CODEX, CBM_GRAPH_TIER_SCOUT,
233+
CBM_GRAPH_ACCESS_DIRECT, binary);
234+
char *verify = cbm_render_graph_profile(CBM_GRAPH_DIALECT_CODEX, CBM_GRAPH_TIER_VERIFY,
235+
CBM_GRAPH_ACCESS_DIRECT, binary);
236+
ASSERT_NOT_NULL(scout);
237+
ASSERT_NOT_NULL(verify);
238+
int valid = strstr(scout, "[mcp_servers.codebase-memory-mcp]\n"
239+
"command = \"C:\\\\cbm bin\\\\codebase-memory-mcp.exe\"\n"
240+
"args = [\"--tool-profile=scout\"]\n"
241+
"enabled_tools = [") != NULL &&
242+
strstr(verify, "command = \"C:\\\\cbm bin\\\\codebase-memory-mcp.exe\"\n"
243+
"args = [\"--tool-profile=analysis\"]\n") != NULL;
244+
free(scout);
245+
free(verify);
246+
ASSERT_TRUE(valid);
247+
ASSERT_NULL(cbm_render_graph_profile(CBM_GRAPH_DIALECT_CODEX, CBM_GRAPH_TIER_VERIFY,
248+
CBM_GRAPH_ACCESS_DIRECT, NULL));
249+
ASSERT_NULL(cbm_render_graph_profile(CBM_GRAPH_DIALECT_CODEX, CBM_GRAPH_TIER_VERIFY,
250+
CBM_GRAPH_ACCESS_DIRECT, ""));
251+
char *handoff = cbm_render_graph_profile(CBM_GRAPH_DIALECT_CODEX, CBM_GRAPH_TIER_VERIFY,
252+
CBM_GRAPH_ACCESS_HANDOFF, NULL);
253+
ASSERT_NOT_NULL(handoff);
254+
ASSERT_TRUE(strstr(handoff, "[mcp_servers.") == NULL);
255+
free(handoff);
256+
char *rc1 = cbm_render_graph_profile_codex_rc1(CBM_GRAPH_TIER_VERIFY);
257+
ASSERT_NOT_NULL(rc1);
258+
ASSERT_TRUE(strstr(rc1, "[mcp_servers.codebase-memory-mcp]\nenabled_tools = [") != NULL);
259+
ASSERT_TRUE(strstr(rc1, "command = ") == NULL);
260+
free(rc1);
261+
PASS();
262+
}
263+
228264
TEST(agent_profiles_vibe_uses_matching_prompt_identifier_and_contract) {
229265
for (int tier = 0; tier < (int)CBM_GRAPH_TIER_COUNT; tier++) {
230266
const char *slug = cbm_graph_tier_slug((cbm_graph_tier_t)tier);
@@ -274,6 +310,7 @@ SUITE(agent_profiles) {
274310
RUN_TEST(agent_profiles_handoff_only_dialects_fail_closed_for_direct_access);
275311
RUN_TEST(agent_profiles_server_level_dialects_hard_enforce_read_only_tools);
276312
RUN_TEST(agent_profiles_kiro_is_valid_json_and_escapes_binary_path);
313+
RUN_TEST(agent_profiles_codex_declares_transport_and_escapes_binary_path);
277314
RUN_TEST(agent_profiles_vibe_uses_matching_prompt_identifier_and_contract);
278315
RUN_TEST(agent_profiles_render_deterministically_and_reject_invalid_inputs);
279316
}

‎tests/test_cli.c‎

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "../src/foundation/compat_thread.h"
1414
#include "test_framework.h"
1515
#include "test_helpers.h"
16+
#include <cli/agent_profiles.h>
1617
#include <cli/cli.h>
1718
#include <cli/progress_sink.h>
1819
#include <daemon/bootstrap.h>
@@ -4894,11 +4895,15 @@ TEST(cli_durable_profiles_follow_current_vendor_paths) {
48944895
files_ok = files_ok && test_file_contains_all(path, claude_terms, 7U);
48954896

48964897
snprintf(path, sizeof(path), "%s/agents/codebase-memory.toml", codex_home);
4897-
const char *const codex_terms[] = {
4898-
"name = \"codebase-memory\"", "description = ",
4899-
"developer_instructions = ", "sandbox_mode = \"read-only\"",
4900-
"[mcp_servers.codebase-memory-mcp]", "check_index_coverage"};
4901-
files_ok = files_ok && test_file_contains_all(path, codex_terms, 6U);
4898+
const char *const codex_terms[] = {"name = \"codebase-memory\"",
4899+
"description = ",
4900+
"developer_instructions = ",
4901+
"sandbox_mode = \"read-only\"",
4902+
"[mcp_servers.codebase-memory-mcp]",
4903+
"command = \"/opt/codebase-memory-mcp\"",
4904+
"args = [\"--tool-profile=analysis\"]",
4905+
"check_index_coverage"};
4906+
files_ok = files_ok && test_file_contains_all(path, codex_terms, 8U);
49024907
char *profile = read_test_file_alloc(path);
49034908
files_ok = files_ok && profile && !strstr(profile, "model =") &&
49044909
!strstr(profile, "index_repository") && !strstr(profile, "delete_project") &&
@@ -5371,25 +5376,45 @@ TEST(cli_tiered_codex_profiles_migrate_preserve_and_uninstall) {
53715376
"name = \"codebase-memory-scout\"\nuser_note = \"preserve scout\"\n";
53725377
write_test_file(verify_path, legacy_verify);
53735378
write_test_file(scout_path, foreign_scout);
5374-
5375-
char *plan = cbm_build_install_plan_json(tmpdir, "/opt/codebase-memory-mcp");
5379+
char *rc1_auditor = cbm_render_graph_profile_codex_rc1(CBM_GRAPH_TIER_AUDIT);
5380+
if (!rc1_auditor)
5381+
FAIL("rc.1 auditor rendering must be available");
5382+
write_test_file(auditor_path, rc1_auditor);
5383+
free(rc1_auditor);
5384+
5385+
/* Uninstall renders expected content with the installed binary path, so
5386+
* install with that same path to exercise exact-content removal. */
5387+
char installed_binary[640];
5388+
char expected_command[768];
5389+
#ifdef _WIN32
5390+
snprintf(installed_binary, sizeof(installed_binary), "%s/.local/bin/codebase-memory-mcp.exe",
5391+
tmpdir);
5392+
#else
5393+
snprintf(installed_binary, sizeof(installed_binary), "%s/.local/bin/codebase-memory-mcp",
5394+
tmpdir);
5395+
#endif
5396+
snprintf(expected_command, sizeof(expected_command), "command = \"%s\"", installed_binary);
5397+
char *plan = cbm_build_install_plan_json(tmpdir, installed_binary);
53765398
bool plan_ok =
53775399
plan && strstr(plan, scout_path) && strstr(plan, verify_path) && strstr(plan, auditor_path);
53785400
free(plan);
53795401

5380-
int install_rc = cbm_install_agent_configs(tmpdir, "/opt/codebase-memory-mcp", false, false);
5402+
int install_rc = cbm_install_agent_configs(tmpdir, installed_binary, false, false);
53815403
char *scout = read_test_file_alloc(scout_path);
53825404
char *verify = read_test_file_alloc(verify_path);
53835405
char *auditor = read_test_file_alloc(auditor_path);
5384-
bool installed = install_rc != 0 && scout && strcmp(scout, foreign_scout) == 0 && verify &&
5385-
strcmp(verify, legacy_verify) != 0 && strstr(verify, "Tier 2") &&
5386-
strstr(verify, "name = \"codebase-memory\"") &&
5387-
strstr(verify, "check_index_coverage") && auditor &&
5388-
strstr(auditor, "Tier 3") && strstr(auditor, "check_index_coverage") &&
5389-
!strstr(verify, "index_repository") && !strstr(verify, "delete_project") &&
5390-
!strstr(verify, "manage_adr") && !strstr(verify, "ingest_traces") &&
5391-
!strstr(auditor, "index_repository") && !strstr(auditor, "delete_project") &&
5392-
!strstr(auditor, "manage_adr") && !strstr(auditor, "ingest_traces");
5406+
bool installed =
5407+
install_rc != 0 && scout && strcmp(scout, foreign_scout) == 0 && verify &&
5408+
strcmp(verify, legacy_verify) != 0 && strstr(verify, "Tier 2") &&
5409+
strstr(verify, "name = \"codebase-memory\"") && strstr(verify, expected_command) &&
5410+
strstr(verify, "args = [\"--tool-profile=analysis\"]") &&
5411+
strstr(verify, "check_index_coverage") && auditor && strstr(auditor, expected_command) &&
5412+
strstr(auditor, "args = [\"--tool-profile=analysis\"]") && strstr(auditor, "Tier 3") &&
5413+
strstr(auditor, "check_index_coverage") && !strstr(verify, "index_repository") &&
5414+
!strstr(verify, "delete_project") && !strstr(verify, "manage_adr") &&
5415+
!strstr(verify, "ingest_traces") && !strstr(auditor, "index_repository") &&
5416+
!strstr(auditor, "delete_project") && !strstr(auditor, "manage_adr") &&
5417+
!strstr(auditor, "ingest_traces");
53935418
free(scout);
53945419
free(verify);
53955420
free(auditor);

0 commit comments

Comments
 (0)