@sireai/optimus 0.1.39 → 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.
- package/dist/cli/optimus.js +405 -56
- package/dist/cli/optimus.js.map +1 -1
- package/dist/config/load-config.js +8 -1
- package/dist/config/load-config.js.map +1 -1
- package/dist/integrations/feishu/feishu-doc-service.d.ts +5 -0
- package/dist/integrations/feishu/feishu-doc-service.js +11 -0
- package/dist/integrations/feishu/feishu-doc-service.js.map +1 -1
- package/dist/integrations/jira/jira-access-manager.d.ts +1 -0
- package/dist/integrations/jira/jira-access-manager.js +24 -0
- package/dist/integrations/jira/jira-access-manager.js.map +1 -1
- package/dist/integrations/jira/jira-cli.js +19 -3
- package/dist/integrations/jira/jira-cli.js.map +1 -1
- package/dist/integrations/jira/jira-submit.js +5 -18
- package/dist/integrations/jira/jira-submit.js.map +1 -1
- package/dist/integrations/sentry/sentry-cli.js +18 -2
- package/dist/integrations/sentry/sentry-cli.js.map +1 -1
- package/dist/problem-solving-core/codex/codex-runner.d.ts +2 -0
- package/dist/problem-solving-core/codex/codex-runner.js +46 -36
- package/dist/problem-solving-core/codex/codex-runner.js.map +1 -1
- package/dist/task-environment/delivery/feishu-analysis-doc-service.d.ts +4 -1
- package/dist/task-environment/delivery/feishu-analysis-doc-service.js +48 -2
- package/dist/task-environment/delivery/feishu-analysis-doc-service.js.map +1 -1
- package/dist/task-environment/delivery/feishu-card-renderer.js +4 -2
- package/dist/task-environment/delivery/feishu-card-renderer.js.map +1 -1
- package/dist/task-environment/delivery/feishu-notifier.d.ts +5 -0
- package/dist/task-environment/delivery/feishu-notifier.js +103 -38
- package/dist/task-environment/delivery/feishu-notifier.js.map +1 -1
- package/dist/task-environment/delivery/feishu-templates/analysis-message-template.js +3 -0
- package/dist/task-environment/delivery/feishu-templates/analysis-message-template.js.map +1 -1
- package/dist/task-environment/delivery/task-delivery-service.js +6 -2
- package/dist/task-environment/delivery/task-delivery-service.js.map +1 -1
- package/dist/task-environment/delivery/task-publication-service.d.ts +1 -0
- package/dist/task-environment/delivery/task-publication-service.js +22 -0
- package/dist/task-environment/delivery/task-publication-service.js.map +1 -1
- package/dist/task-environment/intake/manual-problem-intake.js +54 -0
- package/dist/task-environment/intake/manual-problem-intake.js.map +1 -1
- package/dist/task-environment/orchestration/execution-context-assembler.d.ts +1 -0
- package/dist/task-environment/orchestration/execution-context-assembler.js +58 -3
- package/dist/task-environment/orchestration/execution-context-assembler.js.map +1 -1
- package/dist/task-environment/orchestration/task-orchestrator.d.ts +1 -0
- package/dist/task-environment/orchestration/task-orchestrator.js +41 -5
- package/dist/task-environment/orchestration/task-orchestrator.js.map +1 -1
- package/dist/task-environment/orchestration/task-package-inputs.d.ts +2 -0
- package/dist/task-environment/orchestration/task-package-inputs.js +83 -0
- package/dist/task-environment/orchestration/task-package-inputs.js.map +1 -0
- package/dist/task-environment/orchestration/task-runtime-policy.d.ts +4 -0
- package/dist/task-environment/orchestration/task-runtime-policy.js +38 -0
- package/dist/task-environment/orchestration/task-runtime-policy.js.map +1 -0
- package/dist/task-environment/result-paths.d.ts +9 -0
- package/dist/task-environment/result-paths.js +36 -0
- package/dist/task-environment/result-paths.js.map +1 -0
- package/dist/types.d.ts +43 -1
- package/package.json +1 -1
- package/task-harnesses/pm/ACCEPT.md +96 -0
- package/task-harnesses/pm/ANNOTATION_PATTERN.md +58 -0
- package/task-harnesses/pm/CONSTRAINTS.md +56 -0
- package/task-harnesses/pm/CONTEXT.md +55 -0
- package/task-harnesses/pm/EVOLUTION.md +43 -0
- package/task-harnesses/pm/ROLE.md +46 -0
- package/task-harnesses/pm/STANDARD.md +186 -0
- package/task-harnesses/pm/manifest.json +13 -0
- package/task-harnesses/registry.json +4 -0
package/dist/cli/optimus.js
CHANGED
|
@@ -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> --
|
|
320
|
-
"
|
|
323
|
+
" optimus submit --title <title> --file <path> [options]",
|
|
324
|
+
" optimus submit --url <url> [--title <title>] [options]",
|
|
321
325
|
"",
|
|
322
326
|
"Required:",
|
|
323
|
-
"
|
|
324
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
864
|
-
await input.logger.
|
|
882
|
+
if (!suppressSubmitOutput) {
|
|
883
|
+
await input.logger.info(check.updateAvailable ? "self_update.available" : "self_update.current", {
|
|
865
884
|
command: input.command,
|
|
866
|
-
|
|
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
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
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
|
|
2264
|
-
|
|
2265
|
-
const
|
|
2266
|
-
if (
|
|
2267
|
-
|
|
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
|
-
|
|
2271
|
-
|
|
2272
|
-
if (
|
|
2273
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2328
|
+
requirementHint,
|
|
2283
2329
|
`Example: optimus ${command} --title "..." --description 'literal text with \`optimus setup\` inside'`,
|
|
2284
|
-
|
|
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
|
|
2478
|
-
const
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
if (
|
|
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
|
-
|
|
2501
|
-
console.log(
|
|
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) {
|