@sireai/optimus 0.1.40 → 0.1.42

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