@sireai/optimus 0.1.1

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 (204) hide show
  1. package/.env.example +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +104 -0
  4. package/dist/cli/optimus.d.ts +2 -0
  5. package/dist/cli/optimus.js +2951 -0
  6. package/dist/cli/optimus.js.map +1 -0
  7. package/dist/cli/self-update.d.ts +49 -0
  8. package/dist/cli/self-update.js +264 -0
  9. package/dist/cli/self-update.js.map +1 -0
  10. package/dist/config/load-config.d.ts +3 -0
  11. package/dist/config/load-config.js +321 -0
  12. package/dist/config/load-config.js.map +1 -0
  13. package/dist/config/optimus-paths.d.ts +13 -0
  14. package/dist/config/optimus-paths.js +44 -0
  15. package/dist/config/optimus-paths.js.map +1 -0
  16. package/dist/index.d.ts +25 -0
  17. package/dist/index.js +27 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/integrations/jira/jira-cli.d.ts +1 -0
  20. package/dist/integrations/jira/jira-cli.js +278 -0
  21. package/dist/integrations/jira/jira-cli.js.map +1 -0
  22. package/dist/integrations/jira/jira-client.d.ts +99 -0
  23. package/dist/integrations/jira/jira-client.js +521 -0
  24. package/dist/integrations/jira/jira-client.js.map +1 -0
  25. package/dist/integrations/jira/jira-submit.d.ts +71 -0
  26. package/dist/integrations/jira/jira-submit.js +351 -0
  27. package/dist/integrations/jira/jira-submit.js.map +1 -0
  28. package/dist/problem-solving-core/codex/codex-auth-resolver.d.ts +23 -0
  29. package/dist/problem-solving-core/codex/codex-auth-resolver.js +136 -0
  30. package/dist/problem-solving-core/codex/codex-auth-resolver.js.map +1 -0
  31. package/dist/problem-solving-core/codex/codex-connectivity-checks.d.ts +6 -0
  32. package/dist/problem-solving-core/codex/codex-connectivity-checks.js +81 -0
  33. package/dist/problem-solving-core/codex/codex-connectivity-checks.js.map +1 -0
  34. package/dist/problem-solving-core/codex/codex-failure-classifier.d.ts +2 -0
  35. package/dist/problem-solving-core/codex/codex-failure-classifier.js +49 -0
  36. package/dist/problem-solving-core/codex/codex-failure-classifier.js.map +1 -0
  37. package/dist/problem-solving-core/codex/codex-global-config.d.ts +17 -0
  38. package/dist/problem-solving-core/codex/codex-global-config.js +100 -0
  39. package/dist/problem-solving-core/codex/codex-global-config.js.map +1 -0
  40. package/dist/problem-solving-core/codex/codex-preflight.d.ts +13 -0
  41. package/dist/problem-solving-core/codex/codex-preflight.js +142 -0
  42. package/dist/problem-solving-core/codex/codex-preflight.js.map +1 -0
  43. package/dist/problem-solving-core/codex/codex-provider-profile.d.ts +14 -0
  44. package/dist/problem-solving-core/codex/codex-provider-profile.js +68 -0
  45. package/dist/problem-solving-core/codex/codex-provider-profile.js.map +1 -0
  46. package/dist/problem-solving-core/codex/codex-required-env.d.ts +3 -0
  47. package/dist/problem-solving-core/codex/codex-required-env.js +21 -0
  48. package/dist/problem-solving-core/codex/codex-required-env.js.map +1 -0
  49. package/dist/problem-solving-core/codex/codex-runner.d.ts +37 -0
  50. package/dist/problem-solving-core/codex/codex-runner.js +926 -0
  51. package/dist/problem-solving-core/codex/codex-runner.js.map +1 -0
  52. package/dist/problem-solving-core/codex/evolution-skill-guard.d.ts +36 -0
  53. package/dist/problem-solving-core/codex/evolution-skill-guard.js +143 -0
  54. package/dist/problem-solving-core/codex/evolution-skill-guard.js.map +1 -0
  55. package/dist/problem-solving-core/codex/repo-memory-service.d.ts +24 -0
  56. package/dist/problem-solving-core/codex/repo-memory-service.js +114 -0
  57. package/dist/problem-solving-core/codex/repo-memory-service.js.map +1 -0
  58. package/dist/problem-solving-core/codex/skill-sync-service.d.ts +35 -0
  59. package/dist/problem-solving-core/codex/skill-sync-service.js +280 -0
  60. package/dist/problem-solving-core/codex/skill-sync-service.js.map +1 -0
  61. package/dist/task-environment/cancellation/task-abort-registry.d.ts +17 -0
  62. package/dist/task-environment/cancellation/task-abort-registry.js +51 -0
  63. package/dist/task-environment/cancellation/task-abort-registry.js.map +1 -0
  64. package/dist/task-environment/cancellation/task-cancellation-service.d.ts +25 -0
  65. package/dist/task-environment/cancellation/task-cancellation-service.js +54 -0
  66. package/dist/task-environment/cancellation/task-cancellation-service.js.map +1 -0
  67. package/dist/task-environment/cancellation/task-cleanup-service.d.ts +22 -0
  68. package/dist/task-environment/cancellation/task-cleanup-service.js +67 -0
  69. package/dist/task-environment/cancellation/task-cleanup-service.js.map +1 -0
  70. package/dist/task-environment/delivery/commit-message/bugfix-commit-message-template.d.ts +13 -0
  71. package/dist/task-environment/delivery/commit-message/bugfix-commit-message-template.js +83 -0
  72. package/dist/task-environment/delivery/commit-message/bugfix-commit-message-template.js.map +1 -0
  73. package/dist/task-environment/delivery/commit-message/commit-message-builder.d.ts +6 -0
  74. package/dist/task-environment/delivery/commit-message/commit-message-builder.js +15 -0
  75. package/dist/task-environment/delivery/commit-message/commit-message-builder.js.map +1 -0
  76. package/dist/task-environment/delivery/commit-message/commit-message-template-types.d.ts +16 -0
  77. package/dist/task-environment/delivery/commit-message/commit-message-template-types.js +2 -0
  78. package/dist/task-environment/delivery/commit-message/commit-message-template-types.js.map +1 -0
  79. package/dist/task-environment/delivery/feishu-analysis-doc-service.d.ts +50 -0
  80. package/dist/task-environment/delivery/feishu-analysis-doc-service.js +454 -0
  81. package/dist/task-environment/delivery/feishu-analysis-doc-service.js.map +1 -0
  82. package/dist/task-environment/delivery/feishu-card-renderer.d.ts +38 -0
  83. package/dist/task-environment/delivery/feishu-card-renderer.js +449 -0
  84. package/dist/task-environment/delivery/feishu-card-renderer.js.map +1 -0
  85. package/dist/task-environment/delivery/feishu-content/feishu-content-renderer.d.ts +34 -0
  86. package/dist/task-environment/delivery/feishu-content/feishu-content-renderer.js +201 -0
  87. package/dist/task-environment/delivery/feishu-content/feishu-content-renderer.js.map +1 -0
  88. package/dist/task-environment/delivery/feishu-content/feishu-copy-config.d.ts +27 -0
  89. package/dist/task-environment/delivery/feishu-content/feishu-copy-config.js +74 -0
  90. package/dist/task-environment/delivery/feishu-content/feishu-copy-config.js.map +1 -0
  91. package/dist/task-environment/delivery/feishu-notifier.d.ts +45 -0
  92. package/dist/task-environment/delivery/feishu-notifier.js +250 -0
  93. package/dist/task-environment/delivery/feishu-notifier.js.map +1 -0
  94. package/dist/task-environment/delivery/feishu-templates/analysis-message-template.d.ts +6 -0
  95. package/dist/task-environment/delivery/feishu-templates/analysis-message-template.js +39 -0
  96. package/dist/task-environment/delivery/feishu-templates/analysis-message-template.js.map +1 -0
  97. package/dist/task-environment/delivery/feishu-templates/bugfix-message-template.d.ts +6 -0
  98. package/dist/task-environment/delivery/feishu-templates/bugfix-message-template.js +40 -0
  99. package/dist/task-environment/delivery/feishu-templates/bugfix-message-template.js.map +1 -0
  100. package/dist/task-environment/delivery/feishu-templates/default-message-template.d.ts +6 -0
  101. package/dist/task-environment/delivery/feishu-templates/default-message-template.js +33 -0
  102. package/dist/task-environment/delivery/feishu-templates/default-message-template.js.map +1 -0
  103. package/dist/task-environment/delivery/feishu-templates/patch-message-template.d.ts +6 -0
  104. package/dist/task-environment/delivery/feishu-templates/patch-message-template.js +40 -0
  105. package/dist/task-environment/delivery/feishu-templates/patch-message-template.js.map +1 -0
  106. package/dist/task-environment/delivery/feishu-templates/template-registry.d.ts +2 -0
  107. package/dist/task-environment/delivery/feishu-templates/template-registry.js +11 -0
  108. package/dist/task-environment/delivery/feishu-templates/template-registry.js.map +1 -0
  109. package/dist/task-environment/delivery/feishu-templates/template-types.d.ts +20 -0
  110. package/dist/task-environment/delivery/feishu-templates/template-types.js +2 -0
  111. package/dist/task-environment/delivery/feishu-templates/template-types.js.map +1 -0
  112. package/dist/task-environment/delivery/task-delivery-dispatcher.d.ts +14 -0
  113. package/dist/task-environment/delivery/task-delivery-dispatcher.js +109 -0
  114. package/dist/task-environment/delivery/task-delivery-dispatcher.js.map +1 -0
  115. package/dist/task-environment/delivery/task-delivery-service.d.ts +33 -0
  116. package/dist/task-environment/delivery/task-delivery-service.js +432 -0
  117. package/dist/task-environment/delivery/task-delivery-service.js.map +1 -0
  118. package/dist/task-environment/delivery/task-publication-service.d.ts +97 -0
  119. package/dist/task-environment/delivery/task-publication-service.js +1369 -0
  120. package/dist/task-environment/delivery/task-publication-service.js.map +1 -0
  121. package/dist/task-environment/execution-addresses.d.ts +40 -0
  122. package/dist/task-environment/execution-addresses.js +63 -0
  123. package/dist/task-environment/execution-addresses.js.map +1 -0
  124. package/dist/task-environment/intake/cli-file-intake.d.ts +12 -0
  125. package/dist/task-environment/intake/cli-file-intake.js +56 -0
  126. package/dist/task-environment/intake/cli-file-intake.js.map +1 -0
  127. package/dist/task-environment/intake/manual-problem-intake.d.ts +3 -0
  128. package/dist/task-environment/intake/manual-problem-intake.js +57 -0
  129. package/dist/task-environment/intake/manual-problem-intake.js.map +1 -0
  130. package/dist/task-environment/intake/polling-problem-intake.d.ts +14 -0
  131. package/dist/task-environment/intake/polling-problem-intake.js +232 -0
  132. package/dist/task-environment/intake/polling-problem-intake.js.map +1 -0
  133. package/dist/task-environment/observability/logger.d.ts +76 -0
  134. package/dist/task-environment/observability/logger.js +604 -0
  135. package/dist/task-environment/observability/logger.js.map +1 -0
  136. package/dist/task-environment/observability/runtime-panel.d.ts +82 -0
  137. package/dist/task-environment/observability/runtime-panel.js +1008 -0
  138. package/dist/task-environment/observability/runtime-panel.js.map +1 -0
  139. package/dist/task-environment/observability/sound-notifier.d.ts +18 -0
  140. package/dist/task-environment/observability/sound-notifier.js +71 -0
  141. package/dist/task-environment/observability/sound-notifier.js.map +1 -0
  142. package/dist/task-environment/orchestration/execution-context-assembler.d.ts +41 -0
  143. package/dist/task-environment/orchestration/execution-context-assembler.js +464 -0
  144. package/dist/task-environment/orchestration/execution-context-assembler.js.map +1 -0
  145. package/dist/task-environment/orchestration/git-change-classifier.d.ts +19 -0
  146. package/dist/task-environment/orchestration/git-change-classifier.js +106 -0
  147. package/dist/task-environment/orchestration/git-change-classifier.js.map +1 -0
  148. package/dist/task-environment/orchestration/harness-registry.d.ts +27 -0
  149. package/dist/task-environment/orchestration/harness-registry.js +116 -0
  150. package/dist/task-environment/orchestration/harness-registry.js.map +1 -0
  151. package/dist/task-environment/orchestration/harness-resolver.d.ts +8 -0
  152. package/dist/task-environment/orchestration/harness-resolver.js +39 -0
  153. package/dist/task-environment/orchestration/harness-resolver.js.map +1 -0
  154. package/dist/task-environment/orchestration/task-orchestrator.d.ts +45 -0
  155. package/dist/task-environment/orchestration/task-orchestrator.js +1122 -0
  156. package/dist/task-environment/orchestration/task-orchestrator.js.map +1 -0
  157. package/dist/task-environment/orchestration/task-package-assembler.d.ts +4 -0
  158. package/dist/task-environment/orchestration/task-package-assembler.js +10 -0
  159. package/dist/task-environment/orchestration/task-package-assembler.js.map +1 -0
  160. package/dist/task-environment/orchestration/triage-agent.d.ts +54 -0
  161. package/dist/task-environment/orchestration/triage-agent.js +636 -0
  162. package/dist/task-environment/orchestration/triage-agent.js.map +1 -0
  163. package/dist/task-environment/orchestration/triage-runner.d.ts +65 -0
  164. package/dist/task-environment/orchestration/triage-runner.js +655 -0
  165. package/dist/task-environment/orchestration/triage-runner.js.map +1 -0
  166. package/dist/task-environment/publication-target.d.ts +12 -0
  167. package/dist/task-environment/publication-target.js +174 -0
  168. package/dist/task-environment/publication-target.js.map +1 -0
  169. package/dist/task-environment/runtime/blocking-event-queue.d.ts +7 -0
  170. package/dist/task-environment/runtime/blocking-event-queue.js +27 -0
  171. package/dist/task-environment/runtime/blocking-event-queue.js.map +1 -0
  172. package/dist/task-environment/runtime/optimus-runtime.d.ts +69 -0
  173. package/dist/task-environment/runtime/optimus-runtime.js +751 -0
  174. package/dist/task-environment/runtime/optimus-runtime.js.map +1 -0
  175. package/dist/task-environment/storage/sqlite-event-store.d.ts +52 -0
  176. package/dist/task-environment/storage/sqlite-event-store.js +288 -0
  177. package/dist/task-environment/storage/sqlite-event-store.js.map +1 -0
  178. package/dist/task-environment/storage/sqlite-task-store.d.ts +122 -0
  179. package/dist/task-environment/storage/sqlite-task-store.js +1182 -0
  180. package/dist/task-environment/storage/sqlite-task-store.js.map +1 -0
  181. package/dist/types.d.ts +629 -0
  182. package/dist/types.js +2 -0
  183. package/dist/types.js.map +1 -0
  184. package/embedded-skills/shared/repo-inspection/SKILL.md +9 -0
  185. package/embedded-skills/shared/repo-inspection/skill.json +5 -0
  186. package/embedded-skills/task/bugfix/android-debug-protocol/SKILL.md +10 -0
  187. package/embedded-skills/task/bugfix/android-debug-protocol/skill.json +6 -0
  188. package/harness/AGENTS.md +30 -0
  189. package/harness/CHECKLIST.md +44 -0
  190. package/harness/CONSTRAINTS.md +60 -0
  191. package/harness/FRAMEWORK.md +28 -0
  192. package/harness/GOAL.md +28 -0
  193. package/harness/HANDOFF.md +45 -0
  194. package/harness/TASK_PLAN.md +79 -0
  195. package/optimus.config.template.json +34 -0
  196. package/package.json +109 -0
  197. package/task-harnesses/bugfix/ACCEPT.md +47 -0
  198. package/task-harnesses/bugfix/CONSTRAINTS.md +46 -0
  199. package/task-harnesses/bugfix/CONTEXT.md +29 -0
  200. package/task-harnesses/bugfix/EVOLUTION.md +82 -0
  201. package/task-harnesses/bugfix/ROLE.md +29 -0
  202. package/task-harnesses/bugfix/STANDARD.md +250 -0
  203. package/task-harnesses/bugfix/manifest.json +13 -0
  204. package/task-harnesses/registry.json +8 -0
@@ -0,0 +1,926 @@
1
+ // CodexRunner is the execution bridge between Optimus task orchestration and the Codex SDK.
2
+ // It prepares runtime-owned CODEX_HOME state, builds thread options and prompts, streams SDK events into task states, and validates structured results.
3
+ // This module owns Codex execution behavior, but task claiming, harness selection, and persistence stay outside in orchestration/store layers.
4
+ import { mkdir, writeFile, readFile, access } from "node:fs/promises";
5
+ import { join, resolve, relative, sep, posix as posixPath } from "node:path";
6
+ import { Codex } from "@openai/codex-sdk";
7
+ import { buildExecutionContextPrompt, buildExecutionTaskPackage, getExecutionAdditionalDirectories } from "../../task-environment/execution-addresses.js";
8
+ import { OptimusLogger } from "../../task-environment/observability/logger.js";
9
+ import { CodexAuthResolver } from "./codex-auth-resolver.js";
10
+ import { buildCodexConnectivityChecks } from "./codex-connectivity-checks.js";
11
+ import { renderRuntimeCodexHomeConfig, resolveConfiguredCodexModelProvider, resolveEffectiveCodexModel } from "./codex-provider-profile.js";
12
+ import { CodexPreflight } from "./codex-preflight.js";
13
+ import { classifyCodexFailureCategory } from "./codex-failure-classifier.js";
14
+ function normalizePromptValue(value) {
15
+ const trimmed = value?.trim();
16
+ if (!trimmed) {
17
+ return undefined;
18
+ }
19
+ return trimmed.replace(/\r\n/g, "\n");
20
+ }
21
+ function escapeRegExp(value) {
22
+ return value.replace(/[.*+?^$()|[\]{}]/g, "\\$&");
23
+ }
24
+ function resolveTaskResultPath(resultPath, artifactDir) {
25
+ if (resultPath.startsWith("/")) {
26
+ return resolve(resultPath);
27
+ }
28
+ return resolve(artifactDir, resultPath);
29
+ }
30
+ class CodexExecutionError extends Error {
31
+ failureCategory;
32
+ constructor(message, failureCategory) {
33
+ super(message);
34
+ this.name = "CodexExecutionError";
35
+ this.failureCategory = failureCategory;
36
+ }
37
+ }
38
+ export class CodexRunner {
39
+ config;
40
+ logger;
41
+ constructor(config) {
42
+ this.config = config;
43
+ this.logger = new OptimusLogger(config);
44
+ }
45
+ async runTask(request, context) {
46
+ await this.logger.info("optimus.runner.task_started", {
47
+ taskId: request.task.taskId,
48
+ taskType: request.task.taskType,
49
+ runId: request.task.activeRunId,
50
+ status: request.task.status,
51
+ stage: "runner_start",
52
+ sandboxMode: context.sandboxMode,
53
+ approvalPolicy: context.approvalPolicy
54
+ });
55
+ const preflight = await new CodexPreflight(this.config).run();
56
+ if (!preflight.ok) {
57
+ throw new CodexExecutionError(preflight.summary, preflight.failureCategory ?? "auth_missing");
58
+ }
59
+ const result = await this.runWithCodexSdk(request, context);
60
+ await this.logger.info("optimus.runner.task_finished", {
61
+ taskId: request.task.taskId,
62
+ taskType: request.task.taskType,
63
+ runId: request.task.activeRunId,
64
+ status: result.status,
65
+ stage: "runner_finish"
66
+ });
67
+ return result;
68
+ }
69
+ async runHealthCheck() {
70
+ const effectiveModel = resolveEffectiveCodexModel(this.config);
71
+ const effectiveProvider = resolveConfiguredCodexModelProvider(this.config);
72
+ const diagnosticsBase = {
73
+ codexHomeDir: this.config.codex.homeDir,
74
+ timeoutMs: this.config.runtime.startupTimeoutMs,
75
+ approvalPolicy: this.config.codex.approvalPolicy,
76
+ sandboxMode: this.config.codex.sandboxMode
77
+ };
78
+ if (effectiveModel) {
79
+ diagnosticsBase.model = effectiveModel;
80
+ }
81
+ if (effectiveProvider) {
82
+ diagnosticsBase.provider = effectiveProvider;
83
+ }
84
+ try {
85
+ const preflight = await new CodexPreflight(this.config).run();
86
+ if (!preflight.ok) {
87
+ return {
88
+ ok: false,
89
+ summary: preflight.summary,
90
+ diagnostics: {
91
+ ...diagnosticsBase,
92
+ auth: preflight.auth,
93
+ preflightChecks: preflight.checks,
94
+ connectivityChecks: buildCodexConnectivityChecks({
95
+ config: this.config,
96
+ failureMessage: preflight.summary,
97
+ failureCategory: preflight.failureCategory
98
+ }),
99
+ ...(preflight.failureCategory ? { failureCategory: preflight.failureCategory } : {})
100
+ }
101
+ };
102
+ }
103
+ await this.ensureCodexHome();
104
+ const codex = this.createCodexClient();
105
+ const thread = codex.startThread(this.buildThreadOptions({
106
+ workingDirectory: process.cwd(),
107
+ sandboxMode: this.config.codex.sandboxMode,
108
+ approvalPolicy: this.config.codex.approvalPolicy
109
+ }));
110
+ const turn = await thread.run([{ type: "text", text: "hello" }], {
111
+ signal: this.createExecutionSignal(undefined, this.config.runtime.startupTimeoutMs)
112
+ });
113
+ const healthCheckOutput = this.parseHealthCheckResponse(turn.finalResponse);
114
+ const auth = new CodexAuthResolver(this.config).resolve();
115
+ return {
116
+ ok: true,
117
+ summary: "Codex SDK health-check passed.",
118
+ diagnostics: {
119
+ ...diagnosticsBase,
120
+ auth: auth.diagnostics,
121
+ preflightChecks: preflight.checks,
122
+ connectivityChecks: buildCodexConnectivityChecks({
123
+ config: this.config,
124
+ failureCategory: undefined
125
+ })
126
+ },
127
+ rawOutput: healthCheckOutput
128
+ };
129
+ }
130
+ catch (error) {
131
+ const auth = new CodexAuthResolver(this.config).resolve();
132
+ const preflight = await new CodexPreflight(this.config).run();
133
+ const failureMessage = error instanceof Error ? error.message : "Unknown Codex SDK health-check failure";
134
+ const failureCategory = this.resolveFailureCategory(error) ?? "unknown";
135
+ return {
136
+ ok: false,
137
+ summary: failureMessage,
138
+ diagnostics: {
139
+ ...diagnosticsBase,
140
+ auth: auth.diagnostics,
141
+ preflightChecks: preflight.checks,
142
+ connectivityChecks: buildCodexConnectivityChecks({
143
+ config: this.config,
144
+ failureMessage,
145
+ failureCategory
146
+ }),
147
+ failureCategory
148
+ }
149
+ };
150
+ }
151
+ }
152
+ async runWithCodexSdk(request, context) {
153
+ await this.ensureCodexHome();
154
+ const bundle = this.loadTaskHarnessDocumentSet(request);
155
+ const prompt = this.buildPrompt(request, bundle, context);
156
+ const promptText = prompt.map((item) => item.text).join("\n\n");
157
+ await this.logger.debug("optimus.runner.execution_prompt", {
158
+ taskId: request.task.taskId,
159
+ taskType: request.task.taskType,
160
+ runId: request.task.activeRunId,
161
+ stage: "execution_prompt",
162
+ prompt: promptText
163
+ });
164
+ const codex = this.createCodexClient();
165
+ const threadOptions = this.buildThreadOptions({
166
+ workingDirectory: context.addresses.workspaceDir,
167
+ sandboxMode: context.sandboxMode,
168
+ approvalPolicy: context.approvalPolicy
169
+ });
170
+ const additionalDirectories = getExecutionAdditionalDirectories(context.addresses);
171
+ if (additionalDirectories.length > 0) {
172
+ threadOptions.additionalDirectories = additionalDirectories;
173
+ }
174
+ const thread = codex.startThread(threadOptions);
175
+ await this.logger.debug("optimus.runner.prepared", {
176
+ taskId: request.task.taskId,
177
+ taskType: request.harness.taskType,
178
+ sourceEventId: request.task.sourceEventId,
179
+ runId: request.task.activeRunId,
180
+ workspaceDir: context.addresses.workspaceDir,
181
+ sandboxMode: context.sandboxMode,
182
+ approvalPolicy: context.approvalPolicy,
183
+ workspaceSkillMountDir: context.workspaceSkillMountDir ?? null,
184
+ mountedSkillCount: context.mountedSkills?.length ?? 0
185
+ });
186
+ const turnOptions = {
187
+ outputSchema: this.buildOutputSchema()
188
+ };
189
+ if (context.abortSignal) {
190
+ turnOptions.signal = context.abortSignal;
191
+ }
192
+ const { events } = await thread.runStreamed(prompt, turnOptions);
193
+ const heartbeatAt = () => new Date().toISOString();
194
+ const turnCollection = {
195
+ agentMessages: [],
196
+ sawTurnCompleted: false
197
+ };
198
+ for await (const event of events) {
199
+ await this.logger.debug("optimus.runner.stream_event", {
200
+ taskId: request.task.taskId,
201
+ taskType: request.task.taskType,
202
+ runId: request.task.activeRunId,
203
+ ...(thread.id ? { sdkThreadId: thread.id } : {}),
204
+ stage: "stream_event",
205
+ eventType: event.type,
206
+ ...(event.type.startsWith("item.") && "item" in event ? { itemType: event.item.type } : {}),
207
+ headline: this.describeStreamEvent(event),
208
+ detail: this.describeStreamEventDetail(event),
209
+ event
210
+ });
211
+ await context.onStatusUpdate?.({
212
+ status: "running",
213
+ detail: `Codex stream event received: ${event.type}.`,
214
+ runStatus: "sdk_streaming",
215
+ health: "healthy_running",
216
+ eventType: `stream.${event.type}`,
217
+ ...(thread.id ? { sdkThreadId: thread.id } : {}),
218
+ heartbeatAt: heartbeatAt()
219
+ });
220
+ if (event.type === "turn.started") {
221
+ await context.onStatusUpdate?.({
222
+ status: "running",
223
+ detail: "Codex turn started.",
224
+ runStatus: "sdk_running",
225
+ health: "healthy_running",
226
+ eventType: event.type,
227
+ ...(thread.id ? { sdkThreadId: thread.id } : {}),
228
+ incrementProgressCounter: true,
229
+ heartbeatAt: heartbeatAt()
230
+ });
231
+ }
232
+ if (event.type === "turn.completed") {
233
+ turnCollection.sawTurnCompleted = true;
234
+ }
235
+ if (event.type === "item.completed") {
236
+ const detail = this.describeCompletedItem(event.item);
237
+ if (detail) {
238
+ await context.onStatusUpdate?.({
239
+ ...detail,
240
+ runStatus: event.item.type === "agent_message" ? "validating" : "sdk_streaming",
241
+ health: "healthy_running",
242
+ eventType: event.item.type,
243
+ ...(thread.id ? { sdkThreadId: thread.id } : {}),
244
+ incrementProgressCounter: true,
245
+ incrementCommandCount: event.item.type === "command_execution",
246
+ incrementFileChangeCount: event.item.type === "file_change",
247
+ ...(event.item.type === "agent_message" ? { agentMessagePreview: event.item.text.slice(0, 160) } : {}),
248
+ heartbeatAt: heartbeatAt()
249
+ });
250
+ }
251
+ if (event.item.type === "agent_message") {
252
+ turnCollection.agentMessages.push(event.item.text);
253
+ }
254
+ }
255
+ if (event.type === "turn.failed") {
256
+ await this.logger.error("optimus.runner.turn_failed", {
257
+ taskId: request.task.taskId,
258
+ taskType: request.task.taskType,
259
+ runId: request.task.activeRunId,
260
+ ...(thread.id ? { sdkThreadId: thread.id } : {}),
261
+ stage: "turn_failed",
262
+ reason: event.error.message
263
+ });
264
+ throw new CodexExecutionError(event.error.message, this.classifyFailureCategory(event.error.message));
265
+ }
266
+ if (event.type === "error") {
267
+ await this.logger.error("optimus.runner.turn_failed", {
268
+ taskId: request.task.taskId,
269
+ taskType: request.task.taskType,
270
+ runId: request.task.activeRunId,
271
+ ...(thread.id ? { sdkThreadId: thread.id } : {}),
272
+ stage: "stream_error",
273
+ reason: event.message
274
+ });
275
+ throw new CodexExecutionError(event.message, this.classifyFailureCategory(event.message));
276
+ }
277
+ }
278
+ const latestAgentMessage = turnCollection.agentMessages.at(-1) ?? "";
279
+ const rawResponsePath = await this.writeDebugResponseArtifact(context.addresses.artifactDir, request.task.activeRunId ?? request.task.taskId, "raw-response.json", latestAgentMessage);
280
+ const agentMessagesPath = await this.writeDebugResponseArtifact(context.addresses.artifactDir, request.task.activeRunId ?? request.task.taskId, "agent-messages.json", JSON.stringify(turnCollection.agentMessages, null, 2));
281
+ await this.logger.debug("optimus.runner.raw_response", {
282
+ taskId: request.task.taskId,
283
+ taskType: request.task.taskType,
284
+ runId: request.task.activeRunId,
285
+ ...(thread.id ? { sdkThreadId: thread.id } : {}),
286
+ stage: "raw_response",
287
+ sawTurnCompleted: turnCollection.sawTurnCompleted,
288
+ agentMessageCount: turnCollection.agentMessages.length,
289
+ responsePreview: this.previewResponse(latestAgentMessage, 4000),
290
+ response: latestAgentMessage,
291
+ responseArtifactPath: rawResponsePath,
292
+ agentMessagesArtifactPath: agentMessagesPath
293
+ });
294
+ const resolvedTurnResult = await this.resolveTurnResult(turnCollection, context, {
295
+ taskId: request.task.taskId,
296
+ taskType: request.task.taskType,
297
+ ...(request.task.activeRunId ? { runId: request.task.activeRunId } : {}),
298
+ ...(thread.id ? { sdkThreadId: thread.id } : {})
299
+ });
300
+ const parsed = resolvedTurnResult.response;
301
+ const normalizedResponseText = JSON.stringify(parsed, null, 2);
302
+ const parsedResponsePath = await this.writeDebugResponseArtifact(context.addresses.artifactDir, request.task.activeRunId ?? request.task.taskId, "parsed-response.json", normalizedResponseText);
303
+ await this.logger.debug("optimus.runner.parsed_response", {
304
+ taskId: request.task.taskId,
305
+ taskType: request.task.taskType,
306
+ runId: request.task.activeRunId,
307
+ ...(thread.id ? { sdkThreadId: thread.id } : {}),
308
+ stage: "parsed_response",
309
+ status: parsed.status,
310
+ ...(parsed.resultPath ? { resultPath: parsed.resultPath } : {}),
311
+ ...(parsed.error?.category ? { errorCategory: parsed.error.category } : {}),
312
+ parsedResponse: normalizedResponseText,
313
+ responseArtifactPath: parsedResponsePath,
314
+ usedFallback: resolvedTurnResult.diagnostics?.usedFallback ?? false,
315
+ ...(resolvedTurnResult.diagnostics?.fallbackReason ? { fallbackReason: resolvedTurnResult.diagnostics.fallbackReason } : {})
316
+ });
317
+ await this.logger.info("optimus.runner.result_parsed", {
318
+ taskId: request.task.taskId,
319
+ taskType: request.task.taskType,
320
+ runId: request.task.activeRunId,
321
+ ...(thread.id ? { sdkThreadId: thread.id } : {}),
322
+ stage: "result_parsed",
323
+ status: parsed.status,
324
+ artifactCount: parsed.resultPath ? 1 : 0,
325
+ ...(parsed.error?.category ? { failureCategory: parsed.error.category } : {}),
326
+ resultPath: parsed.resultPath
327
+ });
328
+ if (parsed.resultPath) {
329
+ const resolvedResultPath = resolveTaskResultPath(parsed.resultPath, context.addresses.artifactDir);
330
+ const resultDocument = await this.normalizeResultDocumentPaths(resolvedResultPath, context);
331
+ await this.logger.debug("optimus.runner.result_document", {
332
+ taskId: request.task.taskId,
333
+ taskType: request.task.taskType,
334
+ runId: request.task.activeRunId,
335
+ ...(thread.id ? { sdkThreadId: thread.id } : {}),
336
+ stage: "result_document",
337
+ resultPath: resolvedResultPath,
338
+ resultDocument
339
+ });
340
+ }
341
+ await this.logger.info("optimus.runner.turn_completed", {
342
+ taskId: request.task.taskId,
343
+ taskType: request.task.taskType,
344
+ runId: request.task.activeRunId,
345
+ ...(thread.id ? { sdkThreadId: thread.id } : {}),
346
+ stage: "turn_completed"
347
+ });
348
+ return {
349
+ status: parsed.status,
350
+ ...(parsed.error ? { error: parsed.error } : {}),
351
+ ...(parsed.resultPath ? { resultPath: parsed.resultPath } : {}),
352
+ diagnostics: this.createDiagnostics({
353
+ ...(thread.id ? { sdkThreadId: thread.id } : {}),
354
+ ...(resolvedTurnResult.diagnostics ?? {})
355
+ })
356
+ };
357
+ }
358
+ async resolveTurnResult(turnCollection, context, resolutionContext) {
359
+ if (!turnCollection.sawTurnCompleted) {
360
+ throw new CodexExecutionError("Codex stream ended without turn.completed.", "process_exit");
361
+ }
362
+ let lastContractError;
363
+ for (let index = turnCollection.agentMessages.length - 1; index >= 0; index -= 1) {
364
+ const candidate = turnCollection.agentMessages[index] ?? "";
365
+ if (!candidate.trim()) {
366
+ continue;
367
+ }
368
+ try {
369
+ const parsed = this.parseCodexResponse(candidate);
370
+ this.validateResultContract(parsed, context);
371
+ return {
372
+ response: parsed,
373
+ rawResponse: candidate
374
+ };
375
+ }
376
+ catch (error) {
377
+ if (error instanceof CodexExecutionError) {
378
+ lastContractError = error;
379
+ continue;
380
+ }
381
+ throw error;
382
+ }
383
+ }
384
+ const fallbackResult = await this.tryResolveResultDocumentFallback(context, resolutionContext, lastContractError);
385
+ if (fallbackResult) {
386
+ return fallbackResult;
387
+ }
388
+ if (lastContractError) {
389
+ throw lastContractError;
390
+ }
391
+ throw new CodexExecutionError("Codex turn completed without any agent_message result.", "schema_error");
392
+ }
393
+ async tryResolveResultDocumentFallback(context, resolutionContext, cause) {
394
+ const fallbackPath = join(context.addresses.artifactDir, "result.md");
395
+ try {
396
+ await access(fallbackPath);
397
+ const resultDocument = await readFile(fallbackPath, "utf8");
398
+ if (!resultDocument.trim()) {
399
+ return undefined;
400
+ }
401
+ const normalizedResultDocument = await this.normalizeResultDocumentPaths(fallbackPath, context);
402
+ await this.logger.warn("optimus.runner.result_fallback", {
403
+ taskId: resolutionContext.taskId,
404
+ taskType: resolutionContext.taskType,
405
+ runId: resolutionContext.runId,
406
+ stage: "result_fallback",
407
+ ...(resolutionContext.sdkThreadId ? { sdkThreadId: resolutionContext.sdkThreadId } : {}),
408
+ resultPath: fallbackPath,
409
+ reason: cause?.message ?? "structured_result_missing",
410
+ failureCategory: cause?.failureCategory ?? "schema_error",
411
+ resultDocument: normalizedResultDocument
412
+ });
413
+ return {
414
+ response: {
415
+ status: "completed",
416
+ resultPath: "result.md"
417
+ },
418
+ rawResponse: resultDocument,
419
+ diagnostics: {
420
+ usedFallback: true,
421
+ fallbackReason: "result_document_detected_after_invalid_structured_response",
422
+ failureCategory: cause?.failureCategory ?? "schema_error",
423
+ ...(resolutionContext.sdkThreadId ? { sdkThreadId: resolutionContext.sdkThreadId } : {})
424
+ }
425
+ };
426
+ }
427
+ catch {
428
+ return undefined;
429
+ }
430
+ }
431
+ describeStreamEventDetail(event) {
432
+ if (!(event.type.startsWith("item.") && "item" in event && event.item)) {
433
+ return undefined;
434
+ }
435
+ const itemType = typeof event.item.type === "string" ? event.item.type : undefined;
436
+ if (!itemType) {
437
+ return undefined;
438
+ }
439
+ if (itemType === "command_execution") {
440
+ const command = this.extractCommandPreview(event.item);
441
+ return command ? `cmd: ${command}` : "command execution";
442
+ }
443
+ if (itemType === "reasoning") {
444
+ const text = this.extractTextPreview(event.item.text);
445
+ return text;
446
+ }
447
+ if (itemType === "agent_message") {
448
+ const text = this.extractTextPreview(event.item.text);
449
+ return text;
450
+ }
451
+ if (itemType === "mcp_tool_call") {
452
+ const server = typeof event.item.server === "string" ? event.item.server : undefined;
453
+ const tool = typeof event.item.tool === "string" ? event.item.tool : undefined;
454
+ return [server, tool].filter(Boolean).join("/") || undefined;
455
+ }
456
+ if (itemType === "web_search") {
457
+ const query = typeof event.item.query === "string" ? event.item.query : undefined;
458
+ return this.extractTextPreview(query);
459
+ }
460
+ if (itemType === "todo_list") {
461
+ const items = Array.isArray(event.item.items) ? (event.item.items ?? []) : [];
462
+ const pending = items.filter((item) => !item?.completed).map((item) => item?.text).filter((value) => typeof value === "string" && value.length > 0);
463
+ return this.extractTextPreview(pending.join("; "));
464
+ }
465
+ if (itemType === "file_change") {
466
+ const changes = Array.isArray(event.item.changes) ? (event.item.changes ?? []) : [];
467
+ const paths = changes.slice(0, 3).map((entry) => {
468
+ const filePath = entry["path"] ?? entry["file_path"] ?? entry["new_path"] ?? entry["old_path"];
469
+ return typeof filePath === "string" ? filePath : undefined;
470
+ }).filter((value) => typeof value === "string" && value.length > 0);
471
+ return paths.length > 0 ? this.extractTextPreview(paths.join(", ")) : "file changes";
472
+ }
473
+ return undefined;
474
+ }
475
+ extractCommandPreview(item) {
476
+ const command = item["command"] ?? item["cmd"] ?? item["shell_command"];
477
+ if (typeof command === "string") {
478
+ return this.extractTextPreview(command);
479
+ }
480
+ if (Array.isArray(command)) {
481
+ return this.extractTextPreview(command.filter((value) => typeof value === "string").join(" "));
482
+ }
483
+ return undefined;
484
+ }
485
+ extractTextPreview(value) {
486
+ if (typeof value !== "string") {
487
+ return undefined;
488
+ }
489
+ const collapsed = value.replace(/\s+/g, " ").trim();
490
+ if (!collapsed) {
491
+ return undefined;
492
+ }
493
+ return collapsed.length > 120 ? `${collapsed.slice(0, 117)}...` : collapsed;
494
+ }
495
+ describeStreamEvent(event) {
496
+ if (event.type === "thread.started") {
497
+ return "Execution workspace attached.";
498
+ }
499
+ if (event.type === "turn.started") {
500
+ return "Agent started working.";
501
+ }
502
+ if (event.type === "turn.completed") {
503
+ return "Agent finished this turn.";
504
+ }
505
+ if (event.type === "turn.failed") {
506
+ return "Agent turn failed.";
507
+ }
508
+ if (event.type === "error") {
509
+ return "Execution stream failed.";
510
+ }
511
+ if (event.type === "item.started") {
512
+ switch (event.item?.type) {
513
+ case "reasoning":
514
+ return "Agent is planning.";
515
+ case "command_execution":
516
+ return "Agent started a command.";
517
+ case "mcp_tool_call":
518
+ return "Agent started a tool call.";
519
+ case "web_search":
520
+ return "Agent started a search.";
521
+ case "file_change":
522
+ return "Agent started editing files.";
523
+ case "agent_message":
524
+ return "Agent is preparing the final answer.";
525
+ case "todo_list":
526
+ return "Agent updated its plan.";
527
+ default:
528
+ return `Agent started ${event.item?.type ?? "work"}.`;
529
+ }
530
+ }
531
+ if (event.type === "item.updated") {
532
+ switch (event.item?.type) {
533
+ case "reasoning":
534
+ return "Agent is refining the plan.";
535
+ case "command_execution":
536
+ return "Agent command is running.";
537
+ case "mcp_tool_call":
538
+ return "Agent tool call is running.";
539
+ case "web_search":
540
+ return "Agent search is running.";
541
+ case "file_change":
542
+ return "Agent is updating files.";
543
+ case "todo_list":
544
+ return "Agent adjusted the plan.";
545
+ default:
546
+ return `Agent updated ${event.item?.type ?? "work"}.`;
547
+ }
548
+ }
549
+ if (event.type === "item.completed") {
550
+ switch (event.item?.type) {
551
+ case "reasoning":
552
+ return "Planning step completed.";
553
+ case "command_execution":
554
+ return "Command completed.";
555
+ case "mcp_tool_call":
556
+ return "Tool call completed.";
557
+ case "web_search":
558
+ return "Search completed.";
559
+ case "file_change":
560
+ return "File changes prepared.";
561
+ case "agent_message":
562
+ return "Final answer prepared.";
563
+ case "todo_list":
564
+ return "Plan updated.";
565
+ default:
566
+ return `${event.item?.type ?? "work"} completed.`;
567
+ }
568
+ }
569
+ return `Execution event: ${event.type}.`;
570
+ }
571
+ buildThreadOptions(input) {
572
+ const options = {
573
+ modelReasoningEffort: this.config.codex.reasoningEffort,
574
+ workingDirectory: input.workingDirectory,
575
+ skipGitRepoCheck: true,
576
+ sandboxMode: input.sandboxMode,
577
+ approvalPolicy: input.approvalPolicy,
578
+ networkAccessEnabled: this.config.runtime.networkAccessEnabled
579
+ };
580
+ const effectiveModel = resolveEffectiveCodexModel(this.config);
581
+ if (effectiveModel) {
582
+ options.model = effectiveModel;
583
+ }
584
+ if (input.additionalDirectories && input.additionalDirectories.length > 0) {
585
+ options.additionalDirectories = input.additionalDirectories;
586
+ }
587
+ return options;
588
+ }
589
+ describeCompletedItem(item) {
590
+ if (item.type === "command_execution") {
591
+ return { status: "running", detail: "Agent command execution completed." };
592
+ }
593
+ if (item.type === "file_change") {
594
+ return { status: "patch_generated", detail: "Agent generated file changes." };
595
+ }
596
+ if (item.type === "agent_message") {
597
+ return { status: "validating", detail: "Agent produced final response; validating output." };
598
+ }
599
+ return undefined;
600
+ }
601
+ // Harness documents stay ordered so the execution frame can deliver the task contract in the intended reading sequence.
602
+ loadTaskHarnessDocumentSet(request) {
603
+ return {
604
+ sections: [
605
+ {
606
+ title: request.harness.roleDocument.title,
607
+ content: request.harness.roleDocument.content
608
+ },
609
+ {
610
+ title: request.harness.constraintsDocument.title,
611
+ content: request.harness.constraintsDocument.content
612
+ },
613
+ {
614
+ title: request.harness.contextDocument.title,
615
+ content: request.harness.contextDocument.content
616
+ },
617
+ {
618
+ title: request.harness.standardDocument.title,
619
+ content: request.harness.standardDocument.content
620
+ },
621
+ {
622
+ title: request.harness.evolutionDocument.title,
623
+ content: request.harness.evolutionDocument.content
624
+ }
625
+ ]
626
+ };
627
+ }
628
+ // Prompt assembly applies a stable outer markdown hierarchy and normalizes harness-document headings to avoid flat top-level sections.
629
+ buildPrompt(request, bundle, context) {
630
+ const roleSection = this.renderExecutionFrameSection(bundle.sections[0], 2);
631
+ const constraintsSection = this.renderExecutionFrameSection(bundle.sections[1], 2);
632
+ const contextSection = this.renderExecutionFrameSection(bundle.sections[2], 2);
633
+ const standardSection = this.renderExecutionFrameSection(bundle.sections[3], 1);
634
+ const evolutionSection = this.renderExecutionFrameSection(bundle.sections[4], 1);
635
+ const promptTaskPackage = buildExecutionTaskPackage(request.taskPackage, context.addresses);
636
+ const executionContext = buildExecutionContextPrompt(context);
637
+ const repositoryMemorySection = context.repoMemory?.exists
638
+ ? [
639
+ "## REPOSITORY MEMORY\n\n",
640
+ this.demoteMarkdownHeadings((context.repoMemory.content ?? "").trim(), 2)
641
+ ].join("")
642
+ : [
643
+ "## REPOSITORY MEMORY STATUS\n\n",
644
+ `Repository memory is missing. Create or refresh it at ${context.repoMemory?.memoryFilePath ?? "<memory-path>"} before closing the task.`
645
+ ].join("");
646
+ const executionFrameCore = [
647
+ roleSection,
648
+ "\n\n",
649
+ constraintsSection,
650
+ "\n\n",
651
+ contextSection
652
+ ].join("");
653
+ const finalDeliveryRules = [
654
+ "## FINAL DELIVERY RULES\n\n",
655
+ "After writing `result.md` under `artifactDir`, return only the runtime JSON contract: `status`, optional `error`, and `resultPath`."
656
+ ].join("\n");
657
+ return [{
658
+ type: "text",
659
+ text: [
660
+ "# EXECUTION FRAME\n\n",
661
+ "Read and obey this frame before acting. Do not redo triage. Operate only as the execution agent for the accepted task.\n\n",
662
+ executionFrameCore,
663
+ "\n\n## COMPLETION STANDARD\n\n",
664
+ standardSection,
665
+ "\n\n## EVOLUTION\n\n",
666
+ evolutionSection,
667
+ "\n\n# TASK PACKAGE\n\n",
668
+ "Treat this as the task payload. repo and branch are routing facts. input.metadata is open-ended and must be consumed generically.\n\n",
669
+ "```json\n",
670
+ JSON.stringify(promptTaskPackage, null, 2),
671
+ "\n```\n\n",
672
+ "# EXECUTION CONTEXT\n\n",
673
+ "## RUNTIME\n\n",
674
+ "Use this as runtime context, not as task policy.\n\n",
675
+ "```json\n",
676
+ JSON.stringify(executionContext, null, 2),
677
+ "\n```\n\n",
678
+ repositoryMemorySection,
679
+ "\n\n",
680
+ finalDeliveryRules
681
+ ].join("")
682
+ }];
683
+ }
684
+ renderExecutionFrameSection(section, headingLevels = 1) {
685
+ if (!section) {
686
+ return "";
687
+ }
688
+ return this.normalizeHarnessMarkdown(section.title, section.content, headingLevels);
689
+ }
690
+ normalizeHarnessMarkdown(sectionTitle, content, headingLevels = 1) {
691
+ const stripped = this.stripRedundantTopHeading(sectionTitle, content.trim());
692
+ return this.demoteMarkdownHeadings(stripped, headingLevels);
693
+ }
694
+ stripRedundantTopHeading(sectionTitle, content) {
695
+ const lines = content.split(/\r?\n/);
696
+ if (lines.length === 0) {
697
+ return content;
698
+ }
699
+ const firstLine = lines[0]?.trim() ?? "";
700
+ const headingMatch = firstLine.match(/^(#{1,6})\s+(.+)$/);
701
+ if (!headingMatch) {
702
+ return content;
703
+ }
704
+ const normalizedHeading = headingMatch[2]?.trim().toLowerCase();
705
+ const normalizedSectionTitle = sectionTitle.trim().toLowerCase();
706
+ if (normalizedHeading !== normalizedSectionTitle) {
707
+ return content;
708
+ }
709
+ let startIndex = 1;
710
+ while (startIndex < lines.length && lines[startIndex]?.trim() === "") {
711
+ startIndex += 1;
712
+ }
713
+ return lines.slice(startIndex).join("\n");
714
+ }
715
+ demoteMarkdownHeadings(content, levels) {
716
+ if (!content) {
717
+ return content;
718
+ }
719
+ return content.replace(/^(#{1,6})(\s+.*)$/gm, (_match, hashes, suffix) => {
720
+ const nextLevel = Math.min(6, hashes.length + levels);
721
+ return `${"#".repeat(nextLevel)}${suffix}`;
722
+ });
723
+ }
724
+ async normalizeResultDocumentPaths(resultPath, context) {
725
+ const original = await readFile(resultPath, "utf8");
726
+ const sanitized = this.sanitizeResultDocumentPaths(original, context);
727
+ if (sanitized !== original) {
728
+ await writeFile(resultPath, sanitized, "utf8");
729
+ }
730
+ return sanitized;
731
+ }
732
+ sanitizeResultDocumentPaths(document, context) {
733
+ const roots = Array.from(new Set([context.addresses.workspaceDir, context.addresses.sourceDir]
734
+ .map((value) => resolve(value))
735
+ .sort((left, right) => right.length - left.length)));
736
+ if (roots.length === 0) {
737
+ return document;
738
+ }
739
+ const toRepoRelative = (absolutePath, lineNumber) => {
740
+ const normalizedPath = resolve(absolutePath);
741
+ for (const root of roots) {
742
+ if (normalizedPath !== root && !normalizedPath.startsWith(`${root}${sep}`)) {
743
+ continue;
744
+ }
745
+ const relativePath = relative(root, normalizedPath);
746
+ const displayPath = relativePath.length > 0 ? relativePath.split(sep).join(posixPath.sep) : ".";
747
+ return lineNumber ? `${displayPath}:L${lineNumber}` : displayPath;
748
+ }
749
+ return undefined;
750
+ };
751
+ let sanitized = document.replace(/\[([^\]]+)\]\((\/[^)\s#]+)(?:#L(\d+))?\)/g, (match, _label, absolutePath, lineNumber) => {
752
+ const repoRelative = toRepoRelative(absolutePath, lineNumber);
753
+ return repoRelative ? `\`${repoRelative}\`` : match;
754
+ });
755
+ for (const root of roots) {
756
+ const escapedRoot = escapeRegExp(root);
757
+ const pathPattern = new RegExp(`${escapedRoot}(?:\/[^\s)\]"']*)?(?:#L(\d+))?`, "g");
758
+ sanitized = sanitized.replace(pathPattern, (match, lineNumber) => {
759
+ const pathOnly = typeof lineNumber === "string" ? match.slice(0, match.length - (`#L${lineNumber}`).length) : match;
760
+ const repoRelative = toRepoRelative(pathOnly, lineNumber);
761
+ return repoRelative ?? match;
762
+ });
763
+ }
764
+ return sanitized;
765
+ }
766
+ buildOutputSchema() {
767
+ return {
768
+ type: "object",
769
+ additionalProperties: false,
770
+ // The provider currently enforces that root.required must cover every declared property
771
+ // when additionalProperties=false is used. Keep the transport schema strict-but-nullable,
772
+ // then let parseCodexResponse() apply the real status-dependent contract.
773
+ required: ["status", "error", "resultPath"],
774
+ properties: {
775
+ status: { type: "string", enum: ["completed", "failed"] },
776
+ error: {
777
+ type: ["object", "null"],
778
+ additionalProperties: false,
779
+ required: ["category", "message"],
780
+ properties: {
781
+ category: { type: "string" },
782
+ message: { type: "string" }
783
+ }
784
+ },
785
+ resultPath: { type: ["string", "null"] }
786
+ }
787
+ };
788
+ }
789
+ parseCodexResponse(response) {
790
+ let parsed;
791
+ try {
792
+ parsed = JSON.parse(response);
793
+ }
794
+ catch (error) {
795
+ throw new CodexExecutionError(`Codex response was not valid JSON: ${error instanceof Error ? error.message : "unknown parse error"}`, "parse_error");
796
+ }
797
+ if (!parsed || typeof parsed !== "object") {
798
+ throw new CodexExecutionError("Codex response schema mismatch: root is not an object.", "schema_error");
799
+ }
800
+ const responseObject = parsed;
801
+ if (responseObject.status !== "completed" && responseObject.status !== "failed") {
802
+ throw new CodexExecutionError(`Codex response schema mismatch: missing status. Raw response preview: ${this.previewResponse(response)}`, "schema_error");
803
+ }
804
+ const resultPath = typeof responseObject.resultPath === "string" && responseObject.resultPath.trim().length > 0
805
+ ? responseObject.resultPath
806
+ : undefined;
807
+ if (responseObject.status === "failed") {
808
+ if (!responseObject.error || typeof responseObject.error !== "object") {
809
+ throw new CodexExecutionError("Codex response schema mismatch: failed result requires error.", "schema_error");
810
+ }
811
+ if (typeof responseObject.error.category !== "string" || typeof responseObject.error.message !== "string") {
812
+ throw new CodexExecutionError("Codex response schema mismatch: error.category/message missing.", "schema_error");
813
+ }
814
+ return {
815
+ status: "failed",
816
+ error: {
817
+ category: responseObject.error.category,
818
+ message: responseObject.error.message
819
+ },
820
+ ...(resultPath ? { resultPath } : {})
821
+ };
822
+ }
823
+ return {
824
+ status: "completed",
825
+ ...(resultPath ? { resultPath } : {})
826
+ };
827
+ }
828
+ validateResultContract(response, context) {
829
+ if (response.status === "failed") {
830
+ if (!response.error) {
831
+ throw new CodexExecutionError("Codex result contract mismatch: failed result is missing error.", "schema_error");
832
+ }
833
+ return;
834
+ }
835
+ if (!response.resultPath) {
836
+ throw new CodexExecutionError("Codex result contract mismatch: completed result must include resultPath.", "schema_error");
837
+ }
838
+ const resolvedArtifactDir = resolve(context.addresses.artifactDir);
839
+ const resolvedResultPath = resolveTaskResultPath(response.resultPath, context.addresses.artifactDir);
840
+ const relativeResultPath = relative(resolvedArtifactDir, resolvedResultPath);
841
+ const isWithinArtifactDir = relativeResultPath.length > 0 && !relativeResultPath.startsWith(`..${sep}`) && relativeResultPath !== "..";
842
+ if (!isWithinArtifactDir) {
843
+ throw new CodexExecutionError(`Codex result contract mismatch: resultPath ${response.resultPath} is outside artifactDir ${context.addresses.artifactDir}.`, "schema_error");
844
+ }
845
+ }
846
+ previewResponse(response, limit = 600) {
847
+ const normalized = response.replace(/\s+/g, " ").trim();
848
+ return normalized.length > limit ? `${normalized.slice(0, limit - 3)}...` : normalized;
849
+ }
850
+ // Debug response artifacts preserve the full Codex reply outside the rolling log preview, which is critical for schema troubleshooting.
851
+ async writeDebugResponseArtifact(artifactDir, runId, filename, content) {
852
+ const targetPath = join(artifactDir, `${runId}-${filename}`);
853
+ await writeFile(targetPath, content, "utf8");
854
+ return targetPath;
855
+ }
856
+ parseHealthCheckResponse(response) {
857
+ if (!response.trim()) {
858
+ throw new CodexExecutionError("Codex health-check returned an empty response.", "schema_error");
859
+ }
860
+ return response;
861
+ }
862
+ createExecutionSignal(externalSignal, timeoutMs) {
863
+ const timeoutSignal = AbortSignal.timeout(timeoutMs);
864
+ if (!externalSignal) {
865
+ return timeoutSignal;
866
+ }
867
+ if (typeof AbortSignal.any === "function") {
868
+ return AbortSignal.any([externalSignal, timeoutSignal]);
869
+ }
870
+ const controller = new AbortController();
871
+ const forwardAbort = (signal) => {
872
+ if (signal.aborted) {
873
+ controller.abort(signal.reason);
874
+ return;
875
+ }
876
+ signal.addEventListener("abort", () => controller.abort(signal.reason), { once: true });
877
+ };
878
+ forwardAbort(externalSignal);
879
+ forwardAbort(timeoutSignal);
880
+ return controller.signal;
881
+ }
882
+ // Runtime CODEX_HOME keeps provider routing isolated inside the Optimus workspace.
883
+ async ensureCodexHome() {
884
+ // Runtime always exports CODEX_HOME to Codex SDK, so the private home directory
885
+ // must exist before the first SDK call even if provider config rendering is deferred.
886
+ await mkdir(this.config.codex.homeDir, { recursive: true });
887
+ const configText = renderRuntimeCodexHomeConfig(this.config);
888
+ if (!configText) {
889
+ throw new CodexExecutionError("Unable to render runtime codex-home/config.toml. Check codex.provider and codex.model.", "config_error");
890
+ }
891
+ await writeFile(join(this.config.codex.homeDir, "config.toml"), configText, "utf8");
892
+ }
893
+ createCodexClient() {
894
+ const auth = new CodexAuthResolver(this.config).resolve();
895
+ const env = { ...auth.env };
896
+ // Keep the operator's HOME untouched and scope only Codex state to the private runtime home.
897
+ env.CODEX_HOME = this.config.codex.homeDir;
898
+ const options = { env };
899
+ if (auth.baseUrl) {
900
+ options.baseUrl = auth.baseUrl;
901
+ }
902
+ if (auth.apiKey) {
903
+ options.apiKey = auth.apiKey;
904
+ }
905
+ return new Codex(options);
906
+ }
907
+ resolveFailureCategory(error) {
908
+ if (typeof error === "object" && error && "failureCategory" in error) {
909
+ const category = error.failureCategory;
910
+ if (category) {
911
+ return category;
912
+ }
913
+ }
914
+ if (error instanceof Error) {
915
+ return classifyCodexFailureCategory(error.message);
916
+ }
917
+ return undefined;
918
+ }
919
+ classifyFailureCategory(message) {
920
+ return classifyCodexFailureCategory(message);
921
+ }
922
+ createDiagnostics(base) {
923
+ return base;
924
+ }
925
+ }
926
+ //# sourceMappingURL=codex-runner.js.map