@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,2951 @@
1
+ #!/usr/bin/env node
2
+ import { buildDefaultConfig, loadConfig } from "../config/load-config.js";
3
+ import { access, mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
4
+ import { basename, dirname, join, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { execFile, spawn } from "node:child_process";
7
+ import { createInterface } from "node:readline/promises";
8
+ import { promisify } from "node:util";
9
+ import { stdin as input, stdout as output } from "node:process";
10
+ import { CodexRunner } from "../problem-solving-core/codex/codex-runner.js";
11
+ import { CodexAuthResolver } from "../problem-solving-core/codex/codex-auth-resolver.js";
12
+ import { SkillSyncService } from "../problem-solving-core/codex/skill-sync-service.js";
13
+ import { RepoMemoryService } from "../problem-solving-core/codex/repo-memory-service.js";
14
+ import { resolvePrimaryCodexApiKeyEnvName, resolveRequiredCodexEnvVars } from "../problem-solving-core/codex/codex-required-env.js";
15
+ import { resolveCodexAuthMode, resolveConfiguredCodexModelProvider, resolveConfiguredCodexProvider, resolveEffectiveCodexModel } from "../problem-solving-core/codex/codex-provider-profile.js";
16
+ import { classifyCodexFailureCategory } from "../problem-solving-core/codex/codex-failure-classifier.js";
17
+ import { CodexPreflight } from "../problem-solving-core/codex/codex-preflight.js";
18
+ import { createManualEventContent, createManualProblemEvent } from "../task-environment/intake/manual-problem-intake.js";
19
+ import { OptimusLogger } from "../task-environment/observability/logger.js";
20
+ import { TaskOrchestrator } from "../task-environment/orchestration/task-orchestrator.js";
21
+ import { TriageRunner } from "../task-environment/orchestration/triage-runner.js";
22
+ import { OptimusRuntime } from "../task-environment/runtime/optimus-runtime.js";
23
+ import { SQLiteTaskStore } from "../task-environment/storage/sqlite-task-store.js";
24
+ import { SQLiteEventStore } from "../task-environment/storage/sqlite-event-store.js";
25
+ import { FeishuNotifier } from "../task-environment/delivery/feishu-notifier.js";
26
+ import { TaskDeliveryDispatcher } from "../task-environment/delivery/task-delivery-dispatcher.js";
27
+ import { TaskDeliveryService } from "../task-environment/delivery/task-delivery-service.js";
28
+ import { FeishuAnalysisDocService } from "../task-environment/delivery/feishu-analysis-doc-service.js";
29
+ import { TaskPublicationService } from "../task-environment/delivery/task-publication-service.js";
30
+ import { resolveDefaultConfigPath, resolveDefaultEnvPath, resolveOptimusHomeDir } from "../config/optimus-paths.js";
31
+ import { checkForSelfUpdate, installSelfUpdate, readSelfUpdateState } from "./self-update.js";
32
+ const CLI_ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
33
+ const PACKAGE_JSON_PATH = join(CLI_ROOT_DIR, "package.json");
34
+ const execFileAsync = promisify(execFile);
35
+ const PACKAGE_NAME = "@sireai/optimus";
36
+ function renderSetupResult(result) {
37
+ const lines = [];
38
+ lines.push("Setup Complete");
39
+ lines.push(`Config: ${result.configPath}`);
40
+ lines.push(`Env: ${result.envPath}`);
41
+ lines.push(`Status: ${result.configStatus}`);
42
+ lines.push(`Repository: ${result.repository.alias} (${result.repository.path})`);
43
+ lines.push(`Workspace: ${result.repository.workspaceKind}`);
44
+ lines.push(`Review: ${result.repository.reviewSystem}`);
45
+ lines.push(`Mode: ${result.repository.executionMode}`);
46
+ lines.push(`Delivery: ${result.deliveryChannels.join(", ")}`);
47
+ lines.push(`Jira: ${result.jiraEnabled ? "enabled" : "disabled"}`);
48
+ const providerLabel = result.codexAuth.provider ? ` | Provider: ${result.codexAuth.provider.displayName} (${result.codexAuth.provider.id})` : "";
49
+ if (result.codexAuth.mode === "codex_cli_login") {
50
+ lines.push(`Codex Auth: codex_cli_login selected${providerLabel}`);
51
+ }
52
+ else {
53
+ const envLabel = result.codexAuth.envName ?? "unknown";
54
+ lines.push(`Codex Auth: ${result.codexAuth.mode} | ${result.codexAuth.configured ? `${envLabel} configured` : `${envLabel} missing`}${providerLabel}`);
55
+ }
56
+ lines.push("");
57
+ lines.push(renderQuickDoctorReport(result.doctorQuick));
58
+ return lines.join("\n");
59
+ }
60
+ function renderQuickDoctorReport(report) {
61
+ const lines = [];
62
+ lines.push("Doctor Quick");
63
+ lines.push(`Summary: ${report.summary}`);
64
+ const configurationStatus = report.summary === "blocked" ? "incomplete" : "completed";
65
+ lines.push(`Configuration: ${configurationStatus}`);
66
+ if (report.blocking.length > 0) {
67
+ lines.push("");
68
+ lines.push("Blocking");
69
+ for (const issue of report.blocking) {
70
+ lines.push(`- ${issue.message}`);
71
+ if (issue.fix) {
72
+ lines.push(` Fix: ${issue.fix}`);
73
+ }
74
+ }
75
+ }
76
+ if (report.warnings.length > 0) {
77
+ lines.push("");
78
+ lines.push("Warnings");
79
+ for (const issue of report.warnings) {
80
+ lines.push(`- ${issue.message}`);
81
+ if (issue.impact) {
82
+ lines.push(` Impact: ${issue.impact}`);
83
+ }
84
+ if (issue.fix) {
85
+ lines.push(` Fix: ${issue.fix}`);
86
+ }
87
+ }
88
+ }
89
+ if (report.next.length > 0) {
90
+ lines.push("");
91
+ lines.push("Next");
92
+ for (const step of report.next) {
93
+ lines.push(`- ${step}`);
94
+ }
95
+ }
96
+ return lines.join("\n");
97
+ }
98
+ function renderDoctorTextReport(doctor) {
99
+ const lines = [];
100
+ lines.push("Doctor");
101
+ lines.push(`Runtime: tasks=${doctor.runtime.totalTasks} active=${doctor.runtime.activeRuns} stalled=${doctor.runtime.stalledRuns}`);
102
+ lines.push(`Repositories: registered=${doctor.registeredRepositories.length}`);
103
+ lines.push(`Codex: model=${doctor.codex.model ?? "unknown"} auth=${doctor.codex.authMode ?? "unknown"}`);
104
+ if (doctor.codex.provider) {
105
+ const provider = typeof doctor.codex.provider === "string"
106
+ ? doctor.codex.provider
107
+ : `${doctor.codex.provider.displayName ?? doctor.codex.provider.id ?? "unknown"}`;
108
+ lines.push(`Provider: ${provider}`);
109
+ }
110
+ lines.push(`Triage: ${doctor.triage.status?.ok === false ? "not_ready" : "ready_or_unknown"}`);
111
+ if (doctor.selfUpdate) {
112
+ lines.push(`Self Update: current=${doctor.selfUpdate.currentVersion}`
113
+ + ` latest=${doctor.selfUpdate.cachedState?.latestVersion ?? "unknown"}`
114
+ + ` mode=${doctor.selfUpdate.config.mode}`
115
+ + ` source=${doctor.selfUpdate.cachedState?.installSource ?? "unknown"}`
116
+ + ` status=${doctor.selfUpdate.cachedState?.lastCheckStatus ?? "never_checked"}`);
117
+ }
118
+ lines.push(`Runtime Root: ${doctor.runtimeRootDir}`);
119
+ return lines.join("\n");
120
+ }
121
+ const SETUP_CODEX_AUTH_MODE_OPTIONS = [
122
+ { index: "1", mode: "model_provider", label: "Model provider" },
123
+ { index: "2", mode: "openai_api_key", label: "OpenAI API key" },
124
+ { index: "3", mode: "codex_cli_login", label: "Reuse local Codex CLI login" }
125
+ ];
126
+ function renderCliHelp() {
127
+ return [
128
+ "Optimus CLI",
129
+ "",
130
+ "Usage:",
131
+ " optimus <command> [options]",
132
+ "",
133
+ "First-time setup:",
134
+ " optimus setup",
135
+ " optimus doctor --quick",
136
+ " optimus submit --title \"Login crash\" --description \"Crash on startup\"",
137
+ "",
138
+ "Core commands:",
139
+ " start Start the resident runtime",
140
+ " submit Submit a manual task",
141
+ " upgrade Check for or install the latest Optimus version",
142
+ " retry-task Requeue an existing task",
143
+ " cancel-task Cancel a queued or running task",
144
+ " triage-only Run resident triage without enqueuing a task",
145
+ "",
146
+ "Repository commands:",
147
+ " repo add|remove|update|list|doctor",
148
+ "",
149
+ "Inspection commands:",
150
+ " health-check Validate Codex/provider readiness",
151
+ " doctor Show runtime, skills, and environment diagnostics",
152
+ " task-status Show task execution status",
153
+ " task-result Show persisted result, delivery, and publication data",
154
+ " delivery-status Show focused delivery/publication state",
155
+ " task-events Show task timeline",
156
+ " db-inspect Show raw persisted task data",
157
+ "",
158
+ "Delivery commands:",
159
+ " notify-test Send or preview a Feishu test notification",
160
+ " delivery-retry Replay notification delivery from persisted bundle data",
161
+ " postrun-retry Rebuild postrun artifacts and optionally replay publication",
162
+ "",
163
+ "Integration commands:",
164
+ " intake-status|intake-run|intake-disable|intake-enable|intake-reset-checkpoint",
165
+ " jira-search|jira-get-issue|jira-search-assigned-open|jira-submit-issue|jira-submit-assigned-open|jira-poll-once|jira-poll-daemon",
166
+ "",
167
+ "Global options:",
168
+ " -h, --help Show this help",
169
+ " -v, --version Show the installed package version",
170
+ "",
171
+ "Notes:",
172
+ " - `optimus` with no command prints help instead of starting runtime.",
173
+ ` - Run \`optimus setup\` once; Optimus stores config and runtime data under ${resolveOptimusHomeDir()}.`
174
+ ].join("\n");
175
+ }
176
+ async function readPackageVersion() {
177
+ const raw = await readFile(PACKAGE_JSON_PATH, "utf8");
178
+ const parsed = JSON.parse(raw);
179
+ return parsed.version?.trim() || "0.0.0";
180
+ }
181
+ async function rerunCurrentCommand() {
182
+ const scriptPath = process.argv[1];
183
+ if (!scriptPath) {
184
+ throw new Error("Unable to resolve current Optimus entry script.");
185
+ }
186
+ return new Promise((resolve, reject) => {
187
+ const child = spawn(process.execPath, [scriptPath, ...process.argv.slice(2)], {
188
+ stdio: "inherit",
189
+ env: process.env
190
+ });
191
+ child.on("error", reject);
192
+ child.on("close", (code) => resolve(code ?? 1));
193
+ });
194
+ }
195
+ async function maybeHandleStartupSelfUpdate(input) {
196
+ const check = await checkForSelfUpdate({
197
+ command: input.command,
198
+ config: input.config,
199
+ currentVersion: input.currentVersion,
200
+ packageName: PACKAGE_NAME,
201
+ packageRoot: CLI_ROOT_DIR
202
+ });
203
+ if (!check.checked) {
204
+ if (check.reason === "cached" && check.updateAvailable && check.latestVersion) {
205
+ console.log(`[optimus] update available ${check.currentVersion} -> ${check.latestVersion}. Run \`optimus upgrade\`.`);
206
+ }
207
+ return { handled: false };
208
+ }
209
+ if (check.reason === "check_failed") {
210
+ await input.logger.warn("self_update.check_failed", {
211
+ command: input.command,
212
+ reason: check.state.lastError ?? "unknown"
213
+ });
214
+ return { handled: false };
215
+ }
216
+ await input.logger.info(check.updateAvailable ? "self_update.available" : "self_update.current", {
217
+ command: input.command,
218
+ currentVersion: check.currentVersion,
219
+ latestVersion: check.latestVersion ?? null,
220
+ installSource: check.installSource
221
+ });
222
+ if (!check.updateAvailable || !check.latestVersion) {
223
+ return { handled: false };
224
+ }
225
+ if ((input.config.selfUpdate ?? buildDefaultConfig().selfUpdate).mode !== "auto" || input.command !== "start") {
226
+ console.log(`[optimus] update available ${check.currentVersion} -> ${check.latestVersion}. Run \`optimus upgrade\`.`);
227
+ return { handled: false };
228
+ }
229
+ console.log(`[optimus] upgrading ${check.currentVersion} -> ${check.latestVersion} before start...`);
230
+ await input.logger.info("self_update.install_started", {
231
+ command: input.command,
232
+ currentVersion: check.currentVersion,
233
+ latestVersion: check.latestVersion
234
+ });
235
+ const install = await installSelfUpdate({
236
+ currentVersion: check.currentVersion,
237
+ packageName: PACKAGE_NAME,
238
+ packageRoot: CLI_ROOT_DIR,
239
+ targetVersion: check.latestVersion
240
+ });
241
+ if (!install.success) {
242
+ await input.logger.error("self_update.install_failed", {
243
+ command: input.command,
244
+ currentVersion: check.currentVersion,
245
+ latestVersion: check.latestVersion,
246
+ reason: install.state.lastError ?? install.reason ?? "unknown"
247
+ });
248
+ console.log(`[optimus] upgrade failed, continuing with ${check.currentVersion}.`);
249
+ return { handled: false };
250
+ }
251
+ await input.logger.info("self_update.install_succeeded", {
252
+ command: input.command,
253
+ currentVersion: check.currentVersion,
254
+ latestVersion: check.latestVersion
255
+ });
256
+ console.log(`[optimus] upgrade succeeded, restarting ${input.command}...`);
257
+ return {
258
+ handled: true,
259
+ exitCode: await rerunCurrentCommand()
260
+ };
261
+ }
262
+ async function pathExists(path) {
263
+ try {
264
+ await access(path);
265
+ return true;
266
+ }
267
+ catch {
268
+ return false;
269
+ }
270
+ }
271
+ function buildPublicationReviewSummary(attempts) {
272
+ const latestReviewSubmit = [...attempts].reverse().find((attempt) => attempt.mode === "review_submit") ?? null;
273
+ if (!latestReviewSubmit) {
274
+ return { latestReviewSubmit: null };
275
+ }
276
+ return {
277
+ latestReviewSubmit: {
278
+ mode: latestReviewSubmit.mode,
279
+ status: latestReviewSubmit.status,
280
+ summary: latestReviewSubmit.summary,
281
+ ...(latestReviewSubmit.reviewUrl ? { reviewUrl: latestReviewSubmit.reviewUrl } : {}),
282
+ reviewUrls: latestReviewSubmit.reviewUrls ?? (latestReviewSubmit.reviewUrl ? [latestReviewSubmit.reviewUrl] : []),
283
+ ...(latestReviewSubmit.topic ? { topic: latestReviewSubmit.topic } : {}),
284
+ ...(latestReviewSubmit.topicUrl ? { topicUrl: latestReviewSubmit.topicUrl } : {}),
285
+ changeIds: latestReviewSubmit.changeIds ?? (latestReviewSubmit.changeId ? [latestReviewSubmit.changeId] : []),
286
+ ...(latestReviewSubmit.artifactPath ? { artifactPath: latestReviewSubmit.artifactPath } : {}),
287
+ createdAt: latestReviewSubmit.createdAt
288
+ }
289
+ };
290
+ }
291
+ function buildFeishuEnvStatus() {
292
+ return {
293
+ webhook: Boolean(process.env.OPTIMUS_FEISHU_WEBHOOK?.trim()),
294
+ webhooks: Boolean(process.env.OPTIMUS_FEISHU_WEBHOOKS?.trim()),
295
+ secret: Boolean(process.env.OPTIMUS_FEISHU_SECRET?.trim())
296
+ };
297
+ }
298
+ async function buildFeishuDoctorStatus() {
299
+ const env = buildFeishuEnvStatus();
300
+ const authCommand = "feishu auth login";
301
+ const resolverCommand = "feishu user resolve <xiaomi_email>";
302
+ try {
303
+ const versionResult = await execFileAsync("feishu", ["--version"], { encoding: "utf8" });
304
+ const version = typeof versionResult.stdout === "string" ? versionResult.stdout.trim() : String(versionResult.stdout).trim();
305
+ try {
306
+ const authResult = await execFileAsync("feishu", ["auth", "status"], { encoding: "utf8" });
307
+ const authText = `${typeof authResult.stdout === "string" ? authResult.stdout : String(authResult.stdout)}\n${typeof authResult.stderr === "string" ? authResult.stderr : String(authResult.stderr)}`.trim();
308
+ return {
309
+ env,
310
+ cli: {
311
+ installed: true,
312
+ authenticated: true,
313
+ authCommand,
314
+ ...(version ? { version } : {}),
315
+ detail: authText || "Feishu CLI authenticated."
316
+ },
317
+ mentions: {
318
+ requiresOpenId: true,
319
+ resolverCommand,
320
+ ready: true,
321
+ detail: "Real @mentions require open_id resolution. Auth is ready; if resolve still fails, check Feishu app scope contact:user.id:readonly.",
322
+ recommendation: "If @mentions still degrade to text, verify the Feishu app has contact:user.id:readonly."
323
+ }
324
+ };
325
+ }
326
+ catch (error) {
327
+ const detail = extractCommandFailureDetail(error) || "Feishu CLI is installed but not authenticated.";
328
+ return {
329
+ env,
330
+ cli: {
331
+ installed: true,
332
+ authenticated: false,
333
+ authCommand,
334
+ ...(version ? { version } : {}),
335
+ detail,
336
+ recommendation: "Run 'feishu auth login' to refresh Feishu authorization for doc upload and user identity resolution."
337
+ },
338
+ mentions: {
339
+ requiresOpenId: true,
340
+ resolverCommand,
341
+ ready: false,
342
+ detail: "Real @mentions require open_id resolution, but Feishu CLI auth is not ready.",
343
+ recommendation: "Run 'feishu auth login' before relying on Feishu user resolution or real @mentions."
344
+ }
345
+ };
346
+ }
347
+ }
348
+ catch (error) {
349
+ const detail = extractCommandFailureDetail(error) || "Feishu CLI is not installed.";
350
+ return {
351
+ env,
352
+ cli: {
353
+ installed: false,
354
+ authenticated: false,
355
+ authCommand,
356
+ detail,
357
+ recommendation: "Install Feishu CLI first, then run 'feishu auth login' if you need Feishu delivery, docs, or real @mentions."
358
+ },
359
+ mentions: {
360
+ requiresOpenId: true,
361
+ resolverCommand,
362
+ ready: false,
363
+ detail: "Real @mentions require Feishu CLI user resolution support.",
364
+ recommendation: "Install Feishu CLI and authenticate before relying on Feishu open_id resolution."
365
+ }
366
+ };
367
+ }
368
+ }
369
+ function extractCommandFailureDetail(error) {
370
+ if (!(error instanceof Error)) {
371
+ return typeof error === "string" ? error : undefined;
372
+ }
373
+ const detail = [error.message, error.stderr, error.stdout]
374
+ .filter((value) => typeof value === "string" && value.trim().length > 0)
375
+ .join("\n")
376
+ .trim();
377
+ return detail || undefined;
378
+ }
379
+ async function promptSetupAnswers(defaults) {
380
+ const configPath = resolveDefaultConfigPath();
381
+ const scriptedAnswers = input.isTTY ? undefined : await readScriptedPromptAnswers();
382
+ const rl = scriptedAnswers ? undefined : createInterface({ input, output });
383
+ let scriptedIndex = 0;
384
+ const ask = async (prompt) => {
385
+ if (scriptedAnswers) {
386
+ output.write(prompt);
387
+ const answer = scriptedAnswers[scriptedIndex] ?? "";
388
+ scriptedIndex += 1;
389
+ return answer;
390
+ }
391
+ return rl.question(prompt);
392
+ };
393
+ try {
394
+ if (await pathExists(configPath)) {
395
+ console.log(`Existing config found at ${configPath}. Press Enter to keep current values.`);
396
+ }
397
+ const repoPath = (await ask(`Repository path [${defaults.repoPath}]: `)).trim() || defaults.repoPath;
398
+ const repoAlias = (await ask(`Repository alias [${defaults.repoAlias}]: `)).trim() || defaults.repoAlias;
399
+ const codexAuthMode = await askSetupCodexAuthMode({ ask, defaultMode: defaults.codexAuthMode });
400
+ let codexApiKey;
401
+ let codexProviderId;
402
+ let codexProviderDisplayName;
403
+ let codexProviderBaseUrl;
404
+ let codexProviderApiKeyEnvName;
405
+ if (codexAuthMode === "openai_api_key") {
406
+ codexApiKey = (await ask("OpenAI API key for OPENAI_API_KEY (leave blank to keep existing): ")).trim() || undefined;
407
+ }
408
+ else if (codexAuthMode === "model_provider") {
409
+ codexProviderId = (await ask(`Provider id [${defaults.codexProviderId ?? ""}]: `)).trim() || defaults.codexProviderId;
410
+ codexProviderDisplayName = (await ask(`Provider display name [${defaults.codexProviderDisplayName ?? ""}]: `)).trim() || defaults.codexProviderDisplayName;
411
+ codexProviderBaseUrl = (await ask(`Provider base URL [${defaults.codexProviderBaseUrl ?? ""}]: `)).trim() || defaults.codexProviderBaseUrl;
412
+ codexProviderApiKeyEnvName = (await ask(`Provider API key env name [${defaults.codexProviderApiKeyEnvName ?? ""}]: `)).trim() || defaults.codexProviderApiKeyEnvName;
413
+ const providerKeyLabel = codexProviderApiKeyEnvName || defaults.codexProviderApiKeyEnvName || "the configured provider env";
414
+ codexApiKey = (await ask(`Provider API key value for ${providerKeyLabel} (leave blank to keep existing): `)).trim() || undefined;
415
+ }
416
+ const enableFeishuInput = (await ask(`Enable Feishu delivery? [${defaults.enableFeishu ? "Y/n" : "y/N"}] `)).trim().toLowerCase();
417
+ const enableFeishu = enableFeishuInput.length === 0
418
+ ? defaults.enableFeishu
419
+ : ["y", "yes"].includes(enableFeishuInput);
420
+ const feishuWebhook = enableFeishu
421
+ ? (await ask(`Feishu webhook [${defaults.feishuWebhook ? "configured" : ""}]: `)).trim() || defaults.feishuWebhook
422
+ : undefined;
423
+ const feishuSecret = enableFeishu
424
+ ? (await ask(`Feishu secret (optional) [${defaults.feishuSecret ? "configured" : ""}]: `)).trim() || defaults.feishuSecret
425
+ : undefined;
426
+ const enableJiraInput = (await ask(`Enable Jira integration? [${defaults.enableJira ? "Y/n" : "y/N"}] `)).trim().toLowerCase();
427
+ const enableJira = enableJiraInput.length === 0
428
+ ? defaults.enableJira
429
+ : ["y", "yes"].includes(enableJiraInput);
430
+ const jiraBaseUrl = enableJira
431
+ ? (await ask(`Jira base URL [${defaults.jiraBaseUrl ?? ""}]: `)).trim() || defaults.jiraBaseUrl
432
+ : undefined;
433
+ const jiraToken = enableJira
434
+ ? (await ask(`Jira token [${defaults.jiraToken ? "configured" : ""}]: `)).trim() || defaults.jiraToken
435
+ : undefined;
436
+ return {
437
+ repoPath,
438
+ repoAlias,
439
+ codexAuthMode,
440
+ ...(codexApiKey ? { codexApiKey } : {}),
441
+ ...(codexProviderId ? { codexProviderId } : {}),
442
+ ...(codexProviderDisplayName ? { codexProviderDisplayName } : {}),
443
+ ...(codexProviderBaseUrl ? { codexProviderBaseUrl } : {}),
444
+ ...(codexProviderApiKeyEnvName ? { codexProviderApiKeyEnvName } : {}),
445
+ enableFeishu,
446
+ ...(feishuWebhook ? { feishuWebhook } : {}),
447
+ ...(feishuSecret ? { feishuSecret } : {}),
448
+ enableJira,
449
+ ...(jiraBaseUrl ? { jiraBaseUrl } : {}),
450
+ ...(jiraToken ? { jiraToken } : {})
451
+ };
452
+ }
453
+ finally {
454
+ rl?.close();
455
+ }
456
+ }
457
+ async function readScriptedPromptAnswers() {
458
+ const chunks = [];
459
+ for await (const chunk of input) {
460
+ chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
461
+ }
462
+ return chunks.join("").split(/\r?\n/);
463
+ }
464
+ async function resolveDefaultSetupAnswers() {
465
+ const defaults = buildDefaultConfig();
466
+ const configPath = resolveDefaultConfigPath();
467
+ if (!await pathExists(configPath)) {
468
+ return {
469
+ repoPath: "",
470
+ repoAlias: "",
471
+ codexAuthMode: defaults.codex.auth.mode,
472
+ enableFeishu: false,
473
+ enableJira: false
474
+ };
475
+ }
476
+ const config = await loadConfig(configPath);
477
+ const store = new SQLiteTaskStore(config.storage.rootDir);
478
+ await store.init();
479
+ const repositories = await store.listRepositoryRoots();
480
+ const primaryRepository = repositories[0];
481
+ const provider = config.codex.provider;
482
+ return {
483
+ repoPath: primaryRepository?.path ?? "",
484
+ repoAlias: primaryRepository?.alias ?? "",
485
+ codexAuthMode: config.codex.auth.mode,
486
+ ...(provider?.id ? { codexProviderId: provider.id } : {}),
487
+ ...(provider?.displayName ? { codexProviderDisplayName: provider.displayName } : {}),
488
+ ...(provider?.baseUrl ? { codexProviderBaseUrl: provider.baseUrl } : {}),
489
+ ...(provider?.apiKeyEnvName ? { codexProviderApiKeyEnvName: provider.apiKeyEnvName } : {}),
490
+ enableFeishu: config.delivery.channels.includes("feishu"),
491
+ ...(config.delivery.feishu.webhook ? { feishuWebhook: config.delivery.feishu.webhook } : {}),
492
+ ...(config.delivery.feishu.secret ? { feishuSecret: config.delivery.feishu.secret } : {}),
493
+ enableJira: config.jira.enabled,
494
+ ...(config.jira.baseUrl ? { jiraBaseUrl: config.jira.baseUrl } : {}),
495
+ ...(config.jira.bearerToken ? { jiraToken: config.jira.bearerToken } : {})
496
+ };
497
+ }
498
+ function normalizeSetupCodexAuthMode(value) {
499
+ const normalized = value?.trim();
500
+ if (normalized === "1") {
501
+ return "model_provider";
502
+ }
503
+ if (normalized === "2") {
504
+ return "openai_api_key";
505
+ }
506
+ if (normalized === "3") {
507
+ return "codex_cli_login";
508
+ }
509
+ if (normalized === "openai_api_key" || normalized === "codex_cli_login" || normalized === "model_provider") {
510
+ return normalized;
511
+ }
512
+ return undefined;
513
+ }
514
+ function renderSetupCodexAuthModePrompt(defaultMode) {
515
+ const defaultIndex = SETUP_CODEX_AUTH_MODE_OPTIONS.find((option) => option.mode === defaultMode)?.index ?? "1";
516
+ const lines = ["Codex auth mode:"];
517
+ for (const option of SETUP_CODEX_AUTH_MODE_OPTIONS) {
518
+ const defaultSuffix = option.index === defaultIndex ? " (default)" : "";
519
+ lines.push(`${option.index}. ${option.label} [${option.mode}]${defaultSuffix}`);
520
+ }
521
+ lines.push(`Choose [${defaultIndex}]: `);
522
+ return `${lines.join("\n")}\n`;
523
+ }
524
+ async function askSetupCodexAuthMode(input) {
525
+ while (true) {
526
+ const answer = (await input.ask(renderSetupCodexAuthModePrompt(input.defaultMode))).trim();
527
+ if (answer.length === 0) {
528
+ return input.defaultMode;
529
+ }
530
+ const normalized = normalizeSetupCodexAuthMode(answer);
531
+ if (normalized) {
532
+ return normalized;
533
+ }
534
+ console.log("Invalid selection. Choose 1, 2, or 3.");
535
+ }
536
+ }
537
+ function validateSetupAnswers(answers) {
538
+ if (!answers.repoPath.trim()) {
539
+ return "setup requires a repository path.";
540
+ }
541
+ if (!answers.repoAlias.trim()) {
542
+ return "setup requires a repository alias.";
543
+ }
544
+ if (answers.codexAuthMode === "model_provider" && !answers.codexProviderId?.trim()) {
545
+ return "setup requires provider id when Codex auth mode is model_provider.";
546
+ }
547
+ if (answers.codexAuthMode === "model_provider" && !answers.codexProviderDisplayName?.trim()) {
548
+ return "setup requires provider display name when Codex auth mode is model_provider.";
549
+ }
550
+ if (answers.codexAuthMode === "model_provider" && !answers.codexProviderBaseUrl?.trim()) {
551
+ return "setup requires provider base URL when Codex auth mode is model_provider.";
552
+ }
553
+ if (answers.codexAuthMode === "model_provider" && !answers.codexProviderApiKeyEnvName?.trim()) {
554
+ return "setup requires provider API key env name when Codex auth mode is model_provider.";
555
+ }
556
+ if (answers.enableFeishu && !answers.feishuWebhook?.trim()) {
557
+ return "setup requires a Feishu webhook when Feishu delivery is enabled.";
558
+ }
559
+ if (answers.enableJira && !answers.jiraBaseUrl?.trim()) {
560
+ return "setup requires a Jira base URL when Jira integration is enabled.";
561
+ }
562
+ if (answers.enableJira && !answers.jiraToken?.trim()) {
563
+ return "setup requires a Jira token when Jira integration is enabled.";
564
+ }
565
+ return undefined;
566
+ }
567
+ async function detectRepositoryReviewSystem(repositoryPath, inspection) {
568
+ if (!inspection.rootIsGit) {
569
+ return "manual";
570
+ }
571
+ const remoteNamesOutput = await execFileAsync("git", ["-C", repositoryPath, "remote"], { encoding: "utf8" })
572
+ .then((result) => typeof result.stdout === "string" ? result.stdout : String(result.stdout))
573
+ .catch(() => "");
574
+ const remoteNames = remoteNamesOutput
575
+ .split(/\r?\n/)
576
+ .map((value) => value.trim())
577
+ .filter(Boolean);
578
+ const primaryRemote = remoteNames.includes("origin") ? "origin" : remoteNames[0];
579
+ if (!primaryRemote) {
580
+ return "manual";
581
+ }
582
+ const remoteUrl = await execFileAsync("git", ["-C", repositoryPath, "remote", "get-url", primaryRemote], { encoding: "utf8" })
583
+ .then((result) => (typeof result.stdout === "string" ? result.stdout : String(result.stdout)).trim())
584
+ .catch(() => "");
585
+ if (!remoteUrl) {
586
+ return "manual";
587
+ }
588
+ if (/github\.com[:/]/i.test(remoteUrl)) {
589
+ return "github_pr";
590
+ }
591
+ if (/gerrit/i.test(remoteUrl)) {
592
+ return "gerrit_change";
593
+ }
594
+ return "manual";
595
+ }
596
+ function resolveSetupCodexApiKeyEnvName(answers) {
597
+ if (answers.codexAuthMode === "openai_api_key") {
598
+ return "OPENAI_API_KEY";
599
+ }
600
+ if (answers.codexAuthMode === "model_provider") {
601
+ return answers.codexProviderApiKeyEnvName?.trim() || undefined;
602
+ }
603
+ return undefined;
604
+ }
605
+ function buildSetupConfig(answers) {
606
+ const config = buildDefaultConfig();
607
+ config.codex.auth.mode = answers.codexAuthMode;
608
+ if (answers.codexAuthMode === "model_provider") {
609
+ config.codex.provider = {
610
+ id: answers.codexProviderId.trim(),
611
+ displayName: answers.codexProviderDisplayName.trim(),
612
+ baseUrl: answers.codexProviderBaseUrl.trim(),
613
+ apiKeyEnvName: answers.codexProviderApiKeyEnvName.trim(),
614
+ wireApi: "responses"
615
+ };
616
+ }
617
+ else {
618
+ delete config.codex.provider;
619
+ }
620
+ config.delivery.channels = answers.enableFeishu ? ["console", "feishu"] : ["console"];
621
+ config.delivery.feishu = {
622
+ ...config.delivery.feishu,
623
+ ...(answers.enableFeishu && answers.feishuWebhook ? { webhook: answers.feishuWebhook } : {}),
624
+ ...(answers.enableFeishu && answers.feishuSecret ? { secret: answers.feishuSecret } : {})
625
+ };
626
+ config.jira.enabled = answers.enableJira;
627
+ if (answers.enableJira) {
628
+ config.jira.baseUrl = answers.jiraBaseUrl ?? config.jira.baseUrl;
629
+ config.jira.authType = "bearer";
630
+ if (answers.jiraToken) {
631
+ config.jira.bearerToken = answers.jiraToken;
632
+ }
633
+ config.jira.httpHeaders = {};
634
+ }
635
+ return JSON.stringify(config, null, 2);
636
+ }
637
+ async function upsertDotEnvValue(path, key, value) {
638
+ let lines = [];
639
+ try {
640
+ lines = (await readFile(path, "utf8")).split(/\r?\n/);
641
+ }
642
+ catch {
643
+ lines = [];
644
+ }
645
+ let replaced = false;
646
+ const nextLines = lines.map((line) => {
647
+ if (!line.match(new RegExp(`^\\s*${key}=`))) {
648
+ return line;
649
+ }
650
+ replaced = true;
651
+ return `${key}=${value}`;
652
+ });
653
+ if (!replaced) {
654
+ if (nextLines.length > 0 && nextLines.at(-1)?.trim() !== "") {
655
+ nextLines.push("");
656
+ }
657
+ nextLines.push(`${key}=${value}`);
658
+ }
659
+ await writeFile(path, `${nextLines.join("\n").replace(/\n*$/, "\n")}`, "utf8");
660
+ }
661
+ async function readRawProjectConfig(configPath) {
662
+ try {
663
+ const raw = await readFile(configPath, "utf8");
664
+ return JSON.parse(raw);
665
+ }
666
+ catch {
667
+ return undefined;
668
+ }
669
+ }
670
+ async function readGitIdentity(repositoryPath) {
671
+ const userName = await execFileAsync("git", ["-C", repositoryPath, "config", "--get", "user.name"], { encoding: "utf8" })
672
+ .then((result) => (typeof result.stdout === "string" ? result.stdout : String(result.stdout)).trim() || undefined)
673
+ .catch(() => undefined);
674
+ const userEmail = await execFileAsync("git", ["-C", repositoryPath, "config", "--get", "user.email"], { encoding: "utf8" })
675
+ .then((result) => (typeof result.stdout === "string" ? result.stdout : String(result.stdout)).trim() || undefined)
676
+ .catch(() => undefined);
677
+ return {
678
+ ...(userName ? { userName } : {}),
679
+ ...(userEmail ? { userEmail } : {})
680
+ };
681
+ }
682
+ async function runQuickDoctor(configPath = resolveDefaultConfigPath()) {
683
+ const blocking = [];
684
+ const warnings = [];
685
+ const repositories = [];
686
+ const next = new Set();
687
+ if (!await pathExists(configPath)) {
688
+ return {
689
+ summary: "blocked",
690
+ configPath,
691
+ blocking: [{
692
+ code: "config_missing",
693
+ message: "Config file not found.",
694
+ fix: "Run `optimus setup` first."
695
+ }],
696
+ warnings: [],
697
+ repositories: [],
698
+ next: ["optimus setup"]
699
+ };
700
+ }
701
+ const rawConfig = await readRawProjectConfig(configPath);
702
+ if (!rawConfig) {
703
+ return {
704
+ summary: "blocked",
705
+ configPath,
706
+ blocking: [{
707
+ code: "config_parse_failed",
708
+ message: "Config file exists but could not be parsed.",
709
+ fix: "Fix `optimus.config.json` or rerun `optimus setup --force`."
710
+ }],
711
+ warnings: [],
712
+ repositories: [],
713
+ next: ["optimus setup --force"]
714
+ };
715
+ }
716
+ let config;
717
+ try {
718
+ config = await loadConfig(configPath);
719
+ }
720
+ catch (error) {
721
+ return {
722
+ summary: "blocked",
723
+ configPath,
724
+ blocking: [{
725
+ code: "config_load_failed",
726
+ message: error instanceof Error ? error.message : "Failed to load config.",
727
+ fix: "Fix `optimus.config.json` or rerun `optimus setup --force`."
728
+ }],
729
+ warnings: [],
730
+ repositories: [],
731
+ next: ["optimus setup --force"]
732
+ };
733
+ }
734
+ const codexAuth = new CodexAuthResolver(config).resolve().diagnostics;
735
+ if (!codexAuth.authenticated) {
736
+ blocking.push({
737
+ code: "codex_auth_missing",
738
+ message: `Codex authentication is missing. Required credential: ${codexAuth.apiKeyEnvName ?? "runtime login"}.`,
739
+ fix: codexAuth.apiKeyEnvName
740
+ ? `Provide ${codexAuth.apiKeyEnvName} in .env.local, or rerun \`optimus setup\` and enter the Codex API key.`
741
+ : "Rerun `optimus setup` and select a supported Codex auth mode."
742
+ });
743
+ next.add("optimus setup");
744
+ }
745
+ if (codexAuth.mode === "codex_cli_login") {
746
+ warnings.push({
747
+ code: "codex_cli_login_runtime_validation",
748
+ message: "Codex auth mode is codex_cli_login.",
749
+ impact: "Quick doctor cannot statically verify local Codex CLI login; the first runtime turn will validate it."
750
+ });
751
+ }
752
+ if (!config.codex.model?.trim()) {
753
+ blocking.push({
754
+ code: "codex_model_missing",
755
+ message: "Codex model default is missing from project config.",
756
+ fix: "Rerun `optimus setup --force` to restore project defaults."
757
+ });
758
+ next.add("optimus setup --force");
759
+ }
760
+ if (resolveCodexAuthMode(config) === "model_provider" && !resolveConfiguredCodexProvider(config)) {
761
+ blocking.push({
762
+ code: "codex_provider_missing",
763
+ message: "Codex model provider metadata is missing or invalid.",
764
+ fix: "Rerun `optimus setup --force` and provide provider id, display name, base URL, and API key env name."
765
+ });
766
+ next.add("optimus setup --force");
767
+ }
768
+ const store = new SQLiteTaskStore(config.storage.rootDir);
769
+ await store.init();
770
+ const registeredRepositories = await store.listRepositoryRoots();
771
+ if (registeredRepositories.length === 0) {
772
+ blocking.push({
773
+ code: "repository_missing",
774
+ message: "No repository is registered.",
775
+ fix: "Rerun `optimus setup` and provide a repository path and alias."
776
+ });
777
+ next.add("optimus setup");
778
+ }
779
+ for (const repository of registeredRepositories) {
780
+ const inspection = await inspectRepositoryRoot(repository.path);
781
+ const executionPlan = describeRepositoryExecutionPlan(repository, inspection);
782
+ const reviewSystem = await detectRepositoryReviewSystem(repository.path, inspection);
783
+ repositories.push({
784
+ path: repository.path,
785
+ ...(repository.alias ? { alias: repository.alias } : {}),
786
+ ok: inspection.ok,
787
+ workspaceKind: executionPlan.workspaceKind,
788
+ reviewSystem,
789
+ executionMode: repository.executionMode,
790
+ ...(inspection.reason ? { reason: inspection.reason } : {})
791
+ });
792
+ if (!inspection.ok) {
793
+ blocking.push({
794
+ code: "repository_invalid",
795
+ message: `Repository path is not usable: ${repository.path}`,
796
+ fix: "Rerun `optimus setup` with a valid repository path."
797
+ });
798
+ next.add("optimus setup");
799
+ }
800
+ }
801
+ const firstRepository = registeredRepositories[0];
802
+ if (firstRepository) {
803
+ const gitIdentity = await readGitIdentity(firstRepository.path);
804
+ if (!gitIdentity.userName) {
805
+ blocking.push({
806
+ code: "git_user_name_missing",
807
+ message: "Git user.name is missing for the configured repository.",
808
+ fix: "Run `git config user.name \"Your Name\"` in the repository, then rerun `optimus doctor --quick`."
809
+ });
810
+ next.add("git config user.name \"Your Name\"");
811
+ }
812
+ if (!gitIdentity.userEmail) {
813
+ blocking.push({
814
+ code: "git_user_email_missing",
815
+ message: "Git user.email is missing for the configured repository.",
816
+ fix: "Run `git config user.email \"you@example.com\"` in the repository, then rerun `optimus doctor --quick`."
817
+ });
818
+ next.add("git config user.email \"you@example.com\"");
819
+ }
820
+ }
821
+ const feishuEnabled = config.delivery.channels.includes("feishu");
822
+ const configuredFeishuTargets = [
823
+ config.delivery.feishu.webhook?.trim(),
824
+ ...(config.delivery.feishu.webhooks ?? []).map((value) => value.trim())
825
+ ].filter((value) => Boolean(value));
826
+ if (feishuEnabled && configuredFeishuTargets.length === 0) {
827
+ blocking.push({
828
+ code: "feishu_webhook_missing",
829
+ message: "Feishu delivery is enabled, but no webhook is configured.",
830
+ fix: "Rerun `optimus setup` and provide a Feishu webhook."
831
+ });
832
+ next.add("optimus setup");
833
+ }
834
+ if (!feishuEnabled) {
835
+ warnings.push({
836
+ code: "feishu_disabled",
837
+ message: "Feishu delivery is disabled.",
838
+ impact: "Notifications will stay console-only."
839
+ });
840
+ }
841
+ if (config.jira.enabled) {
842
+ if (!config.jira.baseUrl?.trim() || /jira\.example\.com/i.test(config.jira.baseUrl)) {
843
+ blocking.push({
844
+ code: "jira_base_url_missing",
845
+ message: "Jira integration is enabled, but Jira base URL is missing or still using the placeholder value.",
846
+ fix: "Rerun `optimus setup` and provide a real Jira base URL."
847
+ });
848
+ next.add("optimus setup");
849
+ }
850
+ if (!config.jira.bearerToken?.trim()) {
851
+ blocking.push({
852
+ code: "jira_token_missing",
853
+ message: "Jira integration is enabled, but Jira token is missing.",
854
+ fix: "Rerun `optimus setup` and provide a Jira token."
855
+ });
856
+ next.add("optimus setup");
857
+ }
858
+ }
859
+ else {
860
+ warnings.push({
861
+ code: "jira_disabled",
862
+ message: "Jira integration is disabled.",
863
+ impact: "Jira submission commands will not be ready until setup is rerun with Jira enabled."
864
+ });
865
+ }
866
+ return {
867
+ summary: blocking.length > 0 ? "blocked" : warnings.length > 0 ? "degraded" : "ready",
868
+ configPath,
869
+ blocking,
870
+ warnings,
871
+ repositories,
872
+ next: [...next]
873
+ };
874
+ }
875
+ async function runSetup(args) {
876
+ const configPath = resolveDefaultConfigPath();
877
+ const envPath = resolveDefaultEnvPath();
878
+ const configExists = await pathExists(configPath);
879
+ const defaults = await resolveDefaultSetupAnswers();
880
+ if (args["non-interactive"] === "true") {
881
+ throw new Error("setup no longer supports --non-interactive. Run `optimus setup` interactively.");
882
+ }
883
+ const answers = await promptSetupAnswers(defaults);
884
+ const validationError = validateSetupAnswers(answers);
885
+ if (validationError) {
886
+ throw new Error(validationError);
887
+ }
888
+ const normalizedRepoPath = resolve(answers.repoPath);
889
+ const inspection = await inspectRepositoryRoot(normalizedRepoPath);
890
+ if (!inspection.ok) {
891
+ throw new Error(`Repository path is not usable: ${normalizedRepoPath} (${inspection.reason ?? "unknown"})`);
892
+ }
893
+ const previewExecutionPlan = describeRepositoryExecutionPlan({ executionMode: "copy" }, inspection);
894
+ const reviewSystem = await detectRepositoryReviewSystem(normalizedRepoPath, inspection);
895
+ await mkdir(dirname(configPath), { recursive: true });
896
+ await writeFile(configPath, `${buildSetupConfig({ ...answers, repoPath: normalizedRepoPath })}\n`, "utf8");
897
+ const codexAuthEnvName = resolveSetupCodexApiKeyEnvName(answers);
898
+ if (answers.codexApiKey && codexAuthEnvName) {
899
+ await mkdir(dirname(envPath), { recursive: true });
900
+ await upsertDotEnvValue(envPath, codexAuthEnvName, answers.codexApiKey);
901
+ }
902
+ const config = await loadConfig(configPath);
903
+ const store = new SQLiteTaskStore(config.storage.rootDir);
904
+ await store.init();
905
+ const existingRepositories = await store.listRepositoryRoots();
906
+ for (const repository of existingRepositories) {
907
+ await store.removeRepositoryRoot(repository.path);
908
+ }
909
+ await store.addRepositoryRoot({
910
+ path: normalizedRepoPath,
911
+ alias: answers.repoAlias,
912
+ executionMode: previewExecutionPlan.resolvedDefaultMode
913
+ });
914
+ const doctorQuick = await runQuickDoctor(configPath);
915
+ const codexAuth = new CodexAuthResolver(config).resolve().diagnostics;
916
+ return {
917
+ ok: true,
918
+ configPath,
919
+ envPath,
920
+ configStatus: configExists ? "overwritten" : "created",
921
+ repository: {
922
+ path: normalizedRepoPath,
923
+ alias: answers.repoAlias,
924
+ workspaceKind: previewExecutionPlan.workspaceKind,
925
+ executionMode: previewExecutionPlan.resolvedDefaultMode,
926
+ reviewSystem
927
+ },
928
+ deliveryChannels: config.delivery.channels,
929
+ jiraEnabled: config.jira.enabled,
930
+ codexAuth: {
931
+ mode: codexAuth.mode,
932
+ ...(codexAuth.apiKeyEnvName ? { envName: codexAuth.apiKeyEnvName } : {}),
933
+ configured: codexAuth.authenticated,
934
+ ...(codexAuth.providerId ? {
935
+ provider: {
936
+ id: codexAuth.providerId,
937
+ displayName: codexAuth.providerDisplayName ?? codexAuth.providerId
938
+ }
939
+ } : {})
940
+ },
941
+ doctorQuick
942
+ };
943
+ }
944
+ function inferArtifactDir(artifacts, bundle) {
945
+ const candidates = [
946
+ bundle?.artifacts.resultMd,
947
+ bundle?.artifacts.patchDiff,
948
+ ...artifacts.map((artifact) => artifact.path)
949
+ ].filter((value) => typeof value === "string" && value.trim().length > 0);
950
+ const artifactPath = candidates.find((candidate) => candidate.includes(`${join(".optimus", "artifacts")}${candidate.includes("\\") ? "\\" : "/"}`))
951
+ ?? candidates[0];
952
+ if (!artifactPath) {
953
+ return undefined;
954
+ }
955
+ return dirname(artifactPath);
956
+ }
957
+ function inferTaskRootDir(artifactDir) {
958
+ if (!artifactDir) {
959
+ return undefined;
960
+ }
961
+ return resolve(artifactDir, "..", "..");
962
+ }
963
+ async function buildReplayExecutionContext(input) {
964
+ const artifactDir = inferArtifactDir(input.artifacts, input.deliveryBundle);
965
+ const taskRootDir = inferTaskRootDir(artifactDir);
966
+ const sourceDir = input.deliveryBundle?.publication?.git?.repoDir?.trim()
967
+ || input.latestRun?.addresses?.visibleRepoDir?.trim()
968
+ || input.latestRun?.addresses?.workspaceDir?.trim();
969
+ const workspaceDir = input.latestRun?.addresses?.workspaceDir?.trim()
970
+ || sourceDir;
971
+ const mode = input.latestRun?.addresses?.resolvedExecutionMode
972
+ ?? input.latestRun?.addresses?.configuredExecutionMode
973
+ ?? (input.latestRun?.addresses?.visibleRepoDir ? "inplace" : "copy");
974
+ if (!artifactDir || !taskRootDir || !workspaceDir || !sourceDir) {
975
+ return undefined;
976
+ }
977
+ const repoSelector = input.taskRepo?.trim();
978
+ const repoMemory = repoSelector
979
+ ? await new RepoMemoryService(input.config).load(input.taskType, repoSelector)
980
+ : undefined;
981
+ return {
982
+ taskRootDir,
983
+ addresses: {
984
+ mode,
985
+ workspaceDir,
986
+ sourceDir,
987
+ artifactDir,
988
+ repoRef: repoSelector || sourceDir
989
+ },
990
+ sandboxMode: input.latestRun?.sandboxMode ?? input.config.codex.sandboxMode,
991
+ approvalPolicy: input.latestRun?.approvalPolicy ?? input.config.codex.approvalPolicy,
992
+ ...(input.latestRun?.addresses?.configuredExecutionMode ? { configuredExecutionMode: input.latestRun.addresses.configuredExecutionMode } : {}),
993
+ ...(repoMemory ? { repoMemory } : {})
994
+ };
995
+ }
996
+ function mergeReplayBundle(rebuilt, existing) {
997
+ if (!existing) {
998
+ return rebuilt;
999
+ }
1000
+ const effectivePublication = !rebuilt.publication
1001
+ || (rebuilt.publication.action !== "ready_for_publish" && existing.publication?.action === "ready_for_publish")
1002
+ ? existing.publication
1003
+ : rebuilt.publication;
1004
+ return {
1005
+ ...rebuilt,
1006
+ summary: {
1007
+ ...existing.summary,
1008
+ ...rebuilt.summary,
1009
+ ...(rebuilt.summary.analysisDocUrl ? {} : existing.summary.analysisDocUrl ? { analysisDocUrl: existing.summary.analysisDocUrl } : {}),
1010
+ ...(rebuilt.summary.repo ? {} : existing.summary.repo ? { repo: existing.summary.repo } : {}),
1011
+ ...(rebuilt.summary.sourceRef ? {} : existing.summary.sourceRef ? { sourceRef: existing.summary.sourceRef } : {}),
1012
+ ...(rebuilt.summary.sourceUrl ? {} : existing.summary.sourceUrl ? { sourceUrl: existing.summary.sourceUrl } : {}),
1013
+ ...(rebuilt.summary.qAssignee ? {} : existing.summary.qAssignee ? { qAssignee: existing.summary.qAssignee } : {})
1014
+ },
1015
+ ...(effectivePublication ? { publication: effectivePublication } : {})
1016
+ };
1017
+ }
1018
+ function buildDeliveryAttemptSummary(attempts) {
1019
+ const grouped = new Map();
1020
+ for (const attempt of attempts) {
1021
+ const current = grouped.get(attempt.channel) ?? [];
1022
+ current.push(attempt);
1023
+ grouped.set(attempt.channel, current);
1024
+ }
1025
+ const channels = Array.from(grouped.entries()).map(([channel, channelAttempts]) => {
1026
+ const latest = channelAttempts.at(-1);
1027
+ const failedAttempts = channelAttempts.filter((attempt) => attempt.status === "failed").length;
1028
+ const dispatchedAttempts = channelAttempts.filter((attempt) => attempt.status === "dispatched").length;
1029
+ const skippedAttempts = channelAttempts.filter((attempt) => attempt.status === "skipped").length;
1030
+ const firstFailedIndex = channelAttempts.findIndex((attempt) => attempt.status === "failed");
1031
+ const recoveredAfterFailure = firstFailedIndex >= 0 && channelAttempts.slice(firstFailedIndex + 1).some((attempt) => attempt.status === "dispatched");
1032
+ return {
1033
+ channel,
1034
+ latestStatus: latest.status,
1035
+ latestSummary: latest.summary,
1036
+ latestCreatedAt: latest.createdAt,
1037
+ ...(latest.deliveredAt ? { deliveredAt: latest.deliveredAt } : {}),
1038
+ ...(latest.error ? { error: latest.error } : {}),
1039
+ totalAttempts: channelAttempts.length,
1040
+ failedAttempts,
1041
+ dispatchedAttempts,
1042
+ skippedAttempts,
1043
+ backgroundRecovered: recoveredAfterFailure
1044
+ };
1045
+ });
1046
+ return { channels };
1047
+ }
1048
+ const TASK_EVENT_EXPLANATIONS = {
1049
+ "execution.started": "任务已进入 Codex 执行阶段,正在等待首个运行信号。",
1050
+ "stream.turn.started": "Codex turn 已启动,runner 已收到流式起始事件。",
1051
+ "turn.started": "Codex turn 已启动,任务进入稳定执行态。",
1052
+ "stream.item.completed": "Codex 流式返回了一个完成项,说明 provider 仍在持续输出。",
1053
+ "command_execution": "Agent 完成了一次命令执行。",
1054
+ "file_change": "Agent 产生了文件变更。",
1055
+ "agent_message": "Agent 产出了最终消息或阶段性结论。",
1056
+ "runtime.stall_warning": "任务已长时间没有新事件,runtime 将其标记为疑似卡住。",
1057
+ "runtime.timeout.startup": "任务在启动阶段迟迟没有首个有效进展,触发 startup timeout。",
1058
+ "runtime.timeout.idle": "任务长时间没有任何新事件,触发 idle timeout。",
1059
+ "runtime.timeout.hard": "任务总执行时长超过硬上限,触发 hard timeout。",
1060
+ "execution.completed": "任务执行已结束并完成收口。",
1061
+ "execution.failed": "任务执行已失败并完成收口。",
1062
+ "execution.canceled": "任务执行已取消并完成收口。",
1063
+ "task.needs_human": "任务需要人工补充信息或接手处理。",
1064
+ "task.delivery_bundle.warning": "交付产物存在需要关注的告警,通常表示结果文档缺少关键标准字段。",
1065
+ };
1066
+ const DOCTOR_IMPORTANT_EVENT_TYPES = [
1067
+ "runtime.started",
1068
+ "runtime.stopped",
1069
+ "runtime.health",
1070
+ "runtime.recovery.requeued",
1071
+ "event.received",
1072
+ "triage.accepted",
1073
+ "triage.rejected",
1074
+ "triage.failed",
1075
+ "task.received",
1076
+ "task.dispatched",
1077
+ "task.execution.started",
1078
+ "task.completed",
1079
+ "task.failed",
1080
+ "task.canceled",
1081
+ "task.timed_out",
1082
+ "task.fallback",
1083
+ "task.needs_human",
1084
+ "task.retry_enqueued",
1085
+ "task.cancel_requested",
1086
+ "task.delivery_bundle.warning",
1087
+ "execution.failed",
1088
+ "execution.canceled",
1089
+ "execution.completed",
1090
+ "optimus.runner.turn_completed",
1091
+ "optimus.runner.turn_failed",
1092
+ "optimus.runner.result_parsed",
1093
+ ];
1094
+ function parseArgs(argv) {
1095
+ const parsed = {};
1096
+ for (let index = 0; index < argv.length; index += 1) {
1097
+ const token = argv[index];
1098
+ if (!token) {
1099
+ continue;
1100
+ }
1101
+ if (!token.startsWith("--")) {
1102
+ continue;
1103
+ }
1104
+ const key = token.slice(2);
1105
+ const value = argv[index + 1];
1106
+ if (!value || value.startsWith("--")) {
1107
+ parsed[key] = "true";
1108
+ continue;
1109
+ }
1110
+ parsed[key] = value;
1111
+ index += 1;
1112
+ }
1113
+ return parsed;
1114
+ }
1115
+ async function main() {
1116
+ const argv = process.argv.slice(2);
1117
+ const [firstArg, ...rest] = argv;
1118
+ const helpCommands = new Set(["help", "-h", "--help"]);
1119
+ const versionCommands = new Set(["version", "-v", "--version"]);
1120
+ const command = !firstArg
1121
+ ? "help"
1122
+ : helpCommands.has(firstArg)
1123
+ ? "help"
1124
+ : versionCommands.has(firstArg)
1125
+ ? "version"
1126
+ : firstArg;
1127
+ const commandArgs = rest;
1128
+ if (command === "help") {
1129
+ console.log(renderCliHelp());
1130
+ return;
1131
+ }
1132
+ if (command === "version") {
1133
+ console.log(await readPackageVersion());
1134
+ return;
1135
+ }
1136
+ if (command === "upgrade") {
1137
+ const args = parseArgs(commandArgs);
1138
+ const currentVersion = await readPackageVersion();
1139
+ const config = await loadConfig().catch(() => buildDefaultConfig());
1140
+ if (args.check === "true") {
1141
+ const result = await checkForSelfUpdate({
1142
+ command: "start",
1143
+ config,
1144
+ currentVersion,
1145
+ packageName: PACKAGE_NAME,
1146
+ packageRoot: CLI_ROOT_DIR,
1147
+ force: true
1148
+ });
1149
+ console.log(JSON.stringify({
1150
+ currentVersion: result.currentVersion,
1151
+ latestVersion: result.latestVersion ?? null,
1152
+ updateAvailable: result.updateAvailable,
1153
+ installSource: result.installSource,
1154
+ checked: result.checked,
1155
+ reason: result.reason ?? null
1156
+ }, null, 2));
1157
+ process.exitCode = result.reason === "check_failed" ? 1 : 0;
1158
+ return;
1159
+ }
1160
+ const result = await installSelfUpdate({
1161
+ currentVersion,
1162
+ packageName: PACKAGE_NAME,
1163
+ packageRoot: CLI_ROOT_DIR,
1164
+ force: args.force === "true"
1165
+ });
1166
+ if (result.reason === "already_latest") {
1167
+ console.log(`Optimus is already up to date (${currentVersion}).`);
1168
+ return;
1169
+ }
1170
+ if (!result.success) {
1171
+ console.error(result.state.lastError ?? "Optimus upgrade failed.");
1172
+ process.exitCode = 1;
1173
+ return;
1174
+ }
1175
+ console.log(`Optimus upgraded successfully: ${currentVersion} -> ${result.latestVersion ?? currentVersion}`);
1176
+ return;
1177
+ }
1178
+ if (command === "setup") {
1179
+ const args = parseArgs(commandArgs);
1180
+ const result = await runSetup(args);
1181
+ if (args.json === "true") {
1182
+ console.log(JSON.stringify(result, null, 2));
1183
+ }
1184
+ else {
1185
+ console.log(renderSetupResult(result));
1186
+ }
1187
+ process.exitCode = result.doctorQuick.summary === "blocked" ? 1 : 0;
1188
+ return;
1189
+ }
1190
+ const parsedCommandArgs = parseArgs(commandArgs);
1191
+ if (command === "doctor" && parsedCommandArgs.quick === "true") {
1192
+ const result = await runQuickDoctor();
1193
+ if (parsedCommandArgs.json === "true") {
1194
+ console.log(JSON.stringify(result, null, 2));
1195
+ }
1196
+ else {
1197
+ console.log(renderQuickDoctorReport(result));
1198
+ }
1199
+ process.exitCode = result.summary === "blocked" ? 1 : 0;
1200
+ return;
1201
+ }
1202
+ let config;
1203
+ try {
1204
+ config = await loadConfig();
1205
+ }
1206
+ catch (error) {
1207
+ if (error?.code === "ENOENT") {
1208
+ console.error(`Config file not found at ${resolveDefaultConfigPath()}. Run \`optimus setup\` first.`);
1209
+ process.exitCode = 1;
1210
+ return;
1211
+ }
1212
+ throw error;
1213
+ }
1214
+ const store = new SQLiteTaskStore(config.storage.rootDir);
1215
+ const codexRunner = new CodexRunner(config);
1216
+ const skillSyncService = new SkillSyncService(config);
1217
+ const logger = new OptimusLogger(config);
1218
+ const eventStore = new SQLiteEventStore(config.storage.rootDir);
1219
+ const taskOrchestrator = new TaskOrchestrator(store, codexRunner, config);
1220
+ const runtime = new OptimusRuntime(store, taskOrchestrator, config);
1221
+ const currentVersion = await readPackageVersion();
1222
+ if (command === "start" || command === "submit") {
1223
+ const selfUpdateOutcome = await maybeHandleStartupSelfUpdate({
1224
+ command,
1225
+ config,
1226
+ logger,
1227
+ currentVersion
1228
+ });
1229
+ if (selfUpdateOutcome.handled) {
1230
+ process.exitCode = selfUpdateOutcome.exitCode ?? 0;
1231
+ return;
1232
+ }
1233
+ }
1234
+ if (command === "start") {
1235
+ let shuttingDown = false;
1236
+ const shutdown = () => {
1237
+ if (shuttingDown) {
1238
+ return;
1239
+ }
1240
+ shuttingDown = true;
1241
+ runtime.stop();
1242
+ };
1243
+ process.on("SIGINT", shutdown);
1244
+ process.on("SIGTERM", shutdown);
1245
+ try {
1246
+ await runtime.start();
1247
+ }
1248
+ finally {
1249
+ process.off("SIGINT", shutdown);
1250
+ process.off("SIGTERM", shutdown);
1251
+ shutdown();
1252
+ }
1253
+ return;
1254
+ }
1255
+ if (command === "submit") {
1256
+ const args = parseArgs(commandArgs);
1257
+ const title = args.title?.trim();
1258
+ const description = (args.desc ?? args.description)?.trim();
1259
+ if (!title || !description) {
1260
+ console.error("submit requires both --title and --description");
1261
+ process.exitCode = 1;
1262
+ return;
1263
+ }
1264
+ await store.init();
1265
+ const repositorySelection = await resolveSubmissionRepositorySelector(args, store, config);
1266
+ if (!repositorySelection.ok) {
1267
+ console.error(repositorySelection.reason);
1268
+ process.exitCode = 1;
1269
+ return;
1270
+ }
1271
+ const event = createManualProblemEvent({
1272
+ ...args,
1273
+ ...(repositorySelection.repoSelector ? { repo: repositorySelection.repoSelector } : {}),
1274
+ ...(args["allow-duplicate"] === "true" ? { "allow-duplicate": "true" } : {})
1275
+ });
1276
+ const inboxPath = await runtime.writeManualSubmission(event);
1277
+ console.log(`[optimus] wrote submission ${inboxPath}`);
1278
+ return;
1279
+ }
1280
+ if (command === "retry-task") {
1281
+ const args = parseArgs(commandArgs);
1282
+ const taskId = args["task-id"];
1283
+ if (!taskId) {
1284
+ console.error("Missing required --task-id for retry-task");
1285
+ process.exitCode = 1;
1286
+ return;
1287
+ }
1288
+ const inboxPath = await runtime.writeManualSubmission({
1289
+ type: "task.retry_requested",
1290
+ content: createManualEventContent({
1291
+ title: `Retry ${taskId}`,
1292
+ description: args.reason ?? "Manual retry requested from CLI.",
1293
+ sourceRef: taskId
1294
+ })
1295
+ });
1296
+ console.log(`[optimus] wrote retry request ${inboxPath}`);
1297
+ return;
1298
+ }
1299
+ if (command === "notify-test") {
1300
+ const args = parseArgs(commandArgs);
1301
+ const title = args.title?.trim() || "Optimus Feishu webhook smoke test";
1302
+ const taskId = args["task-id"]?.trim() || "task-feishu-smoke";
1303
+ const taskType = args["task-type"]?.trim() || "bugfix";
1304
+ const outcome = args.outcome?.trim() || "analysis_only";
1305
+ const repo = args.repo?.trim() || "optimus";
1306
+ const decision = args.decision?.trim() || "Manual webhook smoke test.";
1307
+ const validation = args.validation?.trim() || "Triggered from local CLI.";
1308
+ const nextAction = args["next-action"]?.trim() || "If this arrives, webhook delivery is working.";
1309
+ const bundle = {
1310
+ taskId,
1311
+ runId: "run-feishu-smoke",
1312
+ taskType,
1313
+ outcome: outcome,
1314
+ createdAt: new Date().toISOString(),
1315
+ summary: {
1316
+ title,
1317
+ repo,
1318
+ decision,
1319
+ validation,
1320
+ nextAction
1321
+ },
1322
+ artifacts: {
1323
+ extras: []
1324
+ }
1325
+ };
1326
+ const reviewUrls = args["review-url"]
1327
+ ? args["review-url"].split(",").map((item) => item.trim()).filter(Boolean)
1328
+ : [];
1329
+ const changeIds = args["change-id"]
1330
+ ? args["change-id"].split(",").map((item) => item.trim()).filter(Boolean)
1331
+ : [];
1332
+ const publicationAttempts = args["review-url"] || args["change-id"]
1333
+ ? [{
1334
+ taskId,
1335
+ runId: "run-feishu-smoke",
1336
+ mode: "review_submit",
1337
+ status: "submitted",
1338
+ summary: args["review-summary"]?.trim() || "Review submission completed for 1/1 target(s).",
1339
+ createdAt: new Date().toISOString(),
1340
+ ...(reviewUrls.length > 0 ? { reviewUrl: reviewUrls[0], reviewUrls } : {}),
1341
+ ...(changeIds.length > 0 ? { changeId: changeIds[0], changeIds } : {})
1342
+ }]
1343
+ : [];
1344
+ const reviewAwareNotifier = new FeishuNotifier({ config, publicationAttempts, format: config.delivery.feishu.format });
1345
+ const message = reviewAwareNotifier.previewMessage(bundle);
1346
+ const envStatus = buildFeishuEnvStatus();
1347
+ if (args["print-env-status"] === "true") {
1348
+ console.log(JSON.stringify({ envStatus }, null, 2));
1349
+ process.exitCode = envStatus.webhook || envStatus.webhooks ? 0 : 1;
1350
+ return;
1351
+ }
1352
+ if (args["print-message"] === "true") {
1353
+ console.log(message);
1354
+ process.exitCode = 0;
1355
+ return;
1356
+ }
1357
+ const attempts = await reviewAwareNotifier.dispatch(bundle);
1358
+ console.log(JSON.stringify({ envStatus, message, attempts }, null, 2));
1359
+ process.exitCode = attempts.every((attempt) => attempt.status === "dispatched") ? 0 : 1;
1360
+ return;
1361
+ }
1362
+ if (command === "delivery-retry") {
1363
+ const args = parseArgs(commandArgs);
1364
+ await store.init();
1365
+ const taskId = args["task-id"]?.trim();
1366
+ if (!taskId) {
1367
+ console.error("Missing required --task-id for delivery-retry");
1368
+ process.exitCode = 1;
1369
+ return;
1370
+ }
1371
+ const channel = args.channel?.trim() || "feishu";
1372
+ if (channel !== "feishu") {
1373
+ console.error(`Unsupported delivery channel for retry: ${channel}`);
1374
+ process.exitCode = 1;
1375
+ return;
1376
+ }
1377
+ const bundle = await store.getLatestTaskDeliveryBundle(taskId);
1378
+ if (!bundle) {
1379
+ console.error(`Delivery bundle not found for task: ${taskId}`);
1380
+ process.exitCode = 1;
1381
+ return;
1382
+ }
1383
+ const publicationAttempts = await store.listTaskPublicationAttempts(taskId);
1384
+ const notifier = new FeishuNotifier({ config, publicationAttempts, format: config.delivery.feishu.format });
1385
+ const message = notifier.previewMessage(bundle);
1386
+ const envStatus = buildFeishuEnvStatus();
1387
+ if (args["print-env-status"] === "true") {
1388
+ console.log(JSON.stringify({ taskId, channel, envStatus }, null, 2));
1389
+ process.exitCode = envStatus.webhook || envStatus.webhooks ? 0 : 1;
1390
+ return;
1391
+ }
1392
+ if (args["print-message"] === "true") {
1393
+ console.log(message);
1394
+ process.exitCode = 0;
1395
+ return;
1396
+ }
1397
+ const attempts = await notifier.dispatch(bundle);
1398
+ await store.appendTaskDeliveryAttempts(attempts);
1399
+ console.log(JSON.stringify({ taskId, channel, envStatus, message, attempts }, null, 2));
1400
+ process.exitCode = attempts.every((attempt) => attempt.status === "dispatched") ? 0 : 1;
1401
+ return;
1402
+ }
1403
+ if (command === "postrun-retry") {
1404
+ const args = parseArgs(commandArgs);
1405
+ await store.init();
1406
+ const taskId = args["task-id"]?.trim();
1407
+ if (!taskId) {
1408
+ console.error("Missing required --task-id for postrun-retry");
1409
+ process.exitCode = 1;
1410
+ return;
1411
+ }
1412
+ const task = await store.getTask(taskId);
1413
+ if (!task) {
1414
+ console.error(`Task not found: ${taskId}`);
1415
+ process.exitCode = 1;
1416
+ return;
1417
+ }
1418
+ const taskPackage = await store.getTaskPackage(task.taskPackageId);
1419
+ if (!taskPackage) {
1420
+ console.error(`Task package not found for task: ${taskId}`);
1421
+ process.exitCode = 1;
1422
+ return;
1423
+ }
1424
+ const artifacts = await store.listTaskArtifacts(taskId);
1425
+ if (artifacts.length === 0) {
1426
+ console.error(`No persisted artifacts found for task: ${taskId}`);
1427
+ process.exitCode = 1;
1428
+ return;
1429
+ }
1430
+ const existingBundle = await store.getLatestTaskDeliveryBundle(taskId);
1431
+ const latestRun = (await store.listTaskRuns()).filter((run) => run.taskId === taskId).at(-1) ?? null;
1432
+ const replayContext = await buildReplayExecutionContext({
1433
+ config,
1434
+ taskType: task.taskType,
1435
+ taskRepo: taskPackage.repo,
1436
+ latestRun,
1437
+ artifacts,
1438
+ deliveryBundle: existingBundle
1439
+ });
1440
+ const deliveryService = new TaskDeliveryService();
1441
+ const analysisDocService = new FeishuAnalysisDocService({ refStore: store });
1442
+ const publicationService = new TaskPublicationService(logger);
1443
+ const deliveryDispatcher = new TaskDeliveryDispatcher(config);
1444
+ let deliveryBundle = mergeReplayBundle(await deliveryService.buildBundle({
1445
+ task,
1446
+ taskPackage: {
1447
+ title: taskPackage.title,
1448
+ ...(taskPackage.sourceRef ? { sourceRef: taskPackage.sourceRef } : {}),
1449
+ ...(taskPackage.input.metadata ? { metadata: taskPackage.input.metadata } : {})
1450
+ },
1451
+ ...(replayContext ? { context: replayContext } : {}),
1452
+ ...(latestRun ? { latestRun } : {}),
1453
+ artifacts
1454
+ }), existingBundle);
1455
+ let analysisDoc = {
1456
+ status: "skipped",
1457
+ reason: "feishu_delivery_disabled"
1458
+ };
1459
+ if (config.delivery.enabled && config.delivery.channels.includes("feishu")) {
1460
+ const published = await analysisDocService.publishIfNeeded(deliveryBundle);
1461
+ deliveryBundle = published.bundle;
1462
+ analysisDoc = {
1463
+ status: published.status,
1464
+ ...(published.reason ? { reason: published.reason } : {}),
1465
+ ...(published.url ? { url: published.url } : {})
1466
+ };
1467
+ }
1468
+ await store.appendTaskDeliveryBundle(deliveryBundle);
1469
+ const replayPublication = args["replay-publication"] === "true";
1470
+ let publicationAttempts = await store.listTaskPublicationAttempts(taskId);
1471
+ let publicationArtifacts = [];
1472
+ if (replayPublication) {
1473
+ const publicationResult = await publicationService.publish({
1474
+ bundle: deliveryBundle,
1475
+ ...(replayContext ? { context: replayContext } : {}),
1476
+ dryRun: config.publication.dryRun,
1477
+ reviewMode: config.publication.reviewMode
1478
+ });
1479
+ publicationAttempts = publicationResult.attempts;
1480
+ publicationArtifacts = publicationResult.artifacts;
1481
+ if (publicationResult.artifacts.length > 0) {
1482
+ await store.appendArtifacts(taskId, publicationResult.artifacts);
1483
+ }
1484
+ await store.appendTaskPublicationAttempts(publicationResult.attempts);
1485
+ }
1486
+ const deliveryDispatch = await deliveryDispatcher.dispatch({
1487
+ bundle: deliveryBundle,
1488
+ ...(publicationAttempts.length > 0 ? { publicationAttempts } : {})
1489
+ });
1490
+ await store.appendTaskDeliveryAttempts(deliveryDispatch.attempts);
1491
+ const publicationFailed = replayPublication && publicationAttempts.some((attempt) => attempt.status === "failed");
1492
+ const deliveryFailed = deliveryDispatch.attempts.some((attempt) => attempt.status === "failed");
1493
+ console.log(JSON.stringify({
1494
+ taskId,
1495
+ replayedFromArtifacts: true,
1496
+ replayPublication,
1497
+ publicationDryRun: replayPublication ? config.publication.dryRun : undefined,
1498
+ bundle: deliveryBundle,
1499
+ analysisDoc,
1500
+ publication: {
1501
+ attempts: publicationAttempts,
1502
+ artifacts: publicationArtifacts
1503
+ },
1504
+ delivery: {
1505
+ attempts: deliveryDispatch.attempts
1506
+ }
1507
+ }, null, 2));
1508
+ process.exitCode = publicationFailed || deliveryFailed ? 1 : 0;
1509
+ return;
1510
+ }
1511
+ if (command === "cancel-task") {
1512
+ const args = parseArgs(commandArgs);
1513
+ const taskId = args["task-id"];
1514
+ if (!taskId) {
1515
+ console.error("Missing required --task-id for cancel-task");
1516
+ process.exitCode = 1;
1517
+ return;
1518
+ }
1519
+ const content = createManualEventContent({
1520
+ title: `Cancel ${taskId}`,
1521
+ description: args.reason ?? "Manual cancel requested from CLI.",
1522
+ sourceRef: taskId
1523
+ });
1524
+ if (args["keep-artifacts"] === "true" || args.force === "true") {
1525
+ content.metadata = {
1526
+ ...(content.metadata ?? {}),
1527
+ ...(args["keep-artifacts"] === "true" ? { keepArtifacts: true } : {}),
1528
+ ...(args.force === "true" ? { forceCancel: true } : {})
1529
+ };
1530
+ }
1531
+ const inboxPath = await runtime.writeManualSubmission({
1532
+ type: "task.cancel_requested",
1533
+ content
1534
+ });
1535
+ console.log(`[optimus] wrote cancel request ${inboxPath}`);
1536
+ return;
1537
+ }
1538
+ if (command === "intake-run") {
1539
+ const args = parseArgs(commandArgs);
1540
+ const pollerId = args["poller-id"]?.trim();
1541
+ if (!pollerId) {
1542
+ console.error("Missing required --poller-id for intake-run");
1543
+ process.exitCode = 1;
1544
+ return;
1545
+ }
1546
+ await eventStore.init();
1547
+ await runtime.runPollerOnce(pollerId);
1548
+ const state = (await eventStore.listPollerStates()).find((candidate) => candidate.pollerId === pollerId) ?? null;
1549
+ console.log(JSON.stringify({ pollerId, state }, null, 2));
1550
+ return;
1551
+ }
1552
+ if (command === "intake-disable") {
1553
+ const args = parseArgs(commandArgs);
1554
+ const pollerId = args["poller-id"]?.trim();
1555
+ if (!pollerId) {
1556
+ console.error("Missing required --poller-id for intake-disable");
1557
+ process.exitCode = 1;
1558
+ return;
1559
+ }
1560
+ await eventStore.init();
1561
+ await eventStore.updatePollerEnabled(pollerId, false);
1562
+ const state = (await eventStore.listPollerStates()).find((candidate) => candidate.pollerId === pollerId) ?? null;
1563
+ console.log(JSON.stringify({ pollerId, disabled: true, state }, null, 2));
1564
+ return;
1565
+ }
1566
+ if (command === "intake-enable") {
1567
+ const args = parseArgs(commandArgs);
1568
+ const pollerId = args["poller-id"]?.trim();
1569
+ if (!pollerId) {
1570
+ console.error("Missing required --poller-id for intake-enable");
1571
+ process.exitCode = 1;
1572
+ return;
1573
+ }
1574
+ await eventStore.init();
1575
+ await eventStore.updatePollerEnabled(pollerId, true);
1576
+ const state = (await eventStore.listPollerStates()).find((candidate) => candidate.pollerId === pollerId) ?? null;
1577
+ console.log(JSON.stringify({ pollerId, enabled: true, state }, null, 2));
1578
+ return;
1579
+ }
1580
+ if (command === "intake-reset-checkpoint") {
1581
+ const args = parseArgs(commandArgs);
1582
+ const pollerId = args["poller-id"]?.trim();
1583
+ if (!pollerId) {
1584
+ console.error("Missing required --poller-id for intake-reset-checkpoint");
1585
+ process.exitCode = 1;
1586
+ return;
1587
+ }
1588
+ await eventStore.init();
1589
+ await eventStore.resetPollerCheckpoint(pollerId);
1590
+ const state = (await eventStore.listPollerStates()).find((candidate) => candidate.pollerId === pollerId) ?? null;
1591
+ console.log(JSON.stringify({ pollerId, checkpointReset: true, state }, null, 2));
1592
+ return;
1593
+ }
1594
+ if (command === "repo") {
1595
+ const [repoSubcommand = "list", ...repoRest] = commandArgs;
1596
+ const args = parseArgs(repoRest);
1597
+ await store.init();
1598
+ if (repoSubcommand === "add") {
1599
+ const repoPathArg = repoRest.find((token) => !token.startsWith("--")) ?? args.path;
1600
+ const repoPath = repoPathArg?.trim();
1601
+ if (!repoPath) {
1602
+ console.error("repo add requires a repository path");
1603
+ process.exitCode = 1;
1604
+ return;
1605
+ }
1606
+ const normalizedPath = resolve(repoPath);
1607
+ const validation = await inspectRepositoryRoot(normalizedPath);
1608
+ if (!validation.ok) {
1609
+ console.error(JSON.stringify({ ok: false, path: normalizedPath, reason: validation.reason }, null, 2));
1610
+ process.exitCode = 1;
1611
+ return;
1612
+ }
1613
+ const requestedMode = args.mode?.trim();
1614
+ if (requestedMode && requestedMode !== "copy" && requestedMode !== "inplace") {
1615
+ console.error("repo add --mode must be either copy or inplace");
1616
+ process.exitCode = 1;
1617
+ return;
1618
+ }
1619
+ const repositoryInput = {
1620
+ path: normalizedPath
1621
+ };
1622
+ if (args.alias?.trim()) {
1623
+ repositoryInput.alias = args.alias.trim();
1624
+ }
1625
+ if (requestedMode === "copy" || requestedMode === "inplace") {
1626
+ repositoryInput.executionMode = requestedMode;
1627
+ }
1628
+ const saved = await store.addRepositoryRoot(repositoryInput);
1629
+ console.log(JSON.stringify({ ok: true, repository: saved }, null, 2));
1630
+ return;
1631
+ }
1632
+ if (repoSubcommand === "remove") {
1633
+ const repoTarget = (repoRest.find((token) => !token.startsWith("--")) ?? args.path ?? args.alias)?.trim();
1634
+ if (!repoTarget) {
1635
+ console.error("repo remove requires a repository path or alias");
1636
+ process.exitCode = 1;
1637
+ return;
1638
+ }
1639
+ const removed = await store.removeRepositoryRoot(repoTarget);
1640
+ if (!removed) {
1641
+ console.error(`Repository root not found: ${repoTarget}`);
1642
+ process.exitCode = 1;
1643
+ return;
1644
+ }
1645
+ console.log(JSON.stringify({ ok: true, removed }, null, 2));
1646
+ return;
1647
+ }
1648
+ if (repoSubcommand === "update") {
1649
+ const repoTarget = (repoRest.find((token) => !token.startsWith("--")) ?? args.path ?? args.alias)?.trim();
1650
+ if (!repoTarget) {
1651
+ console.error("repo update requires a repository path or alias");
1652
+ process.exitCode = 1;
1653
+ return;
1654
+ }
1655
+ const requestedMode = args.mode?.trim();
1656
+ if (requestedMode && requestedMode !== "copy" && requestedMode !== "inplace") {
1657
+ console.error("repo update --mode must be either copy or inplace");
1658
+ process.exitCode = 1;
1659
+ return;
1660
+ }
1661
+ const requestedAlias = args.alias !== undefined ? args.alias.trim() : undefined;
1662
+ if (requestedAlias === undefined && requestedMode === undefined) {
1663
+ console.error("repo update requires at least one of --mode or --alias");
1664
+ process.exitCode = 1;
1665
+ return;
1666
+ }
1667
+ const updated = await store.updateRepositoryRoot(repoTarget, {
1668
+ ...(requestedAlias !== undefined ? { alias: requestedAlias } : {}),
1669
+ ...(requestedMode === "copy" || requestedMode === "inplace" ? { executionMode: requestedMode } : {})
1670
+ });
1671
+ if (!updated) {
1672
+ console.error(`Repository root not found: ${repoTarget}`);
1673
+ process.exitCode = 1;
1674
+ return;
1675
+ }
1676
+ console.log(JSON.stringify({ ok: true, repository: updated }, null, 2));
1677
+ return;
1678
+ }
1679
+ if (repoSubcommand === "list") {
1680
+ const repositories = await store.listRepositoryRoots();
1681
+ console.log(JSON.stringify({ repositories }, null, 2));
1682
+ return;
1683
+ }
1684
+ if (repoSubcommand === "doctor") {
1685
+ const repositories = await store.listRepositoryRoots();
1686
+ const inspected = await Promise.all(repositories.map(async (repository) => {
1687
+ const inspection = await inspectRepositoryRoot(repository.path);
1688
+ const executionPlan = describeRepositoryExecutionPlan(repository, inspection);
1689
+ return {
1690
+ ...repository,
1691
+ inspection,
1692
+ executionPlan,
1693
+ resolvedDefaultMode: executionPlan.resolvedDefaultMode
1694
+ };
1695
+ }));
1696
+ console.log(JSON.stringify({ repositories: inspected }, null, 2));
1697
+ process.exitCode = inspected.every((repository) => repository.inspection.ok) ? 0 : 1;
1698
+ return;
1699
+ }
1700
+ console.error(`Unknown repo command: ${repoSubcommand}`);
1701
+ process.exitCode = 1;
1702
+ return;
1703
+ }
1704
+ if (command === "list") {
1705
+ await store.init();
1706
+ const tasks = await store.listTasks();
1707
+ console.log(JSON.stringify(tasks, null, 2));
1708
+ return;
1709
+ }
1710
+ if (command === "intake-status") {
1711
+ await eventStore.init();
1712
+ const pollerStates = await eventStore.listPollerStates();
1713
+ const sourceRecords = await eventStore.listSourceRecords();
1714
+ console.log(JSON.stringify({
1715
+ configuredSources: (config.intake.pollingSources ?? []).map((source) => ({
1716
+ id: source.id,
1717
+ type: source.type,
1718
+ enabled: source.enabled,
1719
+ eventType: source.eventType ?? "problem.discovered",
1720
+ pollingIntervalMs: source.intervalMs ?? config.intake.pollingIntervalMs
1721
+ })),
1722
+ pollerStates,
1723
+ sourceRecordCount: sourceRecords.length,
1724
+ recentSourceRecords: sourceRecords.slice(-20)
1725
+ }, null, 2));
1726
+ return;
1727
+ }
1728
+ if (command === "task-status") {
1729
+ const args = parseArgs(commandArgs);
1730
+ await store.init();
1731
+ const tasks = await store.listTasks();
1732
+ const runs = await store.listTaskRuns();
1733
+ const requestedTaskId = args["task-id"]?.trim();
1734
+ const requestedStatus = args.status?.trim();
1735
+ const activeOnly = args["active-only"] === "true";
1736
+ const limit = Number.parseInt(args.limit ?? "20", 10);
1737
+ const normalizedLimit = Number.isFinite(limit) && limit > 0 ? limit : 20;
1738
+ const taskProgressView = tasks
1739
+ .filter((task) => !requestedTaskId || task.taskId === requestedTaskId)
1740
+ .filter((task) => !requestedStatus || task.status === requestedStatus)
1741
+ .filter((task) => !activeOnly || Boolean(task.activeRunId))
1742
+ .slice(-normalizedLimit)
1743
+ .map((task) => {
1744
+ const taskRuns = runs.filter((run) => run.taskId === task.taskId);
1745
+ const activeRun = runs.find((run) => run.runId === task.activeRunId) ?? taskRuns.slice(-1)[0];
1746
+ const timing = activeRun ? buildRunTimingSnapshot(activeRun, config) : { elapsedMs: 0, idleMs: 0, timeoutKind: null };
1747
+ return {
1748
+ taskId: task.taskId,
1749
+ taskPackageId: task.taskPackageId,
1750
+ title: task.title,
1751
+ status: task.status,
1752
+ phase: activeRun?.status ?? null,
1753
+ health: activeRun?.health ?? null,
1754
+ progressMessage: activeRun?.lastEventType ?? null,
1755
+ progressExplanation: activeRun?.lastEventType ? explainTaskEvent(activeRun.lastEventType) : null,
1756
+ failureCategory: activeRun?.failureCategory ?? null,
1757
+ failureSummary: activeRun?.summary ?? null,
1758
+ lastAgentMessagePreview: activeRun?.lastAgentMessagePreview ?? null,
1759
+ sdkThreadId: activeRun?.sdkThreadId ?? null,
1760
+ workspaceSkillMountDir: activeRun?.workspaceSkillMountDir ?? null,
1761
+ mountedSkills: (activeRun?.mountedSkills ?? []).map((skill) => ({
1762
+ id: skill.id,
1763
+ level: skill.level,
1764
+ version: skill.version,
1765
+ taskTypes: skill.taskTypes,
1766
+ targetDir: skill.targetDir
1767
+ })),
1768
+ cancelRequestedAt: activeRun?.cancelRequestedAt ?? null,
1769
+ lastUpdatedAt: task.updatedAt,
1770
+ elapsedMs: activeRun ? timing.elapsedMs : null,
1771
+ idleMs: activeRun ? timing.idleMs : null,
1772
+ timeoutKind: activeRun ? timing.timeoutKind : null,
1773
+ nextAction: buildTaskNextAction(task.status, activeRun?.failureCategory),
1774
+ runs: taskRuns.slice(-3).map((run) => {
1775
+ const timing = buildRunTimingSnapshot(run, config);
1776
+ return {
1777
+ runId: run.runId,
1778
+ status: run.status,
1779
+ taskStatus: run.taskStatus ?? null,
1780
+ health: run.health ?? null,
1781
+ failureCategory: run.failureCategory ?? null,
1782
+ summary: run.summary ?? null,
1783
+ startedAt: run.startedAt,
1784
+ endedAt: run.endedAt ?? null,
1785
+ lastEventType: run.lastEventType ?? null,
1786
+ lastEventExplanation: run.lastEventType ? explainTaskEvent(run.lastEventType) : null,
1787
+ lastAgentMessagePreview: run.lastAgentMessagePreview ?? null,
1788
+ workspaceSkillMountDir: run.workspaceSkillMountDir ?? null,
1789
+ mountedSkills: (run.mountedSkills ?? []).map((skill) => ({
1790
+ id: skill.id,
1791
+ level: skill.level,
1792
+ version: skill.version,
1793
+ taskTypes: skill.taskTypes,
1794
+ targetDir: skill.targetDir
1795
+ })),
1796
+ progressCounter: run.progressCounter ?? 0,
1797
+ commandCount: run.commandCount ?? 0,
1798
+ fileChangeCount: run.fileChangeCount ?? 0,
1799
+ cancelRequestedAt: run.cancelRequestedAt ?? null,
1800
+ elapsedMs: timing.elapsedMs,
1801
+ idleMs: timing.idleMs,
1802
+ timeoutKind: timing.timeoutKind
1803
+ };
1804
+ })
1805
+ };
1806
+ });
1807
+ console.log(JSON.stringify({
1808
+ filters: {
1809
+ taskId: requestedTaskId ?? null,
1810
+ status: requestedStatus ?? null,
1811
+ activeOnly,
1812
+ limit: normalizedLimit
1813
+ },
1814
+ tasks: taskProgressView
1815
+ }, null, 2));
1816
+ return;
1817
+ }
1818
+ if (command === "task-result") {
1819
+ const args = parseArgs(commandArgs);
1820
+ await store.init();
1821
+ const requestedTaskId = args["task-id"]?.trim();
1822
+ if (!requestedTaskId) {
1823
+ console.error("Missing required --task-id for task-result");
1824
+ process.exitCode = 1;
1825
+ return;
1826
+ }
1827
+ const task = await store.getTask(requestedTaskId);
1828
+ if (!task) {
1829
+ console.error(`Task not found: ${requestedTaskId}`);
1830
+ process.exitCode = 1;
1831
+ return;
1832
+ }
1833
+ const taskPackage = await store.getTaskPackage(task.taskPackageId);
1834
+ const runs = (await store.listTaskRuns()).filter((run) => run.taskId === requestedTaskId);
1835
+ const latestRun = runs.at(-1) ?? null;
1836
+ const results = await store.listTaskArtifacts(requestedTaskId);
1837
+ const deliveryBundle = await store.getLatestTaskDeliveryBundle(requestedTaskId);
1838
+ const deliveryAttempts = await store.listTaskDeliveryAttempts(requestedTaskId);
1839
+ const publicationAttempts = await store.listTaskPublicationAttempts(requestedTaskId);
1840
+ if (args.format === "text" || args.print === "true") {
1841
+ console.log(renderTaskResultReport({
1842
+ task,
1843
+ taskPackage,
1844
+ latestRun,
1845
+ resultFiles: results,
1846
+ deliveryBundle,
1847
+ deliveryAttempts,
1848
+ publicationAttempts
1849
+ }));
1850
+ return;
1851
+ }
1852
+ const diagnosis = buildTaskDeliveryDiagnosis({
1853
+ task,
1854
+ latestRun,
1855
+ deliveryBundle,
1856
+ deliveryAttempts,
1857
+ publicationAttempts
1858
+ });
1859
+ console.log(JSON.stringify({
1860
+ task,
1861
+ taskPackage,
1862
+ latestRun,
1863
+ resultFiles: results,
1864
+ deliveryBundle,
1865
+ deliveryAttempts,
1866
+ deliverySummary: buildDeliveryAttemptSummary(deliveryAttempts),
1867
+ diagnosis,
1868
+ publicationAttempts,
1869
+ publicationReview: buildPublicationReviewSummary(publicationAttempts)
1870
+ }, null, 2));
1871
+ return;
1872
+ }
1873
+ if (command === "delivery-status") {
1874
+ const args = parseArgs(commandArgs);
1875
+ await store.init();
1876
+ const requestedTaskId = args["task-id"]?.trim();
1877
+ if (!requestedTaskId) {
1878
+ console.error("Missing required --task-id for delivery-status");
1879
+ process.exitCode = 1;
1880
+ return;
1881
+ }
1882
+ const task = await store.getTask(requestedTaskId);
1883
+ if (!task) {
1884
+ console.error(`Task not found: ${requestedTaskId}`);
1885
+ process.exitCode = 1;
1886
+ return;
1887
+ }
1888
+ const taskPackage = await store.getTaskPackage(task.taskPackageId);
1889
+ const deliveryBundle = await store.getLatestTaskDeliveryBundle(requestedTaskId);
1890
+ const deliveryAttempts = await store.listTaskDeliveryAttempts(requestedTaskId);
1891
+ const publicationAttempts = await store.listTaskPublicationAttempts(requestedTaskId);
1892
+ const runs = (await store.listTaskRuns()).filter((run) => run.taskId === requestedTaskId);
1893
+ const latestRun = runs.at(-1) ?? null;
1894
+ const deliverySummary = buildDeliveryAttemptSummary(deliveryAttempts);
1895
+ const publicationReview = buildPublicationReviewSummary(publicationAttempts);
1896
+ const diagnosis = buildTaskDeliveryDiagnosis({
1897
+ task,
1898
+ latestRun,
1899
+ deliveryBundle,
1900
+ deliveryAttempts,
1901
+ publicationAttempts
1902
+ });
1903
+ if (args.format === "text" || args.print === "true") {
1904
+ console.log(renderDeliveryStatusReport({
1905
+ task,
1906
+ taskPackage,
1907
+ latestRun,
1908
+ deliveryBundle,
1909
+ deliveryAttempts,
1910
+ publicationAttempts
1911
+ }));
1912
+ return;
1913
+ }
1914
+ console.log(JSON.stringify({
1915
+ task,
1916
+ taskPackage,
1917
+ deliveryBundle,
1918
+ deliveryAttempts,
1919
+ deliverySummary,
1920
+ diagnosis,
1921
+ publicationAttempts,
1922
+ publicationReview
1923
+ }, null, 2));
1924
+ return;
1925
+ }
1926
+ if (command === "task-events") {
1927
+ const args = parseArgs(commandArgs);
1928
+ await store.init();
1929
+ const tasks = await store.listTasks();
1930
+ const taskId = args["task-id"]?.trim();
1931
+ const runId = args["run-id"]?.trim();
1932
+ const limit = Number.parseInt(args.limit ?? "50", 10);
1933
+ const normalizedLimit = Number.isFinite(limit) && limit > 0 ? limit : 50;
1934
+ let scopedTaskId = taskId;
1935
+ if (!scopedTaskId && runId) {
1936
+ const runs = await store.listTaskRuns();
1937
+ scopedTaskId = runs.find((candidate) => candidate.runId === runId)?.taskId;
1938
+ }
1939
+ const recentEvents = await store.listRecentTaskEvents(normalizedLimit * 3);
1940
+ const filteredEvents = recentEvents
1941
+ .filter((event) => !scopedTaskId || event.taskId === scopedTaskId)
1942
+ .filter((event) => !runId || event.runId === runId)
1943
+ .slice(0, normalizedLimit)
1944
+ .reverse()
1945
+ .map((event) => ({
1946
+ taskId: event.taskId,
1947
+ runId: event.runId ?? null,
1948
+ type: event.type,
1949
+ detail: event.detail,
1950
+ explanation: explainTaskEvent(event.type),
1951
+ createdAt: event.createdAt,
1952
+ sourceEventId: event.sourceEventId ?? null
1953
+ }));
1954
+ const task = scopedTaskId ? tasks.find((candidate) => candidate.taskId === scopedTaskId) ?? null : null;
1955
+ console.log(JSON.stringify({
1956
+ filters: {
1957
+ taskId: scopedTaskId ?? null,
1958
+ runId: runId ?? null,
1959
+ limit: normalizedLimit
1960
+ },
1961
+ task,
1962
+ events: filteredEvents
1963
+ }, null, 2));
1964
+ return;
1965
+ }
1966
+ if (command === "db-inspect") {
1967
+ const args = parseArgs(commandArgs);
1968
+ await store.init();
1969
+ const tasks = await store.listTasks();
1970
+ const taskPackages = await store.listTaskPackages();
1971
+ const runs = await store.listTaskRuns();
1972
+ const requestedTaskId = args["task-id"]?.trim();
1973
+ const taskMatches = requestedTaskId ? tasks.filter((task) => task.taskId === requestedTaskId) : tasks.slice(-20);
1974
+ const taskIds = new Set(taskMatches.map((task) => task.taskId));
1975
+ const taskPackageIds = new Set(taskMatches.map((task) => task.taskPackageId));
1976
+ const runMatches = runs.filter((run) => taskIds.has(run.taskId)).slice(-50);
1977
+ const taskPackageMatches = taskPackages.filter((taskPackage) => taskPackageIds.has(taskPackage.taskPackageId));
1978
+ console.log(JSON.stringify({
1979
+ databasePath: join(config.storage.rootDir, "optimus.db"),
1980
+ taskCount: tasks.length,
1981
+ taskPackageCount: taskPackages.length,
1982
+ runCount: runs.length,
1983
+ tasks: taskMatches,
1984
+ taskPackages: taskPackageMatches,
1985
+ runs: runMatches,
1986
+ deliveryBundles: requestedTaskId ? [await store.getLatestTaskDeliveryBundle(requestedTaskId)].filter(Boolean) : [],
1987
+ deliveryAttempts: requestedTaskId ? await store.listTaskDeliveryAttempts(requestedTaskId) : [],
1988
+ deliverySummary: requestedTaskId ? buildDeliveryAttemptSummary(await store.listTaskDeliveryAttempts(requestedTaskId)) : { channels: [] },
1989
+ publicationAttempts: requestedTaskId ? await store.listTaskPublicationAttempts(requestedTaskId) : [],
1990
+ publicationReview: requestedTaskId ? buildPublicationReviewSummary(await store.listTaskPublicationAttempts(requestedTaskId)) : { latestReviewSubmit: null }
1991
+ }, null, 2));
1992
+ return;
1993
+ }
1994
+ if (command === "triage-only") {
1995
+ const args = parseArgs(commandArgs);
1996
+ const title = args.title?.trim();
1997
+ const description = (args.desc ?? args.description)?.trim();
1998
+ if (!title || !description) {
1999
+ console.error("triage-only requires both --title and --description");
2000
+ process.exitCode = 1;
2001
+ return;
2002
+ }
2003
+ const triageRunner = new TriageRunner(config);
2004
+ try {
2005
+ await triageRunner.initialize();
2006
+ await logger.writeTriageStatusSnapshot(triageRunner.getStatusSnapshot());
2007
+ const decision = await triageRunner.triage({
2008
+ eventId: `triage-only-${Date.now()}`,
2009
+ type: "task.submitted_manually",
2010
+ content: createManualEventContent(args),
2011
+ createdAt: new Date().toISOString()
2012
+ });
2013
+ console.log(JSON.stringify(decision, null, 2));
2014
+ process.exitCode = decision.decision === "accepted" ? 0 : 2;
2015
+ return;
2016
+ }
2017
+ catch (error) {
2018
+ const failureCategory = error && typeof error === "object" && "failureCategory" in error
2019
+ ? error.failureCategory ?? "unknown"
2020
+ : error instanceof Error
2021
+ ? classifyCodexFailureCategory(error.message)
2022
+ : "unknown";
2023
+ await logger.writeTriageStatusSnapshot({
2024
+ ok: false,
2025
+ startedAt: new Date().toISOString(),
2026
+ failureCategory,
2027
+ reason: error instanceof Error ? error.message : "Unknown triage failure"
2028
+ });
2029
+ console.error(JSON.stringify({
2030
+ ok: false,
2031
+ summary: error instanceof Error ? error.message : "Unknown triage failure",
2032
+ failureCategory
2033
+ }, null, 2));
2034
+ process.exitCode = 1;
2035
+ return;
2036
+ }
2037
+ }
2038
+ if (command === "health-check") {
2039
+ const result = await codexRunner.runHealthCheck();
2040
+ await logger.writeHealthCheckSnapshot(result);
2041
+ await logger.info("optimus.health_check", {
2042
+ ok: result.ok,
2043
+ model: result.diagnostics.model,
2044
+ provider: result.diagnostics.provider,
2045
+ failureCategory: result.diagnostics.failureCategory,
2046
+ connectivityChecks: result.diagnostics.connectivityChecks?.map((check) => `${check.name}:${check.ok ? "ok" : "failed"}${check.host ? `@${check.host}` : ""}`),
2047
+ timeoutMs: result.diagnostics.timeoutMs,
2048
+ codexHomeDir: result.diagnostics.codexHomeDir,
2049
+ approvalPolicy: result.diagnostics.approvalPolicy,
2050
+ sandboxMode: result.diagnostics.sandboxMode
2051
+ });
2052
+ console.log(JSON.stringify(result, null, 2));
2053
+ process.exitCode = result.ok ? 0 : 1;
2054
+ return;
2055
+ }
2056
+ if (command === "doctor") {
2057
+ const healthCheck = await logger.readHealthCheckSnapshot();
2058
+ const triageStatus = await logger.readTriageStatusSnapshot();
2059
+ const skillSyncStatus = await logger.readSkillSyncSnapshot();
2060
+ const evolutionStatus = await logger.readEvolutionSnapshot();
2061
+ const repoMemoryStatus = await logger.readRepoMemorySnapshot();
2062
+ const feishuStatus = await buildFeishuDoctorStatus();
2063
+ const preflight = await new CodexPreflight(config).run();
2064
+ const skillSummary = await inspectBuiltinSkills(config, skillSyncService);
2065
+ const repoMemorySummary = await new RepoMemoryService(config).readDoctorSummary();
2066
+ await store.init();
2067
+ const tasks = await store.listTasks();
2068
+ const runs = await store.listTaskRuns();
2069
+ const recentImportantEvents = await store.listRecentTaskEvents(15, [...DOCTOR_IMPORTANT_EVENT_TYPES]);
2070
+ const taskSummary = tasks.reduce((accumulator, task) => {
2071
+ accumulator[task.status] = (accumulator[task.status] ?? 0) + 1;
2072
+ return accumulator;
2073
+ }, {});
2074
+ const activeRuns = runs.filter((run) => !run.endedAt);
2075
+ const registeredRepositories = await store.listRepositoryRoots();
2076
+ const configuredRuntimeRepositories = await inspectConfiguredRuntimeRepositories(config);
2077
+ const repositoryDiagnostics = buildRepositoryDiagnostics(registeredRepositories, configuredRuntimeRepositories);
2078
+ const doctor = {
2079
+ runtimeRootDir: config.storage.rootDir,
2080
+ codexHomeDir: config.codex.homeDir,
2081
+ cliInboxDir: config.intake.cliInboxDir,
2082
+ healthLogDir: join(config.storage.rootDir, "logs"),
2083
+ codexHomeReady: await pathExists(config.codex.homeDir),
2084
+ inboxReady: await pathExists(config.intake.cliInboxDir),
2085
+ taskHarnessRootReady: await pathExists(config.runtime.taskHarnessRootDir),
2086
+ runtimeLogFiles: await safeList(join(config.storage.rootDir, "logs")),
2087
+ registeredRepositories,
2088
+ configuredRuntimeRepositories,
2089
+ repositoryDiagnostics,
2090
+ runtime: {
2091
+ totalTasks: tasks.length,
2092
+ totalRuns: runs.length,
2093
+ activeRuns: activeRuns.length,
2094
+ stalledRuns: activeRuns.filter((run) => run.health === "suspected_stall").length,
2095
+ taskSummary,
2096
+ recentTasks: tasks.slice(-5).map((task) => ({
2097
+ taskId: task.taskId,
2098
+ taskPackageId: task.taskPackageId,
2099
+ sourceEventId: task.sourceEventId ?? null,
2100
+ status: task.status,
2101
+ activeRunId: task.activeRunId ?? null,
2102
+ updatedAt: task.updatedAt
2103
+ })),
2104
+ activeRunDetails: activeRuns.map((run) => {
2105
+ const timing = buildRunTimingSnapshot(run, config);
2106
+ return {
2107
+ runId: run.runId,
2108
+ taskId: run.taskId,
2109
+ taskPackageId: run.taskPackageId,
2110
+ status: run.status,
2111
+ taskStatus: run.taskStatus,
2112
+ health: run.health ?? null,
2113
+ startedAt: run.startedAt,
2114
+ lastEventAt: run.lastEventAt ?? null,
2115
+ lastEventType: run.lastEventType ?? null,
2116
+ progressCounter: run.progressCounter ?? 0,
2117
+ commandCount: run.commandCount ?? 0,
2118
+ fileChangeCount: run.fileChangeCount ?? 0,
2119
+ sdkThreadId: run.sdkThreadId ?? null,
2120
+ addresses: {
2121
+ workspaceDir: run.addresses?.workspaceDir ?? null,
2122
+ visibleRepoDir: run.addresses?.visibleRepoDir ?? null,
2123
+ configuredExecutionMode: run.addresses?.configuredExecutionMode ?? null,
2124
+ resolvedExecutionMode: run.addresses?.resolvedExecutionMode ?? null
2125
+ },
2126
+ elapsedMs: timing.elapsedMs,
2127
+ idleMs: timing.idleMs,
2128
+ timeoutKind: timing.timeoutKind,
2129
+ failureCategoryLabel: describeFailureCategory(run.failureCategory),
2130
+ lastEventExplanation: run.lastEventType ? explainTaskEvent(run.lastEventType) : null
2131
+ };
2132
+ }),
2133
+ recentFailureBreakdown: buildRecentFailureBreakdown(runs),
2134
+ deliveryWarningSummary: buildDeliveryWarningSummary(recentImportantEvents),
2135
+ deliveryHealthSummary: await buildDeliveryHealthSummary(store, tasks, runs),
2136
+ recentImportantEvents: recentImportantEvents.map((event) => ({
2137
+ taskId: event.taskId,
2138
+ runId: event.runId ?? null,
2139
+ type: event.type,
2140
+ detail: event.detail,
2141
+ explanation: explainTaskEvent(event.type),
2142
+ createdAt: event.createdAt,
2143
+ sourceEventId: event.sourceEventId ?? null
2144
+ })),
2145
+ recentFailureSummary: buildRecentFailureSummary(runs, recentImportantEvents)
2146
+ },
2147
+ triage: {
2148
+ status: triageStatus ?? null
2149
+ },
2150
+ codex: {
2151
+ model: resolveEffectiveCodexModel(config) ?? null,
2152
+ authMode: resolveCodexAuthMode(config),
2153
+ provider: resolveConfiguredCodexProvider(config)
2154
+ ? {
2155
+ id: resolveConfiguredCodexProvider(config).providerId,
2156
+ displayName: resolveConfiguredCodexProvider(config).displayName
2157
+ }
2158
+ : resolveConfiguredCodexModelProvider(config) ?? null,
2159
+ apiKeyEnvName: resolvePrimaryCodexApiKeyEnvName(config) ?? null,
2160
+ requiredEnvVars: resolveRequiredCodexEnvVars(config),
2161
+ baseUrl: resolveConfiguredCodexProvider(config)?.baseUrl ?? config.codex.baseUrl ?? null,
2162
+ sandboxMode: config.codex.sandboxMode,
2163
+ networkAccessEnabled: config.runtime.networkAccessEnabled
2164
+ },
2165
+ skills: {
2166
+ ...skillSummary,
2167
+ recentSync: skillSyncStatus ?? null,
2168
+ recentEvolution: evolutionStatus ?? null
2169
+ },
2170
+ repoMemory: {
2171
+ rootDir: config.memory.rootDir,
2172
+ rootReady: await pathExists(config.memory.rootDir),
2173
+ totalCount: repoMemorySummary.length,
2174
+ entries: repoMemorySummary,
2175
+ recentUpdate: repoMemoryStatus ?? null
2176
+ },
2177
+ feishu: feishuStatus,
2178
+ currentPreflight: preflight,
2179
+ recentHealthCheck: healthCheck ?? null,
2180
+ selfUpdate: {
2181
+ currentVersion,
2182
+ config: config.selfUpdate ?? buildDefaultConfig().selfUpdate,
2183
+ cachedState: await readSelfUpdateState() ?? null
2184
+ }
2185
+ };
2186
+ if (parsedCommandArgs.format === "text") {
2187
+ console.log(renderDoctorTextReport(doctor));
2188
+ process.exitCode = 0;
2189
+ return;
2190
+ }
2191
+ console.log(JSON.stringify(doctor, null, 2));
2192
+ process.exitCode = 0;
2193
+ return;
2194
+ }
2195
+ console.error(`Unknown command: ${command}`);
2196
+ process.exitCode = 1;
2197
+ }
2198
+ async function inspectBuiltinSkills(config, skillSyncService) {
2199
+ const installedSkills = await skillSyncService.readInstalledSkills();
2200
+ const sharedSkills = installedSkills
2201
+ .filter((skill) => skill.level === "shared")
2202
+ .map((skill) => ({ id: skill.id, version: skill.version, sourceKind: "embedded" }))
2203
+ .sort((left, right) => left.id.localeCompare(right.id));
2204
+ const embeddedTaskLevelSkills = installedSkills
2205
+ .filter((skill) => skill.level === "task" && skill.sourceKind === "embedded")
2206
+ .map((skill) => ({ id: skill.id, version: skill.version, taskTypes: skill.taskTypes, sourceKind: "embedded" }))
2207
+ .sort((left, right) => left.id.localeCompare(right.id));
2208
+ const evolutionTaskLevelSkills = installedSkills
2209
+ .filter((skill) => skill.level === "task" && skill.sourceKind === "evolution")
2210
+ .map((skill) => ({ id: skill.id, version: skill.version, taskTypes: skill.taskTypes, sourceKind: "evolution" }))
2211
+ .sort((left, right) => left.id.localeCompare(right.id));
2212
+ return {
2213
+ embeddedRootDir: config.skills.embeddedRootDir,
2214
+ evolutionRootDir: config.skills.evolutionRootDir,
2215
+ storeDir: config.skills.storeDir,
2216
+ embeddedRootReady: await pathExists(config.skills.embeddedRootDir),
2217
+ evolutionRootReady: await pathExists(config.skills.evolutionRootDir),
2218
+ storeReady: await pathExists(config.skills.storeDir),
2219
+ sharedEnabled: config.skills.sharedEnabled,
2220
+ taskLevelEnabled: config.skills.taskLevelEnabled,
2221
+ disabledSkills: config.skills.disabledSkills,
2222
+ installedCount: installedSkills.length,
2223
+ sharedCount: sharedSkills.length,
2224
+ embeddedTaskLevelCount: embeddedTaskLevelSkills.length,
2225
+ evolutionTaskLevelCount: evolutionTaskLevelSkills.length,
2226
+ taskLevelCount: embeddedTaskLevelSkills.length + evolutionTaskLevelSkills.length,
2227
+ sharedSkills,
2228
+ embeddedTaskLevelSkills,
2229
+ evolutionTaskLevelSkills
2230
+ };
2231
+ }
2232
+ async function resolveSubmissionRepositorySelector(args, store, config) {
2233
+ const candidates = await listCliRepositoryCandidates(store, config);
2234
+ const requestedRepo = args.repo?.trim();
2235
+ if (requestedRepo) {
2236
+ const matched = candidates.find((candidate) => matchesRepositorySelector(candidate, requestedRepo));
2237
+ if (!matched) {
2238
+ return {
2239
+ ok: false,
2240
+ reason: `submit received --repo ${requestedRepo}, but it did not match any registered repository. Run 'optimus repo list' or 'optimus repo add <path> --alias <name>' first.`
2241
+ };
2242
+ }
2243
+ return { ok: true, repoSelector: requestedRepo };
2244
+ }
2245
+ if (candidates.length === 1) {
2246
+ const onlyCandidate = candidates[0];
2247
+ if (!onlyCandidate) {
2248
+ return { ok: false, reason: "Repository discovery failed while selecting the only configured repository." };
2249
+ }
2250
+ return { ok: true, repoSelector: buildPreferredRepositorySelector(onlyCandidate) };
2251
+ }
2252
+ if (candidates.length === 0) {
2253
+ return {
2254
+ ok: false,
2255
+ reason: "submit requires --repo because no executable repository is configured. Register one with 'optimus repo add <path> --alias <name>' or set runtime.repoRoots first."
2256
+ };
2257
+ }
2258
+ return {
2259
+ ok: false,
2260
+ reason: "submit requires --repo because multiple repositories are configured. Choose one from 'optimus repo list'."
2261
+ };
2262
+ }
2263
+ async function listCliRepositoryCandidates(store, config) {
2264
+ const registered = await store.listRepositoryRoots();
2265
+ const configured = await inspectConfiguredRuntimeRepositories(config);
2266
+ const deduped = new Map();
2267
+ for (const repository of registered) {
2268
+ const inspection = await inspectRepositoryRoot(repository.path);
2269
+ if (!inspection.ok || !repository.enabled) {
2270
+ continue;
2271
+ }
2272
+ deduped.set(resolve(repository.path), {
2273
+ path: resolve(repository.path),
2274
+ ...(repository.alias ? { alias: repository.alias } : {}),
2275
+ source: "registered"
2276
+ });
2277
+ }
2278
+ for (const repository of configured) {
2279
+ if (!repository.inspection.ok) {
2280
+ continue;
2281
+ }
2282
+ deduped.set(resolve(repository.path), {
2283
+ path: resolve(repository.path),
2284
+ ...(repository.alias ? { alias: repository.alias } : {}),
2285
+ source: "configured"
2286
+ });
2287
+ }
2288
+ return [...deduped.values()];
2289
+ }
2290
+ async function inspectConfiguredRuntimeRepositories(config) {
2291
+ return Promise.all(config.runtime.repoRoots.map(async (repositoryPath) => {
2292
+ const normalizedPath = resolve(repositoryPath);
2293
+ return {
2294
+ path: normalizedPath,
2295
+ alias: basename(normalizedPath),
2296
+ source: "runtime.repoRoots",
2297
+ executionMode: "copy",
2298
+ inspection: await inspectRepositoryRoot(normalizedPath)
2299
+ };
2300
+ }));
2301
+ }
2302
+ function buildPreferredRepositorySelector(repository) {
2303
+ return repository.alias ?? (basename(repository.path) || repository.path);
2304
+ }
2305
+ function matchesRepositorySelector(repository, selector) {
2306
+ const normalizedSelector = resolve(selector);
2307
+ if (repository.path === normalizedSelector) {
2308
+ return true;
2309
+ }
2310
+ if (repository.alias === selector) {
2311
+ return true;
2312
+ }
2313
+ return basename(repository.path) === selector;
2314
+ }
2315
+ function buildRepositoryDiagnostics(registeredRepositories, configuredRuntimeRepositories) {
2316
+ const executablePaths = new Set();
2317
+ for (const repository of registeredRepositories) {
2318
+ if (repository.enabled) {
2319
+ executablePaths.add(resolve(repository.path));
2320
+ }
2321
+ }
2322
+ for (const repository of configuredRuntimeRepositories) {
2323
+ if (repository.inspection.ok) {
2324
+ executablePaths.add(resolve(repository.path));
2325
+ }
2326
+ }
2327
+ if (executablePaths.size === 0) {
2328
+ return {
2329
+ ok: false,
2330
+ totalExecutableRepositories: 0,
2331
+ recommendation: "No executable repository is configured. Register one with 'optimus repo add <path> --alias <name>' or set runtime.repoRoots before submitting bugfix tasks."
2332
+ };
2333
+ }
2334
+ return {
2335
+ ok: true,
2336
+ totalExecutableRepositories: executablePaths.size,
2337
+ recommendation: null
2338
+ };
2339
+ }
2340
+ function describeRepositoryExecutionPlan(repository, inspection) {
2341
+ const resolvedDefaultMode = repository.executionMode === "inplace"
2342
+ ? "inplace"
2343
+ : inspection.rootIsGit
2344
+ ? "copy"
2345
+ : "inplace";
2346
+ if (inspection.repoManaged) {
2347
+ return {
2348
+ workspaceKind: "repo_managed",
2349
+ resolvedDefaultMode,
2350
+ precheckStrategy: "repo_forall_status",
2351
+ cleanupStrategy: "repo_forall_reset_clean",
2352
+ recommendation: resolvedDefaultMode === "inplace"
2353
+ ? "Runtime will execute in place and use repo forall for precheck and cleanup."
2354
+ : "Runtime will default to copy mode. Switch to --mode inplace only if the repo workspace must stay in place."
2355
+ };
2356
+ }
2357
+ if (inspection.rootIsGit) {
2358
+ return {
2359
+ workspaceKind: "git_root",
2360
+ resolvedDefaultMode,
2361
+ precheckStrategy: resolvedDefaultMode === "copy" ? "none" : "git_status",
2362
+ cleanupStrategy: resolvedDefaultMode === "copy" ? "none" : "git_reset_clean",
2363
+ recommendation: resolvedDefaultMode === "copy"
2364
+ ? "Runtime will create a task-scoped git worktree by default."
2365
+ : "Runtime will execute in place and use git status/reset/clean on the repository."
2366
+ };
2367
+ }
2368
+ return {
2369
+ workspaceKind: "plain_directory",
2370
+ resolvedDefaultMode,
2371
+ precheckStrategy: "git_status",
2372
+ cleanupStrategy: "git_reset_clean",
2373
+ recommendation: "Runtime will execute in place. If nested git repositories exist, each one must be clean and will be reset after the task."
2374
+ };
2375
+ }
2376
+ async function inspectRepositoryRoot(path) {
2377
+ try {
2378
+ const metadata = await stat(path);
2379
+ if (!metadata.isDirectory()) {
2380
+ return { ok: false, reason: "path_is_not_directory", rootIsGit: false, repoManaged: false };
2381
+ }
2382
+ }
2383
+ catch {
2384
+ return { ok: false, reason: "path_not_found", rootIsGit: false, repoManaged: false };
2385
+ }
2386
+ const rootIsGit = await pathExists(join(path, ".git"));
2387
+ const repoManaged = await pathExists(join(path, ".repo"));
2388
+ if (rootIsGit) {
2389
+ return { ok: true, rootIsGit: true, repoManaged };
2390
+ }
2391
+ return {
2392
+ ok: true,
2393
+ rootIsGit: false,
2394
+ repoManaged,
2395
+ reason: repoManaged ? "repo_workspace_detected" : "git_metadata_not_found"
2396
+ };
2397
+ }
2398
+ async function safeList(path) {
2399
+ try {
2400
+ return await readdir(path);
2401
+ }
2402
+ catch {
2403
+ return [];
2404
+ }
2405
+ }
2406
+ function explainTaskEvent(eventType) {
2407
+ return TASK_EVENT_EXPLANATIONS[eventType] ?? (eventType.startsWith("stream.")
2408
+ ? "Codex runner 收到了新的流式事件,说明任务仍在持续推进。"
2409
+ : null);
2410
+ }
2411
+ function buildRunTimingSnapshot(run, config) {
2412
+ const now = Date.now();
2413
+ const startedAt = Date.parse(run.startedAt);
2414
+ const lastSignalAt = Date.parse(run.lastEventAt ?? run.startedAt);
2415
+ const endedAt = run.endedAt ? Date.parse(run.endedAt) : now;
2416
+ const elapsedMs = Number.isNaN(startedAt) || Number.isNaN(endedAt) ? 0 : Math.max(0, endedAt - startedAt);
2417
+ const idleMs = Number.isNaN(lastSignalAt) ? 0 : Math.max(0, now - lastSignalAt);
2418
+ const waitingForFirstSignal = run.status === "leased" || run.status === "bootstrapping" || run.status === "sdk_starting";
2419
+ const timeoutKind = run.endedAt ? null : resolveTimeoutKind(run, config, waitingForFirstSignal, elapsedMs, idleMs);
2420
+ return { elapsedMs, idleMs, timeoutKind };
2421
+ }
2422
+ function resolveTimeoutKind(run, config, waitingForFirstSignal, elapsedMs, idleMs) {
2423
+ if (run.status === "timed_out") {
2424
+ if (run.lastEventType === "runtime.timeout.startup") {
2425
+ return "startup_timeout";
2426
+ }
2427
+ if (run.lastEventType === "runtime.timeout.idle") {
2428
+ return "idle_timeout";
2429
+ }
2430
+ if (run.lastEventType === "runtime.timeout.hard") {
2431
+ return "hard_timeout";
2432
+ }
2433
+ }
2434
+ if (run.health === "suspected_stall") {
2435
+ return "stall_timeout";
2436
+ }
2437
+ if (elapsedMs >= config.runtime.runTimeoutMs) {
2438
+ return "hard_timeout";
2439
+ }
2440
+ if (waitingForFirstSignal && elapsedMs >= config.runtime.startupTimeoutMs) {
2441
+ return "startup_timeout";
2442
+ }
2443
+ if (!waitingForFirstSignal && idleMs >= config.runtime.idleTimeoutMs) {
2444
+ return "idle_timeout";
2445
+ }
2446
+ if (!waitingForFirstSignal && idleMs >= config.runtime.stallWarningMs) {
2447
+ return "stall_timeout";
2448
+ }
2449
+ return null;
2450
+ }
2451
+ function renderTaskResultReport(input) {
2452
+ const lines = [];
2453
+ const task = input.task;
2454
+ const taskPackage = input.taskPackage;
2455
+ const latestRun = input.latestRun;
2456
+ lines.push(`Task ${task.taskId}`);
2457
+ lines.push(`Title: ${task.title}`);
2458
+ lines.push(`Status: ${task.status}`);
2459
+ if (taskPackage) {
2460
+ lines.push(`Task Type: ${taskPackage.taskType}`);
2461
+ }
2462
+ if (latestRun) {
2463
+ lines.push(`Run: ${latestRun.runId}`);
2464
+ lines.push(`Run Status: ${latestRun.status}`);
2465
+ if (latestRun.health) {
2466
+ lines.push(`Health: ${latestRun.health}`);
2467
+ }
2468
+ if (latestRun.failureCategory) {
2469
+ lines.push(`Failure Category: ${latestRun.failureCategory}`);
2470
+ }
2471
+ if (latestRun.startedAt) {
2472
+ lines.push(`Started At: ${latestRun.startedAt}`);
2473
+ }
2474
+ if (latestRun.endedAt) {
2475
+ lines.push(`Ended At: ${latestRun.endedAt}`);
2476
+ }
2477
+ }
2478
+ lines.push("");
2479
+ lines.push("Summary");
2480
+ lines.push(latestRun?.summary?.trim() || "No run summary recorded.");
2481
+ const diagnosis = buildTaskDeliveryDiagnosis({
2482
+ task: input.task,
2483
+ latestRun,
2484
+ deliveryBundle: input.deliveryBundle,
2485
+ deliveryAttempts: input.deliveryAttempts,
2486
+ publicationAttempts: input.publicationAttempts
2487
+ });
2488
+ lines.push("");
2489
+ lines.push("Diagnosis");
2490
+ lines.push(`Primary Failure Domain: ${diagnosis.primaryFailureDomain}`);
2491
+ lines.push(`Execution: ${diagnosis.execution.state}${diagnosis.execution.detail ? ` | ${diagnosis.execution.detail}` : ""}`);
2492
+ lines.push(`Publication: ${diagnosis.publication.state}${diagnosis.publication.detail ? ` | ${diagnosis.publication.detail}` : ""}`);
2493
+ lines.push(`Notification: ${diagnosis.notification.state}${diagnosis.notification.detail ? ` | ${diagnosis.notification.detail}` : ""}`);
2494
+ lines.push(`Recommended Action: ${diagnosis.recommendedAction}`);
2495
+ if (input.deliveryBundle) {
2496
+ lines.push("");
2497
+ lines.push("Delivery");
2498
+ lines.push(`Outcome: ${input.deliveryBundle.outcome}`);
2499
+ lines.push(`Decision: ${input.deliveryBundle.summary.decision}`);
2500
+ if (input.deliveryBundle.warnings?.length) {
2501
+ lines.push(`Warnings: ${input.deliveryBundle.warnings.join(", ")}`);
2502
+ }
2503
+ if (input.deliveryBundle.publication) {
2504
+ lines.push(`Publication: ${input.deliveryBundle.publication.action}${input.deliveryBundle.publication.reason ? ` (${input.deliveryBundle.publication.reason})` : ""}`);
2505
+ if (input.deliveryBundle.publication.git?.branch) {
2506
+ lines.push(`Branch: ${input.deliveryBundle.publication.git.branch}`);
2507
+ }
2508
+ if (input.deliveryBundle.publication.git?.upstreamRemote && input.deliveryBundle.publication.git?.upstreamBranch) {
2509
+ lines.push(`Upstream: ${input.deliveryBundle.publication.git.upstreamRemote}/${input.deliveryBundle.publication.git.upstreamBranch}`);
2510
+ }
2511
+ if (input.deliveryBundle.publication.git?.manifestRevision) {
2512
+ lines.push(`Manifest Revision: ${input.deliveryBundle.publication.git.manifestRevision}`);
2513
+ }
2514
+ if (input.deliveryBundle.publication.git?.targetBranch) {
2515
+ lines.push(`Publication Target: ${input.deliveryBundle.publication.git.targetBranch} (${input.deliveryBundle.publication.git.targetSource ?? "unknown"})`);
2516
+ }
2517
+ if (input.deliveryBundle.publication.git?.reviewSystem) {
2518
+ lines.push(`Review System: ${input.deliveryBundle.publication.git.reviewSystem.type} (${input.deliveryBundle.publication.git.reviewSystem.strategy})${input.deliveryBundle.publication.git.reviewSystem.remoteName ? ` | ${input.deliveryBundle.publication.git.reviewSystem.remoteName}` : ""}${input.deliveryBundle.publication.git.reviewSystem.repository ? ` | ${input.deliveryBundle.publication.git.reviewSystem.repository}` : ""}`);
2519
+ }
2520
+ if (input.deliveryBundle.publication.git?.repoProjects?.length) {
2521
+ lines.push("Publication Projects:");
2522
+ for (const project of input.deliveryBundle.publication.git.repoProjects) {
2523
+ const target = project.targetBranch ? `${project.targetBranch} (${project.targetSource ?? "unknown"})` : "n/a";
2524
+ const upstream = project.upstreamRemote && project.upstreamBranch ? ` | ${project.upstreamRemote}/${project.upstreamBranch}` : "";
2525
+ const review = project.reviewSystem ? ` | ${project.reviewSystem.type}/${project.reviewSystem.strategy}` : "";
2526
+ lines.push(`- ${project.projectPath}: ${target}${upstream}${review}`);
2527
+ }
2528
+ }
2529
+ if (input.deliveryBundle.publication.git?.unresolvedPaths?.length) {
2530
+ lines.push(`Unresolved Paths: ${input.deliveryBundle.publication.git.unresolvedPaths.join(", ")}`);
2531
+ }
2532
+ }
2533
+ }
2534
+ const deliverySummary = buildDeliveryAttemptSummary(input.deliveryAttempts);
2535
+ if (deliverySummary.channels.length > 0) {
2536
+ lines.push("");
2537
+ lines.push("Delivery Attempts");
2538
+ for (const channel of deliverySummary.channels) {
2539
+ lines.push(`- ${channel.channel}: ${channel.latestStatus} | attempts=${channel.totalAttempts} | failed=${channel.failedAttempts} | dispatched=${channel.dispatchedAttempts} | skipped=${channel.skippedAttempts}`);
2540
+ lines.push(` Latest: ${channel.latestSummary}`);
2541
+ if (channel.backgroundRecovered) {
2542
+ lines.push(" Recovery: background retry eventually delivered after an earlier failure.");
2543
+ }
2544
+ if (channel.error) {
2545
+ lines.push(` Error: ${channel.error}`);
2546
+ }
2547
+ }
2548
+ }
2549
+ if (input.publicationAttempts.length > 0) {
2550
+ lines.push("");
2551
+ lines.push("Publication Attempts");
2552
+ for (const attempt of input.publicationAttempts) {
2553
+ const reviewRefs = [
2554
+ ...(attempt.topicUrl ? [`Topic=${attempt.topicUrl}`] : []),
2555
+ ...(attempt.reviewUrls ?? []),
2556
+ ...((attempt.reviewUrls?.length ?? 0) === 0 && attempt.reviewUrl ? [attempt.reviewUrl] : []),
2557
+ ...((attempt.changeIds ?? []).map((value) => `Change-Id=${value}`)),
2558
+ ...((attempt.changeIds?.length ?? 0) === 0 && attempt.changeId ? [`Change-Id=${attempt.changeId}`] : [])
2559
+ ];
2560
+ lines.push(`- ${attempt.mode}: ${attempt.status} | ${attempt.summary}${reviewRefs.length > 0 ? ` | ${reviewRefs.join(" | ")}` : ""}${attempt.artifactPath ? ` | ${attempt.artifactPath}` : ""}`);
2561
+ }
2562
+ }
2563
+ const publicationReview = buildPublicationReviewSummary(input.publicationAttempts);
2564
+ if (publicationReview.latestReviewSubmit) {
2565
+ lines.push("");
2566
+ lines.push("Publication Review");
2567
+ lines.push(`Status: ${publicationReview.latestReviewSubmit.status}`);
2568
+ lines.push(`Summary: ${publicationReview.latestReviewSubmit.summary}`);
2569
+ if (publicationReview.latestReviewSubmit.reviewUrl) {
2570
+ lines.push(`Review URL: ${publicationReview.latestReviewSubmit.reviewUrl}`);
2571
+ }
2572
+ if (publicationReview.latestReviewSubmit.topicUrl) {
2573
+ lines.push(`Topic URL: ${publicationReview.latestReviewSubmit.topicUrl}`);
2574
+ }
2575
+ if (publicationReview.latestReviewSubmit.reviewUrls.length > 0) {
2576
+ lines.push(`Review URLs: ${publicationReview.latestReviewSubmit.reviewUrls.join(", ")}`);
2577
+ }
2578
+ if (publicationReview.latestReviewSubmit.changeIds.length > 0) {
2579
+ lines.push(`Change-Ids: ${publicationReview.latestReviewSubmit.changeIds.join(", ")}`);
2580
+ }
2581
+ }
2582
+ if (input.resultFiles.length > 0) {
2583
+ lines.push("");
2584
+ lines.push("Result File");
2585
+ for (const resultFile of input.resultFiles) {
2586
+ lines.push(`- ${resultFile.path}${resultFile.createdAt ? ` @ ${resultFile.createdAt}` : ""}`);
2587
+ }
2588
+ }
2589
+ return lines.join("\n");
2590
+ }
2591
+ function renderDeliveryStatusReport(input) {
2592
+ const lines = [];
2593
+ const deliverySummary = buildDeliveryAttemptSummary(input.deliveryAttempts);
2594
+ const publicationReview = buildPublicationReviewSummary(input.publicationAttempts);
2595
+ const diagnosis = buildTaskDeliveryDiagnosis({
2596
+ task: input.task,
2597
+ latestRun: input.latestRun,
2598
+ deliveryBundle: input.deliveryBundle,
2599
+ deliveryAttempts: input.deliveryAttempts,
2600
+ publicationAttempts: input.publicationAttempts
2601
+ });
2602
+ lines.push(`Task ${input.task.taskId}`);
2603
+ lines.push(`Status: ${input.task.status}`);
2604
+ if (input.taskPackage) {
2605
+ lines.push(`Task Type: ${input.taskPackage.taskType}`);
2606
+ }
2607
+ lines.push("");
2608
+ lines.push("Delivery Status");
2609
+ if (input.deliveryBundle) {
2610
+ lines.push(`Outcome: ${input.deliveryBundle.outcome}`);
2611
+ lines.push(`Decision: ${input.deliveryBundle.summary.decision}`);
2612
+ lines.push(`Created At: ${input.deliveryBundle.createdAt}`);
2613
+ if (input.deliveryBundle.warnings?.length) {
2614
+ lines.push(`Warnings: ${input.deliveryBundle.warnings.join(", ")}`);
2615
+ }
2616
+ }
2617
+ else {
2618
+ lines.push("Outcome: not_generated");
2619
+ lines.push("Decision: No delivery bundle recorded.");
2620
+ }
2621
+ lines.push("");
2622
+ lines.push("Diagnosis");
2623
+ lines.push(`Primary Failure Domain: ${diagnosis.primaryFailureDomain}`);
2624
+ lines.push(`Execution: ${diagnosis.execution.state}${diagnosis.execution.detail ? ` | ${diagnosis.execution.detail}` : ""}`);
2625
+ lines.push(`Publication: ${diagnosis.publication.state}${diagnosis.publication.detail ? ` | ${diagnosis.publication.detail}` : ""}`);
2626
+ lines.push(`Notification: ${diagnosis.notification.state}${diagnosis.notification.detail ? ` | ${diagnosis.notification.detail}` : ""}`);
2627
+ lines.push(`Recommended Action: ${diagnosis.recommendedAction}`);
2628
+ if (deliverySummary.channels.length > 0) {
2629
+ for (const channel of deliverySummary.channels) {
2630
+ lines.push(`- ${channel.channel}: ${channel.latestStatus} | attempts=${channel.totalAttempts} | failed=${channel.failedAttempts} | dispatched=${channel.dispatchedAttempts} | skipped=${channel.skippedAttempts}`);
2631
+ lines.push(` Latest: ${channel.latestSummary}`);
2632
+ lines.push(` Created At: ${channel.latestCreatedAt}`);
2633
+ if (channel.deliveredAt) {
2634
+ lines.push(` Delivered At: ${channel.deliveredAt}`);
2635
+ }
2636
+ if (channel.backgroundRecovered) {
2637
+ lines.push(" Recovery: background retry eventually delivered after an earlier failure.");
2638
+ }
2639
+ if (channel.error) {
2640
+ lines.push(` Error: ${channel.error}`);
2641
+ }
2642
+ }
2643
+ }
2644
+ else {
2645
+ lines.push("No delivery attempts recorded.");
2646
+ }
2647
+ if (publicationReview.latestReviewSubmit) {
2648
+ lines.push("");
2649
+ lines.push("Publication Review");
2650
+ lines.push(`Status: ${publicationReview.latestReviewSubmit.status}`);
2651
+ lines.push(`Summary: ${publicationReview.latestReviewSubmit.summary}`);
2652
+ if (publicationReview.latestReviewSubmit.reviewUrl) {
2653
+ lines.push(`Review URL: ${publicationReview.latestReviewSubmit.reviewUrl}`);
2654
+ }
2655
+ if (publicationReview.latestReviewSubmit.topicUrl) {
2656
+ lines.push(`Topic URL: ${publicationReview.latestReviewSubmit.topicUrl}`);
2657
+ }
2658
+ if (publicationReview.latestReviewSubmit.reviewUrls.length > 0) {
2659
+ lines.push(`Review URLs: ${publicationReview.latestReviewSubmit.reviewUrls.join(", ")}`);
2660
+ }
2661
+ if (publicationReview.latestReviewSubmit.changeIds.length > 0) {
2662
+ lines.push(`Change-Ids: ${publicationReview.latestReviewSubmit.changeIds.join(", ")}`);
2663
+ }
2664
+ }
2665
+ return lines.join("\n");
2666
+ }
2667
+ function buildTaskDeliveryDiagnosis(input) {
2668
+ const latestRun = input.latestRun;
2669
+ const latestPublicationAttempt = input.publicationAttempts.at(-1) ?? null;
2670
+ const latestReviewSubmit = [...input.publicationAttempts].reverse().find((attempt) => attempt.mode === "review_submit") ?? null;
2671
+ const deliverySummary = buildDeliveryAttemptSummary(input.deliveryAttempts);
2672
+ let executionState = "queued";
2673
+ let executionDetail = null;
2674
+ if (input.task.status === "failed" || latestRun?.taskStatus === "failed") {
2675
+ executionState = "failed";
2676
+ executionDetail = normalizeFailureSummaryReason(latestRun?.summary ?? "Task execution failed.");
2677
+ }
2678
+ else if (input.task.status === "needs_human" || latestRun?.taskStatus === "needs_human") {
2679
+ executionState = "needs_human";
2680
+ executionDetail = normalizeFailureSummaryReason(latestRun?.summary ?? "Task requires human input.");
2681
+ }
2682
+ else if (input.task.status === "canceled" || latestRun?.taskStatus === "canceled") {
2683
+ executionState = "canceled";
2684
+ executionDetail = normalizeFailureSummaryReason(latestRun?.summary ?? "Task execution was canceled.");
2685
+ }
2686
+ else if (input.task.status === "completed" || latestRun?.taskStatus === "completed") {
2687
+ executionState = "completed";
2688
+ executionDetail = latestRun?.summary ? normalizeFailureSummaryReason(latestRun.summary) : null;
2689
+ }
2690
+ else if (latestRun) {
2691
+ executionState = "in_progress";
2692
+ executionDetail = latestRun.lastEventType ? explainTaskEvent(latestRun.lastEventType) : null;
2693
+ }
2694
+ let publicationState = "not_attempted";
2695
+ let publicationDetail = null;
2696
+ let publicationMode = latestReviewSubmit?.mode ?? latestPublicationAttempt?.mode ?? null;
2697
+ let publicationStatus = latestReviewSubmit?.status ?? latestPublicationAttempt?.status ?? null;
2698
+ if (latestReviewSubmit) {
2699
+ publicationState = latestReviewSubmit.status;
2700
+ publicationDetail = normalizeFailureSummaryReason(latestReviewSubmit.error ?? latestReviewSubmit.summary);
2701
+ }
2702
+ else if (latestPublicationAttempt) {
2703
+ publicationState = latestPublicationAttempt.status;
2704
+ publicationDetail = normalizeFailureSummaryReason(latestPublicationAttempt.error ?? latestPublicationAttempt.summary);
2705
+ }
2706
+ else if (!input.deliveryBundle?.publication) {
2707
+ publicationState = "not_attempted";
2708
+ publicationDetail = null;
2709
+ }
2710
+ else if (input.deliveryBundle.publication.action === "published") {
2711
+ publicationState = "published";
2712
+ publicationDetail = input.deliveryBundle.publication.git?.targetBranch ?? input.deliveryBundle.publication.git?.upstreamBranch ?? null;
2713
+ }
2714
+ else if (input.deliveryBundle.publication.action === "ready_for_publish") {
2715
+ publicationState = "ready";
2716
+ publicationDetail = input.deliveryBundle.publication.git?.targetBranch ?? input.deliveryBundle.publication.git?.upstreamBranch ?? null;
2717
+ }
2718
+ else if (input.deliveryBundle.publication.applicable === false) {
2719
+ publicationState = "not_applicable";
2720
+ publicationDetail = input.deliveryBundle.publication.reason ? normalizeFailureSummaryReason(input.deliveryBundle.publication.reason) : null;
2721
+ }
2722
+ else {
2723
+ publicationState = "blocked";
2724
+ publicationDetail = input.deliveryBundle.publication.reason ? normalizeFailureSummaryReason(input.deliveryBundle.publication.reason) : null;
2725
+ }
2726
+ const notificationChannels = deliverySummary.channels.map((channel) => ({
2727
+ channel: channel.channel,
2728
+ state: channel.latestStatus,
2729
+ backgroundRecovered: channel.backgroundRecovered
2730
+ }));
2731
+ let notificationState = "not_attempted";
2732
+ let notificationDetail = null;
2733
+ const notificationRelevantChannels = deliverySummary.channels.filter((channel) => channel.channel !== "console");
2734
+ const failedChannel = notificationRelevantChannels.find((channel) => channel.latestStatus === "failed");
2735
+ if (failedChannel) {
2736
+ notificationState = "failed";
2737
+ notificationDetail = normalizeFailureSummaryReason(failedChannel.error ?? failedChannel.latestSummary);
2738
+ }
2739
+ else if (notificationRelevantChannels.some((channel) => channel.latestStatus === "dispatched")) {
2740
+ notificationState = notificationRelevantChannels.some((channel) => channel.backgroundRecovered) ? "delivered_with_recovery" : "delivered";
2741
+ notificationDetail = normalizeFailureSummaryReason(notificationRelevantChannels.find((channel) => channel.latestStatus === "dispatched")?.latestSummary ?? "Notification delivered.");
2742
+ }
2743
+ else if (notificationRelevantChannels.length > 0 && notificationRelevantChannels.every((channel) => channel.latestStatus === "skipped")) {
2744
+ notificationState = "skipped";
2745
+ notificationDetail = normalizeFailureSummaryReason(notificationRelevantChannels.map((channel) => channel.latestSummary).join(" | "));
2746
+ }
2747
+ else if (notificationRelevantChannels.length === 0 && deliverySummary.channels.some((channel) => channel.channel === "console" && channel.latestStatus === "dispatched")) {
2748
+ notificationState = "not_attempted";
2749
+ notificationDetail = null;
2750
+ }
2751
+ let primaryFailureDomain = "none";
2752
+ if (["failed", "needs_human", "canceled"].includes(executionState)) {
2753
+ primaryFailureDomain = "execution";
2754
+ }
2755
+ else if (["failed", "partial"].includes(publicationState)) {
2756
+ primaryFailureDomain = "publication";
2757
+ }
2758
+ else if (notificationState === "failed") {
2759
+ primaryFailureDomain = "notification";
2760
+ }
2761
+ let recommendedAction = "none";
2762
+ if (primaryFailureDomain === "execution") {
2763
+ recommendedAction = buildTaskNextAction(input.task.status, latestRun?.failureCategory);
2764
+ }
2765
+ else if (primaryFailureDomain === "publication") {
2766
+ recommendedAction = "inspect_publication_failure";
2767
+ }
2768
+ else if (primaryFailureDomain === "notification") {
2769
+ recommendedAction = "retry_delivery_notification";
2770
+ }
2771
+ else if (publicationState === "ready" || publicationState === "planned") {
2772
+ recommendedAction = "review_publication_status";
2773
+ }
2774
+ return {
2775
+ primaryFailureDomain,
2776
+ execution: {
2777
+ state: executionState,
2778
+ detail: executionDetail,
2779
+ failureCategory: latestRun?.failureCategory ?? null
2780
+ },
2781
+ publication: {
2782
+ state: publicationState,
2783
+ detail: publicationDetail,
2784
+ latestMode: publicationMode,
2785
+ latestStatus: publicationStatus
2786
+ },
2787
+ notification: {
2788
+ state: notificationState,
2789
+ detail: notificationDetail,
2790
+ channels: notificationChannels
2791
+ },
2792
+ recommendedAction
2793
+ };
2794
+ }
2795
+ function buildTaskNextAction(status, failureCategory) {
2796
+ if (status === "needs_human") {
2797
+ return "await_human_input";
2798
+ }
2799
+ if (status === "completed") {
2800
+ return "consume_artifacts";
2801
+ }
2802
+ if (status === "canceled") {
2803
+ return "review_cancel_reason";
2804
+ }
2805
+ if (status === "failed") {
2806
+ if (failureCategory === "auth_missing" || failureCategory === "config_error") {
2807
+ return "fix_configuration";
2808
+ }
2809
+ if (failureCategory === "network_error" || failureCategory === "provider_error" || failureCategory === "timeout") {
2810
+ return "retry_after_environment_check";
2811
+ }
2812
+ if (failureCategory === "schema_error" || failureCategory === "parse_error") {
2813
+ return "inspect_runner_output";
2814
+ }
2815
+ return "inspect_failure_and_retry";
2816
+ }
2817
+ return "wait_for_runtime";
2818
+ }
2819
+ function describeFailureCategory(failureCategory) {
2820
+ if (!failureCategory) {
2821
+ return null;
2822
+ }
2823
+ const labels = {
2824
+ timeout: "Timed out while waiting for Codex or provider progress.",
2825
+ process_exit: "Codex process exited unexpectedly.",
2826
+ parse_error: "Runner could not parse the Codex reply.",
2827
+ schema_error: "Codex reply shape did not match the result contract.",
2828
+ config_error: "Codex configuration is invalid or incomplete.",
2829
+ network_error: "Network path to Codex control plane or provider failed.",
2830
+ provider_error: "Provider returned an upstream service error.",
2831
+ environment_error: "Runtime environment or workspace setup failed.",
2832
+ input_missing: "Task input is missing critical information.",
2833
+ auth_missing: "Authentication inputs are missing or invalid.",
2834
+ unknown: "Failure category was not classified."
2835
+ };
2836
+ return labels[failureCategory] ?? "Failure category is not yet documented.";
2837
+ }
2838
+ function buildRecentFailureBreakdown(runs) {
2839
+ const counts = runs
2840
+ .filter((run) => run.taskStatus === "failed")
2841
+ .reduce((accumulator, run) => {
2842
+ const key = run.failureCategory ?? "unknown";
2843
+ accumulator.set(key, (accumulator.get(key) ?? 0) + 1);
2844
+ return accumulator;
2845
+ }, new Map());
2846
+ return [...counts.entries()]
2847
+ .sort((left, right) => right[1] - left[1])
2848
+ .map(([failureCategory, count]) => ({
2849
+ failureCategory,
2850
+ count,
2851
+ label: describeFailureCategory(failureCategory) ?? "Unknown failure"
2852
+ }));
2853
+ }
2854
+ function buildDeliveryWarningSummary(recentEvents) {
2855
+ const warningEvents = recentEvents.filter((event) => event.type === "task.delivery_bundle.warning");
2856
+ const warningsByType = warningEvents.reduce((accumulator, event) => {
2857
+ accumulator[event.detail] = (accumulator[event.detail] ?? 0) + 1;
2858
+ return accumulator;
2859
+ }, {});
2860
+ return {
2861
+ totalWarnings: warningEvents.length,
2862
+ latestWarning: warningEvents[0]?.detail ?? null,
2863
+ warningsByType
2864
+ };
2865
+ }
2866
+ function buildRecentFailureSummary(runs, recentEvents) {
2867
+ const recentTerminalRuns = [...runs]
2868
+ .filter((run) => Boolean(run.endedAt) || run.taskStatus === "needs_human" || run.taskStatus === "failed")
2869
+ .sort((left, right) => (right.endedAt ?? right.startedAt).localeCompare(left.endedAt ?? left.startedAt))
2870
+ .slice(0, 50);
2871
+ const failedByCategory = recentTerminalRuns
2872
+ .filter((run) => run.taskStatus === "failed")
2873
+ .reduce((accumulator, run) => {
2874
+ const key = run.failureCategory ?? "unknown";
2875
+ accumulator[key] = (accumulator[key] ?? 0) + 1;
2876
+ return accumulator;
2877
+ }, {});
2878
+ const needsHumanByReason = recentTerminalRuns
2879
+ .filter((run) => run.taskStatus === "needs_human")
2880
+ .reduce((accumulator, run) => {
2881
+ const key = normalizeFailureSummaryReason(run.summary ?? "needs_human");
2882
+ accumulator[key] = (accumulator[key] ?? 0) + 1;
2883
+ return accumulator;
2884
+ }, {});
2885
+ const eventBackfilledReasons = new Set(Object.keys(needsHumanByReason));
2886
+ for (const event of recentEvents) {
2887
+ if (event.type !== "task.needs_human") {
2888
+ continue;
2889
+ }
2890
+ const key = normalizeFailureSummaryReason(event.detail);
2891
+ if (eventBackfilledReasons.has(key)) {
2892
+ continue;
2893
+ }
2894
+ needsHumanByReason[key] = (needsHumanByReason[key] ?? 0) + 1;
2895
+ eventBackfilledReasons.add(key);
2896
+ }
2897
+ return {
2898
+ sampleSize: recentTerminalRuns.length,
2899
+ failedByCategory,
2900
+ needsHumanByReason
2901
+ };
2902
+ }
2903
+ async function buildDeliveryHealthSummary(store, tasks, runs) {
2904
+ const recentTasks = [...tasks]
2905
+ .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
2906
+ .slice(0, 20);
2907
+ const primaryFailureDomain = {};
2908
+ let publicationFailures = 0;
2909
+ let notificationFailures = 0;
2910
+ let notificationRecovered = 0;
2911
+ for (const task of recentTasks) {
2912
+ const latestRun = [...runs].filter((run) => run.taskId === task.taskId).at(-1) ?? null;
2913
+ const deliveryBundle = await store.getLatestTaskDeliveryBundle(task.taskId);
2914
+ const deliveryAttempts = await store.listTaskDeliveryAttempts(task.taskId);
2915
+ const publicationAttempts = await store.listTaskPublicationAttempts(task.taskId);
2916
+ const diagnosis = buildTaskDeliveryDiagnosis({
2917
+ task,
2918
+ latestRun,
2919
+ deliveryBundle,
2920
+ deliveryAttempts,
2921
+ publicationAttempts
2922
+ });
2923
+ primaryFailureDomain[diagnosis.primaryFailureDomain] = (primaryFailureDomain[diagnosis.primaryFailureDomain] ?? 0) + 1;
2924
+ if (diagnosis.publication.state === "failed" || diagnosis.publication.state === "partial") {
2925
+ publicationFailures += 1;
2926
+ }
2927
+ if (diagnosis.notification.state === "failed") {
2928
+ notificationFailures += 1;
2929
+ }
2930
+ if (diagnosis.notification.state === "delivered_with_recovery") {
2931
+ notificationRecovered += 1;
2932
+ }
2933
+ }
2934
+ return {
2935
+ sampleSize: recentTasks.length,
2936
+ primaryFailureDomain,
2937
+ publicationFailures,
2938
+ notificationFailures,
2939
+ notificationRecovered
2940
+ };
2941
+ }
2942
+ function normalizeFailureSummaryReason(reason) {
2943
+ const normalized = reason.replace(/\s+/g, " ").trim();
2944
+ return normalized.length > 120 ? `${normalized.slice(0, 117)}...` : normalized;
2945
+ }
2946
+ void main().catch((error) => {
2947
+ const message = error instanceof Error ? error.message : String(error);
2948
+ console.error(message);
2949
+ process.exitCode = 1;
2950
+ });
2951
+ //# sourceMappingURL=optimus.js.map