@sireai/optimus 0.1.40 → 0.1.43

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 (85) hide show
  1. package/dist/cli/optimus.js +425 -58
  2. package/dist/cli/optimus.js.map +1 -1
  3. package/dist/config/load-config.js +8 -1
  4. package/dist/config/load-config.js.map +1 -1
  5. package/dist/integrations/feishu/feishu-doc-service.d.ts +14 -2
  6. package/dist/integrations/feishu/feishu-doc-service.js +33 -12
  7. package/dist/integrations/feishu/feishu-doc-service.js.map +1 -1
  8. package/dist/integrations/feishu/feishu-document-reader.d.ts +33 -0
  9. package/dist/integrations/feishu/feishu-document-reader.js +597 -0
  10. package/dist/integrations/feishu/feishu-document-reader.js.map +1 -0
  11. package/dist/integrations/jira/jira-access-manager.d.ts +1 -0
  12. package/dist/integrations/jira/jira-access-manager.js +24 -0
  13. package/dist/integrations/jira/jira-access-manager.js.map +1 -1
  14. package/dist/integrations/jira/jira-cli.js +19 -3
  15. package/dist/integrations/jira/jira-cli.js.map +1 -1
  16. package/dist/integrations/jira/jira-submit.js +5 -18
  17. package/dist/integrations/jira/jira-submit.js.map +1 -1
  18. package/dist/integrations/sentry/sentry-cli.js +18 -2
  19. package/dist/integrations/sentry/sentry-cli.js.map +1 -1
  20. package/dist/problem-solving-core/codex/codex-runner.d.ts +2 -0
  21. package/dist/problem-solving-core/codex/codex-runner.js +46 -36
  22. package/dist/problem-solving-core/codex/codex-runner.js.map +1 -1
  23. package/dist/task-environment/delivery/delivery-warning-copy.d.ts +2 -0
  24. package/dist/task-environment/delivery/delivery-warning-copy.js +24 -0
  25. package/dist/task-environment/delivery/delivery-warning-copy.js.map +1 -0
  26. package/dist/task-environment/delivery/feishu-analysis-doc-service.d.ts +32 -1
  27. package/dist/task-environment/delivery/feishu-analysis-doc-service.js +351 -7
  28. package/dist/task-environment/delivery/feishu-analysis-doc-service.js.map +1 -1
  29. package/dist/task-environment/delivery/feishu-card-primitives.d.ts +33 -0
  30. package/dist/task-environment/delivery/feishu-card-primitives.js +95 -0
  31. package/dist/task-environment/delivery/feishu-card-primitives.js.map +1 -0
  32. package/dist/task-environment/delivery/feishu-card-renderer.d.ts +1 -0
  33. package/dist/task-environment/delivery/feishu-card-renderer.js +38 -73
  34. package/dist/task-environment/delivery/feishu-card-renderer.js.map +1 -1
  35. package/dist/task-environment/delivery/feishu-content/feishu-copy-config.js +1 -0
  36. package/dist/task-environment/delivery/feishu-content/feishu-copy-config.js.map +1 -1
  37. package/dist/task-environment/delivery/feishu-notifier.d.ts +5 -0
  38. package/dist/task-environment/delivery/feishu-notifier.js +107 -38
  39. package/dist/task-environment/delivery/feishu-notifier.js.map +1 -1
  40. package/dist/task-environment/delivery/feishu-templates/analysis-message-template.js +3 -0
  41. package/dist/task-environment/delivery/feishu-templates/analysis-message-template.js.map +1 -1
  42. package/dist/task-environment/delivery/pm-feishu-card-renderer.d.ts +19 -0
  43. package/dist/task-environment/delivery/pm-feishu-card-renderer.js +177 -0
  44. package/dist/task-environment/delivery/pm-feishu-card-renderer.js.map +1 -0
  45. package/dist/task-environment/delivery/sentry-feishu-card-renderer.d.ts +1 -0
  46. package/dist/task-environment/delivery/sentry-feishu-card-renderer.js +33 -70
  47. package/dist/task-environment/delivery/sentry-feishu-card-renderer.js.map +1 -1
  48. package/dist/task-environment/delivery/task-delivery-service.d.ts +6 -1
  49. package/dist/task-environment/delivery/task-delivery-service.js +142 -10
  50. package/dist/task-environment/delivery/task-delivery-service.js.map +1 -1
  51. package/dist/task-environment/delivery/task-publication-service.d.ts +1 -0
  52. package/dist/task-environment/delivery/task-publication-service.js +22 -0
  53. package/dist/task-environment/delivery/task-publication-service.js.map +1 -1
  54. package/dist/task-environment/intake/manual-problem-intake.js +54 -0
  55. package/dist/task-environment/intake/manual-problem-intake.js.map +1 -1
  56. package/dist/task-environment/orchestration/execution-context-assembler.d.ts +1 -0
  57. package/dist/task-environment/orchestration/execution-context-assembler.js +58 -3
  58. package/dist/task-environment/orchestration/execution-context-assembler.js.map +1 -1
  59. package/dist/task-environment/orchestration/task-orchestrator.d.ts +1 -0
  60. package/dist/task-environment/orchestration/task-orchestrator.js +41 -5
  61. package/dist/task-environment/orchestration/task-orchestrator.js.map +1 -1
  62. package/dist/task-environment/orchestration/task-package-inputs.d.ts +2 -0
  63. package/dist/task-environment/orchestration/task-package-inputs.js +83 -0
  64. package/dist/task-environment/orchestration/task-package-inputs.js.map +1 -0
  65. package/dist/task-environment/orchestration/task-runtime-policy.d.ts +4 -0
  66. package/dist/task-environment/orchestration/task-runtime-policy.js +38 -0
  67. package/dist/task-environment/orchestration/task-runtime-policy.js.map +1 -0
  68. package/dist/task-environment/result-paths.d.ts +9 -0
  69. package/dist/task-environment/result-paths.js +36 -0
  70. package/dist/task-environment/result-paths.js.map +1 -0
  71. package/dist/types.d.ts +45 -1
  72. package/package.json +1 -1
  73. package/task-harnesses/bugfix/ACCEPT.md +3 -2
  74. package/task-harnesses/bugfix/CONSTRAINTS.md +10 -4
  75. package/task-harnesses/bugfix/EVOLUTION.md +2 -8
  76. package/task-harnesses/bugfix/ROLE.md +7 -11
  77. package/task-harnesses/bugfix/STANDARD.md +81 -0
  78. package/task-harnesses/pm/ACCEPT.md +66 -0
  79. package/task-harnesses/pm/CONSTRAINTS.md +60 -0
  80. package/task-harnesses/pm/CONTEXT.md +50 -0
  81. package/task-harnesses/pm/EVOLUTION.md +77 -0
  82. package/task-harnesses/pm/ROLE.md +44 -0
  83. package/task-harnesses/pm/STANDARD.md +483 -0
  84. package/task-harnesses/pm/manifest.json +13 -0
  85. package/task-harnesses/registry.json +4 -0
@@ -17,6 +17,7 @@ import { classifyCodexFailureCategory } from "../problem-solving-core/codex/code
17
17
  import { createManualEventContent, createManualProblemEvent } from "../task-environment/intake/manual-problem-intake.js";
18
18
  import { OptimusLogger } from "../task-environment/observability/logger.js";
19
19
  import { TaskOrchestrator } from "../task-environment/orchestration/task-orchestrator.js";
20
+ import { getTaskRuntimePolicy } from "../task-environment/orchestration/task-runtime-policy.js";
20
21
  import { TriageRunner } from "../task-environment/orchestration/triage-runner.js";
21
22
  import { OptimusRuntime } from "../task-environment/runtime/optimus-runtime.js";
22
23
  import { SQLiteTaskStore } from "../task-environment/storage/sqlite-task-store.js";
@@ -25,9 +26,11 @@ import { buildTaskExecutionMetrics, formatExecutionMetricsCompact } from "../tas
25
26
  import { FeishuNotifier } from "../task-environment/delivery/feishu-notifier.js";
26
27
  import { TaskDeliveryDispatcher } from "../task-environment/delivery/task-delivery-dispatcher.js";
27
28
  import { TaskDeliveryService } from "../task-environment/delivery/task-delivery-service.js";
29
+ import { formatDeliveryWarningsWithDescriptions } from "../task-environment/delivery/delivery-warning-copy.js";
28
30
  import { FeishuAnalysisDocService } from "../task-environment/delivery/feishu-analysis-doc-service.js";
29
31
  import { TaskPublicationService } from "../task-environment/delivery/task-publication-service.js";
30
32
  import { FeishuClient } from "../integrations/feishu/feishu-client.js";
33
+ import { FeishuDocumentReader, mergeFeishuReferenceMaterials } from "../integrations/feishu/feishu-document-reader.js";
31
34
  import { FeishuUserService } from "../integrations/feishu/feishu-user-service.js";
32
35
  import { createFeedbackReport } from "./feedback.js";
33
36
  import { DEFAULT_FEEDBACK_RECIPIENT_EMAIL, FeishuFeedbackDeliveryService, persistFeedbackDeliveryResult, resolveDefaultFeedbackRecipientFallbackOpenId } from "./feedback-delivery.js";
@@ -63,6 +66,9 @@ function renderSetupResult(result) {
63
66
  lines.push("Repository: not configured");
64
67
  }
65
68
  lines.push(`Delivery: ${result.deliveryChannels.join(", ")}`);
69
+ if (result.feishuDefaultRecipientEmail) {
70
+ lines.push(`Feishu Recipient: ${result.feishuDefaultRecipientEmail}`);
71
+ }
66
72
  lines.push(`Jira: ${result.jiraEnabled ? "enabled" : "disabled"}`);
67
73
  lines.push(`Sentry: ${result.sentryEnabled ? "enabled" : "disabled"}`);
68
74
  const providerLabel = result.codexAuth.provider ? ` | Provider: ${result.codexAuth.provider.displayName} (${result.codexAuth.provider.id})` : "";
@@ -316,23 +322,26 @@ function renderCommandHelp(command) {
316
322
  "",
317
323
  "Usage:",
318
324
  " optimus submit --title <title> --description <text> [options]",
319
- " optimus submit --title <title> --description-file <path> [options]",
320
- " printf '%s' '<text>' | optimus submit --title <title> --description-stdin [options]",
325
+ " optimus submit --title <title> --file <path> [options]",
326
+ " optimus submit --url <url> [--title <title>] [options]",
321
327
  "",
322
328
  "Required:",
323
- " --title <title> Task title",
324
- " --description <text> Task description",
329
+ " exactly one of:",
330
+ " --description <text> Task text",
331
+ " --file <path> Read task text from a file",
332
+ " --url <url> Read task text from a remote document URL",
333
+ " --title <title> Task title; optional only when --url resolves a document title",
325
334
  "",
326
335
  "Common options:",
327
- " --description-file <path> Read task description from a file",
328
- " --description-stdin Read task description from stdin",
329
336
  " --repo <alias> Target registered repository alias",
330
337
  " --task-type <type> Task type, default bugfix",
331
338
  " --source-ref <id> External issue key or source reference",
332
339
  " --allow-duplicate Allow duplicate submission",
333
340
  "",
334
341
  "Example:",
335
- " optimus submit --title \"Login crash\" --description \"Crash on startup\" --repo ohos-pre"
342
+ " optimus submit --title \"Login crash\" --description \"Crash on startup\" --repo ohos-pre",
343
+ " optimus submit --title \"Onboarding prototype\" --file ./docs/onboarding-prd.md --task-type pm",
344
+ " optimus submit --url https://mi.feishu.cn/docx/ABC123 --task-type pm"
336
345
  ].join("\n"),
337
346
  feedback: [
338
347
  "optimus feedback",
@@ -830,6 +839,7 @@ async function promptForStartupSelfUpdate(currentVersion, latestVersion) {
830
839
  }
831
840
  }
832
841
  async function maybeHandleStartupSelfUpdate(input) {
842
+ const suppressSubmitOutput = input.command === "submit";
833
843
  const check = await checkForSelfUpdate({
834
844
  command: input.command,
835
845
  config: input.config,
@@ -846,47 +856,54 @@ async function maybeHandleStartupSelfUpdate(input) {
846
856
  const nextCheckAt = Number.isFinite(lastCheckedAtMs)
847
857
  ? new Date(lastCheckedAtMs + intervalHours * 60 * 60 * 1000).toISOString()
848
858
  : null;
849
- await input.logger.info("self_update.cached", {
859
+ if (!suppressSubmitOutput) {
860
+ await input.logger.info("self_update.cached", {
861
+ command: input.command,
862
+ currentVersion: check.currentVersion,
863
+ latestVersion: check.latestVersion ?? null,
864
+ channel: check.channel,
865
+ installSource: check.installSource,
866
+ intervalHours,
867
+ lastCheckedAt,
868
+ nextCheckAt,
869
+ realtimeCheckSkipped: true
870
+ });
871
+ }
872
+ }
873
+ return { handled: false };
874
+ }
875
+ if (check.reason === "check_failed") {
876
+ if (!suppressSubmitOutput) {
877
+ await input.logger.warn("self_update.check_failed", {
850
878
  command: input.command,
851
- currentVersion: check.currentVersion,
852
- latestVersion: check.latestVersion ?? null,
853
- channel: check.channel,
854
- installSource: check.installSource,
855
- intervalHours,
856
- lastCheckedAt,
857
- nextCheckAt,
858
- realtimeCheckSkipped: true
879
+ reason: check.state.lastError ?? "unknown"
859
880
  });
860
881
  }
861
882
  return { handled: false };
862
883
  }
863
- if (check.reason === "check_failed") {
864
- await input.logger.warn("self_update.check_failed", {
884
+ if (!suppressSubmitOutput) {
885
+ await input.logger.info(check.updateAvailable ? "self_update.available" : "self_update.current", {
865
886
  command: input.command,
866
- reason: check.state.lastError ?? "unknown"
887
+ currentVersion: check.currentVersion,
888
+ latestVersion: check.latestVersion ?? null,
889
+ channel: check.channel,
890
+ installSource: check.installSource
867
891
  });
868
- return { handled: false };
869
892
  }
870
- await input.logger.info(check.updateAvailable ? "self_update.available" : "self_update.current", {
871
- command: input.command,
872
- currentVersion: check.currentVersion,
873
- latestVersion: check.latestVersion ?? null,
874
- channel: check.channel,
875
- installSource: check.installSource
876
- });
877
893
  if (!check.updateAvailable || !check.latestVersion) {
878
894
  return { handled: false };
879
895
  }
880
896
  if (check.reason === "skipped_version") {
881
- await input.logger.info("self_update.skipped_version", {
882
- command: input.command,
883
- currentVersion: check.currentVersion,
884
- latestVersion: check.latestVersion
885
- });
897
+ if (!suppressSubmitOutput) {
898
+ await input.logger.info("self_update.skipped_version", {
899
+ command: input.command,
900
+ currentVersion: check.currentVersion,
901
+ latestVersion: check.latestVersion
902
+ });
903
+ }
886
904
  return { handled: false };
887
905
  }
888
906
  if (input.command !== "start") {
889
- console.log(`[optimus] update available ${check.currentVersion} -> ${check.latestVersion}. Run \`optimus upgrade\`.`);
890
907
  return { handled: false };
891
908
  }
892
909
  if (selfUpdateConfig.mode === "prompt") {
@@ -1197,6 +1214,8 @@ async function promptSetupAnswers(defaults) {
1197
1214
  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.");
1198
1215
  printSetupHint("Feishu webhook: optional; leave empty to use direct Feishu app delivery only.");
1199
1216
  const feishuWebhook = resolveOptionalTextAnswer(await ask(renderSetupPrompt("Optional", "Feishu webhook", defaults.feishuWebhook ? "configured" : "")), defaults.feishuWebhook ?? undefined);
1217
+ printSetupHint("Feishu default recipient email: optional; used as a fallback for direct Feishu app delivery when no webhook and no task assignee are available.");
1218
+ const feishuRecipientEmail = resolveOptionalTextAnswer(await ask(renderSetupPrompt("Optional", "Feishu default recipient email", defaults.feishuRecipientEmail ?? "")), defaults.feishuRecipientEmail ?? undefined);
1200
1219
  printSetupSection("Jira", "Optional. Turn this on only if you want this machine to read Jira issues or write Jira comments.");
1201
1220
  printSetupHint("If disabled, Optimus still works; Jira commands just stay unavailable on this machine.");
1202
1221
  const enableJiraInput = (await ask(renderSetupPrompt("Optional integration", "Enable Jira integration", defaults.enableJira ? "Y/n" : "y/N"))).trim().toLowerCase();
@@ -1235,6 +1254,7 @@ async function promptSetupAnswers(defaults) {
1235
1254
  ...(codexProviderBaseUrl ? { codexProviderBaseUrl } : {}),
1236
1255
  ...(codexProviderApiKeyEnvName ? { codexProviderApiKeyEnvName } : {}),
1237
1256
  ...(feishuWebhook !== undefined ? { feishuWebhook } : {}),
1257
+ ...(feishuRecipientEmail !== undefined ? { feishuRecipientEmail } : {}),
1238
1258
  enableJira,
1239
1259
  ...(jiraBaseUrl ? { jiraBaseUrl } : {}),
1240
1260
  ...(jiraPersonalToken ? { jiraPersonalToken } : {}),
@@ -1286,6 +1306,7 @@ async function resolveDefaultSetupAnswers() {
1286
1306
  ...(provider?.baseUrl ? { codexProviderBaseUrl: provider.baseUrl } : {}),
1287
1307
  ...(provider?.apiKeyEnvName ? { codexProviderApiKeyEnvName: provider.apiKeyEnvName } : {}),
1288
1308
  ...(config.delivery.feishu.webhook ? { feishuWebhook: config.delivery.feishu.webhook } : {}),
1309
+ ...(config.delivery.feishu.defaultRecipient?.email ? { feishuRecipientEmail: config.delivery.feishu.defaultRecipient.email } : {}),
1289
1310
  enableJira: config.jira.enabled,
1290
1311
  ...(config.jira.baseUrl ? { jiraBaseUrl: config.jira.baseUrl } : {}),
1291
1312
  ...(config.jira.personalToken ? { jiraPersonalToken: config.jira.personalToken } : {}),
@@ -1487,6 +1508,19 @@ function buildSetupConfig(answers, rawConfig) {
1487
1508
  delete config.delivery.feishu.webhook;
1488
1509
  delete config.delivery.feishu.webhooks;
1489
1510
  }
1511
+ if (typeof answers.feishuRecipientEmail === "string") {
1512
+ config.delivery.feishu.defaultRecipient = {
1513
+ email: answers.feishuRecipientEmail
1514
+ };
1515
+ }
1516
+ else if (answers.feishuRecipientEmail === null) {
1517
+ delete config.delivery.feishu.defaultRecipient;
1518
+ }
1519
+ else if (rawConfig?.delivery?.feishu?.defaultRecipient) {
1520
+ config.delivery.feishu.defaultRecipient = {
1521
+ ...rawConfig.delivery.feishu.defaultRecipient
1522
+ };
1523
+ }
1490
1524
  config.jira.enabled = answers.enableJira;
1491
1525
  if (answers.enableJira) {
1492
1526
  config.jira.baseUrl = answers.jiraBaseUrl ?? config.jira.baseUrl;
@@ -2044,6 +2078,7 @@ async function runSetup(args) {
2044
2078
  }
2045
2079
  } : {}),
2046
2080
  deliveryChannels: config.delivery.channels,
2081
+ ...(config.delivery.feishu.defaultRecipient?.email ? { feishuDefaultRecipientEmail: config.delivery.feishu.defaultRecipient.email } : {}),
2047
2082
  jiraEnabled: config.jira.enabled,
2048
2083
  sentryEnabled: config.sentry.enabled,
2049
2084
  codexAuth: {
@@ -2065,6 +2100,7 @@ function inferArtifactDir(artifacts, bundle) {
2065
2100
  const candidates = [
2066
2101
  bundle?.artifacts.resultMd,
2067
2102
  bundle?.artifacts.patchDiff,
2103
+ bundle?.artifacts.prototypeHtml,
2068
2104
  ...artifacts.map((artifact) => artifact.path)
2069
2105
  ].filter((value) => typeof value === "string" && value.trim().length > 0);
2070
2106
  const artifactPath = candidates.find((candidate) => candidate.includes(`${join(".optimus", "artifacts")}${candidate.includes("\\") ? "\\" : "/"}`))
@@ -2110,6 +2146,7 @@ async function buildReplayExecutionContext(input) {
2110
2146
  : undefined;
2111
2147
  return {
2112
2148
  taskRootDir,
2149
+ runtimePolicy: getTaskRuntimePolicy(input.taskType),
2113
2150
  addresses: {
2114
2151
  mode,
2115
2152
  workspaceDir,
@@ -2137,6 +2174,8 @@ function mergeReplayBundle(rebuilt, existing) {
2137
2174
  ...existing.summary,
2138
2175
  ...rebuilt.summary,
2139
2176
  ...(rebuilt.summary.analysisDocUrl ? {} : existing.summary.analysisDocUrl ? { analysisDocUrl: existing.summary.analysisDocUrl } : {}),
2177
+ ...(rebuilt.summary.prototypePreviewUrl ? {} : existing.summary.prototypePreviewUrl ? { prototypePreviewUrl: existing.summary.prototypePreviewUrl } : {}),
2178
+ ...(rebuilt.summary.prototypeDownloadUrl ? {} : existing.summary.prototypeDownloadUrl ? { prototypeDownloadUrl: existing.summary.prototypeDownloadUrl } : {}),
2140
2179
  ...(rebuilt.summary.repo ? {} : existing.summary.repo ? { repo: existing.summary.repo } : {}),
2141
2180
  ...(rebuilt.summary.sourceSystem ? {} : existing.summary.sourceSystem ? { sourceSystem: existing.summary.sourceSystem } : {}),
2142
2181
  ...(rebuilt.summary.sourceRef ? {} : existing.summary.sourceRef ? { sourceRef: existing.summary.sourceRef } : {}),
@@ -2260,30 +2299,309 @@ async function resolveLongTextArg(args, keys) {
2260
2299
  return value;
2261
2300
  }
2262
2301
  }
2263
- const filePath = keys.file ? args[keys.file]?.trim() : undefined;
2264
- if (filePath) {
2265
- const content = (await readFile(filePath, "utf8")).trim();
2266
- if (content) {
2267
- return content;
2302
+ const fileKeys = typeof keys.file === "string" ? [keys.file] : (keys.file ?? []);
2303
+ for (const key of fileKeys) {
2304
+ const filePath = args[key]?.trim();
2305
+ if (filePath) {
2306
+ const content = (await readFile(filePath, "utf8")).trim();
2307
+ if (content) {
2308
+ return content;
2309
+ }
2268
2310
  }
2269
2311
  }
2270
- if (keys.stdin && args[keys.stdin] === "true") {
2271
- const content = (await readTextFromStdin()).trim();
2272
- if (content) {
2273
- return content;
2312
+ const stdinKeys = typeof keys.stdin === "string" ? [keys.stdin] : (keys.stdin ?? []);
2313
+ for (const key of stdinKeys) {
2314
+ if (args[key] === "true") {
2315
+ const content = (await readTextFromStdin()).trim();
2316
+ if (content) {
2317
+ return content;
2318
+ }
2274
2319
  }
2275
2320
  }
2276
2321
  return undefined;
2277
2322
  }
2278
2323
  function renderShellQuotingHint(command) {
2324
+ const requirementHint = command === "submit"
2325
+ ? "Provide exactly one of --description, --file, or --url. --title is optional only when --url resolves a document title."
2326
+ : "Use single quotes, or pass the text with --description-file / --description-stdin.";
2279
2327
  return [
2280
- `${command} requires both --title and --description.`,
2328
+ command === "submit"
2329
+ ? "submit requires exactly one of --description, --file, or --url, plus --title unless --url resolves a document title."
2330
+ : "feedback requires both --title and --description.",
2281
2331
  "Tip: shell metacharacters like `...`, $(...), and $VAR may be expanded before Optimus starts.",
2282
- "Use single quotes, or pass the text with --description-file / --description-stdin.",
2332
+ requirementHint,
2283
2333
  `Example: optimus ${command} --title "..." --description 'literal text with \`optimus setup\` inside'`,
2284
- `Example: printf '%s' 'literal text with \`optimus setup\` inside' | optimus ${command} --title "..." --description-stdin`
2334
+ ...(command === "submit"
2335
+ ? [
2336
+ `Example: optimus submit --title "..." --file ./task.md`,
2337
+ `Example: optimus submit --url https://mi.feishu.cn/docx/ABC123`
2338
+ ]
2339
+ : [`Example: printf '%s' 'literal text with \`optimus setup\` inside' | optimus feedback --title "..." --description-stdin`])
2285
2340
  ].join("\n");
2286
2341
  }
2342
+ function isFeishuDocumentUrl(value) {
2343
+ return /^https?:\/\/(?:[\w-]+\.)?feishu\.cn\//iu.test(value.trim())
2344
+ || /^https?:\/\/(?:[\w-]+\.)?larksuite\.com\//iu.test(value.trim());
2345
+ }
2346
+ function extractTitleFromMarkdown(markdown) {
2347
+ const match = markdown.match(/<title>([\s\S]*?)<\/title>/iu);
2348
+ const title = match?.[1]?.trim();
2349
+ return title ? title : undefined;
2350
+ }
2351
+ function parseHtmlAttributeMap(fragment) {
2352
+ const attributes = {};
2353
+ const pattern = /([\w:-]+)="([^"]*)"/gu;
2354
+ let match;
2355
+ while ((match = pattern.exec(fragment)) !== null) {
2356
+ const key = match[1];
2357
+ const value = match[2];
2358
+ if (key !== undefined && value !== undefined) {
2359
+ attributes[key] = value;
2360
+ }
2361
+ }
2362
+ return attributes;
2363
+ }
2364
+ function extractFeishuReferenceMaterials(markdown, media, sourceUrl) {
2365
+ const references = [];
2366
+ const seen = new Set();
2367
+ let embeddedImageCount = 0;
2368
+ let embeddedWhiteboardCount = 0;
2369
+ let embeddedDocCount = 0;
2370
+ let embeddedSheetCount = 0;
2371
+ let embeddedBitableCount = 0;
2372
+ let embeddedFileCount = 0;
2373
+ let rawLinkCount = 0;
2374
+ const pushReference = (reference) => {
2375
+ const identityParts = [
2376
+ reference.type,
2377
+ reference.url ?? "",
2378
+ reference.token ?? "",
2379
+ reference.content ?? ""
2380
+ ];
2381
+ const key = identityParts.some((part) => part.length > 0)
2382
+ ? identityParts.join("|")
2383
+ : [reference.type, reference.title].join("|");
2384
+ if (seen.has(key)) {
2385
+ return;
2386
+ }
2387
+ seen.add(key);
2388
+ references.push(reference);
2389
+ };
2390
+ if (Array.isArray(media)) {
2391
+ for (const entry of media) {
2392
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
2393
+ continue;
2394
+ }
2395
+ const record = entry;
2396
+ const token = typeof record.token === "string" ? record.token.trim() : "";
2397
+ const type = typeof record.type === "string" ? record.type.trim() : "";
2398
+ if (!token || !type) {
2399
+ continue;
2400
+ }
2401
+ if (type === "whiteboard") {
2402
+ embeddedWhiteboardCount += 1;
2403
+ pushReference({
2404
+ type: "whiteboard",
2405
+ title: `Embedded whiteboard ${embeddedWhiteboardCount}`,
2406
+ token,
2407
+ sourceType: "feishu_media"
2408
+ });
2409
+ continue;
2410
+ }
2411
+ embeddedFileCount += 1;
2412
+ pushReference({
2413
+ type: "file",
2414
+ title: `Embedded ${type} ${embeddedFileCount}`,
2415
+ token,
2416
+ sourceType: "feishu_media"
2417
+ });
2418
+ }
2419
+ }
2420
+ const markdownImagePattern = /!\[(.*?)\]\((https?:\/\/[^)\s]+)\)/gu;
2421
+ let imageMatch;
2422
+ while ((imageMatch = markdownImagePattern.exec(markdown)) !== null) {
2423
+ embeddedImageCount += 1;
2424
+ const alt = imageMatch[1]?.trim();
2425
+ const imageUrl = imageMatch[2]?.trim();
2426
+ if (!imageUrl) {
2427
+ continue;
2428
+ }
2429
+ pushReference({
2430
+ type: "image",
2431
+ title: alt || `Embedded image ${embeddedImageCount}`,
2432
+ url: imageUrl,
2433
+ sourceType: "feishu_markdown"
2434
+ });
2435
+ }
2436
+ const htmlImagePattern = /<img\b([^>]*)\/?>/gu;
2437
+ let htmlImageMatch;
2438
+ while ((htmlImageMatch = htmlImagePattern.exec(markdown)) !== null) {
2439
+ embeddedImageCount += 1;
2440
+ const attrs = parseHtmlAttributeMap(htmlImageMatch[1] ?? "");
2441
+ pushReference({
2442
+ type: "image",
2443
+ title: attrs.name?.trim() || `Embedded image ${embeddedImageCount}`,
2444
+ ...(attrs.src?.trim() ? { token: attrs.src.trim() } : {}),
2445
+ ...(attrs.mime?.trim() ? { mimeType: attrs.mime.trim() } : {}),
2446
+ sourceType: "feishu_markdown"
2447
+ });
2448
+ }
2449
+ const whiteboardPattern = /<whiteboard\b([^>]*)>(?:<\/whiteboard>)?/gu;
2450
+ let whiteboardMatch;
2451
+ while ((whiteboardMatch = whiteboardPattern.exec(markdown)) !== null) {
2452
+ embeddedWhiteboardCount += 1;
2453
+ const attrs = parseHtmlAttributeMap(whiteboardMatch[1] ?? "");
2454
+ pushReference({
2455
+ type: "whiteboard",
2456
+ title: `Embedded whiteboard ${embeddedWhiteboardCount}`,
2457
+ ...(attrs.token?.trim() ? { token: attrs.token.trim() } : {}),
2458
+ sourceType: "feishu_markdown"
2459
+ });
2460
+ }
2461
+ const citeDocPattern = /<cite\b([^>]*)><\/cite>/gu;
2462
+ let citeMatch;
2463
+ while ((citeMatch = citeDocPattern.exec(markdown)) !== null) {
2464
+ const attrs = parseHtmlAttributeMap(citeMatch[1] ?? "");
2465
+ if (attrs.type !== "doc") {
2466
+ continue;
2467
+ }
2468
+ const fileType = attrs["file-type"]?.trim();
2469
+ const title = attrs.title?.trim();
2470
+ const token = attrs["doc-id"]?.trim();
2471
+ const referenceType = fileType === "wiki" || fileType === "docx"
2472
+ ? "doc"
2473
+ : fileType === "sheet"
2474
+ ? "sheet"
2475
+ : "doc";
2476
+ if (referenceType === "sheet") {
2477
+ embeddedSheetCount += 1;
2478
+ }
2479
+ else {
2480
+ embeddedDocCount += 1;
2481
+ }
2482
+ pushReference({
2483
+ type: referenceType,
2484
+ title: title || (referenceType === "sheet" ? `Referenced sheet ${embeddedSheetCount}` : `Referenced document ${embeddedDocCount}`),
2485
+ ...(token ? { token } : {}),
2486
+ sourceType: "feishu_cite"
2487
+ });
2488
+ }
2489
+ const sheetPattern = /<sheet\b([^>]*)\/?>/gu;
2490
+ let sheetMatch;
2491
+ while ((sheetMatch = sheetPattern.exec(markdown)) !== null) {
2492
+ embeddedSheetCount += 1;
2493
+ const attrs = parseHtmlAttributeMap(sheetMatch[1] ?? "");
2494
+ pushReference({
2495
+ type: "sheet",
2496
+ title: `Embedded sheet ${embeddedSheetCount}`,
2497
+ ...(attrs.token?.trim() ? { token: attrs.token.trim() } : {}),
2498
+ sourceType: "feishu_markdown"
2499
+ });
2500
+ }
2501
+ const bitablePattern = /<bitable\b([^>]*)\/?>/gu;
2502
+ let bitableMatch;
2503
+ while ((bitableMatch = bitablePattern.exec(markdown)) !== null) {
2504
+ embeddedBitableCount += 1;
2505
+ const attrs = parseHtmlAttributeMap(bitableMatch[1] ?? "");
2506
+ pushReference({
2507
+ type: "bitable",
2508
+ title: `Embedded bitable ${embeddedBitableCount}`,
2509
+ ...(attrs.token?.trim() ? { token: attrs.token.trim() } : {}),
2510
+ sourceType: "feishu_markdown"
2511
+ });
2512
+ }
2513
+ const filePattern = /<file\b([^>]*)\/?>/gu;
2514
+ let fileMatch;
2515
+ while ((fileMatch = filePattern.exec(markdown)) !== null) {
2516
+ embeddedFileCount += 1;
2517
+ const attrs = parseHtmlAttributeMap(fileMatch[1] ?? "");
2518
+ pushReference({
2519
+ type: "file",
2520
+ title: attrs.name?.trim() || `Embedded file ${embeddedFileCount}`,
2521
+ ...(attrs.token?.trim() ? { token: attrs.token.trim() } : {}),
2522
+ sourceType: "feishu_markdown"
2523
+ });
2524
+ }
2525
+ const rawUrlPattern = /https?:\/\/[^\s<>"')]+/gu;
2526
+ let urlMatch;
2527
+ while ((urlMatch = rawUrlPattern.exec(markdown)) !== null) {
2528
+ const link = urlMatch[0];
2529
+ if (link === sourceUrl || /https?:\/\/(?:[\w-]+\.)?feishu\.cn\/file\//iu.test(link)) {
2530
+ continue;
2531
+ }
2532
+ rawLinkCount += 1;
2533
+ pushReference({
2534
+ type: "link",
2535
+ title: rawLinkCount === 1 ? "Referenced link" : `Referenced link ${rawLinkCount}`,
2536
+ url: link,
2537
+ sourceType: "feishu_markdown"
2538
+ });
2539
+ }
2540
+ return references.length > 0 ? references : undefined;
2541
+ }
2542
+ async function resolveTaskTextFromUrl(url) {
2543
+ const trimmedUrl = url.trim();
2544
+ if (!trimmedUrl) {
2545
+ throw new Error("Task document URL is empty.");
2546
+ }
2547
+ if (!isFeishuDocumentUrl(trimmedUrl)) {
2548
+ throw new Error("submit currently supports document URLs from Feishu/Lark only.");
2549
+ }
2550
+ const reader = new FeishuDocumentReader();
2551
+ try {
2552
+ const resolved = await reader.readFromUrl(trimmedUrl);
2553
+ const referenceMaterials = mergeFeishuReferenceMaterials(extractFeishuReferenceMaterials(resolved.content, undefined, trimmedUrl), resolved.referenceMaterials);
2554
+ return {
2555
+ content: resolved.content,
2556
+ ...(resolved.title ? { title: resolved.title } : {}),
2557
+ sourceType: resolved.sourceType,
2558
+ ...(resolved.sourceDocumentType ? { sourceDocumentType: resolved.sourceDocumentType } : {}),
2559
+ ...(referenceMaterials ? { referenceMaterials } : {})
2560
+ };
2561
+ }
2562
+ catch (builtinError) {
2563
+ let stdout;
2564
+ try {
2565
+ const result = await execFileAsync("feishu", ["fetch", trimmedUrl], {
2566
+ maxBuffer: 10 * 1024 * 1024
2567
+ });
2568
+ stdout = result.stdout;
2569
+ }
2570
+ catch (error) {
2571
+ const execError = error;
2572
+ const cliDetail = execError?.code === "ENOENT"
2573
+ ? "the `feishu` CLI is not installed or not on PATH"
2574
+ : execError?.stderr?.trim() || execError?.stdout?.trim() || execError?.message || "unknown error";
2575
+ const builtinDetail = builtinError instanceof Error ? builtinError.message : String(builtinError);
2576
+ throw new Error(`submit could not fetch the Feishu document via built-in app (${builtinDetail}) or via \`feishu fetch\` (${cliDetail}).`);
2577
+ }
2578
+ let payload;
2579
+ try {
2580
+ payload = JSON.parse(stdout);
2581
+ }
2582
+ catch {
2583
+ throw new Error("submit received non-JSON output from `feishu fetch`.");
2584
+ }
2585
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
2586
+ throw new Error("submit received an invalid response from `feishu fetch`.");
2587
+ }
2588
+ const record = payload;
2589
+ const markdown = typeof record.markdown === "string" ? record.markdown.trim() : "";
2590
+ const title = (typeof record.title === "string" ? record.title.trim() : "") || extractTitleFromMarkdown(markdown);
2591
+ const sourceDocumentType = typeof record.type === "string" ? record.type.trim() : undefined;
2592
+ const referenceMaterials = extractFeishuReferenceMaterials(markdown, record.media, trimmedUrl);
2593
+ if (!markdown) {
2594
+ throw new Error("submit fetched the document URL, but it did not return readable markdown content.");
2595
+ }
2596
+ return {
2597
+ content: markdown,
2598
+ ...(title ? { title } : {}),
2599
+ sourceType: "feishu_doc",
2600
+ ...(sourceDocumentType ? { sourceDocumentType } : {}),
2601
+ ...(referenceMaterials ? { referenceMaterials } : {})
2602
+ };
2603
+ }
2604
+ }
2287
2605
  async function main() {
2288
2606
  const argv = process.argv.slice(2);
2289
2607
  const [firstArg, ...rest] = argv;
@@ -2474,19 +2792,57 @@ async function main() {
2474
2792
  }
2475
2793
  if (command === "submit") {
2476
2794
  const args = parseArgs(commandArgs);
2477
- const title = args.title?.trim();
2478
- const description = await resolveLongTextArg(args, {
2479
- inline: ["desc", "description"],
2480
- file: "description-file",
2481
- stdin: "description-stdin"
2482
- });
2483
- if (!title || !description) {
2795
+ const explicitTitle = args.title?.trim();
2796
+ const taskType = args["task-type"]?.trim() || "bugfix";
2797
+ const inlineDescription = args.desc?.trim() || args.description?.trim();
2798
+ const filePath = args.file?.trim();
2799
+ const url = args.url?.trim();
2800
+ const inputSourceCount = [inlineDescription, filePath, url].filter(Boolean).length;
2801
+ if (inputSourceCount !== 1) {
2802
+ console.error(renderShellQuotingHint("submit"));
2803
+ process.exitCode = 1;
2804
+ return;
2805
+ }
2806
+ let description = "";
2807
+ let resolvedTitle = explicitTitle;
2808
+ let remoteSourceType;
2809
+ let remoteDocumentTitle;
2810
+ let remoteDocumentType;
2811
+ let referenceMaterials;
2812
+ if (inlineDescription) {
2813
+ description = inlineDescription;
2814
+ }
2815
+ else if (filePath) {
2816
+ description = (await readFile(filePath, "utf8")).trim();
2817
+ }
2818
+ else {
2819
+ try {
2820
+ const remote = await runWithTerminalLoading("Resolving remote requirement document", async () => await resolveTaskTextFromUrl(url));
2821
+ description = remote.content;
2822
+ resolvedTitle = resolvedTitle || remote.title;
2823
+ remoteSourceType = remote.sourceType;
2824
+ remoteDocumentTitle = remote.title;
2825
+ remoteDocumentType = remote.sourceDocumentType;
2826
+ referenceMaterials = remote.referenceMaterials;
2827
+ }
2828
+ catch (error) {
2829
+ console.error(error instanceof Error ? error.message : String(error));
2830
+ process.exitCode = 1;
2831
+ return;
2832
+ }
2833
+ }
2834
+ if (!resolvedTitle || !description) {
2835
+ console.error(renderShellQuotingHint("submit"));
2836
+ process.exitCode = 1;
2837
+ return;
2838
+ }
2839
+ if (!description) {
2484
2840
  console.error(renderShellQuotingHint("submit"));
2485
2841
  process.exitCode = 1;
2486
2842
  return;
2487
2843
  }
2488
2844
  await store.init();
2489
- const repositorySelection = await resolveSubmissionRepositorySelector(args, store, config);
2845
+ const repositorySelection = await resolveSubmissionRepositorySelector(taskType, args, store, config);
2490
2846
  if (!repositorySelection.ok) {
2491
2847
  console.error(repositorySelection.reason);
2492
2848
  process.exitCode = 1;
@@ -2494,11 +2850,18 @@ async function main() {
2494
2850
  }
2495
2851
  const event = createManualProblemEvent({
2496
2852
  ...args,
2853
+ title: resolvedTitle,
2854
+ description,
2855
+ ...(url ? { sourceUrl: url } : {}),
2856
+ ...(remoteSourceType ? { "source-type": remoteSourceType } : {}),
2857
+ ...(remoteDocumentTitle ? { "source-document-title": remoteDocumentTitle } : {}),
2858
+ ...(remoteDocumentType ? { "source-document-type": remoteDocumentType } : {}),
2859
+ ...(referenceMaterials ? { "reference-materials-json": JSON.stringify(referenceMaterials) } : {}),
2497
2860
  ...(repositorySelection.repoSelector ? { repo: repositorySelection.repoSelector } : {}),
2498
2861
  ...(args["allow-duplicate"] === "true" ? { "allow-duplicate": "true" } : {})
2499
2862
  });
2500
- const inboxPath = await runtime.writeManualSubmission(event);
2501
- console.log(`[optimus] wrote submission ${inboxPath}`);
2863
+ await runtime.writeManualSubmission(event);
2864
+ console.log("[optimus] submit succeeded");
2502
2865
  return;
2503
2866
  }
2504
2867
  if (command === "feedback") {
@@ -3405,7 +3768,11 @@ async function inspectBuiltinSkills(config, skillSyncService) {
3405
3768
  evolutionTaskLevelSkills
3406
3769
  };
3407
3770
  }
3408
- async function resolveSubmissionRepositorySelector(args, store, config) {
3771
+ async function resolveSubmissionRepositorySelector(taskType, args, store, config) {
3772
+ const runtimePolicy = getTaskRuntimePolicy(taskType);
3773
+ if (!runtimePolicy.requiresRepository) {
3774
+ return { ok: true };
3775
+ }
3409
3776
  const candidates = await listCliRepositoryCandidates(store, config);
3410
3777
  const requestedRepo = args.repo?.trim();
3411
3778
  if (requestedRepo) {
@@ -3682,7 +4049,7 @@ function renderTaskResultReport(input) {
3682
4049
  lines.push(`Metrics: ${metrics}`);
3683
4050
  }
3684
4051
  if (input.deliveryBundle.warnings?.length) {
3685
- lines.push(`Warnings: ${input.deliveryBundle.warnings.join(", ")}`);
4052
+ lines.push(`Warnings: ${formatDeliveryWarningsWithDescriptions(input.deliveryBundle.warnings)}`);
3686
4053
  }
3687
4054
  if (input.deliveryBundle.publication) {
3688
4055
  lines.push(`Publication: ${input.deliveryBundle.publication.action}${input.deliveryBundle.publication.reason ? ` (${input.deliveryBundle.publication.reason})` : ""}`);
@@ -3795,7 +4162,7 @@ function renderDeliveryStatusReport(input) {
3795
4162
  lines.push(`Decision: ${input.deliveryBundle.summary.decision}`);
3796
4163
  lines.push(`Created At: ${input.deliveryBundle.createdAt}`);
3797
4164
  if (input.deliveryBundle.warnings?.length) {
3798
- lines.push(`Warnings: ${input.deliveryBundle.warnings.join(", ")}`);
4165
+ lines.push(`Warnings: ${formatDeliveryWarningsWithDescriptions(input.deliveryBundle.warnings)}`);
3799
4166
  }
3800
4167
  }
3801
4168
  else {