@sireai/optimus 0.1.13 → 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.
Files changed (77) hide show
  1. package/README.md +1 -2
  2. package/dist/cli/feedback-delivery.d.ts +56 -0
  3. package/dist/cli/feedback-delivery.js +297 -0
  4. package/dist/cli/feedback-delivery.js.map +1 -0
  5. package/dist/cli/feedback.d.ts +25 -0
  6. package/dist/cli/feedback.js +143 -0
  7. package/dist/cli/feedback.js.map +1 -0
  8. package/dist/cli/optimus.js +385 -196
  9. package/dist/cli/optimus.js.map +1 -1
  10. package/dist/config/load-config.js +5 -4
  11. package/dist/config/load-config.js.map +1 -1
  12. package/dist/config/optimus-paths.d.ts +1 -0
  13. package/dist/config/optimus-paths.js +3 -0
  14. package/dist/config/optimus-paths.js.map +1 -1
  15. package/dist/integrations/feishu/feishu-auth-config.d.ts +5 -1
  16. package/dist/integrations/feishu/feishu-auth-config.js +9 -6
  17. package/dist/integrations/feishu/feishu-auth-config.js.map +1 -1
  18. package/dist/integrations/feishu/feishu-client.d.ts +2 -6
  19. package/dist/integrations/feishu/feishu-client.js +16 -41
  20. package/dist/integrations/feishu/feishu-client.js.map +1 -1
  21. package/dist/integrations/feishu/feishu-doc-service.d.ts +8 -3
  22. package/dist/integrations/feishu/feishu-doc-service.js +16 -2
  23. package/dist/integrations/feishu/feishu-doc-service.js.map +1 -1
  24. package/dist/integrations/feishu/feishu-user-service.d.ts +1 -2
  25. package/dist/integrations/feishu/feishu-user-service.js +1 -2
  26. package/dist/integrations/feishu/feishu-user-service.js.map +1 -1
  27. package/dist/integrations/jira/jira-cli.js +21 -2
  28. package/dist/integrations/jira/jira-cli.js.map +1 -1
  29. package/dist/integrations/jira/jira-client.d.ts +2 -1
  30. package/dist/integrations/jira/jira-client.js +19 -2
  31. package/dist/integrations/jira/jira-client.js.map +1 -1
  32. package/dist/integrations/jira/jira-submit.js +24 -24
  33. package/dist/integrations/jira/jira-submit.js.map +1 -1
  34. package/dist/integrations/sentry/sentry-cli.js +21 -1
  35. package/dist/integrations/sentry/sentry-cli.js.map +1 -1
  36. package/dist/problem-solving-core/codex/codex-preflight.js +8 -0
  37. package/dist/problem-solving-core/codex/codex-preflight.js.map +1 -1
  38. package/dist/problem-solving-core/codex/codex-provider-profile.js +3 -0
  39. package/dist/problem-solving-core/codex/codex-provider-profile.js.map +1 -1
  40. package/dist/task-environment/delivery/feishu-analysis-doc-service.d.ts +7 -3
  41. package/dist/task-environment/delivery/feishu-analysis-doc-service.js +62 -3
  42. package/dist/task-environment/delivery/feishu-analysis-doc-service.js.map +1 -1
  43. package/dist/task-environment/delivery/feishu-card-renderer.d.ts +1 -0
  44. package/dist/task-environment/delivery/feishu-card-renderer.js +17 -6
  45. package/dist/task-environment/delivery/feishu-card-renderer.js.map +1 -1
  46. package/dist/task-environment/delivery/feishu-notifier.d.ts +13 -0
  47. package/dist/task-environment/delivery/feishu-notifier.js +206 -22
  48. package/dist/task-environment/delivery/feishu-notifier.js.map +1 -1
  49. package/dist/task-environment/delivery/sentry-feishu-card-renderer.d.ts +1 -0
  50. package/dist/task-environment/delivery/sentry-feishu-card-renderer.js +16 -5
  51. package/dist/task-environment/delivery/sentry-feishu-card-renderer.js.map +1 -1
  52. package/dist/task-environment/observability/runtime-panel.d.ts +1 -1
  53. package/dist/task-environment/observability/runtime-panel.js +4 -1
  54. package/dist/task-environment/observability/runtime-panel.js.map +1 -1
  55. package/dist/task-environment/orchestration/task-orchestrator.d.ts +0 -1
  56. package/dist/task-environment/orchestration/task-orchestrator.js +3 -23
  57. package/dist/task-environment/orchestration/task-orchestrator.js.map +1 -1
  58. package/dist/task-environment/runtime/optimus-runtime.d.ts +1 -0
  59. package/dist/task-environment/runtime/optimus-runtime.js +2 -1
  60. package/dist/task-environment/runtime/optimus-runtime.js.map +1 -1
  61. package/dist/types.d.ts +3 -2
  62. package/package.json +3 -5
  63. package/dist/integrations/feishu/feishu-auth-service.d.ts +0 -71
  64. package/dist/integrations/feishu/feishu-auth-service.js +0 -399
  65. package/dist/integrations/feishu/feishu-auth-service.js.map +0 -1
  66. package/dist/integrations/feishu/feishu-auth-store.d.ts +0 -29
  67. package/dist/integrations/feishu/feishu-auth-store.js +0 -113
  68. package/dist/integrations/feishu/feishu-auth-store.js.map +0 -1
  69. package/dist/integrations/jira/jira-cli-config.d.ts +0 -15
  70. package/dist/integrations/jira/jira-cli-config.js +0 -100
  71. package/dist/integrations/jira/jira-cli-config.js.map +0 -1
  72. package/dist/problem-solving-core/codex/codex-global-config.d.ts +0 -17
  73. package/dist/problem-solving-core/codex/codex-global-config.js +0 -100
  74. package/dist/problem-solving-core/codex/codex-global-config.js.map +0 -1
  75. package/dist/task-environment/orchestration/triage-agent.d.ts +0 -54
  76. package/dist/task-environment/orchestration/triage-agent.js +0 -636
  77. package/dist/task-environment/orchestration/triage-agent.js.map +0 -1
@@ -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 { FeishuAuthService } from "../integrations/feishu/feishu-auth-service.js";
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
- "Get started:",
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
- "User commands:",
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 authStatus = new FeishuAuthService().readStatus();
847
- const hasAppId = Boolean(config.delivery.feishu.appId?.trim() || process.env.FEISHU_APP_ID?.trim());
848
- const hasAppSecret = Boolean(config.delivery.feishu.appSecret?.trim() || process.env.FEISHU_APP_SECRET?.trim());
849
- const appCredentialConfigured = hasAppId && hasAppSecret;
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: authReady,
863
- baseUrl: authStatus.baseUrl,
864
- mode: authMode,
865
- accessTokenExpired: authStatus.accessTokenExpired,
866
- refreshTokenExpired: authStatus.refreshTokenExpired,
867
- ...(authStatus.userId ? { userId: authStatus.userId } : {}),
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: authReady,
881
- detail: authReady
882
- ? "Authenticated Feishu user lookup is ready for real @mentions."
883
- : "Real @mentions require `optimus feishu auth login`.",
884
- ...(authReady ? {} : { recommendation: "Run `optimus feishu auth login`." })
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 authStatus = new FeishuAuthService().readStatus();
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() || process.env.OPTIMUS_FEISHU_WEBHOOK?.trim()),
892
- webhooks: Boolean(config.delivery.feishu.webhooks?.some((value) => value.trim()) || process.env.OPTIMUS_FEISHU_WEBHOOKS?.trim()),
893
- secret: Boolean(config.delivery.feishu.secret?.trim() || process.env.OPTIMUS_FEISHU_SECRET?.trim()),
894
- auth: authStatus.authenticated || Boolean((config.delivery.feishu.appId?.trim() || process.env.FEISHU_APP_ID?.trim()) && (config.delivery.feishu.appSecret?.trim() || process.env.FEISHU_APP_SECRET?.trim()))
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", "Register the primary local repository used by Optimus.");
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, then fill the fields that follow.");
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", "Enable this module when task notifications should be sent to Feishu. Use `optimus feishu auth login` later for analysis docs and real @mentions.");
957
- const enableFeishuInput = (await ask(renderSetupPrompt("Optional module", "Enable Feishu delivery", defaults.enableFeishu ? "Y/n" : "y/N"))).trim().toLowerCase();
958
- const enableFeishu = enableFeishuInput.length === 0
959
- ? defaults.enableFeishu
960
- : ["y", "yes"].includes(enableFeishuInput);
961
- const feishuWebhook = enableFeishu
962
- ? (await ask(renderSetupPrompt("Required", "Feishu webhook", defaults.feishuWebhook ? "configured" : ""))).trim() || defaults.feishuWebhook
963
- : undefined;
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", "Enable this module when tasks can be created from Sentry events.");
982
- const enableSentryInput = (await ask(renderSetupPrompt("Optional module", "Enable Sentry integration", defaults.enableSentry ? "Y/n" : "y/N"))).trim().toLowerCase();
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
- enableFeishu,
1005
- ...(feishuWebhook ? { feishuWebhook } : {}),
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 = answers.enableFeishu ? ["console", "feishu"] : ["console"];
1384
+ config.delivery.channels = ["console", "feishu"];
1248
1385
  config.delivery.feishu = {
1249
1386
  ...config.delivery.feishu,
1250
- ...(answers.enableFeishu && answers.feishuWebhook ? { webhook: answers.feishuWebhook } : {}),
1251
- ...(answers.enableFeishu && answers.feishuSecret ? { secret: answers.feishuSecret } : {})
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
- const configuredFeishuTargets = [
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: "Feishu delivery is disabled.",
1476
- impact: "Notifications will stay console-only."
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
- const [firstSubcommand, secondSubcommand] = commandArgs;
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("Configuration is missing. Run `optimus setup` first.");
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
  }
@@ -1961,8 +2043,10 @@ async function main() {
1961
2043
  const logger = new OptimusLogger(config);
1962
2044
  const eventStore = new SQLiteEventStore(config.storage.rootDir);
1963
2045
  const taskOrchestrator = new TaskOrchestrator(store, codexRunner, config);
1964
- const runtime = new OptimusRuntime(store, taskOrchestrator, config);
1965
2046
  const currentVersion = await readPackageVersion();
2047
+ const runtime = new OptimusRuntime(store, taskOrchestrator, config, {
2048
+ version: currentVersion
2049
+ });
1966
2050
  if (command === "start" || command === "submit") {
1967
2051
  const selfUpdateOutcome = await maybeHandleStartupSelfUpdate({
1968
2052
  command,
@@ -2021,6 +2105,42 @@ async function main() {
2021
2105
  console.log(`[optimus] wrote submission ${inboxPath}`);
2022
2106
  return;
2023
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
+ }
2024
2144
  if (command === "retry-task") {
2025
2145
  const args = parseArgs(commandArgs);
2026
2146
  const taskId = args["task-id"];
@@ -2042,17 +2162,32 @@ async function main() {
2042
2162
  }
2043
2163
  if (command === "notify-test") {
2044
2164
  const args = parseArgs(commandArgs);
2045
- const title = args.title?.trim() || "Optimus Feishu webhook smoke test";
2046
- const taskId = args["task-id"]?.trim() || "task-feishu-smoke";
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}`;
2047
2182
  const taskType = args["task-type"]?.trim() || "bugfix";
2048
2183
  const outcome = args.outcome?.trim() || "analysis_only";
2049
2184
  const repo = args.repo?.trim() || "optimus";
2050
- const decision = args.decision?.trim() || "Manual webhook smoke test.";
2185
+ const decision = args.decision?.trim() || resolveNotifyTestDefaultDecision(notifyChannel);
2051
2186
  const validation = args.validation?.trim() || "Triggered from local CLI.";
2052
- const nextAction = args["next-action"]?.trim() || "If this arrives, webhook delivery is working.";
2187
+ const nextAction = args["next-action"]?.trim() || resolveNotifyTestDefaultNextAction(notifyChannel);
2053
2188
  const bundle = {
2054
2189
  taskId,
2055
- runId: "run-feishu-smoke",
2190
+ runId,
2056
2191
  taskType,
2057
2192
  outcome: outcome,
2058
2193
  createdAt: new Date().toISOString(),
@@ -2061,7 +2196,13 @@ async function main() {
2061
2196
  repo,
2062
2197
  decision,
2063
2198
  validation,
2064
- nextAction
2199
+ nextAction,
2200
+ ...(notifyChannel === "app" ? {
2201
+ qAssignee: {
2202
+ displayName: "wangkai39",
2203
+ email: DEFAULT_FEEDBACK_RECIPIENT_EMAIL
2204
+ }
2205
+ } : {})
2065
2206
  },
2066
2207
  artifacts: {
2067
2208
  extras: []
@@ -2076,7 +2217,7 @@ async function main() {
2076
2217
  const publicationAttempts = args["review-url"] || args["change-id"]
2077
2218
  ? [{
2078
2219
  taskId,
2079
- runId: "run-feishu-smoke",
2220
+ runId,
2080
2221
  mode: "review_submit",
2081
2222
  status: "submitted",
2082
2223
  summary: args["review-summary"]?.trim() || "Review submission completed for 1/1 target(s).",
@@ -2085,12 +2226,50 @@ async function main() {
2085
2226
  ...(changeIds.length > 0 ? { changeId: changeIds[0], changeIds } : {})
2086
2227
  }]
2087
2228
  : [];
2088
- const reviewAwareNotifier = new FeishuNotifier({ config, publicationAttempts, format: config.delivery.feishu.format });
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
+ });
2089
2268
  const message = reviewAwareNotifier.previewMessage(bundle);
2090
- const envStatus = buildFeishuRuntimeStatus(config);
2269
+ const envStatus = buildFeishuRuntimeStatus(notifyConfig, notifyEnv);
2091
2270
  if (args["print-env-status"] === "true") {
2092
- console.log(JSON.stringify({ envStatus }, null, 2));
2093
- process.exitCode = envStatus.webhook || envStatus.webhooks ? 0 : 1;
2271
+ console.log(JSON.stringify({ channel: notifyChannel, envStatus }, null, 2));
2272
+ process.exitCode = isNotifyTestEnvReady(envStatus, notifyChannel) ? 0 : 1;
2094
2273
  return;
2095
2274
  }
2096
2275
  if (args["print-message"] === "true") {
@@ -2099,7 +2278,17 @@ async function main() {
2099
2278
  return;
2100
2279
  }
2101
2280
  const attempts = await reviewAwareNotifier.dispatch(bundle);
2102
- console.log(JSON.stringify({ envStatus, message, attempts }, null, 2));
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));
2103
2292
  process.exitCode = attempts.every((attempt) => attempt.status === "dispatched") ? 0 : 1;
2104
2293
  return;
2105
2294
  }