agentopology 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +450 -0
  3. package/dist/analyzer/__tests__/analyzer.test.d.ts +2 -0
  4. package/dist/analyzer/__tests__/analyzer.test.d.ts.map +1 -0
  5. package/dist/analyzer/__tests__/analyzer.test.js +258 -0
  6. package/dist/analyzer/__tests__/analyzer.test.js.map +1 -0
  7. package/dist/analyzer/index.d.ts +44 -0
  8. package/dist/analyzer/index.d.ts.map +1 -0
  9. package/dist/analyzer/index.js +308 -0
  10. package/dist/analyzer/index.js.map +1 -0
  11. package/dist/bindings/__tests__/bindings.test.d.ts +2 -0
  12. package/dist/bindings/__tests__/bindings.test.d.ts.map +1 -0
  13. package/dist/bindings/__tests__/bindings.test.js +2089 -0
  14. package/dist/bindings/__tests__/bindings.test.js.map +1 -0
  15. package/dist/bindings/claude-code.d.ts +12 -0
  16. package/dist/bindings/claude-code.d.ts.map +1 -0
  17. package/dist/bindings/claude-code.js +2063 -0
  18. package/dist/bindings/claude-code.js.map +1 -0
  19. package/dist/bindings/codex.d.ts +18 -0
  20. package/dist/bindings/codex.d.ts.map +1 -0
  21. package/dist/bindings/codex.js +1633 -0
  22. package/dist/bindings/codex.js.map +1 -0
  23. package/dist/bindings/copilot-cli.d.ts +20 -0
  24. package/dist/bindings/copilot-cli.d.ts.map +1 -0
  25. package/dist/bindings/copilot-cli.js +1515 -0
  26. package/dist/bindings/copilot-cli.js.map +1 -0
  27. package/dist/bindings/cursor.d.ts +17 -0
  28. package/dist/bindings/cursor.d.ts.map +1 -0
  29. package/dist/bindings/cursor.js +1715 -0
  30. package/dist/bindings/cursor.js.map +1 -0
  31. package/dist/bindings/gemini-cli.d.ts +18 -0
  32. package/dist/bindings/gemini-cli.d.ts.map +1 -0
  33. package/dist/bindings/gemini-cli.js +1327 -0
  34. package/dist/bindings/gemini-cli.js.map +1 -0
  35. package/dist/bindings/index.d.ts +20 -0
  36. package/dist/bindings/index.d.ts.map +1 -0
  37. package/dist/bindings/index.js +33 -0
  38. package/dist/bindings/index.js.map +1 -0
  39. package/dist/bindings/kiro.d.ts +17 -0
  40. package/dist/bindings/kiro.d.ts.map +1 -0
  41. package/dist/bindings/kiro.js +1624 -0
  42. package/dist/bindings/kiro.js.map +1 -0
  43. package/dist/bindings/openclaw.d.ts +18 -0
  44. package/dist/bindings/openclaw.d.ts.map +1 -0
  45. package/dist/bindings/openclaw.js +1578 -0
  46. package/dist/bindings/openclaw.js.map +1 -0
  47. package/dist/bindings/types.d.ts +33 -0
  48. package/dist/bindings/types.d.ts.map +1 -0
  49. package/dist/bindings/types.js +23 -0
  50. package/dist/bindings/types.js.map +1 -0
  51. package/dist/cli/index.d.ts +19 -0
  52. package/dist/cli/index.d.ts.map +1 -0
  53. package/dist/cli/index.js +679 -0
  54. package/dist/cli/index.js.map +1 -0
  55. package/dist/docs/__tests__/docs.test.d.ts +2 -0
  56. package/dist/docs/__tests__/docs.test.d.ts.map +1 -0
  57. package/dist/docs/__tests__/docs.test.js +438 -0
  58. package/dist/docs/__tests__/docs.test.js.map +1 -0
  59. package/dist/docs/content.d.ts +16 -0
  60. package/dist/docs/content.d.ts.map +1 -0
  61. package/dist/docs/content.js +3273 -0
  62. package/dist/docs/content.js.map +1 -0
  63. package/dist/docs/index.d.ts +17 -0
  64. package/dist/docs/index.d.ts.map +1 -0
  65. package/dist/docs/index.js +117 -0
  66. package/dist/docs/index.js.map +1 -0
  67. package/dist/exporters/__tests__/exporters.test.d.ts +2 -0
  68. package/dist/exporters/__tests__/exporters.test.d.ts.map +1 -0
  69. package/dist/exporters/__tests__/exporters.test.js +731 -0
  70. package/dist/exporters/__tests__/exporters.test.js.map +1 -0
  71. package/dist/exporters/index.d.ts +13 -0
  72. package/dist/exporters/index.d.ts.map +1 -0
  73. package/dist/exporters/index.js +18 -0
  74. package/dist/exporters/index.js.map +1 -0
  75. package/dist/exporters/json.d.ts +8 -0
  76. package/dist/exporters/json.d.ts.map +1 -0
  77. package/dist/exporters/json.js +25 -0
  78. package/dist/exporters/json.js.map +1 -0
  79. package/dist/exporters/markdown.d.ts +9 -0
  80. package/dist/exporters/markdown.d.ts.map +1 -0
  81. package/dist/exporters/markdown.js +686 -0
  82. package/dist/exporters/markdown.js.map +1 -0
  83. package/dist/exporters/mermaid.d.ts +11 -0
  84. package/dist/exporters/mermaid.d.ts.map +1 -0
  85. package/dist/exporters/mermaid.js +161 -0
  86. package/dist/exporters/mermaid.js.map +1 -0
  87. package/dist/exporters/types.d.ts +22 -0
  88. package/dist/exporters/types.d.ts.map +1 -0
  89. package/dist/exporters/types.js +10 -0
  90. package/dist/exporters/types.js.map +1 -0
  91. package/dist/import/__tests__/import.test.d.ts +2 -0
  92. package/dist/import/__tests__/import.test.d.ts.map +1 -0
  93. package/dist/import/__tests__/import.test.js +781 -0
  94. package/dist/import/__tests__/import.test.js.map +1 -0
  95. package/dist/import/claude-code.d.ts +38 -0
  96. package/dist/import/claude-code.d.ts.map +1 -0
  97. package/dist/import/claude-code.js +897 -0
  98. package/dist/import/claude-code.js.map +1 -0
  99. package/dist/import/index.d.ts +18 -0
  100. package/dist/import/index.d.ts.map +1 -0
  101. package/dist/import/index.js +28 -0
  102. package/dist/import/index.js.map +1 -0
  103. package/dist/import/serializer.d.ts +11 -0
  104. package/dist/import/serializer.d.ts.map +1 -0
  105. package/dist/import/serializer.js +960 -0
  106. package/dist/import/serializer.js.map +1 -0
  107. package/dist/index.d.ts +26 -0
  108. package/dist/index.d.ts.map +1 -0
  109. package/dist/index.js +21 -0
  110. package/dist/index.js.map +1 -0
  111. package/dist/parser/__tests__/parser.test.d.ts +2 -0
  112. package/dist/parser/__tests__/parser.test.d.ts.map +1 -0
  113. package/dist/parser/__tests__/parser.test.js +6514 -0
  114. package/dist/parser/__tests__/parser.test.js.map +1 -0
  115. package/dist/parser/ast.d.ts +708 -0
  116. package/dist/parser/ast.d.ts.map +1 -0
  117. package/dist/parser/ast.js +12 -0
  118. package/dist/parser/ast.js.map +1 -0
  119. package/dist/parser/index.d.ts +253 -0
  120. package/dist/parser/index.d.ts.map +1 -0
  121. package/dist/parser/index.js +1985 -0
  122. package/dist/parser/index.js.map +1 -0
  123. package/dist/parser/lexer.d.ts +100 -0
  124. package/dist/parser/lexer.d.ts.map +1 -0
  125. package/dist/parser/lexer.js +304 -0
  126. package/dist/parser/lexer.js.map +1 -0
  127. package/dist/parser/validator.d.ts +42 -0
  128. package/dist/parser/validator.d.ts.map +1 -0
  129. package/dist/parser/validator.js +2568 -0
  130. package/dist/parser/validator.js.map +1 -0
  131. package/dist/scaffold/__tests__/scaffold.test.d.ts +7 -0
  132. package/dist/scaffold/__tests__/scaffold.test.d.ts.map +1 -0
  133. package/dist/scaffold/__tests__/scaffold.test.js +295 -0
  134. package/dist/scaffold/__tests__/scaffold.test.js.map +1 -0
  135. package/dist/scaffold/incremental.d.ts +43 -0
  136. package/dist/scaffold/incremental.d.ts.map +1 -0
  137. package/dist/scaffold/incremental.js +185 -0
  138. package/dist/scaffold/incremental.js.map +1 -0
  139. package/dist/scaffold/index.d.ts +13 -0
  140. package/dist/scaffold/index.d.ts.map +1 -0
  141. package/dist/scaffold/index.js +13 -0
  142. package/dist/scaffold/index.js.map +1 -0
  143. package/dist/scaffold/manifest.d.ts +30 -0
  144. package/dist/scaffold/manifest.d.ts.map +1 -0
  145. package/dist/scaffold/manifest.js +71 -0
  146. package/dist/scaffold/manifest.js.map +1 -0
  147. package/dist/scaffold/merge.d.ts +52 -0
  148. package/dist/scaffold/merge.d.ts.map +1 -0
  149. package/dist/scaffold/merge.js +134 -0
  150. package/dist/scaffold/merge.js.map +1 -0
  151. package/dist/scaffold/types.d.ts +51 -0
  152. package/dist/scaffold/types.d.ts.map +1 -0
  153. package/dist/scaffold/types.js +10 -0
  154. package/dist/scaffold/types.js.map +1 -0
  155. package/dist/sync/index.d.ts +34 -0
  156. package/dist/sync/index.d.ts.map +1 -0
  157. package/dist/sync/index.js +392 -0
  158. package/dist/sync/index.js.map +1 -0
  159. package/dist/visualizer/index.d.ts +17 -0
  160. package/dist/visualizer/index.d.ts.map +1 -0
  161. package/dist/visualizer/index.js +1976 -0
  162. package/dist/visualizer/index.js.map +1 -0
  163. package/examples/code-review.at +167 -0
  164. package/examples/data-processing.at +296 -0
  165. package/examples/openclaw-assistant.at +142 -0
  166. package/examples/scheduled-monitor.at +193 -0
  167. package/examples/simple-pipeline.at +163 -0
  168. package/package.json +77 -0
  169. package/skill/SKILL.md +403 -0
  170. package/skill/scripts/emit.ts +853 -0
  171. package/skill/scripts/visualize.ts +89 -0
  172. package/spec/grammar.md +1466 -0
  173. package/spec/reserved-keywords.md +147 -0
  174. package/spec/validation.md +535 -0
@@ -0,0 +1,1633 @@
1
+ /**
2
+ * OpenAI Codex CLI binding.
3
+ *
4
+ * Generates `.codex/config.toml`, `AGENTS.md`, and supporting scripts
5
+ * from a parsed {@link TopologyAST}.
6
+ *
7
+ * Codex CLI uses TOML-based configuration and reads markdown instruction
8
+ * files. Multi-agent topologies generate per-agent TOML files under
9
+ * `.codex/agents/<name>.toml` and an `[agents]` section in config.toml.
10
+ * AGENTS.md provides a high-level topology overview with references to
11
+ * individual agent TOML files.
12
+ *
13
+ * @module
14
+ */
15
+ import { deduplicateFiles } from "./types.js";
16
+ // ---------------------------------------------------------------------------
17
+ // Helpers
18
+ // ---------------------------------------------------------------------------
19
+ /** Convert a kebab-case id to Title Case. */
20
+ function toTitle(id) {
21
+ return id
22
+ .split(/[-_]/)
23
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
24
+ .join(" ");
25
+ }
26
+ /**
27
+ * Map topology permission strings to Codex approval_policy values.
28
+ *
29
+ * Codex CLI supports these approval policies:
30
+ * - "untrusted" — only run trusted commands without asking (default, most restrictive)
31
+ * - "on-request" — model decides when to ask the user
32
+ * - "never" — never ask for user approval
33
+ *
34
+ * Legacy/deprecated:
35
+ * - "on-failure" — deprecated, prefer "on-request" for interactive or "never" for non-interactive
36
+ */
37
+ function mapApprovalPolicy(perm) {
38
+ // Codex-native values — pass through
39
+ const nativeValues = ["untrusted", "on-request", "on-failure", "never"];
40
+ if (nativeValues.includes(perm))
41
+ return perm;
42
+ // Topology-level semantic values
43
+ switch (perm) {
44
+ case "supervised":
45
+ return "untrusted";
46
+ case "autonomous":
47
+ return "on-request";
48
+ case "interactive":
49
+ return "untrusted";
50
+ case "unrestricted":
51
+ return "never";
52
+ // Legacy Codex values — map to current equivalents
53
+ case "suggest":
54
+ return "untrusted";
55
+ case "auto-edit":
56
+ return "on-request";
57
+ case "full-auto":
58
+ return "never";
59
+ // Claude Code native values mapped to Codex equivalents
60
+ case "plan":
61
+ return "untrusted";
62
+ case "auto":
63
+ return "on-request";
64
+ case "confirm":
65
+ return "untrusted";
66
+ case "bypassPermissions":
67
+ return "never";
68
+ default:
69
+ return "untrusted"; // safe default
70
+ }
71
+ }
72
+ /**
73
+ * Escape a TOML string value.
74
+ * Wraps in double quotes, escaping internal quotes and backslashes.
75
+ */
76
+ function tomlString(value) {
77
+ const escaped = value
78
+ .replace(/\\/g, "\\\\")
79
+ .replace(/"/g, '\\"')
80
+ .replace(/\n/g, "\\n");
81
+ return `"${escaped}"`;
82
+ }
83
+ /** Format a TOML array of strings on a single line. */
84
+ function tomlStringArray(values) {
85
+ return `[${values.map(tomlString).join(", ")}]`;
86
+ }
87
+ /** Generate a shell script stub with a header comment. */
88
+ function shellStub(description) {
89
+ return [
90
+ "#!/usr/bin/env bash",
91
+ `# ${description}`,
92
+ "# Auto-generated by agentopology scaffold — edit as needed.",
93
+ "set -euo pipefail",
94
+ "",
95
+ 'echo "TODO: implement this script"',
96
+ "",
97
+ ].join("\n");
98
+ }
99
+ /** Generate an empty directory marker (a .gitkeep file). */
100
+ function gitkeep(dirPath) {
101
+ return { path: `${dirPath}/.gitkeep`, content: "", category: "machine" };
102
+ }
103
+ /** Format a SchemaType to a human-readable string. */
104
+ function formatSchemaType(t) {
105
+ switch (t.kind) {
106
+ case "primitive":
107
+ return t.value;
108
+ case "array":
109
+ return `${formatSchemaType(t.itemType)}[]`;
110
+ case "enum":
111
+ return t.values.join(" | ");
112
+ case "ref":
113
+ return t.name;
114
+ }
115
+ }
116
+ /** Format a SchemaFieldDef to a readable line. */
117
+ function formatSchemaField(f) {
118
+ const opt = f.optional ? "?" : "";
119
+ return `- ${f.name}${opt}: ${formatSchemaType(f.type)}`;
120
+ }
121
+ /** Format a RetryConfig to a readable string. */
122
+ function formatRetryConfig(retry) {
123
+ if (typeof retry === "number")
124
+ return String(retry);
125
+ const parts = [`max ${retry.max}`];
126
+ if (retry.backoff)
127
+ parts.push(`backoff ${retry.backoff}`);
128
+ if (retry.interval)
129
+ parts.push(`interval ${retry.interval}`);
130
+ if (retry.maxInterval)
131
+ parts.push(`max-interval ${retry.maxInterval}`);
132
+ if (retry.jitter)
133
+ parts.push("jitter");
134
+ if (retry.nonRetryable && retry.nonRetryable.length > 0) {
135
+ parts.push(`non-retryable: ${retry.nonRetryable.join(", ")}`);
136
+ }
137
+ return parts.join(", ");
138
+ }
139
+ // ---------------------------------------------------------------------------
140
+ // TOML generation — .codex/config.toml
141
+ // ---------------------------------------------------------------------------
142
+ /**
143
+ * Generate the main `.codex/config.toml` configuration file.
144
+ *
145
+ * Maps topology metadata, the most permissive agent's approval policy,
146
+ * model, sandbox, and provider settings. Extension fields from
147
+ * `extensions["codex"]` on the topology or agents are merged in.
148
+ */
149
+ function generateCodexToml(ast) {
150
+ const lines = [];
151
+ lines.push("# Codex CLI configuration");
152
+ lines.push("# Auto-generated by agentopology scaffold — edit as needed.");
153
+ lines.push("");
154
+ // Determine the primary model and approval policy from agents.
155
+ // Use the first orchestrator or first agent's model, falling back to gpt-4o.
156
+ const orchestrator = ast.nodes.find((n) => n.type === "orchestrator");
157
+ const agents = ast.nodes.filter((n) => n.type === "agent");
158
+ const primaryModel = orchestrator?.model ?? agents[0]?.model ?? "o4-mini";
159
+ const primaryPermission = agents[0]?.permissions ?? "supervised";
160
+ lines.push(`model = ${tomlString(primaryModel)}`);
161
+ lines.push(`approval_policy = ${tomlString(mapApprovalPolicy(primaryPermission))}`);
162
+ // Disable response storage if env has a flag or topology explicitly sets it
163
+ const codexTopologyExt = getTopologyExtensions(ast);
164
+ if (codexTopologyExt?.disable_response_storage != null) {
165
+ lines.push(`disable_response_storage = ${codexTopologyExt.disable_response_storage === true}`);
166
+ }
167
+ lines.push("");
168
+ // Model params from defaults or primary agent
169
+ const temp = ast.defaults?.temperature ?? agents[0]?.temperature;
170
+ const maxTok = ast.defaults?.maxTokens ?? agents[0]?.maxTokens;
171
+ if (temp != null || maxTok != null) {
172
+ lines.push("# Model parameters");
173
+ if (temp != null)
174
+ lines.push(`# temperature = ${temp}`);
175
+ if (maxTok != null)
176
+ lines.push(`# max_tokens = ${maxTok}`);
177
+ lines.push("");
178
+ }
179
+ // History settings from extensions
180
+ if (codexTopologyExt?.history) {
181
+ const history = codexTopologyExt.history;
182
+ lines.push("[history]");
183
+ if (history.max_conversations != null) {
184
+ lines.push(`max_conversations = ${history.max_conversations}`);
185
+ }
186
+ if (history.persistence != null) {
187
+ lines.push(`persistence = ${tomlString(String(history.persistence))}`);
188
+ }
189
+ lines.push("");
190
+ }
191
+ // Sandbox settings — first-class AST field, then extension, then derived
192
+ // Codex supports: "read-only", "workspace-write", "danger-full-access"
193
+ const topSandbox = ast.settings?.sandbox;
194
+ const sandboxType = topSandbox != null
195
+ ? (typeof topSandbox === 'boolean' ? (topSandbox ? 'read-only' : 'danger-full-access') : mapSandboxMode(String(topSandbox)))
196
+ : codexTopologyExt?.sandbox_type ?? deriveSandboxType(ast);
197
+ if (sandboxType) {
198
+ lines.push(`sandbox_mode = ${tomlString(String(sandboxType))}`);
199
+ lines.push("");
200
+ }
201
+ // Providers from extensions or ast.providers
202
+ const extProviders = codexTopologyExt?.providers;
203
+ if (extProviders && extProviders.length > 0) {
204
+ // Extensions path takes priority
205
+ for (const provider of extProviders) {
206
+ lines.push("[[providers]]");
207
+ if (provider.name)
208
+ lines.push(`name = ${tomlString(String(provider.name))}`);
209
+ if (provider.base_url)
210
+ lines.push(`base_url = ${tomlString(String(provider.base_url))}`);
211
+ if (provider.env_key)
212
+ lines.push(`env_key = ${tomlString(String(provider.env_key))}`);
213
+ lines.push("");
214
+ }
215
+ }
216
+ else if (ast.providers && ast.providers.length > 0) {
217
+ // Fallback: map ast.providers to Codex [[providers]] format
218
+ for (const p of ast.providers) {
219
+ lines.push("[[providers]]");
220
+ lines.push(`name = ${tomlString(p.name)}`);
221
+ if (p.baseUrl)
222
+ lines.push(`base_url = ${tomlString(p.baseUrl)}`);
223
+ if (p.apiKey) {
224
+ // Convert ${ENV_VAR} to just ENV_VAR for Codex env_key format
225
+ const envKey = p.apiKey.replace(/^\$\{(.+)\}$/, "$1");
226
+ lines.push(`env_key = ${tomlString(envKey)}`);
227
+ }
228
+ if (p.default)
229
+ lines.push(`default = true`);
230
+ if (p.models && p.models.length > 0) {
231
+ lines.push(`models = ${tomlStringArray(p.models)}`);
232
+ }
233
+ if (p.auth) {
234
+ lines.push(`# Auth type: ${p.auth.type}`);
235
+ if (p.auth.issuer)
236
+ lines.push(`# Auth issuer: ${p.auth.issuer}`);
237
+ if (p.auth.audience)
238
+ lines.push(`# Auth audience: ${p.auth.audience}`);
239
+ if (p.auth.tokenUrl)
240
+ lines.push(`# Auth token URL: ${p.auth.tokenUrl}`);
241
+ if (p.auth.clientId)
242
+ lines.push(`# Auth client ID: ${p.auth.clientId}`);
243
+ if (p.auth.scopes && p.auth.scopes.length > 0) {
244
+ lines.push(`# Auth scopes: ${p.auth.scopes.join(", ")}`);
245
+ }
246
+ }
247
+ if (p.extra) {
248
+ for (const [ek, ev] of Object.entries(p.extra)) {
249
+ if (typeof ev === "string") {
250
+ lines.push(`${ek} = ${tomlString(ev)}`);
251
+ }
252
+ else if (typeof ev === "boolean") {
253
+ lines.push(`${ek} = ${ev}`);
254
+ }
255
+ else if (typeof ev === "number") {
256
+ lines.push(`${ek} = ${ev}`);
257
+ }
258
+ }
259
+ }
260
+ lines.push("");
261
+ }
262
+ }
263
+ // MCP servers from ast.mcpServers — render as [mcp_servers.<name>] tables
264
+ if (ast.mcpServers && Object.keys(ast.mcpServers).length > 0) {
265
+ for (const [name, config] of Object.entries(ast.mcpServers)) {
266
+ lines.push(`[mcp_servers.${name}]`);
267
+ for (const [ck, cv] of Object.entries(config)) {
268
+ if (typeof cv === "string") {
269
+ lines.push(`${ck} = ${tomlString(cv)}`);
270
+ }
271
+ else if (typeof cv === "boolean") {
272
+ lines.push(`${ck} = ${cv}`);
273
+ }
274
+ else if (typeof cv === "number") {
275
+ lines.push(`${ck} = ${cv}`);
276
+ }
277
+ else if (Array.isArray(cv)) {
278
+ lines.push(`${ck} = ${tomlStringArray(cv.map(String))}`);
279
+ }
280
+ }
281
+ lines.push("");
282
+ }
283
+ }
284
+ // Fallback chain — document if present (Codex doesn't natively support it)
285
+ const fallbackChain = ast.settings?.["fallback-chain"];
286
+ if (fallbackChain && fallbackChain.length > 0) {
287
+ lines.push(`# Model fallback chain: ${fallbackChain.join(" -> ")}`);
288
+ lines.push("");
289
+ }
290
+ // Merge any remaining extension fields as top-level TOML keys
291
+ if (codexTopologyExt) {
292
+ const handled = new Set([
293
+ "disable_response_storage",
294
+ "history",
295
+ "sandbox_type",
296
+ "providers",
297
+ ]);
298
+ for (const [key, value] of Object.entries(codexTopologyExt)) {
299
+ if (handled.has(key))
300
+ continue;
301
+ if (typeof value === "string") {
302
+ lines.push(`${key} = ${tomlString(value)}`);
303
+ }
304
+ else if (typeof value === "boolean") {
305
+ lines.push(`${key} = ${value}`);
306
+ }
307
+ else if (typeof value === "number") {
308
+ lines.push(`${key} = ${value}`);
309
+ }
310
+ }
311
+ }
312
+ // Multi-agent support: [agents] section with per-agent references
313
+ if (agents.length > 0) {
314
+ lines.push("");
315
+ lines.push("[agents]");
316
+ lines.push(`max_threads = ${Math.min(agents.length, 10)}`);
317
+ lines.push(`max_depth = 1`);
318
+ lines.push("");
319
+ for (const agent of agents) {
320
+ const desc = agent.description
321
+ ?? ast.roles[agent.role ?? ""]
322
+ ?? ast.roles[agent.id]
323
+ ?? agent.role
324
+ ?? toTitle(agent.id);
325
+ lines.push(`[agents.${agent.id}]`);
326
+ lines.push(`description = ${tomlString(desc)}`);
327
+ lines.push(`config_file = ${tomlString(`agents/${agent.id}.toml`)}`);
328
+ lines.push("");
329
+ }
330
+ }
331
+ return {
332
+ path: ".codex/config.toml",
333
+ content: lines.join("\n") + "\n",
334
+ category: "machine",
335
+ };
336
+ }
337
+ /**
338
+ * Map generic sandbox mode strings to Codex sandbox values.
339
+ * Codex supports: "read-only", "workspace-write", "danger-full-access"
340
+ */
341
+ function mapSandboxMode(mode) {
342
+ // Pass through native Codex values
343
+ const nativeValues = ["read-only", "workspace-write", "danger-full-access"];
344
+ if (nativeValues.includes(mode))
345
+ return mode;
346
+ switch (mode) {
347
+ case "docker":
348
+ case "container":
349
+ case "strict":
350
+ return "read-only";
351
+ case "write":
352
+ case "workspace":
353
+ return "workspace-write";
354
+ case "none":
355
+ case "disabled":
356
+ case "off":
357
+ return "danger-full-access";
358
+ default:
359
+ return "read-only"; // safe default
360
+ }
361
+ }
362
+ /**
363
+ * Map agent sandbox values to Codex sandbox_mode for per-agent TOML.
364
+ * true/"docker"/"strict" → "read-only", "write"/"workspace" → "workspace-write",
365
+ * false/"none" → "danger-full-access", "network-only" → "read-only"
366
+ */
367
+ function mapAgentSandbox(sandbox) {
368
+ if (typeof sandbox === "boolean") {
369
+ return sandbox ? "read-only" : "danger-full-access";
370
+ }
371
+ switch (sandbox) {
372
+ case "docker":
373
+ case "strict":
374
+ case "container":
375
+ case "network-only":
376
+ case "read-only":
377
+ return "read-only";
378
+ case "write":
379
+ case "workspace":
380
+ case "workspace-write":
381
+ return "workspace-write";
382
+ case "none":
383
+ case "disabled":
384
+ case "off":
385
+ case "danger-full-access":
386
+ return "danger-full-access";
387
+ default:
388
+ return "read-only"; // safe default
389
+ }
390
+ }
391
+ /**
392
+ * Map thinking/reasoning level to Codex model_reasoning_effort.
393
+ * off → "" (omit), low → "low", medium → "medium", high → "high", max → "high"
394
+ */
395
+ function mapReasoningEffort(thinking) {
396
+ switch (thinking) {
397
+ case "off":
398
+ return null; // omit from TOML
399
+ case "low":
400
+ return "low";
401
+ case "medium":
402
+ return "medium";
403
+ case "high":
404
+ return "high";
405
+ case "max":
406
+ return "high";
407
+ default:
408
+ return null;
409
+ }
410
+ }
411
+ /**
412
+ * Derive sandbox type from topology permissions.
413
+ * More restrictive permissions suggest sandboxing.
414
+ */
415
+ function deriveSandboxType(ast) {
416
+ const agents = ast.nodes.filter((n) => n.type === "agent");
417
+ const hasUnrestricted = agents.some((a) => a.permissions === "unrestricted" ||
418
+ a.permissions === "full-auto" ||
419
+ a.permissions === "bypassPermissions");
420
+ // If any agent is unrestricted, no sandbox (user opted out of restrictions)
421
+ if (hasUnrestricted)
422
+ return "danger-full-access";
423
+ // Default: no explicit sandbox section needed
424
+ return null;
425
+ }
426
+ /**
427
+ * Extract codex-specific extension fields from the topology level.
428
+ * Checks settings.extensions["codex"] and any top-level extension block.
429
+ */
430
+ function getTopologyExtensions(ast) {
431
+ const settingsExt = ast.settings?.extensions;
432
+ if (settingsExt?.codex)
433
+ return settingsExt.codex;
434
+ // Also check top-level ast.extensions["codex"]
435
+ if (ast.extensions?.codex)
436
+ return ast.extensions.codex;
437
+ return null;
438
+ }
439
+ // ---------------------------------------------------------------------------
440
+ // AGENTS.md generation
441
+ // ---------------------------------------------------------------------------
442
+ /**
443
+ * Generate `AGENTS.md` — the primary instructions file for Codex CLI.
444
+ *
445
+ * Since Codex is a single-agent CLI, multi-agent topologies are expressed
446
+ * as instruction sections describing each agent's role, responsibilities,
447
+ * tools, and memory access patterns. The orchestrator reads this file to
448
+ * understand the full workflow.
449
+ */
450
+ function generateAgentsMd(ast) {
451
+ const sections = [];
452
+ const name = ast.topology.name;
453
+ // Header
454
+ sections.push(`# ${toTitle(name)} — Agent Instructions`);
455
+ sections.push("");
456
+ // #17 — isFragment notice
457
+ if (ast.isFragment) {
458
+ sections.push("**Note:** This is a topology fragment — it defines a reusable sub-topology.");
459
+ sections.push("");
460
+ }
461
+ if (ast.topology.description) {
462
+ sections.push(ast.topology.description);
463
+ sections.push("");
464
+ }
465
+ sections.push(`Version: ${ast.topology.version}`);
466
+ if (ast.topology.patterns.length > 0) {
467
+ sections.push(`Patterns: ${ast.topology.patterns.join(", ")}`);
468
+ }
469
+ // #7 — topology-level domain, timeout, errorHandler, durable, foundations, advanced
470
+ if (ast.topology.domain) {
471
+ sections.push(`Domain: ${ast.topology.domain}`);
472
+ }
473
+ if (ast.topology.timeout) {
474
+ sections.push(`Timeout: ${ast.topology.timeout}`);
475
+ }
476
+ if (ast.topology.errorHandler) {
477
+ sections.push(`Error handler: ${ast.topology.errorHandler}`);
478
+ }
479
+ if (ast.topology.durable) {
480
+ sections.push(`Durable: true`);
481
+ }
482
+ if (ast.topology.foundations && ast.topology.foundations.length > 0) {
483
+ sections.push(`Foundations: ${ast.topology.foundations.join(", ")}`);
484
+ }
485
+ if (ast.topology.advanced && ast.topology.advanced.length > 0) {
486
+ sections.push(`Advanced: ${ast.topology.advanced.join(", ")}`);
487
+ }
488
+ sections.push("");
489
+ // Defaults section
490
+ if (ast.defaults) {
491
+ sections.push("## Defaults");
492
+ sections.push("");
493
+ const d = ast.defaults;
494
+ if (d.temperature != null)
495
+ sections.push(`- **Temperature:** ${d.temperature}`);
496
+ if (d.maxTokens != null)
497
+ sections.push(`- **Max tokens:** ${d.maxTokens}`);
498
+ if (d.topP != null)
499
+ sections.push(`- **Top-p:** ${d.topP}`);
500
+ if (d.topK != null)
501
+ sections.push(`- **Top-k:** ${d.topK}`);
502
+ if (d.stop && d.stop.length > 0)
503
+ sections.push(`- **Stop sequences:** ${d.stop.join(", ")}`);
504
+ if (d.seed != null)
505
+ sections.push(`- **Seed:** ${d.seed}`);
506
+ if (d.thinking)
507
+ sections.push(`- **Thinking:** ${d.thinking}`);
508
+ if (d.thinkingBudget != null)
509
+ sections.push(`- **Thinking budget:** ${d.thinkingBudget}`);
510
+ if (d.outputFormat)
511
+ sections.push(`- **Output format:** ${d.outputFormat}`);
512
+ if (d.timeout)
513
+ sections.push(`- **Timeout:** ${d.timeout}`);
514
+ if (d.logLevel)
515
+ sections.push(`- **Log level:** ${d.logLevel}`);
516
+ sections.push("");
517
+ }
518
+ // Orchestrator section (#14)
519
+ const orch = ast.nodes.find((n) => n.type === "orchestrator");
520
+ if (orch) {
521
+ const orchTitle = orch.label || toTitle(orch.id);
522
+ sections.push(`## Orchestrator — ${orchTitle}`);
523
+ sections.push("");
524
+ sections.push(`Model: ${orch.model}`);
525
+ if (orch.handles?.length > 0) {
526
+ sections.push(`Handles: ${orch.handles.join(", ")}`);
527
+ }
528
+ if (orch.generates) {
529
+ sections.push(`Generates: ${orch.generates}`);
530
+ }
531
+ if (orch.outputs) {
532
+ sections.push("Outputs:");
533
+ for (const [field, values] of Object.entries(orch.outputs)) {
534
+ sections.push(` - ${field}: ${values.join(" | ")}`);
535
+ }
536
+ }
537
+ sections.push("");
538
+ }
539
+ // Flow section
540
+ if (ast.edges.length > 0) {
541
+ sections.push("## Flow");
542
+ sections.push("");
543
+ for (const edge of ast.edges) {
544
+ let line = `${edge.from} -> ${edge.to}`;
545
+ if (edge.condition)
546
+ line += ` [when ${edge.condition}]`;
547
+ if (edge.maxIterations)
548
+ line += ` [max ${edge.maxIterations}]`;
549
+ if (edge.per)
550
+ line += ` [per ${edge.per}]`;
551
+ if (edge.isError)
552
+ line += ` [error${edge.errorType ? `: ${edge.errorType}` : ""}]`;
553
+ if (edge.weight != null)
554
+ line += ` [weight ${edge.weight}]`;
555
+ if (edge.reflection)
556
+ line += ` [reflection]`;
557
+ if (edge.race)
558
+ line += ` [race]`;
559
+ if (edge.tolerance != null)
560
+ line += ` [tolerance: ${edge.tolerance}]`;
561
+ if (edge.wait)
562
+ line += ` [wait ${edge.wait}]`;
563
+ sections.push(`- ${line}`);
564
+ }
565
+ sections.push("");
566
+ }
567
+ // Schedules section (Codex doesn't natively support cron; document for reference)
568
+ if (ast.schedules && ast.schedules.length > 0) {
569
+ sections.push("## Schedules");
570
+ sections.push("");
571
+ for (const job of ast.schedules) {
572
+ const timing = job.cron ? `runs at \`${job.cron}\`` : `runs ${job.every}`;
573
+ const target = job.agent
574
+ ? `triggers agent \`${job.agent}\``
575
+ : job.action
576
+ ? `triggers action \`${job.action}\``
577
+ : "";
578
+ const suffix = target ? `, ${target}` : "";
579
+ const enabled = job.enabled === false ? " (disabled)" : "";
580
+ sections.push(`- **${job.id}**: ${timing}${suffix}${enabled}`);
581
+ }
582
+ sections.push("");
583
+ }
584
+ // Interfaces section (document external interfaces for reference)
585
+ if (ast.interfaces && ast.interfaces.length > 0) {
586
+ sections.push("## Interfaces");
587
+ sections.push("");
588
+ for (const iface of ast.interfaces) {
589
+ const details = [];
590
+ if (iface.type)
591
+ details.push(iface.type);
592
+ for (const [k, v] of Object.entries(iface.config)) {
593
+ details.push(`${k} ${v}`);
594
+ }
595
+ const desc = details.length > 0 ? details.join(", ") : "configured";
596
+ sections.push(`- **${iface.id}**: ${desc}`);
597
+ }
598
+ sections.push("");
599
+ }
600
+ // Agent sections
601
+ const agents = ast.nodes.filter((n) => n.type === "agent");
602
+ if (agents.length > 0) {
603
+ sections.push("## Agents");
604
+ sections.push("");
605
+ for (const agent of agents) {
606
+ sections.push(`### ${agent.label || toTitle(agent.id)}`);
607
+ sections.push("");
608
+ sections.push(`> See \`.codex/agents/${agent.id}.toml\` for full configuration.`);
609
+ sections.push("");
610
+ // Role
611
+ const roleText = ast.roles[agent.role ?? ""] ?? ast.roles[agent.id] ?? agent.role;
612
+ if (roleText) {
613
+ sections.push(`**Role:** ${roleText}`);
614
+ sections.push("");
615
+ }
616
+ // Description
617
+ if (agent.description) {
618
+ sections.push(agent.description);
619
+ sections.push("");
620
+ }
621
+ // Model and permissions
622
+ const model = agent.model ?? "o4-mini";
623
+ const policy = mapApprovalPolicy(agent.permissions ?? "supervised");
624
+ sections.push(`- **Model:** ${model}`);
625
+ sections.push(`- **Approval policy:** ${policy}`);
626
+ // Phase
627
+ if (agent.phase != null) {
628
+ sections.push(`- **Phase:** ${agent.phase}`);
629
+ }
630
+ // Sampling parameters
631
+ if (agent.temperature != null) {
632
+ sections.push(`- **Temperature:** ${agent.temperature}`);
633
+ }
634
+ if (agent.maxTokens != null) {
635
+ sections.push(`- **Max tokens:** ${agent.maxTokens}`);
636
+ }
637
+ if (agent.topP != null) {
638
+ sections.push(`- **Top-p:** ${agent.topP}`);
639
+ }
640
+ if (agent.topK != null) {
641
+ sections.push(`- **Top-k:** ${agent.topK}`);
642
+ }
643
+ if (agent.stop && agent.stop.length > 0) {
644
+ sections.push(`- **Stop sequences:** ${agent.stop.join(", ")}`);
645
+ }
646
+ if (agent.seed != null) {
647
+ sections.push(`- **Seed:** ${agent.seed}`);
648
+ }
649
+ // Thinking / reasoning
650
+ if (agent.thinking) {
651
+ sections.push(`- **Reasoning level:** ${agent.thinking}`);
652
+ }
653
+ if (agent.thinkingBudget != null) {
654
+ sections.push(`- **Thinking budget:** ${agent.thinkingBudget}`);
655
+ }
656
+ // Output format
657
+ if (agent.outputFormat) {
658
+ sections.push(`- **Output format:** ${agent.outputFormat}`);
659
+ }
660
+ // Log level
661
+ if (agent.logLevel) {
662
+ sections.push(`- **Log level:** ${agent.logLevel}`);
663
+ }
664
+ // Timeout
665
+ if (agent.timeout) {
666
+ sections.push(`- **Maximum execution time:** ${agent.timeout}`);
667
+ }
668
+ // On-fail
669
+ if (agent.onFail) {
670
+ sections.push(`- **On failure:** ${agent.onFail}`);
671
+ }
672
+ // Retry
673
+ if (agent.retry != null) {
674
+ sections.push(`- **Retry:** ${formatRetryConfig(agent.retry)}`);
675
+ }
676
+ // Join semantics
677
+ if (agent.join) {
678
+ sections.push(`- **Wait for:** ${agent.join}`);
679
+ }
680
+ // Rate limit
681
+ if (agent.rateLimit) {
682
+ sections.push(`- **Rate limit:** ${agent.rateLimit}`);
683
+ }
684
+ // Circuit breaker
685
+ if (agent.circuitBreaker) {
686
+ const cb = agent.circuitBreaker;
687
+ sections.push(`- **Circuit breaker:** threshold ${cb.threshold}, window ${cb.window}, cooldown ${cb.cooldown}`);
688
+ }
689
+ // Compensates (saga)
690
+ if (agent.compensates) {
691
+ sections.push(`- **Compensates:** ${agent.compensates} (saga rollback)`);
692
+ }
693
+ // Prompt
694
+ if (agent.prompt) {
695
+ sections.push("");
696
+ sections.push("#### Instructions");
697
+ sections.push("");
698
+ sections.push(agent.prompt);
699
+ sections.push("");
700
+ }
701
+ // Prompt variants
702
+ if (agent.variants && agent.variants.length > 0) {
703
+ sections.push("");
704
+ sections.push("#### Prompt Variants");
705
+ sections.push("");
706
+ for (const v of agent.variants) {
707
+ const parts = [`**${v.id}** (weight ${v.weight})`];
708
+ if (v.model)
709
+ parts.push(`model: ${v.model}`);
710
+ if (v.temperature != null)
711
+ parts.push(`temperature: ${v.temperature}`);
712
+ sections.push(`- ${parts.join(", ")}`);
713
+ if (v.prompt) {
714
+ sections.push(` > ${v.prompt.replace(/\n/g, "\n > ")}`);
715
+ }
716
+ }
717
+ sections.push("");
718
+ }
719
+ // Tools
720
+ if (agent.tools && agent.tools.length > 0) {
721
+ sections.push(`- **Tools:** ${agent.tools.join(", ")}`);
722
+ }
723
+ if (agent.disallowedTools && agent.disallowedTools.length > 0) {
724
+ sections.push(`- **Disallowed tools:** ${agent.disallowedTools.join(", ")}`);
725
+ }
726
+ // Skills
727
+ if (agent.skills && agent.skills.length > 0) {
728
+ sections.push(`- **Skills:** ${agent.skills.join(", ")}`);
729
+ }
730
+ // Memory access
731
+ if (agent.reads && agent.reads.length > 0) {
732
+ sections.push(`- **Reads:** ${agent.reads.join(", ")}`);
733
+ }
734
+ if (agent.writes && agent.writes.length > 0) {
735
+ sections.push(`- **Writes:** ${agent.writes.join(", ")}`);
736
+ }
737
+ // Input/Output schemas
738
+ if (agent.inputSchema && agent.inputSchema.length > 0) {
739
+ sections.push("- **Input schema:**");
740
+ for (const field of agent.inputSchema) {
741
+ sections.push(` ${formatSchemaField(field)}`);
742
+ }
743
+ }
744
+ if (agent.outputSchema && agent.outputSchema.length > 0) {
745
+ sections.push("- **Output schema:**");
746
+ for (const field of agent.outputSchema) {
747
+ sections.push(` ${formatSchemaField(field)}`);
748
+ }
749
+ }
750
+ // Outputs
751
+ if (agent.outputs) {
752
+ sections.push("- **Outputs:**");
753
+ for (const [field, values] of Object.entries(agent.outputs)) {
754
+ sections.push(` - ${field}: ${values.join(" | ")}`);
755
+ }
756
+ }
757
+ // Artifact lineage
758
+ if (agent.produces && agent.produces.length > 0) {
759
+ sections.push(`- **Produces:** ${agent.produces.join(", ")}`);
760
+ }
761
+ if (agent.consumes && agent.consumes.length > 0) {
762
+ sections.push(`- **Consumes:** ${agent.consumes.join(", ")}`);
763
+ }
764
+ // Behavior modifiers
765
+ if (agent.behavior) {
766
+ sections.push(`- **Behavior:** ${agent.behavior}`);
767
+ }
768
+ if (agent.skip) {
769
+ sections.push(`- **Skip when:** ${agent.skip}`);
770
+ }
771
+ if (agent.isolation) {
772
+ sections.push(`- **Isolation:** ${agent.isolation}`);
773
+ }
774
+ if (agent.background) {
775
+ sections.push(`- **Background:** true`);
776
+ }
777
+ if (agent.maxTurns != null) {
778
+ sections.push(`- **Max turns:** ${agent.maxTurns}`);
779
+ }
780
+ // Scale (#10 — include batchSize)
781
+ if (agent.scale) {
782
+ let scaleLine = `- **Scale:** ${agent.scale.mode} by ${agent.scale.by} (${agent.scale.min}-${agent.scale.max}`;
783
+ if (agent.scale.batchSize != null) {
784
+ scaleLine += `, batch ${agent.scale.batchSize}`;
785
+ }
786
+ scaleLine += `)`;
787
+ sections.push(scaleLine);
788
+ }
789
+ // #4 — per-agent MCP servers
790
+ if (agent.mcpServers && agent.mcpServers.length > 0) {
791
+ sections.push(`- **MCP servers:** ${agent.mcpServers.join(", ")}`);
792
+ }
793
+ // #5 — sandbox
794
+ if (agent.sandbox != null) {
795
+ sections.push(`- **Sandbox:** ${agent.sandbox}`);
796
+ }
797
+ // #5 — fallbackChain
798
+ if (agent.fallbackChain && agent.fallbackChain.length > 0) {
799
+ sections.push(`- **Fallback chain:** ${agent.fallbackChain.join(" -> ")}`);
800
+ }
801
+ // #5 — per-agent hooks
802
+ if (agent.hooks && agent.hooks.length > 0) {
803
+ const hookDesc = agent.hooks.map((h) => `${h.name} (on ${h.on})`).join(", ");
804
+ sections.push(`- **Hooks:** ${hookDesc}`);
805
+ }
806
+ // Codex-specific extensions
807
+ if (agent.extensions?.codex) {
808
+ sections.push("- **Codex config:**");
809
+ for (const [k, v] of Object.entries(agent.extensions.codex)) {
810
+ sections.push(` - ${k}: ${v}`);
811
+ }
812
+ }
813
+ sections.push("");
814
+ }
815
+ }
816
+ // Human nodes section
817
+ const humans = ast.nodes.filter((n) => n.type === "human");
818
+ if (humans.length > 0) {
819
+ sections.push("## Human Checkpoints");
820
+ sections.push("");
821
+ for (const human of humans) {
822
+ sections.push(`### ${human.label || toTitle(human.id)}`);
823
+ sections.push("");
824
+ if (human.description) {
825
+ sections.push(human.description);
826
+ sections.push("");
827
+ }
828
+ sections.push(`- **Type:** Human pause point — requires manual input or approval`);
829
+ if (human.timeout) {
830
+ sections.push(`- **Timeout:** ${human.timeout}`);
831
+ }
832
+ if (human.onTimeout) {
833
+ sections.push(`- **On timeout:** ${human.onTimeout}`);
834
+ }
835
+ sections.push("");
836
+ }
837
+ }
838
+ // Group nodes section
839
+ const groups = ast.nodes.filter((n) => n.type === "group");
840
+ if (groups.length > 0) {
841
+ sections.push("## Group Chats");
842
+ sections.push("");
843
+ for (const group of groups) {
844
+ sections.push(`### ${group.label || toTitle(group.id)}`);
845
+ sections.push("");
846
+ if (group.description) {
847
+ sections.push(group.description);
848
+ sections.push("");
849
+ }
850
+ sections.push(`- **Members:** ${group.members.join(", ")}`);
851
+ if (group.speakerSelection) {
852
+ sections.push(`- **Speaker selection:** ${group.speakerSelection}`);
853
+ }
854
+ if (group.maxRounds != null) {
855
+ sections.push(`- **Max rounds:** ${group.maxRounds}`);
856
+ }
857
+ if (group.termination) {
858
+ sections.push(`- **Termination:** ${group.termination}`);
859
+ }
860
+ if (group.timeout) {
861
+ sections.push(`- **Timeout:** ${group.timeout}`);
862
+ }
863
+ sections.push("");
864
+ }
865
+ }
866
+ // Gates section
867
+ const gates = ast.nodes.filter((n) => n.type === "gate");
868
+ if (gates.length > 0) {
869
+ sections.push("## Gates");
870
+ sections.push("");
871
+ for (const gate of gates) {
872
+ sections.push(`### ${gate.label || toTitle(gate.id)}`);
873
+ sections.push("");
874
+ if (gate.after)
875
+ sections.push(`- **After:** ${gate.after}`);
876
+ if (gate.before)
877
+ sections.push(`- **Before:** ${gate.before}`);
878
+ if (gate.run)
879
+ sections.push(`- **Run:** ${gate.run}`);
880
+ if (gate.checks && gate.checks.length > 0) {
881
+ sections.push(`- **Checks:** ${gate.checks.join(", ")}`);
882
+ }
883
+ if (gate.onFail)
884
+ sections.push(`- **On fail:** ${gate.onFail}`);
885
+ if (gate.retry != null)
886
+ sections.push(`- **Retry:** ${gate.retry}`);
887
+ if (gate.timeout)
888
+ sections.push(`- **Timeout:** ${gate.timeout}`);
889
+ sections.push(`- **Enforcement:** script-level — run gate script manually between agents`);
890
+ sections.push("");
891
+ }
892
+ }
893
+ // #1 — Action nodes section
894
+ const actions = ast.nodes.filter((n) => n.type === "action");
895
+ if (actions.length > 0) {
896
+ sections.push("## Actions");
897
+ sections.push("");
898
+ for (const action of actions) {
899
+ sections.push(`### ${action.label || toTitle(action.id)}`);
900
+ sections.push("");
901
+ if (action.description) {
902
+ sections.push(action.description);
903
+ sections.push("");
904
+ }
905
+ if (action.kind)
906
+ sections.push(`- **Kind:** ${action.kind}`);
907
+ if (action.source)
908
+ sections.push(`- **Source:** ${action.source}`);
909
+ if (action.commands && action.commands.length > 0) {
910
+ sections.push(`- **Commands:** ${action.commands.join("; ")}`);
911
+ }
912
+ if (action.timeout)
913
+ sections.push(`- **Timeout:** ${action.timeout}`);
914
+ if (action.onFail)
915
+ sections.push(`- **On fail:** ${action.onFail}`);
916
+ if (action.join)
917
+ sections.push(`- **Join:** ${action.join}`);
918
+ sections.push("");
919
+ }
920
+ }
921
+ // Triggers section
922
+ if (ast.triggers.length > 0) {
923
+ sections.push("## Triggers");
924
+ sections.push("");
925
+ for (const trigger of ast.triggers) {
926
+ sections.push(`### ${trigger.name}`);
927
+ sections.push(`Pattern: \`${trigger.pattern}\``);
928
+ if (trigger.argument)
929
+ sections.push(`Argument: ${trigger.argument}`);
930
+ sections.push("");
931
+ }
932
+ }
933
+ // Memory paths section
934
+ if (Object.keys(ast.memory).length > 0) {
935
+ sections.push("## Memory");
936
+ sections.push("");
937
+ if (ast.memory.domains) {
938
+ sections.push(`- **Domains:** .codex/domains/`);
939
+ }
940
+ if (ast.memory.references) {
941
+ sections.push(`- **References:** .codex/references/`);
942
+ }
943
+ if (ast.memory.metrics) {
944
+ sections.push(`- **Metrics:** .codex/metrics.jsonl`);
945
+ }
946
+ if (ast.memory.workspace) {
947
+ sections.push(`- **Workspace:** .codex/runs/`);
948
+ }
949
+ sections.push("");
950
+ }
951
+ // Observability section
952
+ if (ast.observability) {
953
+ const o = ast.observability;
954
+ sections.push("## Observability");
955
+ sections.push("");
956
+ sections.push(`- **Enabled:** ${o.enabled}`);
957
+ sections.push(`- **Level:** ${o.level}`);
958
+ sections.push(`- **Exporter:** ${o.exporter}`);
959
+ if (o.endpoint)
960
+ sections.push(`- **Endpoint:** ${o.endpoint}`);
961
+ if (o.service)
962
+ sections.push(`- **Service:** ${o.service}`);
963
+ sections.push(`- **Sample rate:** ${o.sampleRate}`);
964
+ sections.push(`- **Capture:** prompts=${o.capture.prompts}, completions=${o.capture.completions}, toolArgs=${o.capture.toolArgs}, toolResults=${o.capture.toolResults}`);
965
+ sections.push(`- **Spans:** agents=${o.spans.agents}, tools=${o.spans.tools}, gates=${o.spans.gates}, memory=${o.spans.memory}`);
966
+ sections.push("");
967
+ }
968
+ // Checkpoint section
969
+ if (ast.checkpoint) {
970
+ const cp = ast.checkpoint;
971
+ sections.push("## Checkpoint");
972
+ sections.push("");
973
+ sections.push(`- **Backend:** ${cp.backend}`);
974
+ sections.push(`- **Strategy:** ${cp.strategy}`);
975
+ if (cp.connection)
976
+ sections.push(`- **Connection:** ${cp.connection}`);
977
+ if (cp.ttl)
978
+ sections.push(`- **TTL:** ${cp.ttl}`);
979
+ if (cp.replay) {
980
+ sections.push(`- **Replay:** enabled=${cp.replay.enabled}${cp.replay.maxHistory != null ? `, maxHistory=${cp.replay.maxHistory}` : ""}${cp.replay.branch != null ? `, branch=${cp.replay.branch}` : ""}`);
981
+ }
982
+ sections.push("");
983
+ }
984
+ // Artifacts section
985
+ if (ast.artifacts && ast.artifacts.length > 0) {
986
+ sections.push("## Artifacts");
987
+ sections.push("");
988
+ for (const art of ast.artifacts) {
989
+ const parts = [`type: ${art.type}`];
990
+ if (art.path)
991
+ parts.push(`path: ${art.path}`);
992
+ if (art.retention)
993
+ parts.push(`retention: ${art.retention}`);
994
+ if (art.dependsOn && art.dependsOn.length > 0) {
995
+ parts.push(`depends-on: ${art.dependsOn.join(", ")}`);
996
+ }
997
+ sections.push(`- **${art.id}**: ${parts.join(", ")}`);
998
+ }
999
+ sections.push("");
1000
+ }
1001
+ // #8 — Batch section
1002
+ if (ast.batch && Object.keys(ast.batch).length > 0) {
1003
+ sections.push("## Batch");
1004
+ sections.push("");
1005
+ for (const [key, val] of Object.entries(ast.batch)) {
1006
+ sections.push(`- **${key}:** ${JSON.stringify(val)}`);
1007
+ }
1008
+ sections.push("");
1009
+ }
1010
+ // #9 — Environments section
1011
+ if (ast.environments && Object.keys(ast.environments).length > 0) {
1012
+ sections.push("## Environments");
1013
+ sections.push("");
1014
+ for (const [envName, overrides] of Object.entries(ast.environments)) {
1015
+ sections.push(`### ${toTitle(envName)}`);
1016
+ sections.push("");
1017
+ for (const [key, val] of Object.entries(overrides)) {
1018
+ sections.push(`- **${key}:** ${JSON.stringify(val)}`);
1019
+ }
1020
+ sections.push("");
1021
+ }
1022
+ }
1023
+ // Schemas section
1024
+ if (ast.schemas && ast.schemas.length > 0) {
1025
+ sections.push("## Schemas");
1026
+ sections.push("");
1027
+ for (const schema of ast.schemas) {
1028
+ sections.push(`### ${toTitle(schema.id)}`);
1029
+ sections.push("");
1030
+ for (const field of schema.fields) {
1031
+ sections.push(formatSchemaField(field));
1032
+ }
1033
+ sections.push("");
1034
+ }
1035
+ }
1036
+ // Depth levels
1037
+ if (ast.depth && ast.depth.levels && ast.depth.levels.length > 0) {
1038
+ sections.push("## Depth Levels");
1039
+ sections.push("");
1040
+ if (ast.depth.factors.length > 0) {
1041
+ sections.push(`Factors: ${ast.depth.factors.join(", ")}`);
1042
+ }
1043
+ for (const level of ast.depth.levels) {
1044
+ let line = `- **Level ${level.level}**: ${level.label}`;
1045
+ if (level.omit.length > 0)
1046
+ line += ` (omit: ${level.omit.join(", ")})`;
1047
+ sections.push(line);
1048
+ }
1049
+ sections.push("");
1050
+ }
1051
+ // Composition: params
1052
+ if (ast.params && ast.params.length > 0) {
1053
+ sections.push("## Parameters");
1054
+ sections.push("");
1055
+ for (const p of ast.params) {
1056
+ const req = p.required ? " (required)" : "";
1057
+ const def = p.default != null ? ` = ${p.default}` : "";
1058
+ sections.push(`- **${p.name}**: ${p.type}${req}${def}`);
1059
+ }
1060
+ sections.push("");
1061
+ }
1062
+ // Composition: interface endpoints
1063
+ if (ast.interfaceEndpoints) {
1064
+ sections.push("## Interface");
1065
+ sections.push("");
1066
+ sections.push(`- Entry: ${ast.interfaceEndpoints.entry}`);
1067
+ sections.push(`- Exit: ${ast.interfaceEndpoints.exit}`);
1068
+ sections.push("");
1069
+ }
1070
+ // Composition: imports
1071
+ if (ast.imports && ast.imports.length > 0) {
1072
+ sections.push("## Imports");
1073
+ sections.push("");
1074
+ for (const imp of ast.imports) {
1075
+ let line = `- **${imp.alias}** from \`${imp.source}\``;
1076
+ if (imp.sha256)
1077
+ line += ` (sha256: ${imp.sha256})`;
1078
+ if (imp.registry)
1079
+ line += ` [registry: ${imp.registryPackage}@${imp.registryVersion}]`;
1080
+ sections.push(line);
1081
+ if (Object.keys(imp.params).length > 0) {
1082
+ for (const [k, v] of Object.entries(imp.params)) {
1083
+ sections.push(` - ${k}: ${v}`);
1084
+ }
1085
+ }
1086
+ }
1087
+ sections.push("");
1088
+ }
1089
+ // Composition: includes
1090
+ if (ast.includes && ast.includes.length > 0) {
1091
+ sections.push("## Includes");
1092
+ sections.push("");
1093
+ for (const inc of ast.includes) {
1094
+ sections.push(`- ${inc.source}`);
1095
+ }
1096
+ sections.push("");
1097
+ }
1098
+ // Skills summary (#11 — additional skill fields)
1099
+ if (ast.skills.length > 0) {
1100
+ sections.push("## Skills");
1101
+ sections.push("");
1102
+ for (const skill of ast.skills) {
1103
+ sections.push(`### ${toTitle(skill.id)}`);
1104
+ if (skill.description) {
1105
+ sections.push(skill.description);
1106
+ }
1107
+ if (skill.scripts && skill.scripts.length > 0) {
1108
+ sections.push(`Scripts: ${skill.scripts.join(", ")}`);
1109
+ }
1110
+ if (skill.prompt) {
1111
+ sections.push(`Prompt file: ${skill.prompt}`);
1112
+ }
1113
+ if (skill.userInvocable != null) {
1114
+ sections.push(`User-invocable: ${skill.userInvocable ? "yes" : "no"}`);
1115
+ }
1116
+ if (skill.allowedTools && skill.allowedTools.length > 0) {
1117
+ sections.push(`Allowed tools: ${skill.allowedTools.join(", ")}`);
1118
+ }
1119
+ if (skill.context) {
1120
+ sections.push(`Context: ${skill.context}`);
1121
+ }
1122
+ if (skill.agent) {
1123
+ sections.push(`Agent: ${skill.agent}`);
1124
+ }
1125
+ if (skill.disableModelInvocation) {
1126
+ sections.push(`Model invocation: disabled`);
1127
+ }
1128
+ sections.push("");
1129
+ }
1130
+ }
1131
+ // Context includes (#16 — also show context.file)
1132
+ if ((ast.context.includes && ast.context.includes.length > 0) || ast.context.file) {
1133
+ sections.push("## References");
1134
+ sections.push("");
1135
+ if (ast.context.file) {
1136
+ sections.push(`- Context file: ${ast.context.file}`);
1137
+ }
1138
+ if (ast.context.includes) {
1139
+ for (const inc of ast.context.includes) {
1140
+ sections.push(`- ${inc}`);
1141
+ }
1142
+ }
1143
+ sections.push("");
1144
+ }
1145
+ // Environment variables (#2 — handle sensitive, secretRef, encrypted)
1146
+ if (Object.keys(ast.env).length > 0) {
1147
+ sections.push("## Environment");
1148
+ sections.push("");
1149
+ for (const [key, val] of Object.entries(ast.env)) {
1150
+ if (typeof val === "string") {
1151
+ sections.push(`- \`${key}\`: ${val}`);
1152
+ }
1153
+ else {
1154
+ const sv = val;
1155
+ let displayVal;
1156
+ if (sv.secretRef) {
1157
+ displayVal = `[secret: ${sv.secretRef.scheme}:${sv.secretRef.uri}]`;
1158
+ }
1159
+ else if (sv.encrypted) {
1160
+ displayVal = `[encrypted]`;
1161
+ }
1162
+ else if (sv.sensitive) {
1163
+ displayVal = `\${${key}}`;
1164
+ }
1165
+ else {
1166
+ displayVal = sv.value;
1167
+ }
1168
+ sections.push(`- \`${key}\`: ${displayVal}`);
1169
+ }
1170
+ }
1171
+ sections.push("");
1172
+ }
1173
+ return {
1174
+ path: "AGENTS.md",
1175
+ content: sections.join("\n") + "\n",
1176
+ category: "agent",
1177
+ };
1178
+ }
1179
+ // ---------------------------------------------------------------------------
1180
+ // .codex/instructions.md — REMOVED
1181
+ // ---------------------------------------------------------------------------
1182
+ // Codex CLI does NOT use .codex/instructions.md. It reads AGENTS.md files
1183
+ // from the workspace root and nested directories. Skills, context includes,
1184
+ // and environment info are now folded into AGENTS.md generation above.
1185
+ // ---------------------------------------------------------------------------
1186
+ // Memory directories
1187
+ // ---------------------------------------------------------------------------
1188
+ /** Generate memory structure directories under .codex/. */
1189
+ function generateMemory(ast) {
1190
+ const files = [];
1191
+ if (ast.memory.domains) {
1192
+ files.push(gitkeep(".codex/domains"));
1193
+ }
1194
+ if (ast.memory.references) {
1195
+ files.push(gitkeep(".codex/references"));
1196
+ }
1197
+ if (ast.memory.metrics) {
1198
+ files.push({
1199
+ path: ".codex/metrics.jsonl",
1200
+ content: "",
1201
+ category: "machine",
1202
+ });
1203
+ }
1204
+ if (ast.memory.workspace) {
1205
+ files.push(gitkeep(".codex/runs"));
1206
+ }
1207
+ return files;
1208
+ }
1209
+ // ---------------------------------------------------------------------------
1210
+ // Gate scripts
1211
+ // ---------------------------------------------------------------------------
1212
+ /** Generate gate script stubs under .codex/scripts/. */
1213
+ function generateGateScripts(ast) {
1214
+ const files = [];
1215
+ for (const node of ast.nodes) {
1216
+ if (node.type !== "gate")
1217
+ continue;
1218
+ const gate = node;
1219
+ if (gate.run) {
1220
+ const scriptName = gate.run.replace(/^.*\//, "").replace(/\s.*$/, "");
1221
+ files.push({
1222
+ path: `.codex/scripts/${scriptName}`,
1223
+ content: shellStub(`Gate: ${gate.id} — ${gate.run}`),
1224
+ category: "script",
1225
+ });
1226
+ }
1227
+ }
1228
+ return files;
1229
+ }
1230
+ // ---------------------------------------------------------------------------
1231
+ // Hook scripts
1232
+ // ---------------------------------------------------------------------------
1233
+ /** Generate hook script stubs under .codex/scripts/. */
1234
+ function generateHookScripts(ast) {
1235
+ const files = [];
1236
+ // Global hooks
1237
+ for (const hook of ast.hooks) {
1238
+ if (!hook.run)
1239
+ continue;
1240
+ const scriptName = hook.run.replace(/^.*\//, "").replace(/\s.*$/, "");
1241
+ const hookLines = [
1242
+ "#!/usr/bin/env bash",
1243
+ `# Hook: ${hook.name} — fires on ${hook.on}`,
1244
+ ];
1245
+ // #12 — include matcher, type, timeout as comments
1246
+ if (hook.matcher)
1247
+ hookLines.push(`# Matcher: ${hook.matcher}`);
1248
+ if (hook.type)
1249
+ hookLines.push(`# Type: ${hook.type}`);
1250
+ if (hook.timeout != null)
1251
+ hookLines.push(`# Timeout: ${hook.timeout}ms`);
1252
+ hookLines.push("# Auto-generated by agentopology scaffold — edit as needed.", "set -euo pipefail", "", `echo "TODO: implement ${hook.name} hook"`, "");
1253
+ files.push({
1254
+ path: `.codex/scripts/${scriptName}`,
1255
+ content: hookLines.join("\n"),
1256
+ category: "script",
1257
+ });
1258
+ }
1259
+ // #5 — per-agent hook script stubs
1260
+ for (const node of ast.nodes) {
1261
+ if (node.type !== "agent")
1262
+ continue;
1263
+ const agent = node;
1264
+ if (!agent.hooks)
1265
+ continue;
1266
+ for (const hook of agent.hooks) {
1267
+ if (!hook.run)
1268
+ continue;
1269
+ const scriptName = hook.run.replace(/^.*\//, "").replace(/\s.*$/, "");
1270
+ const hookLines = [
1271
+ "#!/usr/bin/env bash",
1272
+ `# Hook: ${hook.name} — fires on ${hook.on} (agent: ${agent.id})`,
1273
+ ];
1274
+ if (hook.matcher)
1275
+ hookLines.push(`# Matcher: ${hook.matcher}`);
1276
+ if (hook.type)
1277
+ hookLines.push(`# Type: ${hook.type}`);
1278
+ if (hook.timeout != null)
1279
+ hookLines.push(`# Timeout: ${hook.timeout}ms`);
1280
+ hookLines.push("# Auto-generated by agentopology scaffold — edit as needed.", "set -euo pipefail", "", `echo "TODO: implement ${hook.name} hook"`, "");
1281
+ files.push({
1282
+ path: `.codex/scripts/${scriptName}`,
1283
+ content: hookLines.join("\n"),
1284
+ category: "script",
1285
+ });
1286
+ }
1287
+ }
1288
+ return files;
1289
+ }
1290
+ // ---------------------------------------------------------------------------
1291
+ // Tool scripts
1292
+ // ---------------------------------------------------------------------------
1293
+ /** Generate tool script stubs under .codex/scripts/. */
1294
+ function generateToolScripts(ast) {
1295
+ const files = [];
1296
+ for (const tool of ast.toolDefs) {
1297
+ const ext = tool.lang === "python" ? "py" : tool.lang === "node" ? "js" : "sh";
1298
+ const shebang = tool.lang === "python"
1299
+ ? "#!/usr/bin/env python3"
1300
+ : tool.lang === "node"
1301
+ ? "#!/usr/bin/env node"
1302
+ : "#!/usr/bin/env bash";
1303
+ const lines = [
1304
+ shebang,
1305
+ `# Tool: ${tool.id} — ${tool.description}`,
1306
+ "# Auto-generated by agentopology scaffold — edit as needed.",
1307
+ "",
1308
+ ];
1309
+ if (tool.lang === "bash" || !tool.lang) {
1310
+ lines.push("set -euo pipefail");
1311
+ lines.push("");
1312
+ }
1313
+ if (tool.args && tool.args.length > 0) {
1314
+ lines.push(`# Args: ${tool.args.join(", ")}`);
1315
+ lines.push("");
1316
+ }
1317
+ if (tool.lang === "python") {
1318
+ lines.push(`print("TODO: implement ${tool.id}")`);
1319
+ }
1320
+ else if (tool.lang === "node") {
1321
+ lines.push(`console.log("TODO: implement ${tool.id}");`);
1322
+ }
1323
+ else {
1324
+ lines.push(`echo "TODO: implement ${tool.id}"`);
1325
+ }
1326
+ lines.push("");
1327
+ files.push({
1328
+ path: `.codex/scripts/${tool.id}.${ext}`,
1329
+ content: lines.join("\n"),
1330
+ category: "script",
1331
+ });
1332
+ }
1333
+ return files;
1334
+ }
1335
+ // ---------------------------------------------------------------------------
1336
+ // Metering script
1337
+ // ---------------------------------------------------------------------------
1338
+ /** Generate metering script stub. */
1339
+ function generateMetering(ast) {
1340
+ if (!ast.metering)
1341
+ return null;
1342
+ const m = ast.metering;
1343
+ const lines = [
1344
+ "#!/usr/bin/env bash",
1345
+ "# Metering / cost tracking script",
1346
+ "# Auto-generated by agentopology scaffold — edit as needed.",
1347
+ "set -euo pipefail",
1348
+ "",
1349
+ `# Track: ${m.track.join(", ")}`,
1350
+ `# Per: ${m.per.join(", ")}`,
1351
+ `# Output: ${m.output} (${m.format})`,
1352
+ `# Pricing: ${m.pricing}`,
1353
+ "",
1354
+ 'echo "TODO: implement metrics collection"',
1355
+ "",
1356
+ ];
1357
+ return {
1358
+ path: "scripts/collect-metrics.sh",
1359
+ content: lines.join("\n"),
1360
+ category: "script",
1361
+ };
1362
+ }
1363
+ // ---------------------------------------------------------------------------
1364
+ // Workspace protocol
1365
+ // ---------------------------------------------------------------------------
1366
+ /** Generate workspace-protocol.md if workspace memory has a protocol field. */
1367
+ function generateWorkspaceProtocol(ast) {
1368
+ const workspace = ast.memory.workspace;
1369
+ if (!workspace || !workspace.protocol)
1370
+ return null;
1371
+ const protocol = workspace.protocol;
1372
+ const sections = [];
1373
+ sections.push("# Workspace Protocol");
1374
+ sections.push("");
1375
+ // Directory structure from workspace.structure
1376
+ const structure = workspace.structure;
1377
+ if (structure && Array.isArray(structure)) {
1378
+ sections.push("## Directory Structure");
1379
+ for (const item of structure) {
1380
+ sections.push(`- ${item}`);
1381
+ }
1382
+ sections.push("");
1383
+ }
1384
+ // Read/Write rules from agent nodes
1385
+ const agents = ast.nodes.filter((n) => n.type === "agent");
1386
+ if (agents.length > 0) {
1387
+ sections.push("## Read/Write Rules");
1388
+ for (const agent of agents) {
1389
+ const reads = agent.reads ?? [];
1390
+ const writes = agent.writes ?? [];
1391
+ if (reads.length > 0 || writes.length > 0) {
1392
+ sections.push(`### ${toTitle(agent.id)}`);
1393
+ if (reads.length > 0) {
1394
+ sections.push(`- Reads: ${reads.join(", ")}`);
1395
+ }
1396
+ if (writes.length > 0) {
1397
+ sections.push(`- Writes: ${writes.join(", ")}`);
1398
+ }
1399
+ sections.push("");
1400
+ }
1401
+ }
1402
+ }
1403
+ return {
1404
+ path: `.codex/${protocol}`,
1405
+ content: sections.join("\n") + "\n",
1406
+ category: "machine",
1407
+ };
1408
+ }
1409
+ // ---------------------------------------------------------------------------
1410
+ // Skill documentation
1411
+ // ---------------------------------------------------------------------------
1412
+ /** Generate skill documentation files under .codex/skills/. */
1413
+ function generateSkills(ast) {
1414
+ const files = [];
1415
+ for (const skill of ast.skills) {
1416
+ const sections = [];
1417
+ sections.push(`# ${toTitle(skill.id)} Skill`);
1418
+ sections.push("");
1419
+ if (skill.description) {
1420
+ sections.push(skill.description);
1421
+ sections.push("");
1422
+ }
1423
+ if (skill.scripts && skill.scripts.length > 0) {
1424
+ sections.push("## Scripts");
1425
+ for (const s of skill.scripts) {
1426
+ sections.push(`- ${s}`);
1427
+ }
1428
+ sections.push("");
1429
+ }
1430
+ if (skill.domains && skill.domains.length > 0) {
1431
+ sections.push("## Domains");
1432
+ for (const d of skill.domains) {
1433
+ sections.push(`- ${d}`);
1434
+ }
1435
+ sections.push("");
1436
+ }
1437
+ if (skill.references && skill.references.length > 0) {
1438
+ sections.push("## References");
1439
+ for (const r of skill.references) {
1440
+ sections.push(`- ${r}`);
1441
+ }
1442
+ sections.push("");
1443
+ }
1444
+ // #11 — additional skill fields in skill docs
1445
+ if (skill.prompt) {
1446
+ sections.push(`Prompt file: ${skill.prompt}`);
1447
+ sections.push("");
1448
+ }
1449
+ if (skill.userInvocable != null) {
1450
+ sections.push(`User-invocable: ${skill.userInvocable ? "yes" : "no"}`);
1451
+ sections.push("");
1452
+ }
1453
+ if (skill.allowedTools && skill.allowedTools.length > 0) {
1454
+ sections.push(`Allowed tools: ${skill.allowedTools.join(", ")}`);
1455
+ sections.push("");
1456
+ }
1457
+ if (skill.context) {
1458
+ sections.push(`Context: ${skill.context}`);
1459
+ sections.push("");
1460
+ }
1461
+ if (skill.agent) {
1462
+ sections.push(`Agent: ${skill.agent}`);
1463
+ sections.push("");
1464
+ }
1465
+ if (skill.disableModelInvocation) {
1466
+ sections.push(`Model invocation: disabled`);
1467
+ sections.push("");
1468
+ }
1469
+ files.push({
1470
+ path: `.codex/skills/${skill.id}.md`,
1471
+ content: sections.join("\n") + "\n",
1472
+ category: "agent",
1473
+ });
1474
+ }
1475
+ return files;
1476
+ }
1477
+ // ---------------------------------------------------------------------------
1478
+ // Per-agent TOML files — .codex/agents/<name>.toml
1479
+ // ---------------------------------------------------------------------------
1480
+ /**
1481
+ * Build developer_instructions for an agent that has no explicit prompt.
1482
+ * Constructs instructions from role description, tools, reads/writes, and outputs.
1483
+ */
1484
+ function buildDeveloperInstructions(agent, ast) {
1485
+ const parts = [];
1486
+ // Role description
1487
+ const roleText = ast.roles[agent.role ?? ""] ?? ast.roles[agent.id] ?? agent.role;
1488
+ if (roleText) {
1489
+ parts.push(`You are a ${toTitle(agent.id)} agent. ${roleText}`);
1490
+ }
1491
+ else {
1492
+ parts.push(`You are the ${toTitle(agent.id)} agent.`);
1493
+ }
1494
+ // Tools
1495
+ if (agent.tools && agent.tools.length > 0) {
1496
+ parts.push(`\nAvailable tools: ${agent.tools.join(", ")}`);
1497
+ }
1498
+ if (agent.disallowedTools && agent.disallowedTools.length > 0) {
1499
+ parts.push(`Disallowed tools: ${agent.disallowedTools.join(", ")}`);
1500
+ }
1501
+ // Memory access
1502
+ if (agent.reads && agent.reads.length > 0) {
1503
+ parts.push(`\nReads from: ${agent.reads.join(", ")}`);
1504
+ }
1505
+ if (agent.writes && agent.writes.length > 0) {
1506
+ parts.push(`Writes to: ${agent.writes.join(", ")}`);
1507
+ }
1508
+ // Outputs
1509
+ if (agent.outputs) {
1510
+ const outputLines = [];
1511
+ for (const [field, values] of Object.entries(agent.outputs)) {
1512
+ outputLines.push(` ${field}: ${values.join(" | ")}`);
1513
+ }
1514
+ parts.push(`\nOutputs:\n${outputLines.join("\n")}`);
1515
+ }
1516
+ return parts.join("\n");
1517
+ }
1518
+ /**
1519
+ * Generate per-agent TOML files under `.codex/agents/<name>.toml`.
1520
+ *
1521
+ * Each agent in the topology gets its own TOML configuration file with
1522
+ * name, description, developer_instructions, model, sandbox_mode,
1523
+ * model_reasoning_effort, nickname_candidates, and optional MCP servers.
1524
+ */
1525
+ function generateAgentTomlFiles(ast) {
1526
+ const files = [];
1527
+ const agents = ast.nodes.filter((n) => n.type === "agent");
1528
+ for (const agent of agents) {
1529
+ const lines = [];
1530
+ // name
1531
+ lines.push(`name = ${tomlString(agent.id)}`);
1532
+ // description
1533
+ const desc = agent.description
1534
+ ?? ast.roles[agent.role ?? ""]
1535
+ ?? ast.roles[agent.id]
1536
+ ?? agent.role
1537
+ ?? toTitle(agent.id);
1538
+ lines.push(`description = ${tomlString(desc)}`);
1539
+ // developer_instructions — use prompt if available, otherwise construct
1540
+ const instructions = agent.prompt ?? buildDeveloperInstructions(agent, ast);
1541
+ lines.push(`developer_instructions = """\n${instructions}\n"""`);
1542
+ // model
1543
+ if (agent.model) {
1544
+ lines.push(`model = ${tomlString(agent.model)}`);
1545
+ }
1546
+ // sandbox_mode
1547
+ if (agent.sandbox != null) {
1548
+ lines.push(`sandbox_mode = ${tomlString(mapAgentSandbox(agent.sandbox))}`);
1549
+ }
1550
+ // model_reasoning_effort
1551
+ if (agent.thinking) {
1552
+ const effort = mapReasoningEffort(agent.thinking);
1553
+ if (effort) {
1554
+ lines.push(`model_reasoning_effort = ${tomlString(effort)}`);
1555
+ }
1556
+ }
1557
+ // nickname_candidates — derive from agent id
1558
+ const nickname = agent.id
1559
+ .split(/[-_]/)
1560
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
1561
+ .join("");
1562
+ lines.push(`nickname_candidates = [${tomlString(nickname)}]`);
1563
+ // MCP servers — include sections for agent-specific MCP servers
1564
+ if (agent.mcpServers && agent.mcpServers.length > 0 && ast.mcpServers) {
1565
+ lines.push("");
1566
+ for (const serverName of agent.mcpServers) {
1567
+ const config = ast.mcpServers[serverName];
1568
+ if (!config)
1569
+ continue;
1570
+ lines.push(`[mcp_servers.${serverName}]`);
1571
+ for (const [ck, cv] of Object.entries(config)) {
1572
+ if (typeof cv === "string") {
1573
+ lines.push(`${ck} = ${tomlString(cv)}`);
1574
+ }
1575
+ else if (typeof cv === "boolean") {
1576
+ lines.push(`${ck} = ${cv}`);
1577
+ }
1578
+ else if (typeof cv === "number") {
1579
+ lines.push(`${ck} = ${cv}`);
1580
+ }
1581
+ else if (Array.isArray(cv)) {
1582
+ lines.push(`${ck} = ${tomlStringArray(cv.map(String))}`);
1583
+ }
1584
+ }
1585
+ lines.push("");
1586
+ }
1587
+ }
1588
+ files.push({
1589
+ path: `.codex/agents/${agent.id}.toml`,
1590
+ content: lines.join("\n") + "\n",
1591
+ category: "agent",
1592
+ });
1593
+ }
1594
+ return files;
1595
+ }
1596
+ // ---------------------------------------------------------------------------
1597
+ // Binding export
1598
+ // ---------------------------------------------------------------------------
1599
+ /** OpenAI Codex CLI binding — full implementation. */
1600
+ export const codexBinding = {
1601
+ name: "codex",
1602
+ description: "OpenAI Codex CLI — generates .codex/config.toml, AGENTS.md, .codex/ directory structure, and scripts.",
1603
+ scaffold(ast) {
1604
+ const files = [];
1605
+ // 1. .codex/config.toml — main configuration
1606
+ files.push(generateCodexToml(ast));
1607
+ // 2. AGENTS.md — agent instructions with roles, flow, memory paths
1608
+ // (Codex reads AGENTS.md from workspace root; no separate instructions.md)
1609
+ files.push(generateAgentsMd(ast));
1610
+ // 3. Per-agent TOML files under .codex/agents/
1611
+ files.push(...generateAgentTomlFiles(ast));
1612
+ // 4. Memory directories
1613
+ files.push(...generateMemory(ast));
1614
+ // 5. Skill documentation
1615
+ files.push(...generateSkills(ast));
1616
+ // 6. Gate scripts
1617
+ files.push(...generateGateScripts(ast));
1618
+ // 7. Hook scripts
1619
+ files.push(...generateHookScripts(ast));
1620
+ // 8. Tool scripts
1621
+ files.push(...generateToolScripts(ast));
1622
+ // 9. Workspace protocol
1623
+ const wsProtocol = generateWorkspaceProtocol(ast);
1624
+ if (wsProtocol)
1625
+ files.push(wsProtocol);
1626
+ // 10. Metering
1627
+ const meteringFile = generateMetering(ast);
1628
+ if (meteringFile)
1629
+ files.push(meteringFile);
1630
+ return deduplicateFiles(files);
1631
+ },
1632
+ };
1633
+ //# sourceMappingURL=codex.js.map