@sireai/optimus 0.1.44 → 0.1.46
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/feedback-delivery.js +13 -8
- package/dist/cli/feedback-delivery.js.map +1 -1
- package/dist/cli/optimus.js +370 -63
- package/dist/cli/optimus.js.map +1 -1
- package/dist/integrations/feishu/feishu-client.d.ts +13 -0
- package/dist/integrations/feishu/feishu-client.js +111 -6
- package/dist/integrations/feishu/feishu-client.js.map +1 -1
- package/dist/integrations/feishu/feishu-reference-material-downloader.d.ts +34 -0
- package/dist/integrations/feishu/feishu-reference-material-downloader.js +572 -0
- package/dist/integrations/feishu/feishu-reference-material-downloader.js.map +1 -0
- package/dist/integrations/feishu/feishu-token-store.d.ts +25 -1
- package/dist/integrations/feishu/feishu-token-store.js +148 -4
- package/dist/integrations/feishu/feishu-token-store.js.map +1 -1
- package/dist/integrations/feishu/feishu-user-auth-service.d.ts +39 -0
- package/dist/integrations/feishu/feishu-user-auth-service.js +228 -0
- package/dist/integrations/feishu/feishu-user-auth-service.js.map +1 -0
- package/dist/integrations/jira/jira-cli.js +127 -19
- package/dist/integrations/jira/jira-cli.js.map +1 -1
- package/dist/task-environment/delivery/commit-message/coder-commit-message-template.d.ts +12 -0
- package/dist/task-environment/delivery/commit-message/coder-commit-message-template.js +105 -0
- package/dist/task-environment/delivery/commit-message/coder-commit-message-template.js.map +1 -0
- package/dist/task-environment/delivery/commit-message/commit-message-builder.js +2 -1
- package/dist/task-environment/delivery/commit-message/commit-message-builder.js.map +1 -1
- package/dist/task-environment/delivery/feishu-analysis-doc-service.d.ts +6 -0
- package/dist/task-environment/delivery/feishu-analysis-doc-service.js +106 -34
- package/dist/task-environment/delivery/feishu-analysis-doc-service.js.map +1 -1
- package/dist/task-environment/delivery/feishu-content/feishu-content-renderer.js +16 -1
- package/dist/task-environment/delivery/feishu-content/feishu-content-renderer.js.map +1 -1
- package/dist/task-environment/delivery/feishu-content/feishu-copy-config.d.ts +6 -0
- package/dist/task-environment/delivery/feishu-content/feishu-copy-config.js +19 -0
- package/dist/task-environment/delivery/feishu-content/feishu-copy-config.js.map +1 -1
- package/dist/task-environment/delivery/feishu-notifier.js +13 -11
- package/dist/task-environment/delivery/feishu-notifier.js.map +1 -1
- package/dist/task-environment/delivery/feishu-templates/coder-message-template.d.ts +6 -0
- package/dist/task-environment/delivery/feishu-templates/coder-message-template.js +58 -0
- package/dist/task-environment/delivery/feishu-templates/coder-message-template.js.map +1 -0
- package/dist/task-environment/delivery/feishu-templates/template-registry.js +2 -0
- package/dist/task-environment/delivery/feishu-templates/template-registry.js.map +1 -1
- package/dist/task-environment/delivery/task-delivery-dispatcher.js +6 -0
- package/dist/task-environment/delivery/task-delivery-dispatcher.js.map +1 -1
- package/dist/task-environment/delivery/task-delivery-service.d.ts +1 -0
- package/dist/task-environment/delivery/task-delivery-service.js +124 -8
- package/dist/task-environment/delivery/task-delivery-service.js.map +1 -1
- package/dist/task-environment/delivery/task-publication-service.js +9 -6
- package/dist/task-environment/delivery/task-publication-service.js.map +1 -1
- package/dist/task-environment/document-input/document-structure.d.ts +13 -0
- package/dist/task-environment/document-input/document-structure.js +438 -0
- package/dist/task-environment/document-input/document-structure.js.map +1 -0
- package/dist/task-environment/intake/manual-problem-intake.js +36 -0
- package/dist/task-environment/intake/manual-problem-intake.js.map +1 -1
- package/dist/task-environment/observability/logger.d.ts +1 -0
- package/dist/task-environment/observability/logger.js +26 -0
- package/dist/task-environment/observability/logger.js.map +1 -1
- package/dist/task-environment/observability/runtime-panel.js +10 -1
- package/dist/task-environment/observability/runtime-panel.js.map +1 -1
- package/dist/task-environment/orchestration/reference-material-relocator.d.ts +2 -0
- package/dist/task-environment/orchestration/reference-material-relocator.js +69 -0
- package/dist/task-environment/orchestration/reference-material-relocator.js.map +1 -0
- package/dist/task-environment/orchestration/task-orchestrator.d.ts +3 -0
- package/dist/task-environment/orchestration/task-orchestrator.js +182 -8
- package/dist/task-environment/orchestration/task-orchestrator.js.map +1 -1
- package/dist/task-environment/orchestration/task-package-inputs.js +7 -1
- package/dist/task-environment/orchestration/task-package-inputs.js.map +1 -1
- package/dist/task-environment/orchestration/task-runtime-policy.js +11 -0
- package/dist/task-environment/orchestration/task-runtime-policy.js.map +1 -1
- package/dist/task-environment/orchestration/triage-runner.js +3 -0
- package/dist/task-environment/orchestration/triage-runner.js.map +1 -1
- package/dist/task-environment/runtime/optimus-runtime.js +3 -0
- package/dist/task-environment/runtime/optimus-runtime.js.map +1 -1
- package/dist/task-environment/storage/sqlite-task-store.d.ts +4 -0
- package/dist/task-environment/storage/sqlite-task-store.js +70 -1
- package/dist/task-environment/storage/sqlite-task-store.js.map +1 -1
- package/dist/task-environment/task-handler-descriptor.d.ts +5 -0
- package/dist/task-environment/task-handler-descriptor.js +15 -0
- package/dist/task-environment/task-handler-descriptor.js.map +1 -0
- package/dist/types.d.ts +47 -1
- package/embedded-skills/shared/feishu-task-inputs/SKILL.md +56 -0
- package/embedded-skills/shared/feishu-task-inputs/scripts/fetch-feishu-doc.mjs +756 -0
- package/embedded-skills/shared/feishu-task-inputs/skill.json +5 -0
- package/package.json +4 -1
- package/task-harnesses/coder/ACCEPT.md +73 -0
- package/task-harnesses/coder/CONSTRAINTS.md +72 -0
- package/task-harnesses/coder/CONTEXT.md +36 -0
- package/task-harnesses/coder/EVOLUTION.md +83 -0
- package/task-harnesses/coder/ROLE.md +39 -0
- package/task-harnesses/coder/STANDARD.md +258 -0
- package/task-harnesses/coder/manifest.json +13 -0
- package/task-harnesses/pm/ACCEPT.md +7 -0
- package/task-harnesses/pm/CONSTRAINTS.md +5 -0
- package/task-harnesses/pm/ROLE.md +5 -8
- package/task-harnesses/pm/STANDARD.md +83 -124
- package/task-harnesses/registry.json +4 -0
package/dist/cli/optimus.js
CHANGED
|
@@ -29,13 +29,16 @@ import { TaskDeliveryService } from "../task-environment/delivery/task-delivery-
|
|
|
29
29
|
import { formatDeliveryWarningsWithDescriptions } from "../task-environment/delivery/delivery-warning-copy.js";
|
|
30
30
|
import { FeishuAnalysisDocService } from "../task-environment/delivery/feishu-analysis-doc-service.js";
|
|
31
31
|
import { TaskPublicationService } from "../task-environment/delivery/task-publication-service.js";
|
|
32
|
-
import { FeishuClient } from "../integrations/feishu/feishu-client.js";
|
|
32
|
+
import { FeishuClient, FeishuIntegrationError } from "../integrations/feishu/feishu-client.js";
|
|
33
33
|
import { FeishuDocumentReader, mergeFeishuReferenceMaterials } from "../integrations/feishu/feishu-document-reader.js";
|
|
34
|
+
import { FeishuReferenceMaterialDownloader, isFeishuManagedReferenceMaterial } from "../integrations/feishu/feishu-reference-material-downloader.js";
|
|
35
|
+
import { FeishuUserAuthService } from "../integrations/feishu/feishu-user-auth-service.js";
|
|
34
36
|
import { FeishuUserService } from "../integrations/feishu/feishu-user-service.js";
|
|
35
37
|
import { createFeedbackReport } from "./feedback.js";
|
|
36
38
|
import { DEFAULT_FEEDBACK_RECIPIENT_EMAIL, FeishuFeedbackDeliveryService, persistFeedbackDeliveryResult, resolveDefaultFeedbackRecipientFallbackOpenId } from "./feedback-delivery.js";
|
|
37
39
|
import { resolveDefaultConfigPath, resolveDefaultEnvPath } from "../config/optimus-paths.js";
|
|
38
40
|
import { checkForSelfUpdate, installSelfUpdate, recordSkippedSelfUpdate } from "./self-update.js";
|
|
41
|
+
import { buildDocumentStructure } from "../task-environment/document-input/document-structure.js";
|
|
39
42
|
const CLI_ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
40
43
|
const PACKAGE_JSON_PATH = join(CLI_ROOT_DIR, "package.json");
|
|
41
44
|
const execFileAsync = promisify(execFile);
|
|
@@ -43,6 +46,35 @@ const PACKAGE_NAME = "@sireai/optimus";
|
|
|
43
46
|
const JIRA_CLI_ENTRY = join(CLI_ROOT_DIR, "dist", "integrations", "jira", "jira-cli.js");
|
|
44
47
|
const SENTRY_CLI_ENTRY = join(CLI_ROOT_DIR, "dist", "integrations", "sentry", "sentry-cli.js");
|
|
45
48
|
const RELEASE_NOTES_URL = "https://github.com/SireAI/optimus/releases/latest";
|
|
49
|
+
function createSilentSubmitConsoleSink() {
|
|
50
|
+
return () => {
|
|
51
|
+
// Submit preprocessing logs are still written to runtime logs; the interactive CLI keeps only loading + final result.
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function resolveSubmitTaskType(input) {
|
|
55
|
+
const explicitTaskType = input.explicitTaskType?.trim();
|
|
56
|
+
if (explicitTaskType) {
|
|
57
|
+
return explicitTaskType;
|
|
58
|
+
}
|
|
59
|
+
if (looksLikePrototypeInput(input)) {
|
|
60
|
+
return "coder";
|
|
61
|
+
}
|
|
62
|
+
return "bugfix";
|
|
63
|
+
}
|
|
64
|
+
function looksLikePrototypeInput(input) {
|
|
65
|
+
const normalizedFileName = basename(input.filePath?.trim() ?? "").toLowerCase();
|
|
66
|
+
if (normalizedFileName === "prototype.html" || normalizedFileName === "prototype.htm") {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
return (input.referenceMaterials ?? []).some((material) => isPrototypeReferenceMaterial(material));
|
|
70
|
+
}
|
|
71
|
+
function isPrototypeReferenceMaterial(material) {
|
|
72
|
+
if (material.type !== "file") {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
const normalizedTitle = material.title.trim().toLowerCase();
|
|
76
|
+
return normalizedTitle === "prototype.html" || normalizedTitle === "prototype.htm";
|
|
77
|
+
}
|
|
46
78
|
const DOCTOR_ANSI = {
|
|
47
79
|
reset: "\u001B[0m",
|
|
48
80
|
green: "\u001B[32m",
|
|
@@ -181,6 +213,61 @@ async function runWithTerminalLoading(message, action) {
|
|
|
181
213
|
throw error;
|
|
182
214
|
}
|
|
183
215
|
}
|
|
216
|
+
async function runFeishuUserAuth(subcommand, commandArgs) {
|
|
217
|
+
const action = (subcommand?.trim() || "auth").toLowerCase();
|
|
218
|
+
const args = parseArgs(commandArgs);
|
|
219
|
+
const config = await loadConfig().catch(() => buildDefaultConfig());
|
|
220
|
+
const authService = new FeishuUserAuthService({ config });
|
|
221
|
+
if (action === "status") {
|
|
222
|
+
const user = await authService.readAuthorizedUser();
|
|
223
|
+
if (!user) {
|
|
224
|
+
console.log("Feishu user auth: not authorized");
|
|
225
|
+
console.log("Run `optimus feishu auth` to authorize your user account.");
|
|
226
|
+
return 1;
|
|
227
|
+
}
|
|
228
|
+
console.log("Feishu user auth: authorized");
|
|
229
|
+
if (user.openId) {
|
|
230
|
+
console.log(`Open ID: ${user.openId}`);
|
|
231
|
+
}
|
|
232
|
+
if (user.userId) {
|
|
233
|
+
console.log(`User ID: ${user.userId}`);
|
|
234
|
+
}
|
|
235
|
+
if (user.source) {
|
|
236
|
+
console.log(`Source: ${user.source}`);
|
|
237
|
+
}
|
|
238
|
+
console.log(`Expires: ${new Date(user.expireAtMs).toISOString()}`);
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
if (action === "logout") {
|
|
242
|
+
await authService.clearAuthorizedUser();
|
|
243
|
+
console.log("Feishu user authorization cleared.");
|
|
244
|
+
return 0;
|
|
245
|
+
}
|
|
246
|
+
if (action !== "auth") {
|
|
247
|
+
console.error(renderCommandHelp("feishu") ?? "Unknown `optimus feishu` subcommand.");
|
|
248
|
+
return 1;
|
|
249
|
+
}
|
|
250
|
+
let announcedUrl = "";
|
|
251
|
+
const result = await authService.loginDeviceCode({
|
|
252
|
+
openBrowser: args["no-open"] !== "true",
|
|
253
|
+
onVerificationUri: (url, expiresIn) => {
|
|
254
|
+
announcedUrl = url;
|
|
255
|
+
console.log(`Open this URL to authorize Feishu access (expires in ${expiresIn}s):`);
|
|
256
|
+
console.log(url);
|
|
257
|
+
console.log("");
|
|
258
|
+
console.log("Waiting for authorization...");
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
if (!announcedUrl) {
|
|
262
|
+
console.log("Waiting for authorization...");
|
|
263
|
+
}
|
|
264
|
+
console.log("Feishu user authorization succeeded.");
|
|
265
|
+
console.log(`Authorization URL: ${result.verificationUriComplete}`);
|
|
266
|
+
if (result.openId) {
|
|
267
|
+
console.log(`Open ID: ${result.openId}`);
|
|
268
|
+
}
|
|
269
|
+
return 0;
|
|
270
|
+
}
|
|
184
271
|
function renderDoctorSection(title, section, extraLines = []) {
|
|
185
272
|
const lines = [formatDoctorStatusHeading(title, section.status)];
|
|
186
273
|
if (section.reason) {
|
|
@@ -261,6 +348,7 @@ function renderCliHelp() {
|
|
|
261
348
|
"",
|
|
262
349
|
"Common commands:",
|
|
263
350
|
" submit Submit a problem for Optimus to handle",
|
|
351
|
+
" feishu auth Authorize your Feishu user account for protected docs/wiki",
|
|
264
352
|
" jira-submit-issue Submit a Jira issue into Optimus",
|
|
265
353
|
" sentry-submit-event Submit a Sentry event into Optimus",
|
|
266
354
|
" repo list Show registered repositories",
|
|
@@ -294,6 +382,7 @@ function renderAdvancedCliHelp() {
|
|
|
294
382
|
" version Show the installed package version",
|
|
295
383
|
"",
|
|
296
384
|
"Advanced commands:",
|
|
385
|
+
" feishu auth|status|logout",
|
|
297
386
|
" delivery-status Show focused delivery/publication state",
|
|
298
387
|
" notify-test Send or preview a Feishu test notification",
|
|
299
388
|
" delivery-retry Replay notification delivery from persisted bundle data",
|
|
@@ -462,6 +551,22 @@ function renderCommandHelp(command) {
|
|
|
462
551
|
"Optional:",
|
|
463
552
|
" --json Print JSON output for automation"
|
|
464
553
|
].join("\n"),
|
|
554
|
+
feishu: [
|
|
555
|
+
"optimus feishu",
|
|
556
|
+
"",
|
|
557
|
+
"Usage:",
|
|
558
|
+
" optimus feishu auth [options]",
|
|
559
|
+
" optimus feishu status",
|
|
560
|
+
" optimus feishu logout",
|
|
561
|
+
"",
|
|
562
|
+
"Subcommands:",
|
|
563
|
+
" auth Start Feishu user authorization via device code flow",
|
|
564
|
+
" status Show whether a Feishu user token is stored",
|
|
565
|
+
" logout Clear the stored Feishu user token",
|
|
566
|
+
"",
|
|
567
|
+
"Options for `auth`:",
|
|
568
|
+
" --no-open Print the authorization URL without opening a browser"
|
|
569
|
+
].join("\n"),
|
|
465
570
|
advanced: renderAdvancedCliHelp(),
|
|
466
571
|
"notify-test": [
|
|
467
572
|
"optimus notify-test",
|
|
@@ -2346,9 +2451,14 @@ function isFeishuDocumentUrl(value) {
|
|
|
2346
2451
|
|| /^https?:\/\/(?:[\w-]+\.)?larksuite\.com\//iu.test(value.trim());
|
|
2347
2452
|
}
|
|
2348
2453
|
function extractTitleFromMarkdown(markdown) {
|
|
2349
|
-
const
|
|
2350
|
-
const
|
|
2351
|
-
|
|
2454
|
+
const htmlTitleMatch = markdown.match(/<title>([\s\S]*?)<\/title>/iu);
|
|
2455
|
+
const htmlTitle = htmlTitleMatch?.[1]?.trim();
|
|
2456
|
+
if (htmlTitle) {
|
|
2457
|
+
return htmlTitle;
|
|
2458
|
+
}
|
|
2459
|
+
const markdownHeadingMatch = markdown.match(/^\s*#\s+(.+?)\s*$/mu);
|
|
2460
|
+
const markdownHeading = markdownHeadingMatch?.[1]?.trim();
|
|
2461
|
+
return markdownHeading ? markdownHeading : undefined;
|
|
2352
2462
|
}
|
|
2353
2463
|
function parseHtmlAttributeMap(fragment) {
|
|
2354
2464
|
const attributes = {};
|
|
@@ -2397,6 +2507,7 @@ function extractFeishuReferenceMaterials(markdown, media, sourceUrl) {
|
|
|
2397
2507
|
const record = entry;
|
|
2398
2508
|
const token = typeof record.token === "string" ? record.token.trim() : "";
|
|
2399
2509
|
const type = typeof record.type === "string" ? record.type.trim() : "";
|
|
2510
|
+
const name = typeof record.name === "string" ? record.name.trim() : "";
|
|
2400
2511
|
if (!token || !type) {
|
|
2401
2512
|
continue;
|
|
2402
2513
|
}
|
|
@@ -2413,7 +2524,7 @@ function extractFeishuReferenceMaterials(markdown, media, sourceUrl) {
|
|
|
2413
2524
|
embeddedFileCount += 1;
|
|
2414
2525
|
pushReference({
|
|
2415
2526
|
type: "file",
|
|
2416
|
-
title: `Embedded ${type} ${embeddedFileCount}`,
|
|
2527
|
+
title: name || `Embedded ${type} ${embeddedFileCount}`,
|
|
2417
2528
|
token,
|
|
2418
2529
|
sourceType: "feishu_media"
|
|
2419
2530
|
});
|
|
@@ -2440,9 +2551,11 @@ function extractFeishuReferenceMaterials(markdown, media, sourceUrl) {
|
|
|
2440
2551
|
while ((htmlImageMatch = htmlImagePattern.exec(markdown)) !== null) {
|
|
2441
2552
|
embeddedImageCount += 1;
|
|
2442
2553
|
const attrs = parseHtmlAttributeMap(htmlImageMatch[1] ?? "");
|
|
2554
|
+
const title = selectFeishuImageTitle(attrs, embeddedImageCount);
|
|
2443
2555
|
pushReference({
|
|
2444
2556
|
type: "image",
|
|
2445
|
-
title
|
|
2557
|
+
title,
|
|
2558
|
+
...(attrs.href?.trim() ? { url: attrs.href.trim() } : {}),
|
|
2446
2559
|
...(attrs.src?.trim() ? { token: attrs.src.trim() } : {}),
|
|
2447
2560
|
...(attrs.mime?.trim() ? { mimeType: attrs.mime.trim() } : {}),
|
|
2448
2561
|
sourceType: "feishu_markdown"
|
|
@@ -2541,7 +2654,18 @@ function extractFeishuReferenceMaterials(markdown, media, sourceUrl) {
|
|
|
2541
2654
|
}
|
|
2542
2655
|
return references.length > 0 ? references : undefined;
|
|
2543
2656
|
}
|
|
2544
|
-
|
|
2657
|
+
function selectFeishuImageTitle(attributes, ordinal) {
|
|
2658
|
+
const alt = attributes.alt?.trim();
|
|
2659
|
+
if (alt) {
|
|
2660
|
+
return alt;
|
|
2661
|
+
}
|
|
2662
|
+
const name = attributes.name?.trim();
|
|
2663
|
+
if (name) {
|
|
2664
|
+
return name;
|
|
2665
|
+
}
|
|
2666
|
+
return `Embedded image ${ordinal}`;
|
|
2667
|
+
}
|
|
2668
|
+
async function resolveTaskTextFromUrl(url, config, logger) {
|
|
2545
2669
|
const trimmedUrl = url.trim();
|
|
2546
2670
|
if (!trimmedUrl) {
|
|
2547
2671
|
throw new Error("Task document URL is empty.");
|
|
@@ -2549,60 +2673,228 @@ async function resolveTaskTextFromUrl(url) {
|
|
|
2549
2673
|
if (!isFeishuDocumentUrl(trimmedUrl)) {
|
|
2550
2674
|
throw new Error("submit currently supports document URLs from Feishu/Lark only.");
|
|
2551
2675
|
}
|
|
2552
|
-
const reader = new FeishuDocumentReader();
|
|
2553
2676
|
try {
|
|
2554
|
-
|
|
2555
|
-
const referenceMaterials = mergeFeishuReferenceMaterials(extractFeishuReferenceMaterials(resolved.content, undefined, trimmedUrl), resolved.referenceMaterials);
|
|
2556
|
-
return {
|
|
2557
|
-
content: resolved.content,
|
|
2558
|
-
...(resolved.title ? { title: resolved.title } : {}),
|
|
2559
|
-
sourceType: resolved.sourceType,
|
|
2560
|
-
...(resolved.sourceDocumentType ? { sourceDocumentType: resolved.sourceDocumentType } : {}),
|
|
2561
|
-
...(referenceMaterials ? { referenceMaterials } : {})
|
|
2562
|
-
};
|
|
2677
|
+
return await resolveTaskTextFromUrlWithAuthMode(trimmedUrl, "tenant", config, logger);
|
|
2563
2678
|
}
|
|
2564
2679
|
catch (builtinError) {
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
const
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2680
|
+
if (shouldRetryFeishuSubmitWithUserAuth(builtinError) && config) {
|
|
2681
|
+
const userAuthService = new FeishuUserAuthService({ config });
|
|
2682
|
+
const hadStoredUserAuth = await userAuthService.hasAuthorizedUser();
|
|
2683
|
+
if (hadStoredUserAuth) {
|
|
2684
|
+
try {
|
|
2685
|
+
return await resolveTaskTextFromUrlWithAuthMode(trimmedUrl, "user", config, logger);
|
|
2686
|
+
}
|
|
2687
|
+
catch (userError) {
|
|
2688
|
+
if (shouldRetryFeishuSubmitWithFreshUserAuth(userError)) {
|
|
2689
|
+
try {
|
|
2690
|
+
const adopted = await userAuthService.adoptLegacyCliSession();
|
|
2691
|
+
if (adopted) {
|
|
2692
|
+
return await resolveTaskTextFromUrlWithAuthMode(trimmedUrl, "user", config, logger);
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
catch {
|
|
2696
|
+
// Ignore legacy session adoption failures and continue to interactive re-authorization.
|
|
2697
|
+
}
|
|
2698
|
+
try {
|
|
2699
|
+
await startFeishuUserAuthForSubmit(userAuthService);
|
|
2700
|
+
return await resolveTaskTextFromUrlWithAuthMode(trimmedUrl, "user", config, logger);
|
|
2701
|
+
}
|
|
2702
|
+
catch (reauthError) {
|
|
2703
|
+
const appDetail = formatFeishuSubmitError(builtinError);
|
|
2704
|
+
const userDetail = formatFeishuSubmitError(userError);
|
|
2705
|
+
const reauthDetail = reauthError instanceof Error ? reauthError.message : String(reauthError);
|
|
2706
|
+
throw new Error(`submit could not fetch the Feishu document via the built-in Feishu app (${appDetail}). `
|
|
2707
|
+
+ `Stored user authorization was insufficient (${userDetail}). `
|
|
2708
|
+
+ `Automatic user re-authorization also failed (${reauthDetail}).`);
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
const appDetail = formatFeishuSubmitError(builtinError);
|
|
2712
|
+
const userDetail = formatFeishuSubmitError(userError);
|
|
2713
|
+
throw new Error(`submit could not fetch the Feishu document via the built-in Feishu app (${appDetail}). User-auth fallback also failed (${userDetail}).`);
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
const builtinDetail = formatFeishuSubmitError(builtinError);
|
|
2717
|
+
try {
|
|
2718
|
+
await startFeishuUserAuthForSubmit(userAuthService);
|
|
2719
|
+
try {
|
|
2720
|
+
return await resolveTaskTextFromUrlWithAuthMode(trimmedUrl, "user", config, logger);
|
|
2721
|
+
}
|
|
2722
|
+
catch (freshUserError) {
|
|
2723
|
+
if (shouldRetryFeishuSubmitWithFreshUserAuth(freshUserError)) {
|
|
2724
|
+
const scopeDetail = formatFeishuSubmitError(freshUserError);
|
|
2725
|
+
throw new Error(`Feishu user authorization succeeded, but the authorized Optimus Feishu app still lacks the required user privileges (${scopeDetail}). `
|
|
2726
|
+
+ "Open the app's user scopes, publish the updated Feishu app version, then authorize again with `optimus feishu auth`.");
|
|
2727
|
+
}
|
|
2728
|
+
throw freshUserError;
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
catch (authError) {
|
|
2732
|
+
const authDetail = authError instanceof Error ? authError.message : String(authError);
|
|
2733
|
+
throw new Error(`submit could not fetch the Feishu document via the built-in Feishu app (${builtinDetail}). `
|
|
2734
|
+
+ `Automatic Feishu user authorization failed (${authDetail}).`);
|
|
2735
|
+
}
|
|
2571
2736
|
}
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2737
|
+
const builtinDetail = formatFeishuSubmitError(builtinError);
|
|
2738
|
+
throw new Error(`submit could not fetch the Feishu document via the built-in Feishu app (${builtinDetail}).`);
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
async function startFeishuUserAuthForSubmit(authService) {
|
|
2742
|
+
let announcedUrl = "";
|
|
2743
|
+
console.error("Feishu document access requires user authorization. Starting authorization flow...");
|
|
2744
|
+
await authService.loginDeviceCode({
|
|
2745
|
+
onVerificationUri: (url, expiresIn) => {
|
|
2746
|
+
announcedUrl = url;
|
|
2747
|
+
console.error(`Authorize Feishu access in your browser within ${expiresIn}s:`);
|
|
2748
|
+
console.error(url);
|
|
2749
|
+
console.error("Waiting for authorization...");
|
|
2583
2750
|
}
|
|
2584
|
-
|
|
2585
|
-
|
|
2751
|
+
});
|
|
2752
|
+
if (!announcedUrl) {
|
|
2753
|
+
console.error("Waiting for authorization...");
|
|
2754
|
+
}
|
|
2755
|
+
console.error("Feishu user authorization completed. Retrying document fetch...");
|
|
2756
|
+
}
|
|
2757
|
+
async function resolveTaskTextFromUrlWithAuthMode(trimmedUrl, authMode, config, logger) {
|
|
2758
|
+
const reader = new FeishuDocumentReader({
|
|
2759
|
+
...(config ? { config } : {}),
|
|
2760
|
+
client: new FeishuClient({
|
|
2761
|
+
...(config ? { config } : {}),
|
|
2762
|
+
authMode
|
|
2763
|
+
})
|
|
2764
|
+
});
|
|
2765
|
+
const resolved = await reader.readFromUrl(trimmedUrl);
|
|
2766
|
+
const resolvedTitle = resolved.title?.trim() || extractTitleFromMarkdown(resolved.content);
|
|
2767
|
+
const referenceMaterials = mergeFeishuReferenceMaterials(extractFeishuReferenceMaterials(resolved.content, undefined, trimmedUrl), resolved.referenceMaterials);
|
|
2768
|
+
await logger?.info("reference_materials.detected", {
|
|
2769
|
+
sourceUrl: trimmedUrl,
|
|
2770
|
+
sourceDocumentType: resolved.sourceDocumentType ?? null,
|
|
2771
|
+
materialCount: referenceMaterials?.length ?? 0,
|
|
2772
|
+
fileCount: referenceMaterials?.filter((entry) => entry.type === "file").length ?? 0,
|
|
2773
|
+
imageCount: referenceMaterials?.filter((entry) => entry.type === "image").length ?? 0,
|
|
2774
|
+
structuredObjectCount: referenceMaterials?.filter((entry) => !["file", "image", "link"].includes(entry.type)).length ?? 0,
|
|
2775
|
+
feishuAuthMode: authMode
|
|
2776
|
+
});
|
|
2777
|
+
const downloadedReferenceMaterials = config
|
|
2778
|
+
? await new FeishuReferenceMaterialDownloader({
|
|
2779
|
+
config,
|
|
2780
|
+
client: new FeishuClient({
|
|
2781
|
+
config,
|
|
2782
|
+
authMode
|
|
2783
|
+
}),
|
|
2784
|
+
...(logger ? { logger } : {})
|
|
2785
|
+
}).downloadReferenceMaterials(referenceMaterials ?? [], buildRemoteInputNamespace(trimmedUrl), trimmedUrl)
|
|
2786
|
+
: referenceMaterials;
|
|
2787
|
+
await ensureFeishuImageDownloadsSucceeded(downloadedReferenceMaterials, trimmedUrl, logger);
|
|
2788
|
+
const documentStructure = buildDocumentStructure(resolved.content, {
|
|
2789
|
+
...(resolvedTitle ? { title: resolvedTitle } : {}),
|
|
2790
|
+
sourceUrl: trimmedUrl,
|
|
2791
|
+
...(downloadedReferenceMaterials ? { referenceMaterials: downloadedReferenceMaterials } : {})
|
|
2792
|
+
});
|
|
2793
|
+
return {
|
|
2794
|
+
content: resolved.content,
|
|
2795
|
+
...(resolvedTitle ? { title: resolvedTitle } : {}),
|
|
2796
|
+
sourceType: resolved.sourceType,
|
|
2797
|
+
...(resolved.sourceDocumentType ? { sourceDocumentType: resolved.sourceDocumentType } : {}),
|
|
2798
|
+
...(documentStructure.referenceMaterials && documentStructure.referenceMaterials.length > 0 ? { referenceMaterials: documentStructure.referenceMaterials } : {}),
|
|
2799
|
+
documentMap: documentStructure.documentMap,
|
|
2800
|
+
...(documentStructure.evidenceBundles.length > 0 ? { evidenceBundles: documentStructure.evidenceBundles } : {})
|
|
2801
|
+
};
|
|
2802
|
+
}
|
|
2803
|
+
function formatFeishuSubmitError(error) {
|
|
2804
|
+
if (error instanceof FeishuIntegrationError) {
|
|
2805
|
+
const parts = [error.message];
|
|
2806
|
+
const status = Number.isFinite(error.diagnostics.status) ? String(error.diagnostics.status) : undefined;
|
|
2807
|
+
const code = Number.isFinite(error.diagnostics.code) ? String(error.diagnostics.code) : undefined;
|
|
2808
|
+
if (status) {
|
|
2809
|
+
parts.push(`status=${status}`);
|
|
2810
|
+
}
|
|
2811
|
+
if (code) {
|
|
2812
|
+
parts.push(`code=${code}`);
|
|
2813
|
+
}
|
|
2814
|
+
const detail = extractFeishuErrorDetail(error.diagnostics.detail);
|
|
2815
|
+
if (detail) {
|
|
2816
|
+
parts.push(detail);
|
|
2586
2817
|
}
|
|
2587
|
-
|
|
2588
|
-
|
|
2818
|
+
return parts.join(", ");
|
|
2819
|
+
}
|
|
2820
|
+
return error instanceof Error ? error.message : String(error);
|
|
2821
|
+
}
|
|
2822
|
+
function extractFeishuErrorDetail(detail) {
|
|
2823
|
+
const trimmed = detail?.trim();
|
|
2824
|
+
if (!trimmed) {
|
|
2825
|
+
return undefined;
|
|
2826
|
+
}
|
|
2827
|
+
try {
|
|
2828
|
+
const parsed = JSON.parse(trimmed);
|
|
2829
|
+
const candidates = [
|
|
2830
|
+
typeof parsed.msg === "string" ? parsed.msg.trim() : "",
|
|
2831
|
+
typeof parsed.error?.message === "string" ? parsed.error.message.trim() : ""
|
|
2832
|
+
].filter((entry) => entry.length > 0);
|
|
2833
|
+
if (candidates.length > 0) {
|
|
2834
|
+
return candidates.join(" | ");
|
|
2589
2835
|
}
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2836
|
+
}
|
|
2837
|
+
catch {
|
|
2838
|
+
return trimmed;
|
|
2839
|
+
}
|
|
2840
|
+
return trimmed;
|
|
2841
|
+
}
|
|
2842
|
+
function shouldRetryFeishuSubmitWithUserAuth(error) {
|
|
2843
|
+
if (!(error instanceof FeishuIntegrationError)) {
|
|
2844
|
+
return false;
|
|
2845
|
+
}
|
|
2846
|
+
if (error.diagnostics.code === 131006) {
|
|
2847
|
+
return true;
|
|
2848
|
+
}
|
|
2849
|
+
const detail = error.diagnostics.detail?.toLowerCase() ?? "";
|
|
2850
|
+
return /permission denied|tenant needs read permission|node permission denied/.test(detail);
|
|
2851
|
+
}
|
|
2852
|
+
function shouldRetryFeishuSubmitWithFreshUserAuth(error) {
|
|
2853
|
+
if (error instanceof FeishuIntegrationError) {
|
|
2854
|
+
if (error.diagnostics.code === 99991679) {
|
|
2855
|
+
return true;
|
|
2597
2856
|
}
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2857
|
+
const detail = error.diagnostics.detail?.toLowerCase() ?? "";
|
|
2858
|
+
return /request user re-authorization|user re-authorization|docs:document\.media:download|unauthorized/.test(detail);
|
|
2859
|
+
}
|
|
2860
|
+
const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
2861
|
+
return /99991679|request user re-authorization|user re-authorization|docs:document\.media:download/.test(message);
|
|
2862
|
+
}
|
|
2863
|
+
function buildRemoteInputNamespace(sourceUrl) {
|
|
2864
|
+
const compactTime = new Date().toISOString().replace(/[-:TZ.]/gu, "").slice(0, 14);
|
|
2865
|
+
const token = sourceUrl.match(/\/(?:docx|wiki)\/([A-Za-z0-9]+)/u)?.[1]?.trim() || "remote-doc";
|
|
2866
|
+
return `${compactTime}-${token}`;
|
|
2867
|
+
}
|
|
2868
|
+
async function ensureFeishuImageDownloadsSucceeded(materials, sourceUrl, logger) {
|
|
2869
|
+
const images = (materials ?? []).filter((entry) => entry.type === "image" && isFeishuManagedReferenceMaterial(entry));
|
|
2870
|
+
if (images.length === 0) {
|
|
2871
|
+
return;
|
|
2605
2872
|
}
|
|
2873
|
+
const missing = images.filter((entry) => !entry.localPath?.trim() || entry.downloadStatus !== "downloaded");
|
|
2874
|
+
if (missing.length === 0) {
|
|
2875
|
+
return;
|
|
2876
|
+
}
|
|
2877
|
+
const detail = missing
|
|
2878
|
+
.slice(0, 3)
|
|
2879
|
+
.map((entry) => {
|
|
2880
|
+
const title = entry.title?.trim() || "Untitled image";
|
|
2881
|
+
const reason = entry.downloadError?.trim()
|
|
2882
|
+
|| (entry.downloadStatus === "skipped" ? "download was skipped" : "no local file was produced");
|
|
2883
|
+
return `${title} (${reason})`;
|
|
2884
|
+
})
|
|
2885
|
+
.join("; ");
|
|
2886
|
+
const remainder = missing.length > 3 ? `; and ${missing.length - 3} more` : "";
|
|
2887
|
+
await logger?.warn("reference_materials.image_download_blocked", {
|
|
2888
|
+
sourceUrl,
|
|
2889
|
+
imageCount: images.length,
|
|
2890
|
+
missingImageCount: missing.length,
|
|
2891
|
+
missingImageTitles: missing.map((entry) => entry.title),
|
|
2892
|
+
missingImageReasons: missing.map((entry) => entry.downloadError ?? entry.downloadStatus ?? "missing_local_path")
|
|
2893
|
+
});
|
|
2894
|
+
throw new Error(`submit could not prepare ${missing.length}/${images.length} embedded image(s) from the Feishu document. `
|
|
2895
|
+
+ `Embedded screenshots must be downloaded before the task starts. `
|
|
2896
|
+
+ `Failed image(s): ${detail}${remainder}. `
|
|
2897
|
+
+ "Check Feishu document image permissions and retry.");
|
|
2606
2898
|
}
|
|
2607
2899
|
async function main() {
|
|
2608
2900
|
const argv = process.argv.slice(2);
|
|
@@ -2707,8 +2999,7 @@ async function main() {
|
|
|
2707
2999
|
return;
|
|
2708
3000
|
}
|
|
2709
3001
|
if (command === "feishu") {
|
|
2710
|
-
|
|
2711
|
-
process.exitCode = 1;
|
|
3002
|
+
process.exitCode = await runFeishuUserAuth(commandArgs[0], commandArgs.slice(1));
|
|
2712
3003
|
return;
|
|
2713
3004
|
}
|
|
2714
3005
|
if (mapJiraCommand(command)) {
|
|
@@ -2795,7 +3086,6 @@ async function main() {
|
|
|
2795
3086
|
if (command === "submit") {
|
|
2796
3087
|
const args = parseArgs(commandArgs);
|
|
2797
3088
|
const explicitTitle = args.title?.trim();
|
|
2798
|
-
const taskType = args["task-type"]?.trim() || "bugfix";
|
|
2799
3089
|
const inlineDescription = args.desc?.trim() || args.description?.trim();
|
|
2800
3090
|
const filePath = args.file?.trim();
|
|
2801
3091
|
const url = args.url?.trim();
|
|
@@ -2811,6 +3101,8 @@ async function main() {
|
|
|
2811
3101
|
let remoteDocumentTitle;
|
|
2812
3102
|
let remoteDocumentType;
|
|
2813
3103
|
let referenceMaterials;
|
|
3104
|
+
let documentMap;
|
|
3105
|
+
let evidenceBundles;
|
|
2814
3106
|
if (inlineDescription) {
|
|
2815
3107
|
description = inlineDescription;
|
|
2816
3108
|
}
|
|
@@ -2819,13 +3111,15 @@ async function main() {
|
|
|
2819
3111
|
}
|
|
2820
3112
|
else {
|
|
2821
3113
|
try {
|
|
2822
|
-
const remote = await runWithTerminalLoading("Resolving remote requirement document", async () => await resolveTaskTextFromUrl(url));
|
|
3114
|
+
const remote = await OptimusLogger.withConsoleSink(createSilentSubmitConsoleSink(), async () => await runWithTerminalLoading("Resolving remote requirement document", async () => await resolveTaskTextFromUrl(url, config, logger)));
|
|
2823
3115
|
description = remote.content;
|
|
2824
3116
|
resolvedTitle = resolvedTitle || remote.title;
|
|
2825
3117
|
remoteSourceType = remote.sourceType;
|
|
2826
3118
|
remoteDocumentTitle = remote.title;
|
|
2827
3119
|
remoteDocumentType = remote.sourceDocumentType;
|
|
2828
3120
|
referenceMaterials = remote.referenceMaterials;
|
|
3121
|
+
documentMap = remote.documentMap;
|
|
3122
|
+
evidenceBundles = remote.evidenceBundles;
|
|
2829
3123
|
}
|
|
2830
3124
|
catch (error) {
|
|
2831
3125
|
console.error(error instanceof Error ? error.message : String(error));
|
|
@@ -2833,36 +3127,49 @@ async function main() {
|
|
|
2833
3127
|
return;
|
|
2834
3128
|
}
|
|
2835
3129
|
}
|
|
2836
|
-
|
|
2837
|
-
|
|
3130
|
+
const taskType = resolveSubmitTaskType({
|
|
3131
|
+
...(args["task-type"]?.trim() ? { explicitTaskType: args["task-type"].trim() } : {}),
|
|
3132
|
+
...(filePath ? { filePath } : {}),
|
|
3133
|
+
...(referenceMaterials ? { referenceMaterials } : {})
|
|
3134
|
+
});
|
|
3135
|
+
if (!description) {
|
|
3136
|
+
console.error(url
|
|
3137
|
+
? "submit resolved the remote document, but it did not produce readable content."
|
|
3138
|
+
: renderShellQuotingHint("submit"));
|
|
2838
3139
|
process.exitCode = 1;
|
|
2839
3140
|
return;
|
|
2840
3141
|
}
|
|
2841
|
-
if (!
|
|
2842
|
-
console.error(
|
|
3142
|
+
if (!resolvedTitle) {
|
|
3143
|
+
console.error(url
|
|
3144
|
+
? "submit resolved the remote document content, but could not determine a title. Pass --title explicitly."
|
|
3145
|
+
: renderShellQuotingHint("submit"));
|
|
2843
3146
|
process.exitCode = 1;
|
|
2844
3147
|
return;
|
|
2845
3148
|
}
|
|
2846
|
-
await store.init();
|
|
2847
|
-
const repositorySelection = await resolveSubmissionRepositorySelector(taskType, args, store, config);
|
|
3149
|
+
await runWithTerminalLoading("Checking submission workspace", async () => await store.init());
|
|
3150
|
+
const repositorySelection = await runWithTerminalLoading("Resolving submission target", async () => await resolveSubmissionRepositorySelector(taskType, args, store, config));
|
|
2848
3151
|
if (!repositorySelection.ok) {
|
|
2849
3152
|
console.error(repositorySelection.reason);
|
|
2850
3153
|
process.exitCode = 1;
|
|
2851
3154
|
return;
|
|
2852
3155
|
}
|
|
3156
|
+
const shouldPersistTaskTypeHint = Boolean(args["task-type"]?.trim()) || taskType !== "bugfix";
|
|
2853
3157
|
const event = createManualProblemEvent({
|
|
2854
3158
|
...args,
|
|
2855
3159
|
title: resolvedTitle,
|
|
2856
3160
|
description,
|
|
3161
|
+
...(shouldPersistTaskTypeHint ? { "task-type": taskType } : {}),
|
|
2857
3162
|
...(url ? { sourceUrl: url } : {}),
|
|
2858
3163
|
...(remoteSourceType ? { "source-type": remoteSourceType } : {}),
|
|
2859
3164
|
...(remoteDocumentTitle ? { "source-document-title": remoteDocumentTitle } : {}),
|
|
2860
3165
|
...(remoteDocumentType ? { "source-document-type": remoteDocumentType } : {}),
|
|
2861
3166
|
...(referenceMaterials ? { "reference-materials-json": JSON.stringify(referenceMaterials) } : {}),
|
|
3167
|
+
...(documentMap ? { "document-map-json": JSON.stringify(documentMap) } : {}),
|
|
3168
|
+
...(evidenceBundles ? { "evidence-bundles-json": JSON.stringify(evidenceBundles) } : {}),
|
|
2862
3169
|
...(repositorySelection.repoSelector ? { repo: repositorySelection.repoSelector } : {}),
|
|
2863
3170
|
...(args["allow-duplicate"] === "true" ? { "allow-duplicate": "true" } : {})
|
|
2864
3171
|
});
|
|
2865
|
-
await runtime.writeManualSubmission(event);
|
|
3172
|
+
await runWithTerminalLoading("Submitting task", async () => await runtime.writeManualSubmission(event));
|
|
2866
3173
|
console.log("[optimus] submit succeeded");
|
|
2867
3174
|
return;
|
|
2868
3175
|
}
|