@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.
- package/.env.example +16 -0
- package/LICENSE +21 -0
- package/README.md +104 -0
- package/dist/cli/optimus.d.ts +2 -0
- package/dist/cli/optimus.js +2951 -0
- package/dist/cli/optimus.js.map +1 -0
- package/dist/cli/self-update.d.ts +49 -0
- package/dist/cli/self-update.js +264 -0
- package/dist/cli/self-update.js.map +1 -0
- package/dist/config/load-config.d.ts +3 -0
- package/dist/config/load-config.js +321 -0
- package/dist/config/load-config.js.map +1 -0
- package/dist/config/optimus-paths.d.ts +13 -0
- package/dist/config/optimus-paths.js +44 -0
- package/dist/config/optimus-paths.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/jira/jira-cli.d.ts +1 -0
- package/dist/integrations/jira/jira-cli.js +278 -0
- package/dist/integrations/jira/jira-cli.js.map +1 -0
- package/dist/integrations/jira/jira-client.d.ts +99 -0
- package/dist/integrations/jira/jira-client.js +521 -0
- package/dist/integrations/jira/jira-client.js.map +1 -0
- package/dist/integrations/jira/jira-submit.d.ts +71 -0
- package/dist/integrations/jira/jira-submit.js +351 -0
- package/dist/integrations/jira/jira-submit.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-auth-resolver.d.ts +23 -0
- package/dist/problem-solving-core/codex/codex-auth-resolver.js +136 -0
- package/dist/problem-solving-core/codex/codex-auth-resolver.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-connectivity-checks.d.ts +6 -0
- package/dist/problem-solving-core/codex/codex-connectivity-checks.js +81 -0
- package/dist/problem-solving-core/codex/codex-connectivity-checks.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-failure-classifier.d.ts +2 -0
- package/dist/problem-solving-core/codex/codex-failure-classifier.js +49 -0
- package/dist/problem-solving-core/codex/codex-failure-classifier.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-global-config.d.ts +17 -0
- package/dist/problem-solving-core/codex/codex-global-config.js +100 -0
- package/dist/problem-solving-core/codex/codex-global-config.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-preflight.d.ts +13 -0
- package/dist/problem-solving-core/codex/codex-preflight.js +142 -0
- package/dist/problem-solving-core/codex/codex-preflight.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-provider-profile.d.ts +14 -0
- package/dist/problem-solving-core/codex/codex-provider-profile.js +68 -0
- package/dist/problem-solving-core/codex/codex-provider-profile.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-required-env.d.ts +3 -0
- package/dist/problem-solving-core/codex/codex-required-env.js +21 -0
- package/dist/problem-solving-core/codex/codex-required-env.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-runner.d.ts +37 -0
- package/dist/problem-solving-core/codex/codex-runner.js +926 -0
- package/dist/problem-solving-core/codex/codex-runner.js.map +1 -0
- package/dist/problem-solving-core/codex/evolution-skill-guard.d.ts +36 -0
- package/dist/problem-solving-core/codex/evolution-skill-guard.js +143 -0
- package/dist/problem-solving-core/codex/evolution-skill-guard.js.map +1 -0
- package/dist/problem-solving-core/codex/repo-memory-service.d.ts +24 -0
- package/dist/problem-solving-core/codex/repo-memory-service.js +114 -0
- package/dist/problem-solving-core/codex/repo-memory-service.js.map +1 -0
- package/dist/problem-solving-core/codex/skill-sync-service.d.ts +35 -0
- package/dist/problem-solving-core/codex/skill-sync-service.js +280 -0
- package/dist/problem-solving-core/codex/skill-sync-service.js.map +1 -0
- package/dist/task-environment/cancellation/task-abort-registry.d.ts +17 -0
- package/dist/task-environment/cancellation/task-abort-registry.js +51 -0
- package/dist/task-environment/cancellation/task-abort-registry.js.map +1 -0
- package/dist/task-environment/cancellation/task-cancellation-service.d.ts +25 -0
- package/dist/task-environment/cancellation/task-cancellation-service.js +54 -0
- package/dist/task-environment/cancellation/task-cancellation-service.js.map +1 -0
- package/dist/task-environment/cancellation/task-cleanup-service.d.ts +22 -0
- package/dist/task-environment/cancellation/task-cleanup-service.js +67 -0
- package/dist/task-environment/cancellation/task-cleanup-service.js.map +1 -0
- package/dist/task-environment/delivery/commit-message/bugfix-commit-message-template.d.ts +13 -0
- package/dist/task-environment/delivery/commit-message/bugfix-commit-message-template.js +83 -0
- package/dist/task-environment/delivery/commit-message/bugfix-commit-message-template.js.map +1 -0
- package/dist/task-environment/delivery/commit-message/commit-message-builder.d.ts +6 -0
- package/dist/task-environment/delivery/commit-message/commit-message-builder.js +15 -0
- package/dist/task-environment/delivery/commit-message/commit-message-builder.js.map +1 -0
- package/dist/task-environment/delivery/commit-message/commit-message-template-types.d.ts +16 -0
- package/dist/task-environment/delivery/commit-message/commit-message-template-types.js +2 -0
- package/dist/task-environment/delivery/commit-message/commit-message-template-types.js.map +1 -0
- package/dist/task-environment/delivery/feishu-analysis-doc-service.d.ts +50 -0
- package/dist/task-environment/delivery/feishu-analysis-doc-service.js +454 -0
- package/dist/task-environment/delivery/feishu-analysis-doc-service.js.map +1 -0
- package/dist/task-environment/delivery/feishu-card-renderer.d.ts +38 -0
- package/dist/task-environment/delivery/feishu-card-renderer.js +449 -0
- package/dist/task-environment/delivery/feishu-card-renderer.js.map +1 -0
- package/dist/task-environment/delivery/feishu-content/feishu-content-renderer.d.ts +34 -0
- package/dist/task-environment/delivery/feishu-content/feishu-content-renderer.js +201 -0
- package/dist/task-environment/delivery/feishu-content/feishu-content-renderer.js.map +1 -0
- package/dist/task-environment/delivery/feishu-content/feishu-copy-config.d.ts +27 -0
- package/dist/task-environment/delivery/feishu-content/feishu-copy-config.js +74 -0
- package/dist/task-environment/delivery/feishu-content/feishu-copy-config.js.map +1 -0
- package/dist/task-environment/delivery/feishu-notifier.d.ts +45 -0
- package/dist/task-environment/delivery/feishu-notifier.js +250 -0
- package/dist/task-environment/delivery/feishu-notifier.js.map +1 -0
- package/dist/task-environment/delivery/feishu-templates/analysis-message-template.d.ts +6 -0
- package/dist/task-environment/delivery/feishu-templates/analysis-message-template.js +39 -0
- package/dist/task-environment/delivery/feishu-templates/analysis-message-template.js.map +1 -0
- package/dist/task-environment/delivery/feishu-templates/bugfix-message-template.d.ts +6 -0
- package/dist/task-environment/delivery/feishu-templates/bugfix-message-template.js +40 -0
- package/dist/task-environment/delivery/feishu-templates/bugfix-message-template.js.map +1 -0
- package/dist/task-environment/delivery/feishu-templates/default-message-template.d.ts +6 -0
- package/dist/task-environment/delivery/feishu-templates/default-message-template.js +33 -0
- package/dist/task-environment/delivery/feishu-templates/default-message-template.js.map +1 -0
- package/dist/task-environment/delivery/feishu-templates/patch-message-template.d.ts +6 -0
- package/dist/task-environment/delivery/feishu-templates/patch-message-template.js +40 -0
- package/dist/task-environment/delivery/feishu-templates/patch-message-template.js.map +1 -0
- package/dist/task-environment/delivery/feishu-templates/template-registry.d.ts +2 -0
- package/dist/task-environment/delivery/feishu-templates/template-registry.js +11 -0
- package/dist/task-environment/delivery/feishu-templates/template-registry.js.map +1 -0
- package/dist/task-environment/delivery/feishu-templates/template-types.d.ts +20 -0
- package/dist/task-environment/delivery/feishu-templates/template-types.js +2 -0
- package/dist/task-environment/delivery/feishu-templates/template-types.js.map +1 -0
- package/dist/task-environment/delivery/task-delivery-dispatcher.d.ts +14 -0
- package/dist/task-environment/delivery/task-delivery-dispatcher.js +109 -0
- package/dist/task-environment/delivery/task-delivery-dispatcher.js.map +1 -0
- package/dist/task-environment/delivery/task-delivery-service.d.ts +33 -0
- package/dist/task-environment/delivery/task-delivery-service.js +432 -0
- package/dist/task-environment/delivery/task-delivery-service.js.map +1 -0
- package/dist/task-environment/delivery/task-publication-service.d.ts +97 -0
- package/dist/task-environment/delivery/task-publication-service.js +1369 -0
- package/dist/task-environment/delivery/task-publication-service.js.map +1 -0
- package/dist/task-environment/execution-addresses.d.ts +40 -0
- package/dist/task-environment/execution-addresses.js +63 -0
- package/dist/task-environment/execution-addresses.js.map +1 -0
- package/dist/task-environment/intake/cli-file-intake.d.ts +12 -0
- package/dist/task-environment/intake/cli-file-intake.js +56 -0
- package/dist/task-environment/intake/cli-file-intake.js.map +1 -0
- package/dist/task-environment/intake/manual-problem-intake.d.ts +3 -0
- package/dist/task-environment/intake/manual-problem-intake.js +57 -0
- package/dist/task-environment/intake/manual-problem-intake.js.map +1 -0
- package/dist/task-environment/intake/polling-problem-intake.d.ts +14 -0
- package/dist/task-environment/intake/polling-problem-intake.js +232 -0
- package/dist/task-environment/intake/polling-problem-intake.js.map +1 -0
- package/dist/task-environment/observability/logger.d.ts +76 -0
- package/dist/task-environment/observability/logger.js +604 -0
- package/dist/task-environment/observability/logger.js.map +1 -0
- package/dist/task-environment/observability/runtime-panel.d.ts +82 -0
- package/dist/task-environment/observability/runtime-panel.js +1008 -0
- package/dist/task-environment/observability/runtime-panel.js.map +1 -0
- package/dist/task-environment/observability/sound-notifier.d.ts +18 -0
- package/dist/task-environment/observability/sound-notifier.js +71 -0
- package/dist/task-environment/observability/sound-notifier.js.map +1 -0
- package/dist/task-environment/orchestration/execution-context-assembler.d.ts +41 -0
- package/dist/task-environment/orchestration/execution-context-assembler.js +464 -0
- package/dist/task-environment/orchestration/execution-context-assembler.js.map +1 -0
- package/dist/task-environment/orchestration/git-change-classifier.d.ts +19 -0
- package/dist/task-environment/orchestration/git-change-classifier.js +106 -0
- package/dist/task-environment/orchestration/git-change-classifier.js.map +1 -0
- package/dist/task-environment/orchestration/harness-registry.d.ts +27 -0
- package/dist/task-environment/orchestration/harness-registry.js +116 -0
- package/dist/task-environment/orchestration/harness-registry.js.map +1 -0
- package/dist/task-environment/orchestration/harness-resolver.d.ts +8 -0
- package/dist/task-environment/orchestration/harness-resolver.js +39 -0
- package/dist/task-environment/orchestration/harness-resolver.js.map +1 -0
- package/dist/task-environment/orchestration/task-orchestrator.d.ts +45 -0
- package/dist/task-environment/orchestration/task-orchestrator.js +1122 -0
- package/dist/task-environment/orchestration/task-orchestrator.js.map +1 -0
- package/dist/task-environment/orchestration/task-package-assembler.d.ts +4 -0
- package/dist/task-environment/orchestration/task-package-assembler.js +10 -0
- package/dist/task-environment/orchestration/task-package-assembler.js.map +1 -0
- package/dist/task-environment/orchestration/triage-agent.d.ts +54 -0
- package/dist/task-environment/orchestration/triage-agent.js +636 -0
- package/dist/task-environment/orchestration/triage-agent.js.map +1 -0
- package/dist/task-environment/orchestration/triage-runner.d.ts +65 -0
- package/dist/task-environment/orchestration/triage-runner.js +655 -0
- package/dist/task-environment/orchestration/triage-runner.js.map +1 -0
- package/dist/task-environment/publication-target.d.ts +12 -0
- package/dist/task-environment/publication-target.js +174 -0
- package/dist/task-environment/publication-target.js.map +1 -0
- package/dist/task-environment/runtime/blocking-event-queue.d.ts +7 -0
- package/dist/task-environment/runtime/blocking-event-queue.js +27 -0
- package/dist/task-environment/runtime/blocking-event-queue.js.map +1 -0
- package/dist/task-environment/runtime/optimus-runtime.d.ts +69 -0
- package/dist/task-environment/runtime/optimus-runtime.js +751 -0
- package/dist/task-environment/runtime/optimus-runtime.js.map +1 -0
- package/dist/task-environment/storage/sqlite-event-store.d.ts +52 -0
- package/dist/task-environment/storage/sqlite-event-store.js +288 -0
- package/dist/task-environment/storage/sqlite-event-store.js.map +1 -0
- package/dist/task-environment/storage/sqlite-task-store.d.ts +122 -0
- package/dist/task-environment/storage/sqlite-task-store.js +1182 -0
- package/dist/task-environment/storage/sqlite-task-store.js.map +1 -0
- package/dist/types.d.ts +629 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/embedded-skills/shared/repo-inspection/SKILL.md +9 -0
- package/embedded-skills/shared/repo-inspection/skill.json +5 -0
- package/embedded-skills/task/bugfix/android-debug-protocol/SKILL.md +10 -0
- package/embedded-skills/task/bugfix/android-debug-protocol/skill.json +6 -0
- package/harness/AGENTS.md +30 -0
- package/harness/CHECKLIST.md +44 -0
- package/harness/CONSTRAINTS.md +60 -0
- package/harness/FRAMEWORK.md +28 -0
- package/harness/GOAL.md +28 -0
- package/harness/HANDOFF.md +45 -0
- package/harness/TASK_PLAN.md +79 -0
- package/optimus.config.template.json +34 -0
- package/package.json +109 -0
- package/task-harnesses/bugfix/ACCEPT.md +47 -0
- package/task-harnesses/bugfix/CONSTRAINTS.md +46 -0
- package/task-harnesses/bugfix/CONTEXT.md +29 -0
- package/task-harnesses/bugfix/EVOLUTION.md +82 -0
- package/task-harnesses/bugfix/ROLE.md +29 -0
- package/task-harnesses/bugfix/STANDARD.md +250 -0
- package/task-harnesses/bugfix/manifest.json +13 -0
- 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
|