@sireai/optimus 0.1.14 → 0.1.15
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 +1 -2
- package/dist/cli/feedback-delivery.d.ts +56 -0
- package/dist/cli/feedback-delivery.js +297 -0
- package/dist/cli/feedback-delivery.js.map +1 -0
- package/dist/cli/feedback.d.ts +25 -0
- package/dist/cli/feedback.js +143 -0
- package/dist/cli/feedback.js.map +1 -0
- package/dist/cli/optimus.js +382 -195
- package/dist/cli/optimus.js.map +1 -1
- package/dist/config/load-config.js +5 -4
- package/dist/config/load-config.js.map +1 -1
- package/dist/config/optimus-paths.d.ts +1 -0
- package/dist/config/optimus-paths.js +3 -0
- package/dist/config/optimus-paths.js.map +1 -1
- package/dist/integrations/feishu/feishu-auth-config.d.ts +5 -1
- package/dist/integrations/feishu/feishu-auth-config.js +9 -6
- package/dist/integrations/feishu/feishu-auth-config.js.map +1 -1
- package/dist/integrations/feishu/feishu-client.d.ts +2 -6
- package/dist/integrations/feishu/feishu-client.js +16 -41
- package/dist/integrations/feishu/feishu-client.js.map +1 -1
- package/dist/integrations/feishu/feishu-doc-service.d.ts +8 -3
- package/dist/integrations/feishu/feishu-doc-service.js +16 -2
- package/dist/integrations/feishu/feishu-doc-service.js.map +1 -1
- package/dist/integrations/feishu/feishu-user-service.d.ts +1 -2
- package/dist/integrations/feishu/feishu-user-service.js +1 -2
- package/dist/integrations/feishu/feishu-user-service.js.map +1 -1
- package/dist/integrations/jira/jira-cli.js +21 -2
- package/dist/integrations/jira/jira-cli.js.map +1 -1
- package/dist/integrations/jira/jira-client.d.ts +2 -1
- package/dist/integrations/jira/jira-client.js +19 -2
- package/dist/integrations/jira/jira-client.js.map +1 -1
- package/dist/integrations/jira/jira-submit.js +24 -24
- package/dist/integrations/jira/jira-submit.js.map +1 -1
- package/dist/integrations/sentry/sentry-cli.js +21 -1
- package/dist/integrations/sentry/sentry-cli.js.map +1 -1
- package/dist/task-environment/delivery/feishu-analysis-doc-service.d.ts +7 -3
- package/dist/task-environment/delivery/feishu-analysis-doc-service.js +62 -3
- package/dist/task-environment/delivery/feishu-analysis-doc-service.js.map +1 -1
- package/dist/task-environment/delivery/feishu-card-renderer.d.ts +1 -0
- package/dist/task-environment/delivery/feishu-card-renderer.js +17 -6
- package/dist/task-environment/delivery/feishu-card-renderer.js.map +1 -1
- package/dist/task-environment/delivery/feishu-notifier.d.ts +13 -0
- package/dist/task-environment/delivery/feishu-notifier.js +206 -22
- package/dist/task-environment/delivery/feishu-notifier.js.map +1 -1
- package/dist/task-environment/delivery/sentry-feishu-card-renderer.d.ts +1 -0
- package/dist/task-environment/delivery/sentry-feishu-card-renderer.js +16 -5
- package/dist/task-environment/delivery/sentry-feishu-card-renderer.js.map +1 -1
- package/dist/task-environment/orchestration/task-orchestrator.d.ts +0 -1
- package/dist/task-environment/orchestration/task-orchestrator.js +3 -23
- package/dist/task-environment/orchestration/task-orchestrator.js.map +1 -1
- package/dist/task-environment/runtime/optimus-runtime.js +1 -0
- package/dist/task-environment/runtime/optimus-runtime.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/package.json +3 -5
- package/dist/integrations/feishu/feishu-auth-service.d.ts +0 -71
- package/dist/integrations/feishu/feishu-auth-service.js +0 -399
- package/dist/integrations/feishu/feishu-auth-service.js.map +0 -1
- package/dist/integrations/feishu/feishu-auth-store.d.ts +0 -29
- package/dist/integrations/feishu/feishu-auth-store.js +0 -113
- package/dist/integrations/feishu/feishu-auth-store.js.map +0 -1
- package/dist/integrations/jira/jira-cli-config.d.ts +0 -15
- package/dist/integrations/jira/jira-cli-config.js +0 -100
- package/dist/integrations/jira/jira-cli-config.js.map +0 -1
- package/dist/problem-solving-core/codex/codex-global-config.d.ts +0 -17
- package/dist/problem-solving-core/codex/codex-global-config.js +0 -100
- package/dist/problem-solving-core/codex/codex-global-config.js.map +0 -1
- package/dist/task-environment/orchestration/triage-agent.d.ts +0 -54
- package/dist/task-environment/orchestration/triage-agent.js +0 -636
- package/dist/task-environment/orchestration/triage-agent.js.map +0 -1
package/dist/cli/optimus.js
CHANGED
|
@@ -29,7 +29,10 @@ import { TaskDeliveryDispatcher } from "../task-environment/delivery/task-delive
|
|
|
29
29
|
import { TaskDeliveryService } from "../task-environment/delivery/task-delivery-service.js";
|
|
30
30
|
import { FeishuAnalysisDocService } from "../task-environment/delivery/feishu-analysis-doc-service.js";
|
|
31
31
|
import { TaskPublicationService } from "../task-environment/delivery/task-publication-service.js";
|
|
32
|
-
import {
|
|
32
|
+
import { FeishuClient } from "../integrations/feishu/feishu-client.js";
|
|
33
|
+
import { FeishuUserService } from "../integrations/feishu/feishu-user-service.js";
|
|
34
|
+
import { createFeedbackReport } from "./feedback.js";
|
|
35
|
+
import { DEFAULT_FEEDBACK_RECIPIENT_EMAIL, FeishuFeedbackDeliveryService, resolveDefaultFeedbackRecipientFallbackOpenId } from "./feedback-delivery.js";
|
|
33
36
|
import { resolveDefaultConfigPath, resolveDefaultEnvPath, resolveOptimusHomeDir } from "../config/optimus-paths.js";
|
|
34
37
|
import { checkForSelfUpdate, installSelfUpdate, recordSkippedSelfUpdate, readSelfUpdateState, shouldPromptForCachedStartupUpdate } from "./self-update.js";
|
|
35
38
|
const CLI_ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
@@ -61,6 +64,11 @@ function renderSetupResult(result) {
|
|
|
61
64
|
lines.push(`Codex Auth: ${result.codexAuth.mode} | ${result.codexAuth.configured ? `${envLabel} configured` : `${envLabel} missing`}${providerLabel}`);
|
|
62
65
|
}
|
|
63
66
|
lines.push("");
|
|
67
|
+
lines.push("Next");
|
|
68
|
+
lines.push("- optimus start");
|
|
69
|
+
lines.push("- optimus submit --title \"Login crash\" --description \"Crash on startup\"");
|
|
70
|
+
lines.push("- optimus doctor --quick");
|
|
71
|
+
lines.push("");
|
|
64
72
|
lines.push(renderQuickDoctorReport(result.doctorQuick));
|
|
65
73
|
return lines.join("\n");
|
|
66
74
|
}
|
|
@@ -138,16 +146,41 @@ function renderCliHelp() {
|
|
|
138
146
|
"Usage:",
|
|
139
147
|
" optimus <command> [options]",
|
|
140
148
|
"",
|
|
141
|
-
"
|
|
142
|
-
" optimus setup",
|
|
143
|
-
" optimus start",
|
|
144
|
-
" optimus submit --title \"Login crash\" --description \"Crash on startup\"",
|
|
149
|
+
"First time here:",
|
|
150
|
+
" 1. optimus setup",
|
|
151
|
+
" 2. optimus start",
|
|
152
|
+
" 3. optimus submit --title \"Login crash\" --description \"Crash on startup\"",
|
|
153
|
+
"",
|
|
154
|
+
"Everyday commands:",
|
|
155
|
+
" setup First-time setup or update config",
|
|
156
|
+
" start Start the resident runtime",
|
|
157
|
+
" submit Submit a problem for Optimus to handle",
|
|
158
|
+
" task-status Check what is running now",
|
|
159
|
+
" task-result Read the latest result for a task",
|
|
160
|
+
" doctor --quick Quick readiness check",
|
|
161
|
+
" version Show the installed package version",
|
|
162
|
+
"",
|
|
163
|
+
"Need more?",
|
|
164
|
+
" optimus help advanced Show integration, retry, and developer commands",
|
|
165
|
+
" optimus help <command> Show details for one command",
|
|
145
166
|
"",
|
|
146
|
-
"
|
|
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`."
|
|
170
|
+
].join("\n");
|
|
171
|
+
}
|
|
172
|
+
function renderAdvancedCliHelp() {
|
|
173
|
+
return [
|
|
174
|
+
"Optimus CLI",
|
|
175
|
+
"",
|
|
176
|
+
"Advanced command index",
|
|
177
|
+
"",
|
|
178
|
+
"Everyday commands:",
|
|
147
179
|
" help [command] Show CLI help or command help",
|
|
148
180
|
" setup Configure Optimus for first use or update config",
|
|
149
181
|
" start Start the resident runtime",
|
|
150
182
|
" submit Submit a manual task",
|
|
183
|
+
" feedback Create a local user feedback bundle",
|
|
151
184
|
" retry-task Requeue an existing task",
|
|
152
185
|
" cancel-task Cancel a queued or running task",
|
|
153
186
|
" task-status Show task execution status",
|
|
@@ -166,7 +199,6 @@ function renderCliHelp() {
|
|
|
166
199
|
" postrun-retry Rebuild postrun artifacts and optionally replay publication",
|
|
167
200
|
"",
|
|
168
201
|
"Integration commands:",
|
|
169
|
-
" feishu auth login|status|logout",
|
|
170
202
|
" jira-search|jira-get-issue|jira-search-assigned-open|jira-submit-issue|jira-submit-assigned-open|jira-add-comment|jira-refresh-auth|jira-poll-once|jira-poll-daemon",
|
|
171
203
|
" sentry-get-event|sentry-submit-event",
|
|
172
204
|
" Example: optimus help jira-submit-issue",
|
|
@@ -178,11 +210,7 @@ function renderCliHelp() {
|
|
|
178
210
|
"",
|
|
179
211
|
"Global options:",
|
|
180
212
|
" -h, --help Show this help",
|
|
181
|
-
" -v, --version Show the installed package version"
|
|
182
|
-
"",
|
|
183
|
-
"Notes:",
|
|
184
|
-
" - `optimus` with no command prints help instead of starting runtime.",
|
|
185
|
-
` - Run \`optimus setup\` once; Optimus stores config and runtime data under ${resolveOptimusHomeDir()}.`
|
|
213
|
+
" -v, --version Show the installed package version"
|
|
186
214
|
].join("\n");
|
|
187
215
|
}
|
|
188
216
|
function renderCommandHelp(command) {
|
|
@@ -207,6 +235,27 @@ function renderCommandHelp(command) {
|
|
|
207
235
|
"Example:",
|
|
208
236
|
" optimus submit --title \"Login crash\" --description \"Crash on startup\" --repo ohos-pre"
|
|
209
237
|
].join("\n"),
|
|
238
|
+
feedback: [
|
|
239
|
+
"optimus feedback",
|
|
240
|
+
"",
|
|
241
|
+
"Usage:",
|
|
242
|
+
" optimus feedback --title <title> --description <text>",
|
|
243
|
+
"",
|
|
244
|
+
"Required:",
|
|
245
|
+
" --title <title> Feedback title",
|
|
246
|
+
" --description <text> Feedback description",
|
|
247
|
+
"",
|
|
248
|
+
"Behavior:",
|
|
249
|
+
" - writes a local feedback bundle under ~/.optimus/runtime/feedback",
|
|
250
|
+
" - creates a .tar.gz archive for the bundle",
|
|
251
|
+
" - attaches runtime and runtime-trace logs for the submission date when present",
|
|
252
|
+
` - creates a Feishu feedback document for ${DEFAULT_FEEDBACK_RECIPIENT_EMAIL}`,
|
|
253
|
+
" - grants that document edit access to the fixed feedback recipient",
|
|
254
|
+
" - sends a Feishu app card message with the feedback document link",
|
|
255
|
+
"",
|
|
256
|
+
"Example:",
|
|
257
|
+
" optimus feedback --title \"Upgrade prompt missing\" --description \"upgrade --check found 0.1.14 but start did not open the upgrade prompt.\""
|
|
258
|
+
].join("\n"),
|
|
210
259
|
"retry-task": [
|
|
211
260
|
"optimus retry-task",
|
|
212
261
|
"",
|
|
@@ -301,6 +350,34 @@ function renderCommandHelp(command) {
|
|
|
301
350
|
" --json Print JSON when used with --quick",
|
|
302
351
|
" --format text Print text report"
|
|
303
352
|
].join("\n"),
|
|
353
|
+
advanced: renderAdvancedCliHelp(),
|
|
354
|
+
"notify-test": [
|
|
355
|
+
"optimus notify-test",
|
|
356
|
+
"",
|
|
357
|
+
"Usage:",
|
|
358
|
+
" optimus notify-test [options]",
|
|
359
|
+
"",
|
|
360
|
+
"Optional:",
|
|
361
|
+
" --channel <auto|webhook|app> Select delivery path to validate, default auto",
|
|
362
|
+
" --format <card|text> Override Feishu message format for this test",
|
|
363
|
+
" --title <title> Override message title",
|
|
364
|
+
" --task-id <task-id> Override synthetic task id",
|
|
365
|
+
" --task-type <type> Override synthetic task type",
|
|
366
|
+
" --outcome <value> Override synthetic outcome",
|
|
367
|
+
" --repo <alias> Override synthetic repo",
|
|
368
|
+
" --decision <text> Override decision line",
|
|
369
|
+
" --validation <text> Override validation line",
|
|
370
|
+
" --next-action <text> Override next action line",
|
|
371
|
+
" --print-message true Preview the final payload without dispatch",
|
|
372
|
+
" --print-env-status true Print Feishu runtime status only",
|
|
373
|
+
"",
|
|
374
|
+
"Examples:",
|
|
375
|
+
" optimus notify-test",
|
|
376
|
+
" optimus notify-test --channel webhook",
|
|
377
|
+
` optimus notify-test --channel app --title "Optimus Feishu app smoke test"`,
|
|
378
|
+
" optimus notify-test --format text",
|
|
379
|
+
" optimus notify-test --format card --print-message true"
|
|
380
|
+
].join("\n"),
|
|
304
381
|
upgrade: [
|
|
305
382
|
"optimus upgrade",
|
|
306
383
|
"",
|
|
@@ -317,32 +394,6 @@ function renderCommandHelp(command) {
|
|
|
317
394
|
" optimus upgrade --check --channel snapshot",
|
|
318
395
|
" optimus upgrade --channel snapshot"
|
|
319
396
|
].join("\n"),
|
|
320
|
-
feishu: [
|
|
321
|
-
"optimus feishu auth",
|
|
322
|
-
"",
|
|
323
|
-
"Usage:",
|
|
324
|
-
" optimus feishu auth login [--json]",
|
|
325
|
-
" optimus feishu auth status [--json]",
|
|
326
|
-
" optimus feishu auth logout",
|
|
327
|
-
"",
|
|
328
|
-
"Notes:",
|
|
329
|
-
" - `login` starts Feishu device-code login for document upload and real @mentions.",
|
|
330
|
-
" - `status` shows whether the current Optimus Feishu session is still valid.",
|
|
331
|
-
" - `logout` clears the local Optimus Feishu auth token."
|
|
332
|
-
].join("\n"),
|
|
333
|
-
"feishu auth": [
|
|
334
|
-
"optimus feishu auth",
|
|
335
|
-
"",
|
|
336
|
-
"Usage:",
|
|
337
|
-
" optimus feishu auth login [--json]",
|
|
338
|
-
" optimus feishu auth status [--json]",
|
|
339
|
-
" optimus feishu auth logout",
|
|
340
|
-
"",
|
|
341
|
-
"Notes:",
|
|
342
|
-
" - `login` starts Feishu device-code login for document upload and real @mentions.",
|
|
343
|
-
" - `status` shows whether the current Optimus Feishu session is still valid.",
|
|
344
|
-
" - `logout` clears the local Optimus Feishu auth token."
|
|
345
|
-
].join("\n"),
|
|
346
397
|
"jira-submit-issue": [
|
|
347
398
|
"optimus jira-submit-issue",
|
|
348
399
|
"",
|
|
@@ -696,6 +747,26 @@ async function maybeHandleStartupSelfUpdate(input) {
|
|
|
696
747
|
interactivePromptAllowed: isInteractiveSelfUpdatePromptAllowed()
|
|
697
748
|
});
|
|
698
749
|
if (!check.checked) {
|
|
750
|
+
if (check.reason === "cached") {
|
|
751
|
+
const intervalHours = Math.max(1, selfUpdateConfig.checkIntervalHours);
|
|
752
|
+
const lastCheckedAt = check.state.lastCheckedAt ?? null;
|
|
753
|
+
const lastCheckedAtMs = lastCheckedAt ? Date.parse(lastCheckedAt) : Number.NaN;
|
|
754
|
+
const nextCheckAt = Number.isFinite(lastCheckedAtMs)
|
|
755
|
+
? new Date(lastCheckedAtMs + intervalHours * 60 * 60 * 1000).toISOString()
|
|
756
|
+
: null;
|
|
757
|
+
await input.logger.info("self_update.cached", {
|
|
758
|
+
command: input.command,
|
|
759
|
+
currentVersion: check.currentVersion,
|
|
760
|
+
latestVersion: check.latestVersion ?? null,
|
|
761
|
+
channel: check.channel,
|
|
762
|
+
installSource: check.installSource,
|
|
763
|
+
intervalHours,
|
|
764
|
+
lastCheckedAt,
|
|
765
|
+
nextCheckAt,
|
|
766
|
+
updateAvailable: check.updateAvailable,
|
|
767
|
+
promptPlanned: shouldPromptCachedUpdate
|
|
768
|
+
});
|
|
769
|
+
}
|
|
699
770
|
if (!shouldPromptCachedUpdate && check.reason === "cached" && check.updateAvailable && check.latestVersion) {
|
|
700
771
|
console.log(`[optimus] update available ${check.currentVersion} -> ${check.latestVersion}. Run \`optimus upgrade\`.`);
|
|
701
772
|
}
|
|
@@ -843,57 +914,107 @@ function buildPublicationReviewSummary(attempts) {
|
|
|
843
914
|
}
|
|
844
915
|
async function buildFeishuDoctorStatus(config) {
|
|
845
916
|
const webhookConfigured = Boolean(config.delivery.feishu.webhook?.trim() || config.delivery.feishu.webhooks?.some((value) => value.trim()));
|
|
846
|
-
const
|
|
847
|
-
const
|
|
848
|
-
const
|
|
849
|
-
const
|
|
850
|
-
const authReady = authStatus.authenticated || appCredentialConfigured;
|
|
851
|
-
const authMode = authStatus.authenticated
|
|
852
|
-
? "user_access_token"
|
|
853
|
-
: appCredentialConfigured
|
|
854
|
-
? "app_credentials"
|
|
855
|
-
: "none";
|
|
917
|
+
const client = new FeishuClient({ config });
|
|
918
|
+
const userService = new FeishuUserService({ config });
|
|
919
|
+
const appReady = client.isConfigured();
|
|
920
|
+
const mentionReady = userService.isConfigured();
|
|
856
921
|
return {
|
|
857
922
|
webhook: {
|
|
858
923
|
configured: webhookConfigured,
|
|
859
924
|
signed: Boolean(config.delivery.feishu.secret?.trim())
|
|
860
925
|
},
|
|
861
926
|
auth: {
|
|
862
|
-
configured:
|
|
863
|
-
baseUrl:
|
|
864
|
-
mode:
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
...(authStatus.openId ? { openId: authStatus.openId } : {}),
|
|
869
|
-
...(authStatus.accessTokenExpiresAt ? { accessTokenExpiresAt: authStatus.accessTokenExpiresAt } : {}),
|
|
870
|
-
...(authStatus.refreshTokenExpiresAt ? { refreshTokenExpiresAt: authStatus.refreshTokenExpiresAt } : {}),
|
|
871
|
-
detail: authStatus.authenticated
|
|
872
|
-
? "Feishu auth token is ready for document upload and real @mentions."
|
|
873
|
-
: appCredentialConfigured
|
|
874
|
-
? "Feishu runtime auth is available for document upload and user lookup."
|
|
875
|
-
: "Feishu webhook delivery can still work, but document upload and real @mentions are unavailable until Optimus signs in to Feishu.",
|
|
876
|
-
...(authReady ? {} : { recommendation: "Run `optimus feishu auth login` to enable analysis docs and real @mentions." })
|
|
927
|
+
configured: appReady,
|
|
928
|
+
baseUrl: config.delivery.feishu.baseUrl?.trim() || process.env.FEISHU_BASE_URL?.trim() || "https://open.feishu.cn",
|
|
929
|
+
mode: "builtin_app",
|
|
930
|
+
detail: appReady
|
|
931
|
+
? "Built-in Optimus Feishu app is ready for message delivery, document publishing, and capability probes."
|
|
932
|
+
: "Feishu app delivery is unavailable."
|
|
877
933
|
},
|
|
878
934
|
mentions: {
|
|
879
935
|
requiresOpenId: true,
|
|
880
|
-
ready:
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
936
|
+
ready: mentionReady,
|
|
937
|
+
mode: mentionReady ? "app_identity" : "unavailable",
|
|
938
|
+
detail: mentionReady
|
|
939
|
+
? "App-identity user lookup is ready for private delivery and real @mentions."
|
|
940
|
+
: "Real @mentions require Feishu app availability.",
|
|
941
|
+
...(mentionReady
|
|
942
|
+
? {}
|
|
943
|
+
: { recommendation: "Rerun `optimus doctor --quick` after restoring Feishu app availability." })
|
|
885
944
|
}
|
|
886
945
|
};
|
|
887
946
|
}
|
|
888
|
-
function buildFeishuRuntimeStatus(config) {
|
|
889
|
-
const
|
|
947
|
+
function buildFeishuRuntimeStatus(config, env = process.env) {
|
|
948
|
+
const client = new FeishuClient({ config, env });
|
|
890
949
|
return {
|
|
891
|
-
webhook: Boolean(config.delivery.feishu.webhook?.trim()
|
|
892
|
-
webhooks: Boolean(config.delivery.feishu.webhooks?.some((value) => value.trim())
|
|
893
|
-
secret: Boolean(config.delivery.feishu.secret?.trim() ||
|
|
894
|
-
auth:
|
|
950
|
+
webhook: Boolean(config.delivery.feishu.webhook?.trim()),
|
|
951
|
+
webhooks: Boolean(config.delivery.feishu.webhooks?.some((value) => value.trim())),
|
|
952
|
+
secret: Boolean(config.delivery.feishu.secret?.trim() || env.OPTIMUS_FEISHU_SECRET?.trim()),
|
|
953
|
+
auth: client.isConfigured()
|
|
895
954
|
};
|
|
896
955
|
}
|
|
956
|
+
function buildNotifyTestConfig(config, channel) {
|
|
957
|
+
if (channel === "auto") {
|
|
958
|
+
return config;
|
|
959
|
+
}
|
|
960
|
+
if (channel === "webhook") {
|
|
961
|
+
return {
|
|
962
|
+
...config,
|
|
963
|
+
delivery: {
|
|
964
|
+
...config.delivery,
|
|
965
|
+
feishu: {
|
|
966
|
+
...config.delivery.feishu
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
return {
|
|
972
|
+
...config,
|
|
973
|
+
delivery: {
|
|
974
|
+
...config.delivery,
|
|
975
|
+
feishu: {
|
|
976
|
+
...config.delivery.feishu,
|
|
977
|
+
webhook: "",
|
|
978
|
+
webhooks: []
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
function buildNotifyTestEnv(channel) {
|
|
984
|
+
return process.env;
|
|
985
|
+
}
|
|
986
|
+
function resolveNotifyTestDefaultTitle(channel) {
|
|
987
|
+
if (channel === "app") {
|
|
988
|
+
return "Optimus Feishu app smoke test";
|
|
989
|
+
}
|
|
990
|
+
return "Optimus Feishu webhook smoke test";
|
|
991
|
+
}
|
|
992
|
+
function buildNotifyTestInvocationId(now = new Date()) {
|
|
993
|
+
const timestamp = now.toISOString().replace(/[-:TZ.]/gu, "").slice(0, 14);
|
|
994
|
+
const randomSuffix = Math.random().toString(36).slice(2, 6);
|
|
995
|
+
return `${timestamp}-${randomSuffix}`;
|
|
996
|
+
}
|
|
997
|
+
function resolveNotifyTestDefaultDecision(channel) {
|
|
998
|
+
if (channel === "app") {
|
|
999
|
+
return "Manual Feishu app smoke test.";
|
|
1000
|
+
}
|
|
1001
|
+
return "Manual webhook smoke test.";
|
|
1002
|
+
}
|
|
1003
|
+
function resolveNotifyTestDefaultNextAction(channel) {
|
|
1004
|
+
if (channel === "app") {
|
|
1005
|
+
return "If this arrives, app delivery is working.";
|
|
1006
|
+
}
|
|
1007
|
+
return "If this arrives, webhook delivery is working.";
|
|
1008
|
+
}
|
|
1009
|
+
function isNotifyTestEnvReady(envStatus, channel) {
|
|
1010
|
+
if (channel === "app") {
|
|
1011
|
+
return envStatus.auth;
|
|
1012
|
+
}
|
|
1013
|
+
if (channel === "webhook") {
|
|
1014
|
+
return envStatus.webhook || envStatus.webhooks;
|
|
1015
|
+
}
|
|
1016
|
+
return envStatus.webhook || envStatus.webhooks || envStatus.auth;
|
|
1017
|
+
}
|
|
897
1018
|
function extractCommandFailureDetail(error) {
|
|
898
1019
|
if (!(error instanceof Error)) {
|
|
899
1020
|
return typeof error === "string" ? error : undefined;
|
|
@@ -911,7 +1032,7 @@ async function promptSetupAnswers(defaults) {
|
|
|
911
1032
|
let scriptedIndex = 0;
|
|
912
1033
|
const ask = async (prompt) => {
|
|
913
1034
|
if (scriptedAnswers) {
|
|
914
|
-
output.write(prompt);
|
|
1035
|
+
output.write(`${prompt}\n`);
|
|
915
1036
|
const answer = scriptedAnswers[scriptedIndex] ?? "";
|
|
916
1037
|
scriptedIndex += 1;
|
|
917
1038
|
return answer;
|
|
@@ -924,18 +1045,38 @@ async function promptSetupAnswers(defaults) {
|
|
|
924
1045
|
console.log(detail);
|
|
925
1046
|
}
|
|
926
1047
|
};
|
|
1048
|
+
const printSetupHint = (detail) => {
|
|
1049
|
+
console.log(` -> ${detail}`);
|
|
1050
|
+
};
|
|
927
1051
|
const renderSetupPrompt = (label, promptLabel, defaultValue) => {
|
|
928
1052
|
const suffix = defaultValue !== undefined ? ` [${defaultValue}]` : "";
|
|
929
1053
|
return `[${label}] ${promptLabel}${suffix}: `;
|
|
930
1054
|
};
|
|
1055
|
+
const resolveOptionalTextAnswer = (rawAnswer, defaultValue) => {
|
|
1056
|
+
const trimmed = rawAnswer.trim();
|
|
1057
|
+
if (trimmed === '""' || trimmed === "''") {
|
|
1058
|
+
return null;
|
|
1059
|
+
}
|
|
1060
|
+
if (trimmed.length === 0) {
|
|
1061
|
+
return defaultValue;
|
|
1062
|
+
}
|
|
1063
|
+
return trimmed;
|
|
1064
|
+
};
|
|
931
1065
|
try {
|
|
1066
|
+
console.log("Optimus setup");
|
|
1067
|
+
console.log("This usually takes about 2 minutes.");
|
|
1068
|
+
console.log("Required: repository + Codex auth. Optional: Feishu webhook, Jira, Sentry.");
|
|
1069
|
+
console.log("You can rerun `optimus setup` later to change anything.");
|
|
932
1070
|
if (await pathExists(configPath)) {
|
|
933
1071
|
console.log(`Existing config found at ${configPath}. Press Enter to keep current values.`);
|
|
934
1072
|
}
|
|
935
|
-
printSetupSection("Repository", "
|
|
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.");
|
|
936
1075
|
const repoPath = (await ask(renderSetupPrompt("Required", "Repository path", defaults.repoPath))).trim() || defaults.repoPath;
|
|
1076
|
+
printSetupHint("Repository alias: the short name used later in commands like --repo ohos-pre.");
|
|
937
1077
|
const repoAlias = (await ask(renderSetupPrompt("Required", "Repository alias", defaults.repoAlias))).trim() || defaults.repoAlias;
|
|
938
|
-
printSetupSection("Codex", "Choose one authentication mode
|
|
1078
|
+
printSetupSection("Codex", "Choose one model authentication mode. This is required before tasks can run.");
|
|
1079
|
+
printSetupHint("Codex auth mode: choose how Optimus reaches the model you want to use.");
|
|
939
1080
|
const codexAuthMode = await askSetupCodexAuthMode({ ask, defaultMode: defaults.codexAuthMode });
|
|
940
1081
|
let codexApiKey;
|
|
941
1082
|
let codexProviderId;
|
|
@@ -943,54 +1084,56 @@ async function promptSetupAnswers(defaults) {
|
|
|
943
1084
|
let codexProviderBaseUrl;
|
|
944
1085
|
let codexProviderApiKeyEnvName;
|
|
945
1086
|
if (codexAuthMode === "openai_api_key") {
|
|
1087
|
+
printSetupHint("OpenAI API key: used only when you choose direct OpenAI API authentication.");
|
|
946
1088
|
codexApiKey = (await ask(renderSetupPrompt("Optional", "OpenAI API key for OPENAI_API_KEY", "leave blank to keep existing"))).trim() || undefined;
|
|
947
1089
|
}
|
|
948
1090
|
else if (codexAuthMode === "model_provider") {
|
|
1091
|
+
printSetupHint("Provider id: a short stable id for this model provider, for example palebluedot.");
|
|
949
1092
|
codexProviderId = (await ask(renderSetupPrompt("Required", "Provider id", defaults.codexProviderId ?? ""))).trim() || defaults.codexProviderId;
|
|
1093
|
+
printSetupHint("Provider display name: what Optimus shows in status and doctor output.");
|
|
950
1094
|
codexProviderDisplayName = (await ask(renderSetupPrompt("Required", "Provider display name", defaults.codexProviderDisplayName ?? ""))).trim() || defaults.codexProviderDisplayName;
|
|
1095
|
+
printSetupHint("Provider base URL: the model API endpoint root Optimus should call.");
|
|
951
1096
|
codexProviderBaseUrl = (await ask(renderSetupPrompt("Required", "Provider base URL", defaults.codexProviderBaseUrl ?? ""))).trim() || defaults.codexProviderBaseUrl;
|
|
1097
|
+
printSetupHint("Provider API key env name: the environment variable name Optimus will read at runtime.");
|
|
952
1098
|
codexProviderApiKeyEnvName = (await ask(renderSetupPrompt("Required", "Provider API key env name", defaults.codexProviderApiKeyEnvName ?? ""))).trim() || defaults.codexProviderApiKeyEnvName;
|
|
953
1099
|
const providerKeyLabel = codexProviderApiKeyEnvName || defaults.codexProviderApiKeyEnvName || "the configured provider env";
|
|
1100
|
+
printSetupHint(`Provider API key value: optional here; leave blank to keep the current ${providerKeyLabel} value.`);
|
|
954
1101
|
codexApiKey = (await ask(renderSetupPrompt("Optional", `Provider API key value for ${providerKeyLabel}`, "leave blank to keep existing"))).trim() || undefined;
|
|
955
1102
|
}
|
|
956
|
-
printSetupSection("Feishu", "
|
|
957
|
-
|
|
958
|
-
const
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
const feishuSecret = enableFeishu
|
|
965
|
-
? (await ask(renderSetupPrompt("Optional", "Feishu secret", defaults.feishuSecret ? "configured" : ""))).trim() || defaults.feishuSecret
|
|
966
|
-
: undefined;
|
|
967
|
-
printSetupSection("Jira", "Enable this module when Optimus should read issues or comment back to Jira.");
|
|
968
|
-
const enableJiraInput = (await ask(renderSetupPrompt("Optional module", "Enable Jira integration", defaults.enableJira ? "Y/n" : "y/N"))).trim().toLowerCase();
|
|
1103
|
+
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
|
+
printSetupHint("Feishu webhook: optional; leave empty to use direct Feishu app delivery only.");
|
|
1105
|
+
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
|
+
printSetupSection("Jira", "Optional. Turn this on only if you want this machine to read Jira issues or write Jira comments.");
|
|
1109
|
+
printSetupHint("If disabled, Optimus still works; Jira commands just stay unavailable on this machine.");
|
|
1110
|
+
const enableJiraInput = (await ask(renderSetupPrompt("Optional integration", "Enable Jira integration", defaults.enableJira ? "Y/n" : "y/N"))).trim().toLowerCase();
|
|
969
1111
|
const enableJira = enableJiraInput.length === 0
|
|
970
1112
|
? defaults.enableJira
|
|
971
1113
|
: ["y", "yes"].includes(enableJiraInput);
|
|
972
1114
|
const jiraBaseUrl = enableJira
|
|
973
|
-
? (await ask(renderSetupPrompt("Required", "Jira base URL", defaults.jiraBaseUrl ?? ""))).trim() || defaults.jiraBaseUrl
|
|
1115
|
+
? (printSetupHint("Jira base URL: the Jira site root, for example https://jira.example.com/."), (await ask(renderSetupPrompt("Required", "Jira base URL", defaults.jiraBaseUrl ?? ""))).trim() || defaults.jiraBaseUrl)
|
|
974
1116
|
: undefined;
|
|
975
1117
|
const jiraPersonalToken = enableJira
|
|
976
|
-
? (await ask(renderSetupPrompt("Required", "Jira personal token", defaults.jiraPersonalToken ? "configured" : ""))).trim() || defaults.jiraPersonalToken
|
|
1118
|
+
? (printSetupHint("Jira personal token: required for Jira reads, comments, and issue submission commands."), (await ask(renderSetupPrompt("Required", "Jira personal token", defaults.jiraPersonalToken ? "configured" : ""))).trim() || defaults.jiraPersonalToken)
|
|
977
1119
|
: undefined;
|
|
978
1120
|
const jiraCasCookieName = enableJira
|
|
979
|
-
? (await ask(renderSetupPrompt("Optional", "Jira CAS cookie name", defaults.jiraCasCookieName ?? "_aegis_cas_p"))).trim() || defaults.jiraCasCookieName || "_aegis_cas_p"
|
|
1121
|
+
? (printSetupHint("Jira CAS cookie name: optional helper for environments that need refreshed CAS login cookies."), (await ask(renderSetupPrompt("Optional", "Jira CAS cookie name", defaults.jiraCasCookieName ?? "_aegis_cas_p"))).trim() || defaults.jiraCasCookieName || "_aegis_cas_p")
|
|
980
1122
|
: undefined;
|
|
981
|
-
printSetupSection("Sentry", "
|
|
982
|
-
|
|
1123
|
+
printSetupSection("Sentry", "Optional. Turn this on only if you want this machine to read Sentry events and create tasks from them.");
|
|
1124
|
+
printSetupHint("If disabled, Optimus still works; Sentry commands just stay unavailable on this machine.");
|
|
1125
|
+
const enableSentryInput = (await ask(renderSetupPrompt("Optional integration", "Enable Sentry integration", defaults.enableSentry ? "Y/n" : "y/N"))).trim().toLowerCase();
|
|
983
1126
|
const enableSentry = enableSentryInput.length === 0
|
|
984
1127
|
? defaults.enableSentry
|
|
985
1128
|
: ["y", "yes"].includes(enableSentryInput);
|
|
986
1129
|
const sentryBaseUrl = enableSentry
|
|
987
|
-
? (await ask(renderSetupPrompt("Required", "Sentry base URL", defaults.sentryBaseUrl ?? ""))).trim() || defaults.sentryBaseUrl
|
|
1130
|
+
? (printSetupHint("Sentry base URL: the Sentry site root, for example https://sentry.example.com/."), (await ask(renderSetupPrompt("Required", "Sentry base URL", defaults.sentryBaseUrl ?? ""))).trim() || defaults.sentryBaseUrl)
|
|
988
1131
|
: undefined;
|
|
989
1132
|
const sentryOrg = enableSentry
|
|
990
|
-
? (await ask(renderSetupPrompt("Required", "Sentry org", defaults.sentryOrg ?? ""))).trim() || defaults.sentryOrg
|
|
1133
|
+
? (printSetupHint("Sentry org: the organization slug used in Sentry API requests."), (await ask(renderSetupPrompt("Required", "Sentry org", defaults.sentryOrg ?? ""))).trim() || defaults.sentryOrg)
|
|
991
1134
|
: undefined;
|
|
992
1135
|
const sentryAuthToken = enableSentry
|
|
993
|
-
? (await ask(renderSetupPrompt("Required", "Sentry auth token", defaults.sentryAuthToken ? "configured" : ""))).trim() || defaults.sentryAuthToken
|
|
1136
|
+
? (printSetupHint("Sentry auth token: required if this machine should read events from Sentry."), (await ask(renderSetupPrompt("Required", "Sentry auth token", defaults.sentryAuthToken ? "configured" : ""))).trim() || defaults.sentryAuthToken)
|
|
994
1137
|
: undefined;
|
|
995
1138
|
return {
|
|
996
1139
|
repoPath,
|
|
@@ -1001,9 +1144,8 @@ async function promptSetupAnswers(defaults) {
|
|
|
1001
1144
|
...(codexProviderDisplayName ? { codexProviderDisplayName } : {}),
|
|
1002
1145
|
...(codexProviderBaseUrl ? { codexProviderBaseUrl } : {}),
|
|
1003
1146
|
...(codexProviderApiKeyEnvName ? { codexProviderApiKeyEnvName } : {}),
|
|
1004
|
-
|
|
1005
|
-
...(
|
|
1006
|
-
...(feishuSecret ? { feishuSecret } : {}),
|
|
1147
|
+
...(feishuWebhook !== undefined ? { feishuWebhook } : {}),
|
|
1148
|
+
...(feishuSecret !== undefined ? { feishuSecret } : {}),
|
|
1007
1149
|
enableJira,
|
|
1008
1150
|
...(jiraBaseUrl ? { jiraBaseUrl } : {}),
|
|
1009
1151
|
...(jiraPersonalToken ? { jiraPersonalToken } : {}),
|
|
@@ -1034,7 +1176,6 @@ async function resolveDefaultSetupAnswers() {
|
|
|
1034
1176
|
repoPath: "",
|
|
1035
1177
|
repoAlias: "",
|
|
1036
1178
|
codexAuthMode: defaults.codex.auth.mode,
|
|
1037
|
-
enableFeishu: false,
|
|
1038
1179
|
enableJira: false,
|
|
1039
1180
|
enableSentry: false
|
|
1040
1181
|
};
|
|
@@ -1054,7 +1195,6 @@ async function resolveDefaultSetupAnswers() {
|
|
|
1054
1195
|
...(provider?.displayName ? { codexProviderDisplayName: provider.displayName } : {}),
|
|
1055
1196
|
...(provider?.baseUrl ? { codexProviderBaseUrl: provider.baseUrl } : {}),
|
|
1056
1197
|
...(provider?.apiKeyEnvName ? { codexProviderApiKeyEnvName: provider.apiKeyEnvName } : {}),
|
|
1057
|
-
enableFeishu: config.delivery.channels.includes("feishu"),
|
|
1058
1198
|
...(config.delivery.feishu.webhook ? { feishuWebhook: config.delivery.feishu.webhook } : {}),
|
|
1059
1199
|
...(config.delivery.feishu.secret ? { feishuSecret: config.delivery.feishu.secret } : {}),
|
|
1060
1200
|
enableJira: config.jira.enabled,
|
|
@@ -1126,9 +1266,6 @@ function validateSetupAnswers(answers) {
|
|
|
1126
1266
|
if (answers.codexAuthMode === "model_provider" && !answers.codexProviderApiKeyEnvName?.trim()) {
|
|
1127
1267
|
return "setup requires provider API key env name when Codex auth mode is model_provider.";
|
|
1128
1268
|
}
|
|
1129
|
-
if (answers.enableFeishu && !answers.feishuWebhook?.trim()) {
|
|
1130
|
-
return "setup requires a Feishu webhook when Feishu delivery is enabled.";
|
|
1131
|
-
}
|
|
1132
1269
|
if (answers.enableJira && !answers.jiraBaseUrl?.trim()) {
|
|
1133
1270
|
return "setup requires a Jira base URL when Jira integration is enabled.";
|
|
1134
1271
|
}
|
|
@@ -1244,12 +1381,19 @@ function buildSetupConfig(answers, rawConfig) {
|
|
|
1244
1381
|
else {
|
|
1245
1382
|
delete config.codex.setupCache;
|
|
1246
1383
|
}
|
|
1247
|
-
config.delivery.channels =
|
|
1384
|
+
config.delivery.channels = ["console", "feishu"];
|
|
1248
1385
|
config.delivery.feishu = {
|
|
1249
1386
|
...config.delivery.feishu,
|
|
1250
|
-
...(
|
|
1251
|
-
...(
|
|
1387
|
+
...(typeof answers.feishuWebhook === "string" ? { webhook: answers.feishuWebhook } : {}),
|
|
1388
|
+
...(typeof answers.feishuSecret === "string" ? { secret: answers.feishuSecret } : {})
|
|
1252
1389
|
};
|
|
1390
|
+
if (answers.feishuWebhook === null) {
|
|
1391
|
+
delete config.delivery.feishu.webhook;
|
|
1392
|
+
delete config.delivery.feishu.webhooks;
|
|
1393
|
+
}
|
|
1394
|
+
if (answers.feishuSecret === null) {
|
|
1395
|
+
delete config.delivery.feishu.secret;
|
|
1396
|
+
}
|
|
1253
1397
|
config.jira.enabled = answers.enableJira;
|
|
1254
1398
|
if (answers.enableJira) {
|
|
1255
1399
|
config.jira.baseUrl = answers.jiraBaseUrl ?? config.jira.baseUrl;
|
|
@@ -1456,31 +1600,12 @@ async function runQuickDoctor(configPath = resolveDefaultConfigPath()) {
|
|
|
1456
1600
|
next.add("git config user.email \"you@example.com\"");
|
|
1457
1601
|
}
|
|
1458
1602
|
}
|
|
1459
|
-
const feishuEnabled = config.delivery.channels.includes("feishu");
|
|
1460
|
-
|
|
1461
|
-
config.delivery.feishu.webhook?.trim(),
|
|
1462
|
-
...(config.delivery.feishu.webhooks ?? []).map((value) => value.trim())
|
|
1463
|
-
].filter((value) => Boolean(value));
|
|
1464
|
-
if (feishuEnabled && configuredFeishuTargets.length === 0) {
|
|
1465
|
-
blocking.push({
|
|
1466
|
-
code: "feishu_webhook_missing",
|
|
1467
|
-
message: "Feishu delivery is enabled, but no webhook is configured.",
|
|
1468
|
-
fix: "Rerun `optimus setup` and provide a Feishu webhook."
|
|
1469
|
-
});
|
|
1470
|
-
next.add("optimus setup");
|
|
1471
|
-
}
|
|
1472
|
-
if (!feishuEnabled) {
|
|
1603
|
+
const feishuEnabled = config.delivery.enabled && config.delivery.channels.includes("feishu");
|
|
1604
|
+
if (!config.delivery.enabled) {
|
|
1473
1605
|
warnings.push({
|
|
1474
1606
|
code: "feishu_disabled",
|
|
1475
|
-
message: "
|
|
1476
|
-
impact: "
|
|
1477
|
-
});
|
|
1478
|
-
}
|
|
1479
|
-
else if (!new FeishuAuthService().readStatus().authenticated && (!config.delivery.feishu.appId?.trim() || !config.delivery.feishu.appSecret?.trim())) {
|
|
1480
|
-
warnings.push({
|
|
1481
|
-
code: "feishu_auth_missing",
|
|
1482
|
-
message: "Feishu auth token is missing.",
|
|
1483
|
-
impact: "Webhook messages can still be sent, but analysis document upload and real @mentions will be skipped."
|
|
1607
|
+
message: "Task delivery is disabled.",
|
|
1608
|
+
impact: "Console, Feishu, and analysis-document delivery are all paused."
|
|
1484
1609
|
});
|
|
1485
1610
|
}
|
|
1486
1611
|
if (config.jira.enabled) {
|
|
@@ -1870,56 +1995,7 @@ async function main() {
|
|
|
1870
1995
|
return;
|
|
1871
1996
|
}
|
|
1872
1997
|
if (command === "feishu") {
|
|
1873
|
-
|
|
1874
|
-
const args = parseArgs(commandArgs.slice(2));
|
|
1875
|
-
if (firstSubcommand !== "auth" || !secondSubcommand) {
|
|
1876
|
-
console.log(renderCommandHelp("feishu") ?? renderCliHelp());
|
|
1877
|
-
process.exitCode = 1;
|
|
1878
|
-
return;
|
|
1879
|
-
}
|
|
1880
|
-
const authService = new FeishuAuthService();
|
|
1881
|
-
if (secondSubcommand === "status") {
|
|
1882
|
-
const status = authService.readStatus();
|
|
1883
|
-
if (args.json === "true") {
|
|
1884
|
-
console.log(JSON.stringify(status, null, 2));
|
|
1885
|
-
}
|
|
1886
|
-
else if (status.authenticated) {
|
|
1887
|
-
console.log(`Feishu auth is ready. user=${status.userId ?? "unknown"} open_id=${status.openId ?? "unknown"} access_token_expires=${status.accessTokenExpiresAt ?? "unknown"} refresh_token_expires=${status.refreshTokenExpiresAt ?? "unknown"}`);
|
|
1888
|
-
}
|
|
1889
|
-
else {
|
|
1890
|
-
console.log("Feishu auth is not ready. Run `optimus feishu auth login`.");
|
|
1891
|
-
}
|
|
1892
|
-
process.exitCode = status.authenticated ? 0 : 1;
|
|
1893
|
-
return;
|
|
1894
|
-
}
|
|
1895
|
-
if (secondSubcommand === "login") {
|
|
1896
|
-
const result = await authService.login({
|
|
1897
|
-
onPrompt: ({ verificationUrl, expiresInSeconds }) => {
|
|
1898
|
-
console.error(`Open this URL to authorize:\n${verificationUrl}\n`);
|
|
1899
|
-
console.error(`Waiting for authorization (expires in ${expiresInSeconds}s)...`);
|
|
1900
|
-
}
|
|
1901
|
-
});
|
|
1902
|
-
if (args.json === "true") {
|
|
1903
|
-
console.log(JSON.stringify({
|
|
1904
|
-
ok: true,
|
|
1905
|
-
userId: result.userId ?? null,
|
|
1906
|
-
openId: result.openId ?? null,
|
|
1907
|
-
verificationUrl: result.verificationUrl,
|
|
1908
|
-
accessTokenExpiresAt: new Date(result.expiresAtMs).toISOString(),
|
|
1909
|
-
refreshTokenExpiresAt: new Date(result.refreshExpiresAtMs).toISOString()
|
|
1910
|
-
}, null, 2));
|
|
1911
|
-
}
|
|
1912
|
-
else {
|
|
1913
|
-
console.log(`Feishu auth succeeded. user=${result.userId ?? "unknown"} open_id=${result.openId ?? "unknown"}`);
|
|
1914
|
-
}
|
|
1915
|
-
return;
|
|
1916
|
-
}
|
|
1917
|
-
if (secondSubcommand === "logout") {
|
|
1918
|
-
await authService.logout();
|
|
1919
|
-
console.log("Feishu auth cleared.");
|
|
1920
|
-
return;
|
|
1921
|
-
}
|
|
1922
|
-
console.log(renderCommandHelp("feishu") ?? renderCliHelp());
|
|
1998
|
+
console.log("Feishu user-session commands have been removed. Optimus now uses the built-in Feishu app for delivery and document publishing.");
|
|
1923
1999
|
process.exitCode = 1;
|
|
1924
2000
|
return;
|
|
1925
2001
|
}
|
|
@@ -1949,7 +2025,13 @@ async function main() {
|
|
|
1949
2025
|
}
|
|
1950
2026
|
catch (error) {
|
|
1951
2027
|
if (error?.code === "ENOENT") {
|
|
1952
|
-
console.error(
|
|
2028
|
+
console.error([
|
|
2029
|
+
"Optimus is not set up on this machine yet.",
|
|
2030
|
+
"Next:",
|
|
2031
|
+
" 1. optimus setup",
|
|
2032
|
+
" 2. optimus start",
|
|
2033
|
+
" 3. optimus submit --title \"Login crash\" --description \"Crash on startup\""
|
|
2034
|
+
].join("\n"));
|
|
1953
2035
|
process.exitCode = 1;
|
|
1954
2036
|
return;
|
|
1955
2037
|
}
|
|
@@ -2023,6 +2105,42 @@ async function main() {
|
|
|
2023
2105
|
console.log(`[optimus] wrote submission ${inboxPath}`);
|
|
2024
2106
|
return;
|
|
2025
2107
|
}
|
|
2108
|
+
if (command === "feedback") {
|
|
2109
|
+
const args = parseArgs(commandArgs);
|
|
2110
|
+
const title = args.title?.trim();
|
|
2111
|
+
const description = (args.desc ?? args.description)?.trim();
|
|
2112
|
+
if (!title || !description) {
|
|
2113
|
+
console.error("feedback requires both --title and --description");
|
|
2114
|
+
process.exitCode = 1;
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
const result = await createFeedbackReport({
|
|
2118
|
+
title,
|
|
2119
|
+
description,
|
|
2120
|
+
currentVersion,
|
|
2121
|
+
packageRoot: CLI_ROOT_DIR
|
|
2122
|
+
});
|
|
2123
|
+
console.log("[optimus] feedback saved locally");
|
|
2124
|
+
console.log(`id: ${result.id}`);
|
|
2125
|
+
console.log(`path: ${result.feedbackDir}`);
|
|
2126
|
+
console.log(`report: ${result.reportPath}`);
|
|
2127
|
+
console.log(`archive: ${result.archivePath}`);
|
|
2128
|
+
console.log(`logs: ${result.files.filter((file) => file.found).length}/${result.files.length}`);
|
|
2129
|
+
const feedbackDelivery = new FeishuFeedbackDeliveryService({
|
|
2130
|
+
config
|
|
2131
|
+
});
|
|
2132
|
+
const deliveryResult = await feedbackDelivery.deliver({
|
|
2133
|
+
title,
|
|
2134
|
+
description,
|
|
2135
|
+
currentVersion,
|
|
2136
|
+
report: result
|
|
2137
|
+
});
|
|
2138
|
+
console.log(`recipient: ${deliveryResult.recipientEmail}`);
|
|
2139
|
+
console.log(`open_id: ${deliveryResult.recipientOpenId}`);
|
|
2140
|
+
console.log(`doc: ${deliveryResult.document.status}${deliveryResult.document.url ? ` (${deliveryResult.document.url})` : deliveryResult.document.reason ? ` (${deliveryResult.document.reason})` : ""}`);
|
|
2141
|
+
console.log(`notify: ${deliveryResult.notification.status}${deliveryResult.notification.reason ? ` (${deliveryResult.notification.reason})` : ""}`);
|
|
2142
|
+
return;
|
|
2143
|
+
}
|
|
2026
2144
|
if (command === "retry-task") {
|
|
2027
2145
|
const args = parseArgs(commandArgs);
|
|
2028
2146
|
const taskId = args["task-id"];
|
|
@@ -2044,17 +2162,32 @@ async function main() {
|
|
|
2044
2162
|
}
|
|
2045
2163
|
if (command === "notify-test") {
|
|
2046
2164
|
const args = parseArgs(commandArgs);
|
|
2047
|
-
const
|
|
2048
|
-
|
|
2165
|
+
const requestedChannel = args.channel?.trim().toLowerCase();
|
|
2166
|
+
if (requestedChannel && requestedChannel !== "auto" && requestedChannel !== "webhook" && requestedChannel !== "app") {
|
|
2167
|
+
console.error("notify-test only supports --channel auto, webhook, or app");
|
|
2168
|
+
process.exitCode = 1;
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
const requestedFormat = args.format?.trim().toLowerCase();
|
|
2172
|
+
if (requestedFormat && requestedFormat !== "text" && requestedFormat !== "card") {
|
|
2173
|
+
console.error("notify-test only supports --format text or --format card");
|
|
2174
|
+
process.exitCode = 1;
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
const notifyChannel = requestedChannel === "webhook" || requestedChannel === "app" ? requestedChannel : "auto";
|
|
2178
|
+
const invocationId = buildNotifyTestInvocationId();
|
|
2179
|
+
const title = args.title?.trim() || `${resolveNotifyTestDefaultTitle(notifyChannel)} [${invocationId}]`;
|
|
2180
|
+
const taskId = args["task-id"]?.trim() || `task-feishu-smoke-${invocationId}`;
|
|
2181
|
+
const runId = `run-feishu-smoke-${invocationId}`;
|
|
2049
2182
|
const taskType = args["task-type"]?.trim() || "bugfix";
|
|
2050
2183
|
const outcome = args.outcome?.trim() || "analysis_only";
|
|
2051
2184
|
const repo = args.repo?.trim() || "optimus";
|
|
2052
|
-
const decision = args.decision?.trim() ||
|
|
2185
|
+
const decision = args.decision?.trim() || resolveNotifyTestDefaultDecision(notifyChannel);
|
|
2053
2186
|
const validation = args.validation?.trim() || "Triggered from local CLI.";
|
|
2054
|
-
const nextAction = args["next-action"]?.trim() ||
|
|
2187
|
+
const nextAction = args["next-action"]?.trim() || resolveNotifyTestDefaultNextAction(notifyChannel);
|
|
2055
2188
|
const bundle = {
|
|
2056
2189
|
taskId,
|
|
2057
|
-
runId
|
|
2190
|
+
runId,
|
|
2058
2191
|
taskType,
|
|
2059
2192
|
outcome: outcome,
|
|
2060
2193
|
createdAt: new Date().toISOString(),
|
|
@@ -2063,7 +2196,13 @@ async function main() {
|
|
|
2063
2196
|
repo,
|
|
2064
2197
|
decision,
|
|
2065
2198
|
validation,
|
|
2066
|
-
nextAction
|
|
2199
|
+
nextAction,
|
|
2200
|
+
...(notifyChannel === "app" ? {
|
|
2201
|
+
qAssignee: {
|
|
2202
|
+
displayName: "wangkai39",
|
|
2203
|
+
email: DEFAULT_FEEDBACK_RECIPIENT_EMAIL
|
|
2204
|
+
}
|
|
2205
|
+
} : {})
|
|
2067
2206
|
},
|
|
2068
2207
|
artifacts: {
|
|
2069
2208
|
extras: []
|
|
@@ -2078,7 +2217,7 @@ async function main() {
|
|
|
2078
2217
|
const publicationAttempts = args["review-url"] || args["change-id"]
|
|
2079
2218
|
? [{
|
|
2080
2219
|
taskId,
|
|
2081
|
-
runId
|
|
2220
|
+
runId,
|
|
2082
2221
|
mode: "review_submit",
|
|
2083
2222
|
status: "submitted",
|
|
2084
2223
|
summary: args["review-summary"]?.trim() || "Review submission completed for 1/1 target(s).",
|
|
@@ -2087,12 +2226,50 @@ async function main() {
|
|
|
2087
2226
|
...(changeIds.length > 0 ? { changeId: changeIds[0], changeIds } : {})
|
|
2088
2227
|
}]
|
|
2089
2228
|
: [];
|
|
2090
|
-
const
|
|
2229
|
+
const effectiveFormat = requestedFormat === "text" || requestedFormat === "card"
|
|
2230
|
+
? requestedFormat
|
|
2231
|
+
: config.delivery.feishu.format;
|
|
2232
|
+
const notifyConfig = buildNotifyTestConfig(config, notifyChannel);
|
|
2233
|
+
const notifyEnv = buildNotifyTestEnv(notifyChannel);
|
|
2234
|
+
let appResolution;
|
|
2235
|
+
if (notifyChannel === "app" && args["print-message"] !== "true") {
|
|
2236
|
+
const assigneeEmail = bundle.summary.qAssignee?.email?.trim();
|
|
2237
|
+
appResolution = assigneeEmail ? { requestedEmail: assigneeEmail } : {};
|
|
2238
|
+
if (assigneeEmail) {
|
|
2239
|
+
try {
|
|
2240
|
+
const resolvedOpenId = await new FeishuUserService({ config: notifyConfig }).resolveOpenId(assigneeEmail);
|
|
2241
|
+
if (resolvedOpenId && bundle.summary.qAssignee) {
|
|
2242
|
+
bundle.summary.qAssignee.openId = resolvedOpenId;
|
|
2243
|
+
appResolution.resolvedOpenId = resolvedOpenId;
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
catch (error) {
|
|
2247
|
+
appResolution.error = extractCommandFailureDetail(error) ?? String(error);
|
|
2248
|
+
// Keep the smoke test non-blocking and let the notifier report the final delivery status.
|
|
2249
|
+
}
|
|
2250
|
+
if (!bundle.summary.qAssignee?.openId) {
|
|
2251
|
+
const fallbackOpenId = resolveDefaultFeedbackRecipientFallbackOpenId(notifyEnv, notifyConfig);
|
|
2252
|
+
if (fallbackOpenId && bundle.summary.qAssignee) {
|
|
2253
|
+
bundle.summary.qAssignee.openId = fallbackOpenId;
|
|
2254
|
+
appResolution.resolvedOpenId = fallbackOpenId;
|
|
2255
|
+
if (!appResolution.error) {
|
|
2256
|
+
appResolution.error = "lookup_unavailable_used_fallback_open_id";
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
const reviewAwareNotifier = new FeishuNotifier({
|
|
2263
|
+
config: notifyConfig,
|
|
2264
|
+
env: notifyEnv,
|
|
2265
|
+
publicationAttempts,
|
|
2266
|
+
format: effectiveFormat
|
|
2267
|
+
});
|
|
2091
2268
|
const message = reviewAwareNotifier.previewMessage(bundle);
|
|
2092
|
-
const envStatus = buildFeishuRuntimeStatus(
|
|
2269
|
+
const envStatus = buildFeishuRuntimeStatus(notifyConfig, notifyEnv);
|
|
2093
2270
|
if (args["print-env-status"] === "true") {
|
|
2094
|
-
console.log(JSON.stringify({ envStatus }, null, 2));
|
|
2095
|
-
process.exitCode = envStatus
|
|
2271
|
+
console.log(JSON.stringify({ channel: notifyChannel, envStatus }, null, 2));
|
|
2272
|
+
process.exitCode = isNotifyTestEnvReady(envStatus, notifyChannel) ? 0 : 1;
|
|
2096
2273
|
return;
|
|
2097
2274
|
}
|
|
2098
2275
|
if (args["print-message"] === "true") {
|
|
@@ -2101,7 +2278,17 @@ async function main() {
|
|
|
2101
2278
|
return;
|
|
2102
2279
|
}
|
|
2103
2280
|
const attempts = await reviewAwareNotifier.dispatch(bundle);
|
|
2104
|
-
console.log(JSON.stringify({
|
|
2281
|
+
console.log(JSON.stringify({
|
|
2282
|
+
channel: notifyChannel,
|
|
2283
|
+
envStatus,
|
|
2284
|
+
format: effectiveFormat,
|
|
2285
|
+
...(notifyChannel === "app" ? {
|
|
2286
|
+
receiver: bundle.summary.qAssignee,
|
|
2287
|
+
appResolution
|
|
2288
|
+
} : {}),
|
|
2289
|
+
message,
|
|
2290
|
+
attempts
|
|
2291
|
+
}, null, 2));
|
|
2105
2292
|
process.exitCode = attempts.every((attempt) => attempt.status === "dispatched") ? 0 : 1;
|
|
2106
2293
|
return;
|
|
2107
2294
|
}
|