@sireai/optimus 0.1.17 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -5
- package/dist/cli/optimus.js +374 -282
- package/dist/cli/optimus.js.map +1 -1
- package/dist/config/load-config.js +1 -1
- package/dist/config/load-config.js.map +1 -1
- package/dist/integrations/jira/jira-cli.js +13 -1
- package/dist/integrations/jira/jira-cli.js.map +1 -1
- package/dist/integrations/jira/jira-client.d.ts +3 -0
- package/dist/integrations/jira/jira-client.js +27 -1
- package/dist/integrations/jira/jira-client.js.map +1 -1
- package/dist/integrations/jira/jira-submit.js +14 -7
- package/dist/integrations/jira/jira-submit.js.map +1 -1
- package/dist/problem-solving-core/codex/codex-auth-resolver.js +1 -1
- package/dist/problem-solving-core/codex/codex-auth-resolver.js.map +1 -1
- package/dist/task-environment/delivery/commit-message/bugfix-commit-message-template.d.ts +1 -1
- package/dist/task-environment/delivery/commit-message/bugfix-commit-message-template.js +14 -3
- package/dist/task-environment/delivery/commit-message/bugfix-commit-message-template.js.map +1 -1
- package/dist/task-environment/delivery/task-delivery-service.d.ts +5 -0
- package/dist/task-environment/delivery/task-delivery-service.js +7 -2
- package/dist/task-environment/delivery/task-delivery-service.js.map +1 -1
- package/dist/task-environment/delivery/task-publication-service.js +1 -0
- package/dist/task-environment/delivery/task-publication-service.js.map +1 -1
- package/dist/task-environment/evidence/evidence-preparation-service.d.ts +5 -1
- package/dist/task-environment/evidence/evidence-preparation-service.js +170 -21
- package/dist/task-environment/evidence/evidence-preparation-service.js.map +1 -1
- package/dist/task-environment/observability/logger.d.ts +1 -0
- package/dist/task-environment/observability/logger.js +31 -1
- package/dist/task-environment/observability/logger.js.map +1 -1
- package/dist/task-environment/orchestration/execution-context-assembler.js +1 -1
- package/dist/task-environment/orchestration/execution-context-assembler.js.map +1 -1
- package/dist/task-environment/orchestration/task-orchestrator.js +3 -1
- package/dist/task-environment/orchestration/task-orchestrator.js.map +1 -1
- package/optimus.config.template.json +1 -1
- package/package.json +1 -1
- package/embedded-skills/task/bugfix/android-debug-protocol/SKILL.md +0 -10
- package/embedded-skills/task/bugfix/android-debug-protocol/skill.json +0 -6
package/dist/cli/optimus.js
CHANGED
|
@@ -12,10 +12,8 @@ import { CodexRunner } from "../problem-solving-core/codex/codex-runner.js";
|
|
|
12
12
|
import { CodexAuthResolver } from "../problem-solving-core/codex/codex-auth-resolver.js";
|
|
13
13
|
import { SkillSyncService } from "../problem-solving-core/codex/skill-sync-service.js";
|
|
14
14
|
import { RepoMemoryService } from "../problem-solving-core/codex/repo-memory-service.js";
|
|
15
|
-
import {
|
|
16
|
-
import { resolveCodexAuthMode, resolveConfiguredCodexModelProvider, resolveConfiguredCodexProvider, resolveEffectiveCodexModel } from "../problem-solving-core/codex/codex-provider-profile.js";
|
|
15
|
+
import { resolveCodexAuthMode, resolveConfiguredCodexProvider } from "../problem-solving-core/codex/codex-provider-profile.js";
|
|
17
16
|
import { classifyCodexFailureCategory } from "../problem-solving-core/codex/codex-failure-classifier.js";
|
|
18
|
-
import { CodexPreflight } from "../problem-solving-core/codex/codex-preflight.js";
|
|
19
17
|
import { createManualEventContent, createManualProblemEvent } from "../task-environment/intake/manual-problem-intake.js";
|
|
20
18
|
import { OptimusLogger } from "../task-environment/observability/logger.js";
|
|
21
19
|
import { TaskOrchestrator } from "../task-environment/orchestration/task-orchestrator.js";
|
|
@@ -33,8 +31,8 @@ import { FeishuClient } from "../integrations/feishu/feishu-client.js";
|
|
|
33
31
|
import { FeishuUserService } from "../integrations/feishu/feishu-user-service.js";
|
|
34
32
|
import { createFeedbackReport } from "./feedback.js";
|
|
35
33
|
import { DEFAULT_FEEDBACK_RECIPIENT_EMAIL, FeishuFeedbackDeliveryService, persistFeedbackDeliveryResult, resolveDefaultFeedbackRecipientFallbackOpenId } from "./feedback-delivery.js";
|
|
36
|
-
import { resolveDefaultConfigPath, resolveDefaultEnvPath
|
|
37
|
-
import { checkForSelfUpdate, installSelfUpdate, recordSkippedSelfUpdate,
|
|
34
|
+
import { resolveDefaultConfigPath, resolveDefaultEnvPath } from "../config/optimus-paths.js";
|
|
35
|
+
import { checkForSelfUpdate, installSelfUpdate, recordSkippedSelfUpdate, shouldPromptForCachedStartupUpdate } from "./self-update.js";
|
|
38
36
|
const CLI_ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
39
37
|
const PACKAGE_JSON_PATH = join(CLI_ROOT_DIR, "package.json");
|
|
40
38
|
const execFileAsync = promisify(execFile);
|
|
@@ -42,16 +40,28 @@ const PACKAGE_NAME = "@sireai/optimus";
|
|
|
42
40
|
const JIRA_CLI_ENTRY = join(CLI_ROOT_DIR, "dist", "integrations", "jira", "jira-cli.js");
|
|
43
41
|
const SENTRY_CLI_ENTRY = join(CLI_ROOT_DIR, "dist", "integrations", "sentry", "sentry-cli.js");
|
|
44
42
|
const RELEASE_NOTES_URL = "https://github.com/SireAI/optimus/releases/latest";
|
|
43
|
+
const DOCTOR_ANSI = {
|
|
44
|
+
reset: "\u001B[0m",
|
|
45
|
+
green: "\u001B[32m",
|
|
46
|
+
yellow: "\u001B[33m",
|
|
47
|
+
red: "\u001B[31m",
|
|
48
|
+
gray: "\u001B[90m"
|
|
49
|
+
};
|
|
45
50
|
function renderSetupResult(result) {
|
|
46
51
|
const lines = [];
|
|
47
52
|
lines.push("Setup Complete");
|
|
48
53
|
lines.push(`Config: ${result.configPath}`);
|
|
49
54
|
lines.push(`Env: ${result.envPath}`);
|
|
50
55
|
lines.push(`Status: ${result.configStatus}`);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
if (result.repository) {
|
|
57
|
+
lines.push(`Repository: ${result.repository.alias} (${result.repository.path})`);
|
|
58
|
+
lines.push(`Workspace: ${result.repository.workspaceKind}`);
|
|
59
|
+
lines.push(`Review: ${result.repository.reviewSystem}`);
|
|
60
|
+
lines.push(`Mode: ${result.repository.executionMode}`);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
lines.push("Repository: not configured");
|
|
64
|
+
}
|
|
55
65
|
lines.push(`Delivery: ${result.deliveryChannels.join(", ")}`);
|
|
56
66
|
lines.push(`Jira: ${result.jiraEnabled ? "enabled" : "disabled"}`);
|
|
57
67
|
lines.push(`Sentry: ${result.sentryEnabled ? "enabled" : "disabled"}`);
|
|
@@ -67,7 +77,7 @@ function renderSetupResult(result) {
|
|
|
67
77
|
lines.push("Next");
|
|
68
78
|
lines.push("- optimus start");
|
|
69
79
|
lines.push("- optimus submit --title \"Login crash\" --description \"Crash on startup\"");
|
|
70
|
-
lines.push("- optimus doctor
|
|
80
|
+
lines.push("- optimus doctor");
|
|
71
81
|
lines.push("");
|
|
72
82
|
lines.push(renderQuickDoctorReport(result.doctorQuick));
|
|
73
83
|
return lines.join("\n");
|
|
@@ -112,28 +122,96 @@ function renderQuickDoctorReport(report) {
|
|
|
112
122
|
}
|
|
113
123
|
function renderDoctorTextReport(doctor) {
|
|
114
124
|
const lines = [];
|
|
115
|
-
lines.push("Doctor");
|
|
116
|
-
lines.push(
|
|
117
|
-
lines.push(`
|
|
118
|
-
lines.push(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
lines.push(
|
|
126
|
-
|
|
127
|
-
lines.push(`
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
125
|
+
lines.push("Optimus Doctor");
|
|
126
|
+
lines.push("");
|
|
127
|
+
lines.push(`Overall: ${doctor.overall}`);
|
|
128
|
+
lines.push("");
|
|
129
|
+
lines.push(...renderDoctorSection("Repository", doctor.repository, doctor.repository.using ? [`Using: ${doctor.repository.using}`] : []));
|
|
130
|
+
lines.push("");
|
|
131
|
+
lines.push(...renderDoctorSection("Codex", doctor.codex, renderDoctorSubstatusLines(doctor.codex)));
|
|
132
|
+
lines.push("");
|
|
133
|
+
lines.push(...renderDoctorSection("Delivery", doctor.delivery));
|
|
134
|
+
lines.push("");
|
|
135
|
+
lines.push("Integrations:");
|
|
136
|
+
for (const integration of doctor.integrations) {
|
|
137
|
+
lines.push(` ${formatDoctorStatusHeading(integration.name, integration.status)}`);
|
|
138
|
+
if (integration.reason) {
|
|
139
|
+
lines.push(` Reason: ${integration.reason}`);
|
|
140
|
+
}
|
|
141
|
+
if (integration.fix) {
|
|
142
|
+
lines.push(` Fix: ${integration.fix}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (doctor.nextActions.length > 0) {
|
|
146
|
+
lines.push("");
|
|
147
|
+
lines.push("Next actions:");
|
|
148
|
+
doctor.nextActions.forEach((action, index) => {
|
|
149
|
+
lines.push(`${index + 1}. ${action}`);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
135
152
|
return lines.join("\n");
|
|
136
153
|
}
|
|
154
|
+
function renderDoctorSection(title, section, extraLines = []) {
|
|
155
|
+
const lines = [formatDoctorStatusHeading(title, section.status)];
|
|
156
|
+
if (section.reason) {
|
|
157
|
+
lines.push(`Reason: ${section.reason}`);
|
|
158
|
+
}
|
|
159
|
+
if (section.fix) {
|
|
160
|
+
lines.push(`Fix: ${section.fix}`);
|
|
161
|
+
}
|
|
162
|
+
lines.push(...extraLines);
|
|
163
|
+
return lines;
|
|
164
|
+
}
|
|
165
|
+
function renderDoctorSubstatusLines(section) {
|
|
166
|
+
const lines = [];
|
|
167
|
+
lines.push(...renderDoctorSubstatus("Auth", section.auth));
|
|
168
|
+
lines.push(...renderDoctorSubstatus("Connectivity", section.connectivity));
|
|
169
|
+
return lines;
|
|
170
|
+
}
|
|
171
|
+
function renderDoctorSubstatus(label, section) {
|
|
172
|
+
const lines = [`${label}: ${formatDoctorStatusMarker(section.status)}`];
|
|
173
|
+
if (section.reason) {
|
|
174
|
+
lines.push(` Reason: ${section.reason}`);
|
|
175
|
+
}
|
|
176
|
+
if (section.fix) {
|
|
177
|
+
lines.push(` Fix: ${section.fix}`);
|
|
178
|
+
}
|
|
179
|
+
return lines;
|
|
180
|
+
}
|
|
181
|
+
function formatDoctorStatusHeading(title, status) {
|
|
182
|
+
return `${formatDoctorStatusMarker(status)} ${title}`;
|
|
183
|
+
}
|
|
184
|
+
function formatDoctorStatusMarker(status) {
|
|
185
|
+
const marker = resolveDoctorStatusMarker(status);
|
|
186
|
+
if (!output.isTTY) {
|
|
187
|
+
return marker;
|
|
188
|
+
}
|
|
189
|
+
return `${resolveDoctorStatusColor(status)}${marker}${DOCTOR_ANSI.reset}`;
|
|
190
|
+
}
|
|
191
|
+
function resolveDoctorStatusMarker(status) {
|
|
192
|
+
switch (status) {
|
|
193
|
+
case "ok":
|
|
194
|
+
return "[✓]";
|
|
195
|
+
case "warning":
|
|
196
|
+
return "[!]";
|
|
197
|
+
case "not_ok":
|
|
198
|
+
return "[x]";
|
|
199
|
+
case "disabled":
|
|
200
|
+
return "[-]";
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function resolveDoctorStatusColor(status) {
|
|
204
|
+
switch (status) {
|
|
205
|
+
case "ok":
|
|
206
|
+
return DOCTOR_ANSI.green;
|
|
207
|
+
case "warning":
|
|
208
|
+
return DOCTOR_ANSI.yellow;
|
|
209
|
+
case "not_ok":
|
|
210
|
+
return DOCTOR_ANSI.red;
|
|
211
|
+
case "disabled":
|
|
212
|
+
return DOCTOR_ANSI.gray;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
137
215
|
const SETUP_CODEX_AUTH_MODE_OPTIONS = [
|
|
138
216
|
{ index: "1", mode: "model_provider", label: "Model provider" },
|
|
139
217
|
{ index: "2", mode: "openai_api_key", label: "OpenAI API key" },
|
|
@@ -148,25 +226,19 @@ function renderCliHelp() {
|
|
|
148
226
|
"",
|
|
149
227
|
"First time here:",
|
|
150
228
|
" 1. optimus setup",
|
|
151
|
-
" 2. optimus
|
|
152
|
-
" 3. optimus
|
|
229
|
+
" 2. optimus doctor",
|
|
230
|
+
" 3. optimus start",
|
|
153
231
|
"",
|
|
154
|
-
"
|
|
155
|
-
" setup First-time setup or update config",
|
|
156
|
-
" start Start the resident runtime",
|
|
232
|
+
"Common commands:",
|
|
157
233
|
" submit Submit a problem for Optimus to handle",
|
|
158
|
-
"
|
|
159
|
-
"
|
|
160
|
-
"
|
|
234
|
+
" jira-submit-issue Submit a Jira issue into Optimus",
|
|
235
|
+
" sentry-submit-event Submit a Sentry event into Optimus",
|
|
236
|
+
" repo list Show registered repositories",
|
|
161
237
|
" version Show the installed package version",
|
|
162
238
|
"",
|
|
163
239
|
"Need more?",
|
|
164
240
|
" optimus help advanced Show integration, retry, and developer commands",
|
|
165
|
-
" optimus help <command> Show details for one command"
|
|
166
|
-
"",
|
|
167
|
-
"Notes:",
|
|
168
|
-
` - Optimus stores config and runtime data under ${resolveOptimusHomeDir()}.`,
|
|
169
|
-
" - Jira and Sentry are optional integrations. You can enable them later in `optimus setup`."
|
|
241
|
+
" optimus help <command> Show details for one command"
|
|
170
242
|
].join("\n");
|
|
171
243
|
}
|
|
172
244
|
function renderAdvancedCliHelp() {
|
|
@@ -186,9 +258,8 @@ function renderAdvancedCliHelp() {
|
|
|
186
258
|
" task-status Show task execution status",
|
|
187
259
|
" task-result Show persisted result, delivery, and publication data",
|
|
188
260
|
" task-events Show task timeline",
|
|
189
|
-
" repo add|remove|update|list
|
|
190
|
-
" doctor
|
|
191
|
-
" health-check Validate Codex/provider readiness",
|
|
261
|
+
" repo add|remove|update|list",
|
|
262
|
+
" doctor Check repository, Codex, delivery, and integrations in one place",
|
|
192
263
|
" upgrade Check for or install a latest/snapshot Optimus update",
|
|
193
264
|
" version Show the installed package version",
|
|
194
265
|
"",
|
|
@@ -328,7 +399,6 @@ function renderCommandHelp(command) {
|
|
|
328
399
|
" optimus repo remove <path|alias>",
|
|
329
400
|
" optimus repo update <path|alias> [options]",
|
|
330
401
|
" optimus repo list",
|
|
331
|
-
" optimus repo doctor",
|
|
332
402
|
"",
|
|
333
403
|
"Common options:",
|
|
334
404
|
" --alias <name> Repository alias",
|
|
@@ -346,9 +416,7 @@ function renderCommandHelp(command) {
|
|
|
346
416
|
" optimus doctor [options]",
|
|
347
417
|
"",
|
|
348
418
|
"Optional:",
|
|
349
|
-
" --
|
|
350
|
-
" --json Print JSON when used with --quick",
|
|
351
|
-
" --format text Print text report"
|
|
419
|
+
" --json Print JSON output for automation"
|
|
352
420
|
].join("\n"),
|
|
353
421
|
advanced: renderAdvancedCliHelp(),
|
|
354
422
|
"notify-test": [
|
|
@@ -940,7 +1008,7 @@ async function buildFeishuDoctorStatus(config) {
|
|
|
940
1008
|
: "Real @mentions require Feishu app availability.",
|
|
941
1009
|
...(mentionReady
|
|
942
1010
|
? {}
|
|
943
|
-
: { recommendation: "Rerun `optimus doctor
|
|
1011
|
+
: { recommendation: "Rerun `optimus doctor` after restoring Feishu app availability." })
|
|
944
1012
|
}
|
|
945
1013
|
};
|
|
946
1014
|
}
|
|
@@ -1065,16 +1133,16 @@ async function promptSetupAnswers(defaults) {
|
|
|
1065
1133
|
try {
|
|
1066
1134
|
console.log("Optimus setup");
|
|
1067
1135
|
console.log("This usually takes about 2 minutes.");
|
|
1068
|
-
console.log("Required:
|
|
1136
|
+
console.log("Required: Codex auth. Optional: repository, Feishu webhook, Jira, Sentry.");
|
|
1069
1137
|
console.log("You can rerun `optimus setup` later to change anything.");
|
|
1070
1138
|
if (await pathExists(configPath)) {
|
|
1071
1139
|
console.log(`Existing config found at ${configPath}. Press Enter to keep current values.`);
|
|
1072
1140
|
}
|
|
1073
|
-
printSetupSection("Repository", "Point Optimus at the main local repository it should work in.");
|
|
1074
|
-
printSetupHint("Repository path: the local code directory Optimus will read and edit.");
|
|
1075
|
-
const repoPath = (await ask(renderSetupPrompt("
|
|
1076
|
-
printSetupHint("Repository alias: the short name used later in commands like --repo ohos-pre.");
|
|
1077
|
-
const repoAlias = (await ask(renderSetupPrompt("
|
|
1141
|
+
printSetupSection("Repository", "Optional. Point Optimus at the main local repository it should work in, or leave both fields empty and add one later.");
|
|
1142
|
+
printSetupHint("Repository path: the local code directory Optimus will read and edit. Leave empty if you only want to finish setup first.");
|
|
1143
|
+
const repoPath = (await ask(renderSetupPrompt("Optional", "Repository path", defaults.repoPath))).trim() || defaults.repoPath;
|
|
1144
|
+
printSetupHint("Repository alias: the short name used later in commands like --repo ohos-pre. Leave empty if no repository should be registered now.");
|
|
1145
|
+
const repoAlias = (await ask(renderSetupPrompt("Optional", "Repository alias", defaults.repoAlias))).trim() || defaults.repoAlias;
|
|
1078
1146
|
printSetupSection("Codex", "Choose one model authentication mode. This is required before tasks can run.");
|
|
1079
1147
|
printSetupHint("Codex auth mode: choose how Optimus reaches the model you want to use.");
|
|
1080
1148
|
const codexAuthMode = await askSetupCodexAuthMode({ ask, defaultMode: defaults.codexAuthMode });
|
|
@@ -1103,8 +1171,6 @@ async function promptSetupAnswers(defaults) {
|
|
|
1103
1171
|
printSetupSection("Feishu", "Built-in Feishu app delivery is already available. Add a webhook only if group notifications should go to a chat first. Type \"\" to clear an existing optional value.");
|
|
1104
1172
|
printSetupHint("Feishu webhook: optional; leave empty to use direct Feishu app delivery only.");
|
|
1105
1173
|
const feishuWebhook = resolveOptionalTextAnswer(await ask(renderSetupPrompt("Optional", "Feishu webhook", defaults.feishuWebhook ? "configured" : "")), defaults.feishuWebhook ?? undefined);
|
|
1106
|
-
printSetupHint("Feishu secret: optional; only needed when the webhook itself requires signature verification.");
|
|
1107
|
-
const feishuSecret = resolveOptionalTextAnswer(await ask(renderSetupPrompt("Optional", "Feishu secret", defaults.feishuSecret ? "configured" : "")), defaults.feishuSecret ?? undefined);
|
|
1108
1174
|
printSetupSection("Jira", "Optional. Turn this on only if you want this machine to read Jira issues or write Jira comments.");
|
|
1109
1175
|
printSetupHint("If disabled, Optimus still works; Jira commands just stay unavailable on this machine.");
|
|
1110
1176
|
const enableJiraInput = (await ask(renderSetupPrompt("Optional integration", "Enable Jira integration", defaults.enableJira ? "Y/n" : "y/N"))).trim().toLowerCase();
|
|
@@ -1145,7 +1211,6 @@ async function promptSetupAnswers(defaults) {
|
|
|
1145
1211
|
...(codexProviderBaseUrl ? { codexProviderBaseUrl } : {}),
|
|
1146
1212
|
...(codexProviderApiKeyEnvName ? { codexProviderApiKeyEnvName } : {}),
|
|
1147
1213
|
...(feishuWebhook !== undefined ? { feishuWebhook } : {}),
|
|
1148
|
-
...(feishuSecret !== undefined ? { feishuSecret } : {}),
|
|
1149
1214
|
enableJira,
|
|
1150
1215
|
...(jiraBaseUrl ? { jiraBaseUrl } : {}),
|
|
1151
1216
|
...(jiraPersonalToken ? { jiraPersonalToken } : {}),
|
|
@@ -1196,7 +1261,6 @@ async function resolveDefaultSetupAnswers() {
|
|
|
1196
1261
|
...(provider?.baseUrl ? { codexProviderBaseUrl: provider.baseUrl } : {}),
|
|
1197
1262
|
...(provider?.apiKeyEnvName ? { codexProviderApiKeyEnvName: provider.apiKeyEnvName } : {}),
|
|
1198
1263
|
...(config.delivery.feishu.webhook ? { feishuWebhook: config.delivery.feishu.webhook } : {}),
|
|
1199
|
-
...(config.delivery.feishu.secret ? { feishuSecret: config.delivery.feishu.secret } : {}),
|
|
1200
1264
|
enableJira: config.jira.enabled,
|
|
1201
1265
|
...(config.jira.baseUrl ? { jiraBaseUrl: config.jira.baseUrl } : {}),
|
|
1202
1266
|
...(config.jira.personalToken ? { jiraPersonalToken: config.jira.personalToken } : {}),
|
|
@@ -1248,11 +1312,10 @@ async function askSetupCodexAuthMode(input) {
|
|
|
1248
1312
|
}
|
|
1249
1313
|
}
|
|
1250
1314
|
function validateSetupAnswers(answers) {
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
return "setup requires a repository alias.";
|
|
1315
|
+
const repoPath = answers.repoPath.trim();
|
|
1316
|
+
const repoAlias = answers.repoAlias.trim();
|
|
1317
|
+
if ((repoPath && !repoAlias) || (!repoPath && repoAlias)) {
|
|
1318
|
+
return "setup repository registration requires both repository path and repository alias, or neither.";
|
|
1256
1319
|
}
|
|
1257
1320
|
if (answers.codexAuthMode === "model_provider" && !answers.codexProviderId?.trim()) {
|
|
1258
1321
|
return "setup requires provider id when Codex auth mode is model_provider.";
|
|
@@ -1384,16 +1447,15 @@ function buildSetupConfig(answers, rawConfig) {
|
|
|
1384
1447
|
config.delivery.channels = ["console", "feishu"];
|
|
1385
1448
|
config.delivery.feishu = {
|
|
1386
1449
|
...config.delivery.feishu,
|
|
1387
|
-
...(typeof
|
|
1388
|
-
|
|
1450
|
+
...(typeof rawConfig?.delivery?.feishu?.secret === "string" && rawConfig.delivery.feishu.secret.trim()
|
|
1451
|
+
? { secret: rawConfig.delivery.feishu.secret.trim() }
|
|
1452
|
+
: {}),
|
|
1453
|
+
...(typeof answers.feishuWebhook === "string" ? { webhook: answers.feishuWebhook } : {})
|
|
1389
1454
|
};
|
|
1390
1455
|
if (answers.feishuWebhook === null) {
|
|
1391
1456
|
delete config.delivery.feishu.webhook;
|
|
1392
1457
|
delete config.delivery.feishu.webhooks;
|
|
1393
1458
|
}
|
|
1394
|
-
if (answers.feishuSecret === null) {
|
|
1395
|
-
delete config.delivery.feishu.secret;
|
|
1396
|
-
}
|
|
1397
1459
|
config.jira.enabled = answers.enableJira;
|
|
1398
1460
|
if (answers.enableJira) {
|
|
1399
1461
|
config.jira.baseUrl = answers.jiraBaseUrl ?? config.jira.baseUrl;
|
|
@@ -1551,12 +1613,12 @@ async function runQuickDoctor(configPath = resolveDefaultConfigPath()) {
|
|
|
1551
1613
|
await store.init();
|
|
1552
1614
|
const registeredRepositories = await store.listRepositoryRoots();
|
|
1553
1615
|
if (registeredRepositories.length === 0) {
|
|
1554
|
-
|
|
1616
|
+
warnings.push({
|
|
1555
1617
|
code: "repository_missing",
|
|
1556
1618
|
message: "No repository is registered.",
|
|
1557
|
-
fix: "
|
|
1619
|
+
fix: "Register one with `optimus repo add <path> --alias <name>` or rerun `optimus setup`."
|
|
1558
1620
|
});
|
|
1559
|
-
next.add("optimus
|
|
1621
|
+
next.add("optimus repo add <path> --alias <name>");
|
|
1560
1622
|
}
|
|
1561
1623
|
for (const repository of registeredRepositories) {
|
|
1562
1624
|
const inspection = await inspectRepositoryRoot(repository.path);
|
|
@@ -1587,7 +1649,7 @@ async function runQuickDoctor(configPath = resolveDefaultConfigPath()) {
|
|
|
1587
1649
|
blocking.push({
|
|
1588
1650
|
code: "git_user_name_missing",
|
|
1589
1651
|
message: "Git user.name is missing for the configured repository.",
|
|
1590
|
-
fix: "Run `git config user.name \"Your Name\"` in the repository, then rerun `optimus doctor
|
|
1652
|
+
fix: "Run `git config user.name \"Your Name\"` in the repository, then rerun `optimus doctor`."
|
|
1591
1653
|
});
|
|
1592
1654
|
next.add("git config user.name \"Your Name\"");
|
|
1593
1655
|
}
|
|
@@ -1595,7 +1657,7 @@ async function runQuickDoctor(configPath = resolveDefaultConfigPath()) {
|
|
|
1595
1657
|
blocking.push({
|
|
1596
1658
|
code: "git_user_email_missing",
|
|
1597
1659
|
message: "Git user.email is missing for the configured repository.",
|
|
1598
|
-
fix: "Run `git config user.email \"you@example.com\"` in the repository, then rerun `optimus doctor
|
|
1660
|
+
fix: "Run `git config user.email \"you@example.com\"` in the repository, then rerun `optimus doctor`."
|
|
1599
1661
|
});
|
|
1600
1662
|
next.add("git config user.email \"you@example.com\"");
|
|
1601
1663
|
}
|
|
@@ -1640,6 +1702,196 @@ async function runQuickDoctor(configPath = resolveDefaultConfigPath()) {
|
|
|
1640
1702
|
next: [...next]
|
|
1641
1703
|
};
|
|
1642
1704
|
}
|
|
1705
|
+
function summarizeIssue(issue, fallback) {
|
|
1706
|
+
if (!issue?.message) {
|
|
1707
|
+
return fallback;
|
|
1708
|
+
}
|
|
1709
|
+
return issue.message.replace(/\.$/u, "");
|
|
1710
|
+
}
|
|
1711
|
+
function doctorNextActions(quick, healthCheck, delivery) {
|
|
1712
|
+
const actions = new Set();
|
|
1713
|
+
for (const step of quick.next) {
|
|
1714
|
+
actions.add(step);
|
|
1715
|
+
}
|
|
1716
|
+
if (!healthCheck.ok) {
|
|
1717
|
+
const fixHint = healthCheck.diagnostics.auth?.fixHint?.trim();
|
|
1718
|
+
if (fixHint) {
|
|
1719
|
+
actions.add(fixHint);
|
|
1720
|
+
}
|
|
1721
|
+
else {
|
|
1722
|
+
actions.add("Rerun `optimus setup`.");
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
if (delivery.status === "not_ok" && delivery.fix) {
|
|
1726
|
+
actions.add(delivery.fix);
|
|
1727
|
+
}
|
|
1728
|
+
if (actions.size === 0) {
|
|
1729
|
+
actions.add("Run `optimus start`.");
|
|
1730
|
+
actions.add("Submit a task with `optimus submit --title \"...\" --description \"...\"`.");
|
|
1731
|
+
}
|
|
1732
|
+
return [...actions].slice(0, 3);
|
|
1733
|
+
}
|
|
1734
|
+
async function runDoctor(configPath = resolveDefaultConfigPath()) {
|
|
1735
|
+
const quick = await runQuickDoctor(configPath);
|
|
1736
|
+
const configIssue = quick.blocking.find((issue) => issue.code === "config_missing" || issue.code === "config_parse_failed" || issue.code === "config_load_failed");
|
|
1737
|
+
if (configIssue) {
|
|
1738
|
+
const repoIssue = quick.blocking.find((issue) => issue.code.startsWith("repository_") || issue.code.startsWith("git_user_"));
|
|
1739
|
+
const codexIssue = quick.blocking.find((issue) => issue.code.startsWith("codex_"));
|
|
1740
|
+
return {
|
|
1741
|
+
overall: "not_ready",
|
|
1742
|
+
repository: {
|
|
1743
|
+
status: repoIssue ? "not_ok" : "warning",
|
|
1744
|
+
...(repoIssue ? { reason: summarizeIssue(repoIssue, "Repository setup is incomplete.") } : { reason: "Repository checks could not complete yet." }),
|
|
1745
|
+
...(repoIssue?.fix ? { fix: repoIssue.fix } : {})
|
|
1746
|
+
},
|
|
1747
|
+
codex: {
|
|
1748
|
+
status: codexIssue ? "not_ok" : "warning",
|
|
1749
|
+
...(codexIssue ? { reason: summarizeIssue(codexIssue, "Codex setup is incomplete.") } : { reason: "Codex checks could not complete yet." }),
|
|
1750
|
+
...(codexIssue?.fix ? { fix: codexIssue.fix } : {}),
|
|
1751
|
+
auth: {
|
|
1752
|
+
status: codexIssue ? "not_ok" : "warning",
|
|
1753
|
+
...(codexIssue ? { reason: summarizeIssue(codexIssue, "Codex authentication is not ready.") } : { reason: "Authentication checks could not complete yet." }),
|
|
1754
|
+
...(codexIssue?.fix ? { fix: codexIssue.fix } : {})
|
|
1755
|
+
},
|
|
1756
|
+
connectivity: {
|
|
1757
|
+
status: "disabled",
|
|
1758
|
+
reason: "Connectivity check is deferred until authentication is ready."
|
|
1759
|
+
}
|
|
1760
|
+
},
|
|
1761
|
+
delivery: {
|
|
1762
|
+
status: "warning",
|
|
1763
|
+
reason: "Delivery checks are deferred until setup is complete."
|
|
1764
|
+
},
|
|
1765
|
+
integrations: [
|
|
1766
|
+
{
|
|
1767
|
+
name: "Jira",
|
|
1768
|
+
status: "disabled"
|
|
1769
|
+
},
|
|
1770
|
+
{
|
|
1771
|
+
name: "Sentry",
|
|
1772
|
+
status: "disabled"
|
|
1773
|
+
}
|
|
1774
|
+
],
|
|
1775
|
+
nextActions: quick.next.slice(0, 3)
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
const config = await loadConfig(configPath);
|
|
1779
|
+
const codexRunner = new CodexRunner(config);
|
|
1780
|
+
const feishuStatus = await buildFeishuDoctorStatus(config);
|
|
1781
|
+
const healthCheck = await codexRunner.runHealthCheck();
|
|
1782
|
+
const repositoryIssue = quick.blocking.find((issue) => issue.code.startsWith("repository_") || issue.code.startsWith("git_user_"))
|
|
1783
|
+
?? quick.warnings.find((issue) => issue.code.startsWith("repository_") || issue.code.startsWith("git_user_"));
|
|
1784
|
+
const repositoryStatus = repositoryIssue
|
|
1785
|
+
? quick.blocking.includes(repositoryIssue)
|
|
1786
|
+
? "not_ok"
|
|
1787
|
+
: "warning"
|
|
1788
|
+
: "ok";
|
|
1789
|
+
const repository = repositoryIssue
|
|
1790
|
+
? {
|
|
1791
|
+
status: repositoryStatus,
|
|
1792
|
+
reason: summarizeIssue(repositoryIssue, "Repository is not ready."),
|
|
1793
|
+
...(repositoryIssue.fix ? { fix: repositoryIssue.fix } : {}),
|
|
1794
|
+
...(quick.repositories[0]?.alias ?? quick.repositories[0]?.path ? { using: quick.repositories[0]?.alias ?? quick.repositories[0]?.path } : {})
|
|
1795
|
+
}
|
|
1796
|
+
: {
|
|
1797
|
+
status: "ok",
|
|
1798
|
+
...(quick.repositories[0]?.alias ?? quick.repositories[0]?.path ? { using: quick.repositories[0]?.alias ?? quick.repositories[0]?.path } : {})
|
|
1799
|
+
};
|
|
1800
|
+
const authDiagnostics = healthCheck.diagnostics.auth;
|
|
1801
|
+
const codexIssue = quick.blocking.find((issue) => issue.code.startsWith("codex_"));
|
|
1802
|
+
const codexAuth = authDiagnostics?.authenticated
|
|
1803
|
+
? { status: "ok" }
|
|
1804
|
+
: {
|
|
1805
|
+
status: "not_ok",
|
|
1806
|
+
reason: summarizeIssue(codexIssue, healthCheck.diagnostics.failureCategory === "auth_missing"
|
|
1807
|
+
? "authentication is not usable."
|
|
1808
|
+
: "Codex authentication is not ready."),
|
|
1809
|
+
fix: authDiagnostics?.fixHint?.trim() || "Rerun `optimus setup`."
|
|
1810
|
+
};
|
|
1811
|
+
const codexConnectivity = !authDiagnostics?.authenticated
|
|
1812
|
+
? {
|
|
1813
|
+
status: "disabled",
|
|
1814
|
+
reason: "Connectivity check is deferred until authentication is ready."
|
|
1815
|
+
}
|
|
1816
|
+
: healthCheck.ok
|
|
1817
|
+
? { status: "ok" }
|
|
1818
|
+
: {
|
|
1819
|
+
status: "not_ok",
|
|
1820
|
+
reason: healthCheck.diagnostics.failureCategory === "provider_error"
|
|
1821
|
+
? "model provider is unreachable."
|
|
1822
|
+
: healthCheck.diagnostics.failureCategory === "network_error"
|
|
1823
|
+
? "network connectivity to Codex is unavailable."
|
|
1824
|
+
: "Codex request could not complete.",
|
|
1825
|
+
fix: authDiagnostics.fixHint?.trim() || "Retry `optimus doctor` after fixing the Codex connection."
|
|
1826
|
+
};
|
|
1827
|
+
const codex = healthCheck.ok
|
|
1828
|
+
? {
|
|
1829
|
+
status: "ok",
|
|
1830
|
+
auth: codexAuth,
|
|
1831
|
+
connectivity: codexConnectivity
|
|
1832
|
+
}
|
|
1833
|
+
: {
|
|
1834
|
+
status: "not_ok",
|
|
1835
|
+
reason: summarizeIssue(codexIssue, !authDiagnostics?.authenticated
|
|
1836
|
+
? "authentication is not usable."
|
|
1837
|
+
: healthCheck.diagnostics.failureCategory === "provider_error"
|
|
1838
|
+
? "model provider is unreachable."
|
|
1839
|
+
: "Codex is not ready."),
|
|
1840
|
+
fix: authDiagnostics?.fixHint?.trim() || "Rerun `optimus setup`.",
|
|
1841
|
+
auth: codexAuth,
|
|
1842
|
+
connectivity: codexConnectivity
|
|
1843
|
+
};
|
|
1844
|
+
const feishuIssue = quick.blocking.find((issue) => issue.code.startsWith("feishu_"))
|
|
1845
|
+
?? quick.warnings.find((issue) => issue.code.startsWith("feishu_"));
|
|
1846
|
+
const delivery = !config.delivery.enabled
|
|
1847
|
+
? {
|
|
1848
|
+
status: "warning",
|
|
1849
|
+
reason: "task delivery is disabled.",
|
|
1850
|
+
fix: "Enable delivery in config if this machine should send notifications."
|
|
1851
|
+
}
|
|
1852
|
+
: feishuIssue
|
|
1853
|
+
? {
|
|
1854
|
+
status: "not_ok",
|
|
1855
|
+
reason: summarizeIssue(feishuIssue, "Feishu delivery is not usable."),
|
|
1856
|
+
...(feishuIssue.fix ? { fix: feishuIssue.fix } : {})
|
|
1857
|
+
}
|
|
1858
|
+
: feishuStatus.auth.configured
|
|
1859
|
+
? {
|
|
1860
|
+
status: "ok"
|
|
1861
|
+
}
|
|
1862
|
+
: {
|
|
1863
|
+
status: "warning",
|
|
1864
|
+
reason: "Feishu app delivery is unavailable.",
|
|
1865
|
+
fix: "Rerun `optimus setup` and verify Feishu configuration."
|
|
1866
|
+
};
|
|
1867
|
+
const jiraIssues = config.jira.enabled ? resolveJiraQuickDoctorIssues(config.jira) : [];
|
|
1868
|
+
const sentryIssues = config.sentry.enabled ? resolveSentryQuickDoctorIssues(config.sentry) : [];
|
|
1869
|
+
const integrations = [
|
|
1870
|
+
config.jira.enabled
|
|
1871
|
+
? jiraIssues.length > 0
|
|
1872
|
+
? { name: "Jira", status: "not_ok", reason: summarizeIssue(jiraIssues[0], "Jira is not ready."), ...(jiraIssues[0]?.fix ? { fix: jiraIssues[0].fix } : {}) }
|
|
1873
|
+
: { name: "Jira", status: "ok" }
|
|
1874
|
+
: { name: "Jira", status: "disabled" },
|
|
1875
|
+
config.sentry.enabled
|
|
1876
|
+
? sentryIssues.length > 0
|
|
1877
|
+
? { name: "Sentry", status: "not_ok", reason: summarizeIssue(sentryIssues[0], "Sentry is not ready."), ...(sentryIssues[0]?.fix ? { fix: sentryIssues[0].fix } : {}) }
|
|
1878
|
+
: { name: "Sentry", status: "ok" }
|
|
1879
|
+
: { name: "Sentry", status: "disabled" }
|
|
1880
|
+
];
|
|
1881
|
+
const overall = repository.status === "not_ok" || codex.status === "not_ok"
|
|
1882
|
+
? "not_ready"
|
|
1883
|
+
: delivery.status === "warning" || integrations.some((integration) => integration.status === "not_ok")
|
|
1884
|
+
? "warning"
|
|
1885
|
+
: "ready";
|
|
1886
|
+
return {
|
|
1887
|
+
overall,
|
|
1888
|
+
repository,
|
|
1889
|
+
codex,
|
|
1890
|
+
delivery,
|
|
1891
|
+
integrations,
|
|
1892
|
+
nextActions: doctorNextActions(quick, healthCheck, delivery)
|
|
1893
|
+
};
|
|
1894
|
+
}
|
|
1643
1895
|
async function runSetup(args) {
|
|
1644
1896
|
const configPath = resolveDefaultConfigPath();
|
|
1645
1897
|
const envPath = resolveDefaultEnvPath();
|
|
@@ -1653,27 +1905,34 @@ async function runSetup(args) {
|
|
|
1653
1905
|
if (validationError) {
|
|
1654
1906
|
throw new Error(validationError);
|
|
1655
1907
|
}
|
|
1656
|
-
const normalizedRepoPath = resolve(answers.repoPath);
|
|
1657
|
-
const inspection = await inspectRepositoryRoot(normalizedRepoPath);
|
|
1658
|
-
if (!inspection.ok) {
|
|
1659
|
-
throw new Error(`Repository path is not usable: ${normalizedRepoPath} (${inspection.reason ?? "unknown"})`);
|
|
1660
|
-
}
|
|
1661
1908
|
const preflightConfig = configExists ? await loadConfig(configPath) : buildDefaultConfig();
|
|
1662
1909
|
const preflightStore = new SQLiteTaskStore(preflightConfig.storage.rootDir);
|
|
1663
1910
|
await preflightStore.init();
|
|
1664
1911
|
const existingRepositories = await preflightStore.listRepositoryRoots();
|
|
1665
|
-
const
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1912
|
+
const hasRepositorySelection = answers.repoPath.trim().length > 0 && answers.repoAlias.trim().length > 0;
|
|
1913
|
+
let normalizedRepoPath;
|
|
1914
|
+
let resolvedExecutionMode;
|
|
1915
|
+
let previewExecutionPlan;
|
|
1916
|
+
let reviewSystem;
|
|
1917
|
+
if (hasRepositorySelection) {
|
|
1918
|
+
normalizedRepoPath = resolve(answers.repoPath);
|
|
1919
|
+
const inspection = await inspectRepositoryRoot(normalizedRepoPath);
|
|
1920
|
+
if (!inspection.ok) {
|
|
1921
|
+
throw new Error(`Repository path is not usable: ${normalizedRepoPath} (${inspection.reason ?? "unknown"})`);
|
|
1922
|
+
}
|
|
1923
|
+
const aliasConflict = existingRepositories.find((repository) => repository.alias === answers.repoAlias && repository.path !== normalizedRepoPath);
|
|
1924
|
+
if (aliasConflict) {
|
|
1925
|
+
throw new Error(`Repository alias already exists for another path: ${answers.repoAlias} -> ${aliasConflict.path}. `
|
|
1926
|
+
+ "Use `optimus repo update` or `optimus repo remove` before re-running setup.");
|
|
1927
|
+
}
|
|
1928
|
+
const existingRepository = existingRepositories.find((repository) => repository.path === normalizedRepoPath);
|
|
1929
|
+
resolvedExecutionMode = existingRepository?.executionMode ?? describeRepositoryExecutionPlan({ executionMode: "copy" }, inspection).resolvedDefaultMode;
|
|
1930
|
+
previewExecutionPlan = describeRepositoryExecutionPlan({ executionMode: resolvedExecutionMode }, inspection);
|
|
1931
|
+
reviewSystem = await detectRepositoryReviewSystem(normalizedRepoPath, inspection);
|
|
1932
|
+
}
|
|
1674
1933
|
const rawConfig = configExists ? await readRawProjectConfig(configPath) : undefined;
|
|
1675
1934
|
await mkdir(dirname(configPath), { recursive: true });
|
|
1676
|
-
await writeFile(configPath, `${buildSetupConfig({ ...answers, repoPath: normalizedRepoPath }, rawConfig)}\n`, "utf8");
|
|
1935
|
+
await writeFile(configPath, `${buildSetupConfig({ ...answers, ...(normalizedRepoPath ? { repoPath: normalizedRepoPath } : {}) }, rawConfig)}\n`, "utf8");
|
|
1677
1936
|
const codexAuthEnvName = resolveSetupCodexApiKeyEnvName(answers);
|
|
1678
1937
|
if (answers.codexApiKey && codexAuthEnvName) {
|
|
1679
1938
|
await mkdir(dirname(envPath), { recursive: true });
|
|
@@ -1682,11 +1941,13 @@ async function runSetup(args) {
|
|
|
1682
1941
|
const config = await loadConfig(configPath);
|
|
1683
1942
|
const store = new SQLiteTaskStore(config.storage.rootDir);
|
|
1684
1943
|
await store.init();
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1944
|
+
if (normalizedRepoPath && resolvedExecutionMode) {
|
|
1945
|
+
await store.addRepositoryRoot({
|
|
1946
|
+
path: normalizedRepoPath,
|
|
1947
|
+
alias: answers.repoAlias,
|
|
1948
|
+
executionMode: resolvedExecutionMode
|
|
1949
|
+
});
|
|
1950
|
+
}
|
|
1690
1951
|
const doctorQuick = await runQuickDoctor(configPath);
|
|
1691
1952
|
const codexAuth = new CodexAuthResolver(config).resolve().diagnostics;
|
|
1692
1953
|
return {
|
|
@@ -1694,13 +1955,15 @@ async function runSetup(args) {
|
|
|
1694
1955
|
configPath,
|
|
1695
1956
|
envPath,
|
|
1696
1957
|
configStatus: configExists ? "overwritten" : "created",
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1958
|
+
...(normalizedRepoPath && previewExecutionPlan && resolvedExecutionMode && reviewSystem ? {
|
|
1959
|
+
repository: {
|
|
1960
|
+
path: normalizedRepoPath,
|
|
1961
|
+
alias: answers.repoAlias,
|
|
1962
|
+
workspaceKind: previewExecutionPlan.workspaceKind,
|
|
1963
|
+
executionMode: resolvedExecutionMode,
|
|
1964
|
+
reviewSystem
|
|
1965
|
+
}
|
|
1966
|
+
} : {}),
|
|
1704
1967
|
deliveryChannels: config.delivery.channels,
|
|
1705
1968
|
jiraEnabled: config.jira.enabled,
|
|
1706
1969
|
sentryEnabled: config.sentry.enabled,
|
|
@@ -1934,7 +2197,7 @@ async function main() {
|
|
|
1934
2197
|
if (command === "repo") {
|
|
1935
2198
|
const repoSubcommand = commandArgs.find((token) => !token.startsWith("--"))?.trim();
|
|
1936
2199
|
console.log(renderCommandHelp("repo") ?? renderCliHelp());
|
|
1937
|
-
process.exitCode = repoSubcommand && !["add", "remove", "update", "list"
|
|
2200
|
+
process.exitCode = repoSubcommand && !["add", "remove", "update", "list"].includes(repoSubcommand) ? 1 : 0;
|
|
1938
2201
|
return;
|
|
1939
2202
|
}
|
|
1940
2203
|
console.log(renderCommandHelp(command) ?? renderCliHelp());
|
|
@@ -2018,15 +2281,15 @@ async function main() {
|
|
|
2018
2281
|
return;
|
|
2019
2282
|
}
|
|
2020
2283
|
const parsedCommandArgs = parseArgs(commandArgs);
|
|
2021
|
-
if (command === "doctor"
|
|
2022
|
-
const result = await
|
|
2284
|
+
if (command === "doctor") {
|
|
2285
|
+
const result = await runDoctor();
|
|
2023
2286
|
if (parsedCommandArgs.json === "true") {
|
|
2024
2287
|
console.log(JSON.stringify(result, null, 2));
|
|
2025
2288
|
}
|
|
2026
2289
|
else {
|
|
2027
|
-
console.log(
|
|
2290
|
+
console.log(renderDoctorTextReport(result));
|
|
2028
2291
|
}
|
|
2029
|
-
process.exitCode = result.
|
|
2292
|
+
process.exitCode = result.overall === "not_ready" ? 1 : 0;
|
|
2030
2293
|
return;
|
|
2031
2294
|
}
|
|
2032
2295
|
let config;
|
|
@@ -2380,7 +2643,9 @@ async function main() {
|
|
|
2380
2643
|
artifacts,
|
|
2381
2644
|
deliveryBundle: existingBundle
|
|
2382
2645
|
});
|
|
2383
|
-
const deliveryService = new TaskDeliveryService(
|
|
2646
|
+
const deliveryService = new TaskDeliveryService({
|
|
2647
|
+
jiraBaseUrl: config.jira.baseUrl
|
|
2648
|
+
});
|
|
2384
2649
|
const analysisDocService = new FeishuAnalysisDocService({ config, refStore: store });
|
|
2385
2650
|
const publicationService = new TaskPublicationService(logger);
|
|
2386
2651
|
const deliveryDispatcher = new TaskDeliveryDispatcher(config);
|
|
@@ -2628,22 +2893,6 @@ async function main() {
|
|
|
2628
2893
|
console.log(JSON.stringify({ repositories }, null, 2));
|
|
2629
2894
|
return;
|
|
2630
2895
|
}
|
|
2631
|
-
if (repoSubcommand === "doctor") {
|
|
2632
|
-
const repositories = await store.listRepositoryRoots();
|
|
2633
|
-
const inspected = await Promise.all(repositories.map(async (repository) => {
|
|
2634
|
-
const inspection = await inspectRepositoryRoot(repository.path);
|
|
2635
|
-
const executionPlan = describeRepositoryExecutionPlan(repository, inspection);
|
|
2636
|
-
return {
|
|
2637
|
-
...repository,
|
|
2638
|
-
inspection,
|
|
2639
|
-
executionPlan,
|
|
2640
|
-
resolvedDefaultMode: executionPlan.resolvedDefaultMode
|
|
2641
|
-
};
|
|
2642
|
-
}));
|
|
2643
|
-
console.log(JSON.stringify({ repositories: inspected }, null, 2));
|
|
2644
|
-
process.exitCode = inspected.every((repository) => repository.inspection.ok) ? 0 : 1;
|
|
2645
|
-
return;
|
|
2646
|
-
}
|
|
2647
2896
|
console.error(`Unknown repo command: ${repoSubcommand}`);
|
|
2648
2897
|
process.exitCode = 1;
|
|
2649
2898
|
return;
|
|
@@ -2992,163 +3241,6 @@ async function main() {
|
|
|
2992
3241
|
return;
|
|
2993
3242
|
}
|
|
2994
3243
|
}
|
|
2995
|
-
if (command === "health-check") {
|
|
2996
|
-
const result = await codexRunner.runHealthCheck();
|
|
2997
|
-
await logger.writeHealthCheckSnapshot(result);
|
|
2998
|
-
await logger.info("optimus.health_check", {
|
|
2999
|
-
ok: result.ok,
|
|
3000
|
-
model: result.diagnostics.model,
|
|
3001
|
-
provider: result.diagnostics.provider,
|
|
3002
|
-
failureCategory: result.diagnostics.failureCategory,
|
|
3003
|
-
connectivityChecks: result.diagnostics.connectivityChecks?.map((check) => `${check.name}:${check.ok ? "ok" : "failed"}${check.host ? `@${check.host}` : ""}`),
|
|
3004
|
-
timeoutMs: result.diagnostics.timeoutMs,
|
|
3005
|
-
codexHomeDir: result.diagnostics.codexHomeDir,
|
|
3006
|
-
approvalPolicy: result.diagnostics.approvalPolicy,
|
|
3007
|
-
sandboxMode: result.diagnostics.sandboxMode
|
|
3008
|
-
});
|
|
3009
|
-
console.log(JSON.stringify(result, null, 2));
|
|
3010
|
-
process.exitCode = result.ok ? 0 : 1;
|
|
3011
|
-
return;
|
|
3012
|
-
}
|
|
3013
|
-
if (command === "doctor") {
|
|
3014
|
-
const healthCheck = await logger.readHealthCheckSnapshot();
|
|
3015
|
-
const triageStatus = await logger.readTriageStatusSnapshot();
|
|
3016
|
-
const skillSyncStatus = await logger.readSkillSyncSnapshot();
|
|
3017
|
-
const evolutionStatus = await logger.readEvolutionSnapshot();
|
|
3018
|
-
const repoMemoryStatus = await logger.readRepoMemorySnapshot();
|
|
3019
|
-
const feishuStatus = await buildFeishuDoctorStatus(config);
|
|
3020
|
-
const preflight = await new CodexPreflight(config).run();
|
|
3021
|
-
const skillSummary = await inspectBuiltinSkills(config, skillSyncService);
|
|
3022
|
-
const repoMemorySummary = await new RepoMemoryService(config).readDoctorSummary();
|
|
3023
|
-
await store.init();
|
|
3024
|
-
const tasks = await store.listTasks();
|
|
3025
|
-
const runs = await store.listTaskRuns();
|
|
3026
|
-
const recentImportantEvents = await store.listRecentTaskEvents(15, [...DOCTOR_IMPORTANT_EVENT_TYPES]);
|
|
3027
|
-
const taskSummary = tasks.reduce((accumulator, task) => {
|
|
3028
|
-
accumulator[task.status] = (accumulator[task.status] ?? 0) + 1;
|
|
3029
|
-
return accumulator;
|
|
3030
|
-
}, {});
|
|
3031
|
-
const activeRuns = runs.filter((run) => !run.endedAt);
|
|
3032
|
-
const registeredRepositories = await store.listRepositoryRoots();
|
|
3033
|
-
const configuredRuntimeRepositories = await inspectConfiguredRuntimeRepositories(config);
|
|
3034
|
-
const repositoryDiagnostics = buildRepositoryDiagnostics(registeredRepositories, configuredRuntimeRepositories);
|
|
3035
|
-
const doctor = {
|
|
3036
|
-
runtimeRootDir: config.storage.rootDir,
|
|
3037
|
-
codexHomeDir: config.codex.homeDir,
|
|
3038
|
-
cliInboxDir: config.intake.cliInboxDir,
|
|
3039
|
-
healthLogDir: join(config.storage.rootDir, "logs"),
|
|
3040
|
-
codexHomeReady: await pathExists(config.codex.homeDir),
|
|
3041
|
-
inboxReady: await pathExists(config.intake.cliInboxDir),
|
|
3042
|
-
taskHarnessRootReady: await pathExists(config.runtime.taskHarnessRootDir),
|
|
3043
|
-
runtimeLogFiles: await safeList(join(config.storage.rootDir, "logs")),
|
|
3044
|
-
registeredRepositories,
|
|
3045
|
-
configuredRuntimeRepositories,
|
|
3046
|
-
repositoryDiagnostics,
|
|
3047
|
-
runtime: {
|
|
3048
|
-
totalTasks: tasks.length,
|
|
3049
|
-
totalRuns: runs.length,
|
|
3050
|
-
activeRuns: activeRuns.length,
|
|
3051
|
-
stalledRuns: activeRuns.filter((run) => run.health === "suspected_stall").length,
|
|
3052
|
-
taskSummary,
|
|
3053
|
-
recentTasks: tasks.slice(-5).map((task) => ({
|
|
3054
|
-
taskId: task.taskId,
|
|
3055
|
-
taskPackageId: task.taskPackageId,
|
|
3056
|
-
sourceEventId: task.sourceEventId ?? null,
|
|
3057
|
-
status: task.status,
|
|
3058
|
-
activeRunId: task.activeRunId ?? null,
|
|
3059
|
-
updatedAt: task.updatedAt
|
|
3060
|
-
})),
|
|
3061
|
-
activeRunDetails: activeRuns.map((run) => {
|
|
3062
|
-
const timing = buildRunTimingSnapshot(run, config);
|
|
3063
|
-
return {
|
|
3064
|
-
runId: run.runId,
|
|
3065
|
-
taskId: run.taskId,
|
|
3066
|
-
taskPackageId: run.taskPackageId,
|
|
3067
|
-
status: run.status,
|
|
3068
|
-
taskStatus: run.taskStatus,
|
|
3069
|
-
health: run.health ?? null,
|
|
3070
|
-
startedAt: run.startedAt,
|
|
3071
|
-
lastEventAt: run.lastEventAt ?? null,
|
|
3072
|
-
lastEventType: run.lastEventType ?? null,
|
|
3073
|
-
progressCounter: run.progressCounter ?? 0,
|
|
3074
|
-
commandCount: run.commandCount ?? 0,
|
|
3075
|
-
fileChangeCount: run.fileChangeCount ?? 0,
|
|
3076
|
-
sdkThreadId: run.sdkThreadId ?? null,
|
|
3077
|
-
addresses: {
|
|
3078
|
-
workspaceDir: run.addresses?.workspaceDir ?? null,
|
|
3079
|
-
visibleRepoDir: run.addresses?.visibleRepoDir ?? null,
|
|
3080
|
-
configuredExecutionMode: run.addresses?.configuredExecutionMode ?? null,
|
|
3081
|
-
resolvedExecutionMode: run.addresses?.resolvedExecutionMode ?? null
|
|
3082
|
-
},
|
|
3083
|
-
elapsedMs: timing.elapsedMs,
|
|
3084
|
-
idleMs: timing.idleMs,
|
|
3085
|
-
timeoutKind: timing.timeoutKind,
|
|
3086
|
-
failureCategoryLabel: describeFailureCategory(run.failureCategory),
|
|
3087
|
-
lastEventExplanation: run.lastEventType ? explainTaskEvent(run.lastEventType) : null
|
|
3088
|
-
};
|
|
3089
|
-
}),
|
|
3090
|
-
recentFailureBreakdown: buildRecentFailureBreakdown(runs),
|
|
3091
|
-
deliveryWarningSummary: buildDeliveryWarningSummary(recentImportantEvents),
|
|
3092
|
-
deliveryHealthSummary: await buildDeliveryHealthSummary(store, tasks, runs),
|
|
3093
|
-
recentImportantEvents: recentImportantEvents.map((event) => ({
|
|
3094
|
-
taskId: event.taskId,
|
|
3095
|
-
runId: event.runId ?? null,
|
|
3096
|
-
type: event.type,
|
|
3097
|
-
detail: event.detail,
|
|
3098
|
-
explanation: explainTaskEvent(event.type),
|
|
3099
|
-
createdAt: event.createdAt,
|
|
3100
|
-
sourceEventId: event.sourceEventId ?? null
|
|
3101
|
-
})),
|
|
3102
|
-
recentFailureSummary: buildRecentFailureSummary(runs, recentImportantEvents)
|
|
3103
|
-
},
|
|
3104
|
-
triage: {
|
|
3105
|
-
status: triageStatus ?? null
|
|
3106
|
-
},
|
|
3107
|
-
codex: {
|
|
3108
|
-
model: resolveEffectiveCodexModel(config) ?? null,
|
|
3109
|
-
authMode: resolveCodexAuthMode(config),
|
|
3110
|
-
provider: resolveConfiguredCodexProvider(config)
|
|
3111
|
-
? {
|
|
3112
|
-
id: resolveConfiguredCodexProvider(config).providerId,
|
|
3113
|
-
displayName: resolveConfiguredCodexProvider(config).displayName
|
|
3114
|
-
}
|
|
3115
|
-
: resolveConfiguredCodexModelProvider(config) ?? null,
|
|
3116
|
-
apiKeyEnvName: resolvePrimaryCodexApiKeyEnvName(config) ?? null,
|
|
3117
|
-
requiredEnvVars: resolveRequiredCodexEnvVars(config),
|
|
3118
|
-
baseUrl: resolveConfiguredCodexProvider(config)?.baseUrl ?? config.codex.baseUrl ?? null,
|
|
3119
|
-
sandboxMode: config.codex.sandboxMode,
|
|
3120
|
-
networkAccessEnabled: config.runtime.networkAccessEnabled
|
|
3121
|
-
},
|
|
3122
|
-
skills: {
|
|
3123
|
-
...skillSummary,
|
|
3124
|
-
recentSync: skillSyncStatus ?? null,
|
|
3125
|
-
recentEvolution: evolutionStatus ?? null
|
|
3126
|
-
},
|
|
3127
|
-
repoMemory: {
|
|
3128
|
-
rootDir: config.memory.rootDir,
|
|
3129
|
-
rootReady: await pathExists(config.memory.rootDir),
|
|
3130
|
-
totalCount: repoMemorySummary.length,
|
|
3131
|
-
entries: repoMemorySummary,
|
|
3132
|
-
recentUpdate: repoMemoryStatus ?? null
|
|
3133
|
-
},
|
|
3134
|
-
feishu: feishuStatus,
|
|
3135
|
-
currentPreflight: preflight,
|
|
3136
|
-
recentHealthCheck: healthCheck ?? null,
|
|
3137
|
-
selfUpdate: {
|
|
3138
|
-
currentVersion,
|
|
3139
|
-
config: config.selfUpdate ?? buildDefaultConfig().selfUpdate,
|
|
3140
|
-
cachedState: await readSelfUpdateState() ?? null
|
|
3141
|
-
}
|
|
3142
|
-
};
|
|
3143
|
-
if (parsedCommandArgs.format === "text") {
|
|
3144
|
-
console.log(renderDoctorTextReport(doctor));
|
|
3145
|
-
process.exitCode = 0;
|
|
3146
|
-
return;
|
|
3147
|
-
}
|
|
3148
|
-
console.log(JSON.stringify(doctor, null, 2));
|
|
3149
|
-
process.exitCode = 0;
|
|
3150
|
-
return;
|
|
3151
|
-
}
|
|
3152
3244
|
console.error(`Unknown command: ${command}`);
|
|
3153
3245
|
process.exitCode = 1;
|
|
3154
3246
|
}
|