@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,655 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFileSync } from "node:fs";
3
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ import { Codex } from "@openai/codex-sdk";
6
+ import { CodexAuthResolver } from "../../problem-solving-core/codex/codex-auth-resolver.js";
7
+ import { classifyCodexFailureCategory } from "../../problem-solving-core/codex/codex-failure-classifier.js";
8
+ import { renderRuntimeCodexHomeConfig, resolveConfiguredCodexModelProvider, resolveEffectiveCodexModel } from "../../problem-solving-core/codex/codex-provider-profile.js";
9
+ import { OptimusLogger } from "../observability/logger.js";
10
+ import { HarnessRegistry } from "./harness-registry.js";
11
+ class TriageRunnerError extends Error {
12
+ failureCategory;
13
+ constructor(message, failureCategory) {
14
+ super(message);
15
+ this.name = "TriageRunnerError";
16
+ this.failureCategory = failureCategory;
17
+ }
18
+ }
19
+ // TriageRunner owns the resident triage thread and all task admission semantics.
20
+ // Runtime only sees one stable triage interface, while this class handles acceptance-contract loading,
21
+ // resident session reuse, Codex prompting, and task-package materialization.
22
+ export class TriageRunner {
23
+ config;
24
+ harnessRegistry;
25
+ logger;
26
+ dependencies;
27
+ initialized = false;
28
+ harnessContracts = [];
29
+ harnessDigest = "";
30
+ residentThreadId;
31
+ residentThreadRestored = false;
32
+ initializedAt;
33
+ constructor(config, dependencies = {}) {
34
+ this.config = config;
35
+ this.harnessRegistry = new HarnessRegistry(config);
36
+ this.logger = new OptimusLogger(config);
37
+ this.dependencies = dependencies;
38
+ }
39
+ // initialize() bootstraps the resident triage worker once at runtime startup.
40
+ // It either restores a previously initialized thread or creates a new one and injects all ACCEPT contracts.
41
+ async initialize() {
42
+ if (this.initialized) {
43
+ return;
44
+ }
45
+ this.harnessContracts = this.loadHarnessContracts();
46
+ this.harnessDigest = this.buildHarnessDigest(this.harnessContracts);
47
+ this.initializedAt = new Date().toISOString();
48
+ await this.logger.info("optimus.triage.starting", {
49
+ model: resolveEffectiveCodexModel(this.config),
50
+ provider: resolveConfiguredCodexModelProvider(this.config),
51
+ sandboxMode: this.config.codex.sandboxMode,
52
+ approvalPolicy: this.config.codex.approvalPolicy,
53
+ taskTypes: this.harnessContracts.map((contract) => contract.taskType)
54
+ });
55
+ try {
56
+ await this.restoreOrInitializeResidentThread();
57
+ this.initialized = true;
58
+ await this.logger.info("optimus.triage.ready", this.getStatusSnapshot());
59
+ }
60
+ catch (error) {
61
+ const failureCategory = this.classifyFailure(error);
62
+ await this.logger.error("optimus.triage.start_failed", {
63
+ failureCategory,
64
+ reason: error instanceof Error ? error.message : "Unknown triage startup failure"
65
+ });
66
+ throw error;
67
+ }
68
+ }
69
+ // triage() is runtime's only semantic admission API.
70
+ // It validates minimal event shape and then uses the resident Codex triage thread to classify the event.
71
+ async triage(event) {
72
+ const content = event.content.content.trim();
73
+ const title = event.content.title?.trim() || this.deriveTitle(content);
74
+ if (!content) {
75
+ return {
76
+ decision: "rejected",
77
+ summary: "Runtime event is missing usable content for triage.",
78
+ reason: "input_missing",
79
+ missingInfo: ["content"]
80
+ };
81
+ }
82
+ if (!title) {
83
+ return {
84
+ decision: "rejected",
85
+ summary: "Runtime event is missing a stable title for triage.",
86
+ reason: "title_missing",
87
+ missingInfo: ["title"]
88
+ };
89
+ }
90
+ if (event.type === "task.retry_requested" || event.type === "task.cancel_requested") {
91
+ return {
92
+ decision: "rejected",
93
+ summary: `Event ${event.type} is handled directly by runtime without triage packaging.`,
94
+ reason: "runtime_control_event"
95
+ };
96
+ }
97
+ await this.initialize();
98
+ const decision = this.materializeDecision(event, title, content, await this.decideWithResidentThread(event, title, content));
99
+ if (decision.decision === "accepted") {
100
+ await this.logger.info("triage.accepted", {
101
+ eventId: event.eventId,
102
+ taskType: decision.taskType,
103
+ source: event.content.source,
104
+ sourceRef: event.content.sourceRef,
105
+ title: decision.taskPackage.title,
106
+ ...(this.residentThreadId ? { sdkThreadId: this.residentThreadId } : {})
107
+ });
108
+ return decision;
109
+ }
110
+ await this.logger.warn("triage.rejected", {
111
+ eventId: event.eventId,
112
+ source: event.content.source,
113
+ reason: decision.reason,
114
+ summary: decision.summary,
115
+ missingInfo: decision.missingInfo,
116
+ ...(this.residentThreadId ? { sdkThreadId: this.residentThreadId } : {})
117
+ });
118
+ return decision;
119
+ }
120
+ classifyFailure(error) {
121
+ if (typeof error === "object" && error && "failureCategory" in error) {
122
+ const category = error.failureCategory;
123
+ if (category) {
124
+ return category;
125
+ }
126
+ }
127
+ if (error instanceof Error) {
128
+ return classifyCodexFailureCategory(error.message);
129
+ }
130
+ return "unknown";
131
+ }
132
+ // getStatusSnapshot() exposes the resident triage state so doctor/runtime can report whether triage is initialized,
133
+ // restored from a previous session, and which harness contracts it is currently serving.
134
+ getStatusSnapshot() {
135
+ return {
136
+ ok: this.initialized,
137
+ startedAt: this.initializedAt,
138
+ mode: "codex",
139
+ model: resolveEffectiveCodexModel(this.config),
140
+ provider: resolveConfiguredCodexModelProvider(this.config),
141
+ taskTypes: this.harnessContracts.map((contract) => contract.taskType),
142
+ harnessDigest: this.harnessDigest || undefined,
143
+ restored: this.residentThreadRestored,
144
+ threadId: this.residentThreadId,
145
+ sessionStatePath: this.getSessionStatePath()
146
+ };
147
+ }
148
+ loadHarnessContracts() {
149
+ return this.harnessRegistry.list().map((definition) => this.readHarnessContract(definition));
150
+ }
151
+ // ACCEPT.md is the only source of triage admission rules for a task harness.
152
+ // Runtime only loads the markdown so the resident triage runner sees the original candidate contract text.
153
+ readHarnessContract(definition) {
154
+ const manifest = this.harnessRegistry.getManifest(definition.taskType);
155
+ const acceptMarkdown = manifest.triageRules
156
+ .map((file) => readFileSync(join(manifest.harnessDir, file), "utf8").trim())
157
+ .filter(Boolean)
158
+ .join("\n\n---\n\n");
159
+ return {
160
+ taskType: definition.taskType,
161
+ acceptMarkdown
162
+ };
163
+ }
164
+ buildHarnessDigest(contracts) {
165
+ const serialized = contracts
166
+ .map((contract) => [contract.taskType, contract.acceptMarkdown].join("\n"))
167
+ .join("\n\n====\n\n");
168
+ return createHash("sha256").update(serialized).digest("hex");
169
+ }
170
+ async restoreOrInitializeResidentThread() {
171
+ const sessionState = await this.readSessionState();
172
+ const taskTypes = this.harnessContracts.map((contract) => contract.taskType);
173
+ if (sessionState &&
174
+ sessionState.harnessDigest === this.harnessDigest &&
175
+ this.sameStringArray(sessionState.taskTypes, taskTypes)) {
176
+ this.residentThreadId = sessionState.threadId;
177
+ this.residentThreadRestored = true;
178
+ await this.logger.info("optimus.triage.session_restored", {
179
+ threadId: sessionState.threadId,
180
+ taskTypes,
181
+ harnessDigest: this.harnessDigest
182
+ });
183
+ return;
184
+ }
185
+ if (sessionState) {
186
+ await this.logger.info("optimus.triage.session_rebuild_required", {
187
+ previousThreadId: sessionState.threadId,
188
+ previousHarnessDigest: sessionState.harnessDigest,
189
+ currentHarnessDigest: this.harnessDigest,
190
+ previousTaskTypes: sessionState.taskTypes,
191
+ currentTaskTypes: taskTypes
192
+ });
193
+ }
194
+ await this.startFreshResidentThread();
195
+ }
196
+ // A fresh triage thread receives the full acceptance-contract corpus once.
197
+ // Later task turns only send the event payload and rely on the resident thread memory to choose the task type or reject.
198
+ async startFreshResidentThread() {
199
+ const prompt = this.buildInitializationPrompt();
200
+ const promptPath = await this.writeTriageArtifact("triage-init-prompt.md", prompt[0]?.text ?? "");
201
+ await this.logger.debug("optimus.triage.init_prompt", {
202
+ artifactPath: promptPath,
203
+ taskTypes: this.harnessContracts.map((contract) => contract.taskType)
204
+ });
205
+ try {
206
+ const result = await this.executeCodexTurn({
207
+ prompt,
208
+ outputSchema: this.buildInitializationOutputSchema(),
209
+ timeoutMs: this.config.runtime.startupTimeoutMs,
210
+ sandboxMode: this.config.codex.sandboxMode
211
+ });
212
+ const responsePath = await this.writeTriageArtifact("triage-init-response.json", result.response);
213
+ await this.logger.debug("optimus.triage.init_response", {
214
+ ...(result.sdkThreadId ? { sdkThreadId: result.sdkThreadId } : {}),
215
+ responsePreview: this.previewResponse(result.response, 3000),
216
+ responseArtifactPath: responsePath
217
+ });
218
+ const parsed = this.parseInitializationResponse(result.response);
219
+ if (!parsed.ready) {
220
+ throw new TriageRunnerError(`Triage initialization returned ready=false: ${parsed.summary}`, "schema_error");
221
+ }
222
+ if (!result.sdkThreadId) {
223
+ throw new TriageRunnerError("Triage initialization completed without a persisted thread id.", "environment_error");
224
+ }
225
+ this.residentThreadId = result.sdkThreadId;
226
+ this.residentThreadRestored = false;
227
+ await this.writeSessionState(this.buildSessionState(result.sdkThreadId, new Date().toISOString()));
228
+ }
229
+ catch (error) {
230
+ this.residentThreadId = undefined;
231
+ this.residentThreadRestored = false;
232
+ await this.deleteSessionState();
233
+ throw error;
234
+ }
235
+ }
236
+ buildInitializationPrompt() {
237
+ const renderedContracts = this.harnessContracts.map((contract) => [
238
+ `## TASK HARNESS CANDIDATE: ${contract.taskType}`,
239
+ contract.acceptMarkdown
240
+ ].join("\n\n")).join("\n\n---\n\n");
241
+ return [{
242
+ type: "text",
243
+ text: [
244
+ "# TRIAGE RUNNER INITIALIZATION",
245
+ "You are the resident triage runner for Optimus.",
246
+ "Your job is to classify future runtime events into one registered task harness, or reject them with a concrete reason.",
247
+ "The task harness candidate list below is the complete set of task types you may choose from.",
248
+ "Read and retain each ACCEPT contract exactly as written before handling future events.",
249
+ "Never invent a taskType. Only select one taskType from the candidate list below.",
250
+ "If no task harness safely accepts the event, reject it.",
251
+ "When rejecting for missing information, only use field names explicitly declared by the acceptance contracts.",
252
+ "Later event turns will only send event payloads. You must keep using the candidate list loaded in this initialization turn.",
253
+ "Return JSON only.",
254
+ "",
255
+ "# OUTPUT CONTRACT",
256
+ JSON.stringify({ ready: true, summary: "triage_initialized", taskTypes: this.harnessContracts.map((contract) => contract.taskType) }, null, 2),
257
+ "",
258
+ "# TASK HARNESS CANDIDATE LIST",
259
+ renderedContracts
260
+ ].join("\n")
261
+ }];
262
+ }
263
+ buildInitializationOutputSchema() {
264
+ return {
265
+ type: "object",
266
+ additionalProperties: false,
267
+ required: ["ready", "summary", "taskTypes"],
268
+ properties: {
269
+ ready: { type: "boolean" },
270
+ summary: { type: "string" },
271
+ taskTypes: {
272
+ type: "array",
273
+ items: { type: "string" }
274
+ }
275
+ }
276
+ };
277
+ }
278
+ parseInitializationResponse(response) {
279
+ let parsed;
280
+ try {
281
+ parsed = JSON.parse(response);
282
+ }
283
+ catch (error) {
284
+ throw new TriageRunnerError(`Triage initialization response was not valid JSON: ${error instanceof Error ? error.message : "unknown parse error"}`, "parse_error");
285
+ }
286
+ if (!parsed || typeof parsed !== "object") {
287
+ throw new TriageRunnerError("Triage initialization response schema mismatch: root is not an object.", "schema_error");
288
+ }
289
+ const candidate = parsed;
290
+ if (typeof candidate.ready !== "boolean" || typeof candidate.summary !== "string" || !Array.isArray(candidate.taskTypes)) {
291
+ throw new TriageRunnerError("Triage initialization response schema mismatch: ready/summary/taskTypes missing.", "schema_error");
292
+ }
293
+ return {
294
+ ready: candidate.ready,
295
+ summary: candidate.summary,
296
+ taskTypes: candidate.taskTypes.filter((item) => typeof item === "string")
297
+ };
298
+ }
299
+ async decideWithResidentThread(event, title, content) {
300
+ const prompt = this.buildTaskPrompt(event, title, content);
301
+ try {
302
+ return await this.executeDecisionTurn(event, prompt);
303
+ }
304
+ catch (error) {
305
+ if (!this.residentThreadId) {
306
+ throw error;
307
+ }
308
+ await this.logger.warn("optimus.triage.resume_failed", {
309
+ eventId: event.eventId,
310
+ threadId: this.residentThreadId,
311
+ failureCategory: this.classifyFailure(error),
312
+ reason: error instanceof Error ? error.message : "Unknown resident thread failure"
313
+ });
314
+ await this.startFreshResidentThread();
315
+ await this.logger.info("optimus.triage.thread_rebuilt", {
316
+ eventId: event.eventId,
317
+ threadId: this.residentThreadId,
318
+ harnessDigest: this.harnessDigest
319
+ });
320
+ return this.executeDecisionTurn(event, prompt);
321
+ }
322
+ }
323
+ buildTaskPrompt(event, title, content) {
324
+ return [{
325
+ type: "text",
326
+ text: [
327
+ "# TRIAGE TASK",
328
+ "Decide whether this runtime event should be accepted into one registered task harness, or rejected.",
329
+ "Use only the acceptance contracts already loaded during initialization.",
330
+ "Return JSON only and satisfy the schema exactly.",
331
+ "",
332
+ "# RUNTIME EVENT",
333
+ JSON.stringify({
334
+ eventId: event.eventId,
335
+ type: event.type,
336
+ source: event.content.source,
337
+ title,
338
+ description: event.content.description ?? "",
339
+ content,
340
+ repo: event.content.repo,
341
+ branch: event.content.branch,
342
+ metadata: event.content.metadata ?? {}
343
+ }, null, 2),
344
+ "",
345
+ "# OUTPUT RULES",
346
+ "- accepted: include decision=accepted, summary, and taskType.",
347
+ "- rejected: include decision=rejected, summary, reason, and optional missingInfo.",
348
+ "- reason must be a short machine-readable snake_case string.",
349
+ "- missingInfo must only contain field names declared by the acceptance contracts."
350
+ ].join("\n")
351
+ }];
352
+ }
353
+ async executeDecisionTurn(event, prompt) {
354
+ const result = await this.executeCodexTurn({
355
+ prompt,
356
+ outputSchema: this.buildDecisionOutputSchema(),
357
+ timeoutMs: this.config.runtime.startupTimeoutMs,
358
+ sandboxMode: this.config.codex.sandboxMode,
359
+ ...(this.residentThreadId ? { threadId: this.residentThreadId } : {})
360
+ });
361
+ const rawResponsePath = await this.writeTriageArtifact(`${event.eventId}-triage-raw-response.json`, result.response);
362
+ await this.logger.debug("optimus.triage.raw_response", {
363
+ eventId: event.eventId,
364
+ eventType: event.type,
365
+ ...(result.sdkThreadId ? { sdkThreadId: result.sdkThreadId } : {}),
366
+ responsePreview: this.previewResponse(result.response, 3000),
367
+ response: result.response,
368
+ responseArtifactPath: rawResponsePath
369
+ });
370
+ if (result.sdkThreadId && result.sdkThreadId !== this.residentThreadId) {
371
+ this.residentThreadId = result.sdkThreadId;
372
+ await this.writeSessionState(this.buildSessionState(result.sdkThreadId, this.initializedAt ?? new Date().toISOString()));
373
+ }
374
+ const parsed = this.parseCodexDecision(result.response);
375
+ const parsedPath = await this.writeTriageArtifact(`${event.eventId}-triage-parsed-response.json`, JSON.stringify(parsed, null, 2));
376
+ await this.logger.debug("optimus.triage.parsed_response", {
377
+ eventId: event.eventId,
378
+ eventType: event.type,
379
+ ...(this.residentThreadId ? { sdkThreadId: this.residentThreadId } : {}),
380
+ decision: parsed.decision,
381
+ ...(parsed.taskType ? { taskType: parsed.taskType } : {}),
382
+ parsedResponsePath: parsedPath
383
+ });
384
+ return parsed;
385
+ }
386
+ buildDecisionOutputSchema() {
387
+ return {
388
+ type: "object",
389
+ additionalProperties: false,
390
+ // The current Codex/Responses schema validator requires root.required to enumerate
391
+ // every declared property when additionalProperties=false is used.
392
+ // We still treat taskType/reason/missingInfo as decision-dependent in parseCodexDecision(),
393
+ // but the wire schema must list them all and allow null so rejected/accepted variants remain valid.
394
+ required: ["decision", "summary", "taskType", "reason", "missingInfo"],
395
+ properties: {
396
+ decision: { type: "string", enum: ["accepted", "rejected"] },
397
+ summary: { type: "string" },
398
+ taskType: {
399
+ type: ["string", "null"]
400
+ },
401
+ reason: {
402
+ type: ["string", "null"]
403
+ },
404
+ missingInfo: {
405
+ type: ["array", "null"],
406
+ items: { type: "string" }
407
+ }
408
+ }
409
+ };
410
+ }
411
+ parseCodexDecision(response) {
412
+ let parsed;
413
+ try {
414
+ parsed = JSON.parse(response);
415
+ }
416
+ catch (error) {
417
+ throw new TriageRunnerError(`Codex triage response was not valid JSON: ${error instanceof Error ? error.message : "unknown parse error"}`, "parse_error");
418
+ }
419
+ if (!parsed || typeof parsed !== "object") {
420
+ throw new TriageRunnerError("Codex triage response schema mismatch: root is not an object.", "schema_error");
421
+ }
422
+ const candidate = parsed;
423
+ if ((candidate.decision !== "accepted" && candidate.decision !== "rejected") || typeof candidate.summary !== "string" || candidate.summary.trim().length === 0) {
424
+ throw new TriageRunnerError("Codex triage response schema mismatch: decision/summary missing.", "schema_error");
425
+ }
426
+ if (candidate.decision === "accepted") {
427
+ if (typeof candidate.taskType !== "string" || candidate.taskType.trim().length === 0) {
428
+ throw new TriageRunnerError("Codex triage accepted the event but omitted taskType.", "schema_error");
429
+ }
430
+ if (!this.harnessContracts.some((contract) => contract.taskType === candidate.taskType)) {
431
+ throw new TriageRunnerError(`Codex triage selected unregistered taskType ${candidate.taskType}.`, "schema_error");
432
+ }
433
+ return {
434
+ decision: "accepted",
435
+ summary: candidate.summary,
436
+ taskType: candidate.taskType
437
+ };
438
+ }
439
+ const normalizedMissingInfo = Array.isArray(candidate.missingInfo)
440
+ ? candidate.missingInfo
441
+ .filter((item) => typeof item === "string" && item.trim().length > 0)
442
+ .map((item) => item.trim())
443
+ : undefined;
444
+ return {
445
+ decision: "rejected",
446
+ summary: candidate.summary,
447
+ reason: typeof candidate.reason === "string" && candidate.reason.trim().length > 0 ? candidate.reason : "unsupported_problem_type",
448
+ ...(normalizedMissingInfo && normalizedMissingInfo.length > 0 ? { missingInfo: normalizedMissingInfo } : {})
449
+ };
450
+ }
451
+ // Materialization intentionally keeps Codex output narrow.
452
+ // The agent only chooses accept/reject and taskType; system code still builds the governed TaskPackage contract.
453
+ materializeDecision(event, title, content, decision) {
454
+ if (decision.decision === "rejected") {
455
+ return {
456
+ decision: "rejected",
457
+ summary: decision.summary,
458
+ reason: decision.reason ?? "unsupported_problem_type",
459
+ ...(decision.missingInfo && decision.missingInfo.length > 0 ? { missingInfo: decision.missingInfo } : {})
460
+ };
461
+ }
462
+ const matchedContract = this.harnessContracts.find((contract) => contract.taskType === decision.taskType);
463
+ if (!matchedContract) {
464
+ throw new TriageRunnerError(`Codex triage selected unsupported taskType ${decision.taskType ?? "<empty>"}.`, "schema_error");
465
+ }
466
+ const taskPackage = {
467
+ taskPackageId: `pkg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
468
+ sourceEventId: event.eventId,
469
+ source: event.content.source,
470
+ ...(event.content.sourceRef ? { sourceRef: event.content.sourceRef } : {}),
471
+ ...(this.shouldUseIdempotency(event) ? { idempotencyKey: this.buildIdempotencyKey(title) } : {}),
472
+ taskType: matchedContract.taskType,
473
+ title,
474
+ ...(event.content.repo ? { repo: event.content.repo } : {}),
475
+ ...(event.content.branch ? { branch: event.content.branch } : {}),
476
+ input: {
477
+ description: event.content.description ?? title,
478
+ content,
479
+ ...(event.content.metadata ? { metadata: event.content.metadata } : {})
480
+ },
481
+ createdAt: new Date().toISOString()
482
+ };
483
+ return {
484
+ decision: "accepted",
485
+ summary: decision.summary,
486
+ taskType: matchedContract.taskType,
487
+ taskPackage
488
+ };
489
+ }
490
+ async executeCodexTurn(input) {
491
+ const turnExecutor = this.dependencies.runCodexTurn ?? ((payload) => this.runCodexTurn(payload));
492
+ return turnExecutor(input);
493
+ }
494
+ async runCodexTurn(input) {
495
+ await this.ensureCodexHome();
496
+ const codex = this.createCodexClient();
497
+ const thread = input.threadId
498
+ ? codex.resumeThread(input.threadId, this.buildThreadOptions({
499
+ workingDirectory: process.cwd(),
500
+ sandboxMode: input.sandboxMode
501
+ }))
502
+ : codex.startThread(this.buildThreadOptions({
503
+ workingDirectory: process.cwd(),
504
+ sandboxMode: input.sandboxMode
505
+ }));
506
+ const turn = await thread.run(input.prompt, {
507
+ outputSchema: input.outputSchema,
508
+ signal: this.createExecutionSignal(undefined, input.timeoutMs)
509
+ });
510
+ return {
511
+ response: turn.finalResponse,
512
+ ...(thread.id ? { sdkThreadId: thread.id } : {})
513
+ };
514
+ }
515
+ buildThreadOptions(input) {
516
+ const options = {
517
+ modelReasoningEffort: this.config.codex.reasoningEffort,
518
+ workingDirectory: input.workingDirectory,
519
+ skipGitRepoCheck: true,
520
+ sandboxMode: input.sandboxMode,
521
+ approvalPolicy: this.config.codex.approvalPolicy,
522
+ networkAccessEnabled: this.config.runtime.networkAccessEnabled
523
+ };
524
+ const effectiveModel = resolveEffectiveCodexModel(this.config);
525
+ if (effectiveModel) {
526
+ options.model = effectiveModel;
527
+ }
528
+ return options;
529
+ }
530
+ createExecutionSignal(externalSignal, timeoutMs) {
531
+ const timeoutSignal = AbortSignal.timeout(timeoutMs);
532
+ if (!externalSignal) {
533
+ return timeoutSignal;
534
+ }
535
+ if (typeof AbortSignal.any === "function") {
536
+ return AbortSignal.any([externalSignal, timeoutSignal]);
537
+ }
538
+ const controller = new AbortController();
539
+ const forwardAbort = (signal) => {
540
+ if (signal.aborted) {
541
+ controller.abort(signal.reason);
542
+ return;
543
+ }
544
+ signal.addEventListener("abort", () => controller.abort(signal.reason), { once: true });
545
+ };
546
+ forwardAbort(externalSignal);
547
+ forwardAbort(timeoutSignal);
548
+ return controller.signal;
549
+ }
550
+ async ensureCodexHome() {
551
+ // Runtime must always materialize the private CODEX_HOME directory first.
552
+ // Even when no runtime config.toml is rendered yet, the SDK still receives CODEX_HOME,
553
+ // and Codex Exec aborts immediately if that relative path does not exist.
554
+ await mkdir(this.config.codex.homeDir, { recursive: true });
555
+ const configText = renderRuntimeCodexHomeConfig(this.config);
556
+ if (!configText) {
557
+ throw new TriageRunnerError("Unable to render runtime codex-home/config.toml. Check codex.provider and codex.model.", "config_error");
558
+ }
559
+ await writeFile(join(this.config.codex.homeDir, "config.toml"), configText, "utf8");
560
+ }
561
+ createCodexClient() {
562
+ const auth = new CodexAuthResolver(this.config).resolve();
563
+ const env = { ...auth.env };
564
+ // Keep the operator's HOME untouched and scope only Codex state to the private runtime home.
565
+ env.CODEX_HOME = this.config.codex.homeDir;
566
+ const options = { env };
567
+ if (auth.baseUrl) {
568
+ options.baseUrl = auth.baseUrl;
569
+ }
570
+ if (auth.apiKey) {
571
+ options.apiKey = auth.apiKey;
572
+ }
573
+ return new Codex(options);
574
+ }
575
+ async readSessionState() {
576
+ try {
577
+ const raw = await readFile(this.getSessionStatePath(), "utf8");
578
+ const parsed = JSON.parse(raw);
579
+ if (parsed.runner !== "triage" ||
580
+ typeof parsed.threadId !== "string" ||
581
+ typeof parsed.harnessDigest !== "string" ||
582
+ !Array.isArray(parsed.taskTypes) ||
583
+ typeof parsed.initializedAt !== "string") {
584
+ return undefined;
585
+ }
586
+ return {
587
+ runner: "triage",
588
+ threadId: parsed.threadId,
589
+ harnessDigest: parsed.harnessDigest,
590
+ taskTypes: parsed.taskTypes.filter((item) => typeof item === "string"),
591
+ initializedAt: parsed.initializedAt,
592
+ ...(typeof parsed.model === "string" ? { model: parsed.model } : {})
593
+ };
594
+ }
595
+ catch {
596
+ return undefined;
597
+ }
598
+ }
599
+ async writeSessionState(state) {
600
+ await mkdir(this.config.storage.rootDir, { recursive: true });
601
+ await writeFile(this.getSessionStatePath(), JSON.stringify(state, null, 2), "utf8");
602
+ }
603
+ async deleteSessionState() {
604
+ await rm(this.getSessionStatePath(), { force: true });
605
+ }
606
+ buildSessionState(threadId, initializedAt) {
607
+ const state = {
608
+ runner: "triage",
609
+ threadId,
610
+ harnessDigest: this.harnessDigest,
611
+ taskTypes: this.harnessContracts.map((contract) => contract.taskType),
612
+ initializedAt
613
+ };
614
+ const model = resolveEffectiveCodexModel(this.config);
615
+ if (model) {
616
+ state.model = model;
617
+ }
618
+ return state;
619
+ }
620
+ getSessionStatePath() {
621
+ return join(this.config.storage.rootDir, "triage-session.json");
622
+ }
623
+ sameStringArray(left, right) {
624
+ if (left.length !== right.length) {
625
+ return false;
626
+ }
627
+ return left.every((value, index) => value === right[index]);
628
+ }
629
+ async writeTriageArtifact(fileName, content) {
630
+ const artifactDir = join(this.config.storage.rootDir, "artifacts", "triage");
631
+ await mkdir(artifactDir, { recursive: true });
632
+ const targetPath = join(artifactDir, fileName);
633
+ await writeFile(targetPath, content, "utf8");
634
+ return targetPath;
635
+ }
636
+ previewResponse(response, limit = 600) {
637
+ const normalized = response.replace(/\s+/g, " ").trim();
638
+ return normalized.length > limit ? `${normalized.slice(0, limit - 3)}...` : normalized;
639
+ }
640
+ deriveTitle(content) {
641
+ return content.split(/\r?\n/).map((line) => line.trim()).find(Boolean)?.slice(0, 120) ?? "Untitled task";
642
+ }
643
+ buildIdempotencyKey(title) {
644
+ const normalized = title.replace(/\s+/g, " ").trim().toLowerCase();
645
+ return `title-${createHash("sha256").update(normalized).digest("hex").slice(0, 16)}`;
646
+ }
647
+ shouldUseIdempotency(event) {
648
+ const metadata = event.content.metadata;
649
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
650
+ return true;
651
+ }
652
+ return metadata.allowDuplicate !== true;
653
+ }
654
+ }
655
+ //# sourceMappingURL=triage-runner.js.map