@sireai/optimus 0.1.8 → 0.1.9

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 (66) hide show
  1. package/README.md +2 -1
  2. package/dist/cli/optimus.js +145 -81
  3. package/dist/cli/optimus.js.map +1 -1
  4. package/dist/config/load-config.js +3 -0
  5. package/dist/config/load-config.js.map +1 -1
  6. package/dist/integrations/feishu/feishu-auth-config.d.ts +7 -0
  7. package/dist/integrations/feishu/feishu-auth-config.js +37 -0
  8. package/dist/integrations/feishu/feishu-auth-config.js.map +1 -0
  9. package/dist/integrations/feishu/feishu-auth-service.d.ts +71 -0
  10. package/dist/integrations/feishu/feishu-auth-service.js +399 -0
  11. package/dist/integrations/feishu/feishu-auth-service.js.map +1 -0
  12. package/dist/integrations/feishu/feishu-auth-store.d.ts +29 -0
  13. package/dist/integrations/feishu/feishu-auth-store.js +113 -0
  14. package/dist/integrations/feishu/feishu-auth-store.js.map +1 -0
  15. package/dist/integrations/feishu/feishu-client.d.ts +52 -0
  16. package/dist/integrations/feishu/feishu-client.js +217 -0
  17. package/dist/integrations/feishu/feishu-client.js.map +1 -0
  18. package/dist/integrations/feishu/feishu-doc-service.d.ts +46 -0
  19. package/dist/integrations/feishu/feishu-doc-service.js +281 -0
  20. package/dist/integrations/feishu/feishu-doc-service.js.map +1 -0
  21. package/dist/integrations/feishu/feishu-token-store.d.ts +17 -0
  22. package/dist/integrations/feishu/feishu-token-store.js +34 -0
  23. package/dist/integrations/feishu/feishu-token-store.js.map +1 -0
  24. package/dist/integrations/feishu/feishu-user-service.d.ts +14 -0
  25. package/dist/integrations/feishu/feishu-user-service.js +60 -0
  26. package/dist/integrations/feishu/feishu-user-service.js.map +1 -0
  27. package/dist/integrations/jira/jira-cli.js +25 -11
  28. package/dist/integrations/jira/jira-cli.js.map +1 -1
  29. package/dist/integrations/jira/jira-client.d.ts +26 -0
  30. package/dist/integrations/jira/jira-client.js +111 -0
  31. package/dist/integrations/jira/jira-client.js.map +1 -1
  32. package/dist/integrations/jira/jira-submit.d.ts +5 -5
  33. package/dist/integrations/jira/jira-submit.js +97 -66
  34. package/dist/integrations/jira/jira-submit.js.map +1 -1
  35. package/dist/problem-solving-core/codex/codex-runner.d.ts +2 -0
  36. package/dist/problem-solving-core/codex/codex-runner.js +65 -0
  37. package/dist/problem-solving-core/codex/codex-runner.js.map +1 -1
  38. package/dist/task-environment/delivery/feishu-analysis-doc-service.d.ts +7 -25
  39. package/dist/task-environment/delivery/feishu-analysis-doc-service.js +32 -337
  40. package/dist/task-environment/delivery/feishu-analysis-doc-service.js.map +1 -1
  41. package/dist/task-environment/delivery/task-publication-service.d.ts +4 -0
  42. package/dist/task-environment/delivery/task-publication-service.js +56 -6
  43. package/dist/task-environment/delivery/task-publication-service.js.map +1 -1
  44. package/dist/task-environment/evidence/evidence-preparation-service.d.ts +36 -0
  45. package/dist/task-environment/evidence/evidence-preparation-service.js +341 -0
  46. package/dist/task-environment/evidence/evidence-preparation-service.js.map +1 -0
  47. package/dist/task-environment/intake/manual-problem-intake.js +15 -0
  48. package/dist/task-environment/intake/manual-problem-intake.js.map +1 -1
  49. package/dist/task-environment/observability/logger.js +21 -0
  50. package/dist/task-environment/observability/logger.js.map +1 -1
  51. package/dist/task-environment/orchestration/task-orchestrator.d.ts +3 -0
  52. package/dist/task-environment/orchestration/task-orchestrator.js +87 -1
  53. package/dist/task-environment/orchestration/task-orchestrator.js.map +1 -1
  54. package/dist/task-environment/orchestration/triage-runner.d.ts +2 -0
  55. package/dist/task-environment/orchestration/triage-runner.js +27 -1
  56. package/dist/task-environment/orchestration/triage-runner.js.map +1 -1
  57. package/dist/task-environment/runtime/optimus-runtime.js +4 -3
  58. package/dist/task-environment/runtime/optimus-runtime.js.map +1 -1
  59. package/dist/types.d.ts +3 -0
  60. package/embedded-skills/shared/video-keyframe-analyzer/SKILL.md +88 -0
  61. package/embedded-skills/shared/video-keyframe-analyzer/references/encountered-problems.md +12 -0
  62. package/embedded-skills/shared/video-keyframe-analyzer/references/triage-checklist.md +48 -0
  63. package/embedded-skills/shared/video-keyframe-analyzer/scripts/extract-keyframes.mjs +614 -0
  64. package/embedded-skills/shared/{repo-inspection → video-keyframe-analyzer}/skill.json +1 -1
  65. package/package.json +6 -2
  66. package/embedded-skills/shared/repo-inspection/SKILL.md +0 -9
package/README.md CHANGED
@@ -35,7 +35,7 @@ npx playwright install chromium
35
35
  optimus setup
36
36
  ```
37
37
 
38
- Setup creates the `~/.optimus` runtime home, registers the first repository, configures Codex authentication, and records Jira, Sentry, and Feishu integration settings. When Jira and Sentry are both enabled, setup also stores the Jira project key used to auto-create Jira issues for Sentry-origin patch publication.
38
+ Setup creates the `~/.optimus` runtime home, registers the first repository, configures Codex authentication, and records Jira, Sentry, and Feishu delivery settings. If Feishu delivery is enabled, run `optimus feishu auth login` after setup to enable analysis-doc upload and real @mentions. When Jira and Sentry are both enabled, setup also stores the Jira project key used to auto-create Jira issues for Sentry-origin patch publication.
39
39
 
40
40
  Supported Codex auth modes:
41
41
 
@@ -76,6 +76,7 @@ optimus setup
76
76
  optimus doctor --quick
77
77
  optimus start
78
78
  optimus submit --title "..." --description "..."
79
+ optimus feishu auth login
79
80
  ```
80
81
 
81
82
  Task inspection:
@@ -29,6 +29,7 @@ import { TaskDeliveryDispatcher } from "../task-environment/delivery/task-delive
29
29
  import { TaskDeliveryService } from "../task-environment/delivery/task-delivery-service.js";
30
30
  import { FeishuAnalysisDocService } from "../task-environment/delivery/feishu-analysis-doc-service.js";
31
31
  import { TaskPublicationService } from "../task-environment/delivery/task-publication-service.js";
32
+ import { FeishuAuthService } from "../integrations/feishu/feishu-auth-service.js";
32
33
  import { resolveDefaultConfigPath, resolveDefaultEnvPath, resolveOptimusHomeDir } from "../config/optimus-paths.js";
33
34
  import { checkForSelfUpdate, installSelfUpdate, recordSkippedSelfUpdate, readSelfUpdateState, shouldPromptForCachedStartupUpdate } from "./self-update.js";
34
35
  const CLI_ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
@@ -165,6 +166,7 @@ function renderCliHelp() {
165
166
  " postrun-retry Rebuild postrun artifacts and optionally replay publication",
166
167
  "",
167
168
  "Integration commands:",
169
+ " feishu auth login|status|logout",
168
170
  " jira-search|jira-get-issue|jira-search-assigned-open|jira-submit-issue|jira-submit-assigned-open|jira-add-comment|jira-refresh-auth|jira-poll-once|jira-poll-daemon",
169
171
  " sentry-get-event|sentry-submit-event",
170
172
  " Example: optimus help jira-submit-issue",
@@ -315,6 +317,32 @@ function renderCommandHelp(command) {
315
317
  " optimus upgrade --check --channel snapshot",
316
318
  " optimus upgrade --channel snapshot"
317
319
  ].join("\n"),
320
+ feishu: [
321
+ "optimus feishu auth",
322
+ "",
323
+ "Usage:",
324
+ " optimus feishu auth login [--json]",
325
+ " optimus feishu auth status [--json]",
326
+ " optimus feishu auth logout",
327
+ "",
328
+ "Notes:",
329
+ " - `login` starts Feishu device-code login for document upload and real @mentions.",
330
+ " - `status` shows whether the current Optimus Feishu session is still valid.",
331
+ " - `logout` clears the local Optimus Feishu auth token."
332
+ ].join("\n"),
333
+ "feishu auth": [
334
+ "optimus feishu auth",
335
+ "",
336
+ "Usage:",
337
+ " optimus feishu auth login [--json]",
338
+ " optimus feishu auth status [--json]",
339
+ " optimus feishu auth logout",
340
+ "",
341
+ "Notes:",
342
+ " - `login` starts Feishu device-code login for document upload and real @mentions.",
343
+ " - `status` shows whether the current Optimus Feishu session is still valid.",
344
+ " - `logout` clears the local Optimus Feishu auth token."
345
+ ].join("\n"),
318
346
  "jira-submit-issue": [
319
347
  "optimus jira-submit-issue",
320
348
  "",
@@ -813,83 +841,58 @@ function buildPublicationReviewSummary(attempts) {
813
841
  }
814
842
  };
815
843
  }
816
- function buildFeishuEnvStatus() {
844
+ async function buildFeishuDoctorStatus(config) {
845
+ const webhookConfigured = Boolean(config.delivery.feishu.webhook?.trim() || config.delivery.feishu.webhooks?.some((value) => value.trim()));
846
+ const authStatus = new FeishuAuthService().readStatus();
847
+ const hasAppId = Boolean(config.delivery.feishu.appId?.trim() || process.env.FEISHU_APP_ID?.trim());
848
+ const hasAppSecret = Boolean(config.delivery.feishu.appSecret?.trim() || process.env.FEISHU_APP_SECRET?.trim());
849
+ const appCredentialConfigured = hasAppId && hasAppSecret;
850
+ const authReady = authStatus.authenticated || appCredentialConfigured;
851
+ const authMode = authStatus.authenticated
852
+ ? "user_access_token"
853
+ : appCredentialConfigured
854
+ ? "app_credentials"
855
+ : "none";
817
856
  return {
818
- webhook: Boolean(process.env.OPTIMUS_FEISHU_WEBHOOK?.trim()),
819
- webhooks: Boolean(process.env.OPTIMUS_FEISHU_WEBHOOKS?.trim()),
820
- secret: Boolean(process.env.OPTIMUS_FEISHU_SECRET?.trim())
857
+ webhook: {
858
+ configured: webhookConfigured,
859
+ signed: Boolean(config.delivery.feishu.secret?.trim())
860
+ },
861
+ auth: {
862
+ configured: authReady,
863
+ baseUrl: authStatus.baseUrl,
864
+ mode: authMode,
865
+ accessTokenExpired: authStatus.accessTokenExpired,
866
+ refreshTokenExpired: authStatus.refreshTokenExpired,
867
+ ...(authStatus.userId ? { userId: authStatus.userId } : {}),
868
+ ...(authStatus.openId ? { openId: authStatus.openId } : {}),
869
+ ...(authStatus.expiresAt ? { expiresAt: authStatus.expiresAt } : {}),
870
+ ...(authStatus.refreshExpiresAt ? { refreshExpiresAt: authStatus.refreshExpiresAt } : {}),
871
+ detail: authStatus.authenticated
872
+ ? "Feishu auth token is ready for document upload and real @mentions."
873
+ : appCredentialConfigured
874
+ ? "Feishu runtime auth is available for document upload and user lookup."
875
+ : "Feishu webhook delivery can still work, but document upload and real @mentions are unavailable until Optimus signs in to Feishu.",
876
+ ...(authReady ? {} : { recommendation: "Run `optimus feishu auth login` to enable analysis docs and real @mentions." })
877
+ },
878
+ mentions: {
879
+ requiresOpenId: true,
880
+ ready: authReady,
881
+ detail: authReady
882
+ ? "Authenticated Feishu user lookup is ready for real @mentions."
883
+ : "Real @mentions require `optimus feishu auth login`.",
884
+ ...(authReady ? {} : { recommendation: "Run `optimus feishu auth login`." })
885
+ }
821
886
  };
822
887
  }
823
- async function buildFeishuDoctorStatus() {
824
- const env = buildFeishuEnvStatus();
825
- const authCommand = "feishu auth login";
826
- const resolverCommand = "feishu user resolve <xiaomi_email>";
827
- try {
828
- const versionResult = await execFileAsync("feishu", ["--version"], { encoding: "utf8" });
829
- const version = typeof versionResult.stdout === "string" ? versionResult.stdout.trim() : String(versionResult.stdout).trim();
830
- try {
831
- const authResult = await execFileAsync("feishu", ["auth", "status"], { encoding: "utf8" });
832
- const authText = `${typeof authResult.stdout === "string" ? authResult.stdout : String(authResult.stdout)}\n${typeof authResult.stderr === "string" ? authResult.stderr : String(authResult.stderr)}`.trim();
833
- return {
834
- env,
835
- cli: {
836
- installed: true,
837
- authenticated: true,
838
- authCommand,
839
- ...(version ? { version } : {}),
840
- detail: authText || "Feishu CLI authenticated."
841
- },
842
- mentions: {
843
- requiresOpenId: true,
844
- resolverCommand,
845
- ready: true,
846
- detail: "Real @mentions require open_id resolution. Auth is ready; if resolve still fails, check Feishu app scope contact:user.id:readonly.",
847
- recommendation: "If @mentions still degrade to text, verify the Feishu app has contact:user.id:readonly."
848
- }
849
- };
850
- }
851
- catch (error) {
852
- const detail = extractCommandFailureDetail(error) || "Feishu CLI is installed but not authenticated.";
853
- return {
854
- env,
855
- cli: {
856
- installed: true,
857
- authenticated: false,
858
- authCommand,
859
- ...(version ? { version } : {}),
860
- detail,
861
- recommendation: "Run 'feishu auth login' to refresh Feishu authorization for doc upload and user identity resolution."
862
- },
863
- mentions: {
864
- requiresOpenId: true,
865
- resolverCommand,
866
- ready: false,
867
- detail: "Real @mentions require open_id resolution, but Feishu CLI auth is not ready.",
868
- recommendation: "Run 'feishu auth login' before relying on Feishu user resolution or real @mentions."
869
- }
870
- };
871
- }
872
- }
873
- catch (error) {
874
- const detail = extractCommandFailureDetail(error) || "Feishu CLI is not installed.";
875
- return {
876
- env,
877
- cli: {
878
- installed: false,
879
- authenticated: false,
880
- authCommand,
881
- detail,
882
- recommendation: "Install Feishu CLI first, then run 'feishu auth login' if you need Feishu delivery, docs, or real @mentions."
883
- },
884
- mentions: {
885
- requiresOpenId: true,
886
- resolverCommand,
887
- ready: false,
888
- detail: "Real @mentions require Feishu CLI user resolution support.",
889
- recommendation: "Install Feishu CLI and authenticate before relying on Feishu open_id resolution."
890
- }
891
- };
892
- }
888
+ function buildFeishuRuntimeStatus(config) {
889
+ const authStatus = new FeishuAuthService().readStatus();
890
+ return {
891
+ webhook: Boolean(config.delivery.feishu.webhook?.trim() || process.env.OPTIMUS_FEISHU_WEBHOOK?.trim()),
892
+ webhooks: Boolean(config.delivery.feishu.webhooks?.some((value) => value.trim()) || process.env.OPTIMUS_FEISHU_WEBHOOKS?.trim()),
893
+ secret: Boolean(config.delivery.feishu.secret?.trim() || process.env.OPTIMUS_FEISHU_SECRET?.trim()),
894
+ auth: authStatus.authenticated || Boolean((config.delivery.feishu.appId?.trim() || process.env.FEISHU_APP_ID?.trim()) && (config.delivery.feishu.appSecret?.trim() || process.env.FEISHU_APP_SECRET?.trim()))
895
+ };
893
896
  }
894
897
  function extractCommandFailureDetail(error) {
895
898
  if (!(error instanceof Error)) {
@@ -950,7 +953,7 @@ async function promptSetupAnswers(defaults) {
950
953
  const providerKeyLabel = codexProviderApiKeyEnvName || defaults.codexProviderApiKeyEnvName || "the configured provider env";
951
954
  codexApiKey = (await ask(renderSetupPrompt("Optional", `Provider API key value for ${providerKeyLabel}`, "leave blank to keep existing"))).trim() || undefined;
952
955
  }
953
- printSetupSection("Feishu", "Enable this module when task notifications should be sent to Feishu.");
956
+ printSetupSection("Feishu", "Enable this module when task notifications should be sent to Feishu. Use `optimus feishu auth login` later for analysis docs and real @mentions.");
954
957
  const enableFeishuInput = (await ask(renderSetupPrompt("Optional module", "Enable Feishu delivery", defaults.enableFeishu ? "Y/n" : "y/N"))).trim().toLowerCase();
955
958
  const enableFeishu = enableFeishuInput.length === 0
956
959
  ? defaults.enableFeishu
@@ -1429,6 +1432,13 @@ async function runQuickDoctor(configPath = resolveDefaultConfigPath()) {
1429
1432
  impact: "Notifications will stay console-only."
1430
1433
  });
1431
1434
  }
1435
+ else if (!new FeishuAuthService().readStatus().authenticated && (!config.delivery.feishu.appId?.trim() || !config.delivery.feishu.appSecret?.trim())) {
1436
+ warnings.push({
1437
+ code: "feishu_auth_missing",
1438
+ message: "Feishu auth token is missing.",
1439
+ impact: "Webhook messages can still be sent, but analysis document upload and real @mentions will be skipped."
1440
+ });
1441
+ }
1432
1442
  if (config.jira.enabled) {
1433
1443
  const jiraIssues = resolveJiraQuickDoctorIssues(config.jira, {
1434
1444
  sentryEnabled: config.sentry.enabled
@@ -1727,7 +1737,7 @@ async function main() {
1727
1737
  : firstArg;
1728
1738
  const commandArgs = rest;
1729
1739
  if (command === "help") {
1730
- const requestedCommand = commandArgs[0]?.trim();
1740
+ const requestedCommand = commandArgs.slice(0, 2).join(" ").trim() || commandArgs[0]?.trim();
1731
1741
  if (requestedCommand) {
1732
1742
  console.log(renderCommandHelp(requestedCommand) ?? renderCliHelp());
1733
1743
  process.exitCode = renderCommandHelp(requestedCommand) ? 0 : 1;
@@ -1814,6 +1824,60 @@ async function main() {
1814
1824
  process.exitCode = result.doctorQuick.summary === "blocked" ? 1 : 0;
1815
1825
  return;
1816
1826
  }
1827
+ if (command === "feishu") {
1828
+ const [firstSubcommand, secondSubcommand] = commandArgs;
1829
+ const args = parseArgs(commandArgs.slice(2));
1830
+ if (firstSubcommand !== "auth" || !secondSubcommand) {
1831
+ console.log(renderCommandHelp("feishu") ?? renderCliHelp());
1832
+ process.exitCode = 1;
1833
+ return;
1834
+ }
1835
+ const authService = new FeishuAuthService();
1836
+ if (secondSubcommand === "status") {
1837
+ const status = authService.readStatus();
1838
+ if (args.json === "true") {
1839
+ console.log(JSON.stringify(status, null, 2));
1840
+ }
1841
+ else if (status.authenticated) {
1842
+ console.log(`Feishu auth is ready. user=${status.userId ?? "unknown"} open_id=${status.openId ?? "unknown"} expires=${status.expiresAt ?? "unknown"}`);
1843
+ }
1844
+ else {
1845
+ console.log("Feishu auth is not ready. Run `optimus feishu auth login`.");
1846
+ }
1847
+ process.exitCode = status.authenticated ? 0 : 1;
1848
+ return;
1849
+ }
1850
+ if (secondSubcommand === "login") {
1851
+ const result = await authService.login({
1852
+ onPrompt: ({ verificationUrl, expiresInSeconds }) => {
1853
+ console.error(`Open this URL to authorize:\n${verificationUrl}\n`);
1854
+ console.error(`Waiting for authorization (expires in ${expiresInSeconds}s)...`);
1855
+ }
1856
+ });
1857
+ if (args.json === "true") {
1858
+ console.log(JSON.stringify({
1859
+ ok: true,
1860
+ userId: result.userId ?? null,
1861
+ openId: result.openId ?? null,
1862
+ verificationUrl: result.verificationUrl,
1863
+ expiresAt: new Date(result.expiresAtMs).toISOString(),
1864
+ refreshExpiresAt: new Date(result.refreshExpiresAtMs).toISOString()
1865
+ }, null, 2));
1866
+ }
1867
+ else {
1868
+ console.log(`Feishu auth succeeded. user=${result.userId ?? "unknown"} open_id=${result.openId ?? "unknown"}`);
1869
+ }
1870
+ return;
1871
+ }
1872
+ if (secondSubcommand === "logout") {
1873
+ await authService.logout();
1874
+ console.log("Feishu auth cleared.");
1875
+ return;
1876
+ }
1877
+ console.log(renderCommandHelp("feishu") ?? renderCliHelp());
1878
+ process.exitCode = 1;
1879
+ return;
1880
+ }
1817
1881
  if (mapJiraCommand(command)) {
1818
1882
  process.exitCode = await forwardJiraCliCommand(command, commandArgs);
1819
1883
  return;
@@ -1978,7 +2042,7 @@ async function main() {
1978
2042
  : [];
1979
2043
  const reviewAwareNotifier = new FeishuNotifier({ config, publicationAttempts, format: config.delivery.feishu.format });
1980
2044
  const message = reviewAwareNotifier.previewMessage(bundle);
1981
- const envStatus = buildFeishuEnvStatus();
2045
+ const envStatus = buildFeishuRuntimeStatus(config);
1982
2046
  if (args["print-env-status"] === "true") {
1983
2047
  console.log(JSON.stringify({ envStatus }, null, 2));
1984
2048
  process.exitCode = envStatus.webhook || envStatus.webhooks ? 0 : 1;
@@ -2018,7 +2082,7 @@ async function main() {
2018
2082
  const publicationAttempts = await store.listTaskPublicationAttempts(taskId);
2019
2083
  const notifier = new FeishuNotifier({ config, publicationAttempts, format: config.delivery.feishu.format });
2020
2084
  const message = notifier.previewMessage(bundle);
2021
- const envStatus = buildFeishuEnvStatus();
2085
+ const envStatus = buildFeishuRuntimeStatus(config);
2022
2086
  if (args["print-env-status"] === "true") {
2023
2087
  console.log(JSON.stringify({ taskId, channel, envStatus }, null, 2));
2024
2088
  process.exitCode = envStatus.webhook || envStatus.webhooks ? 0 : 1;
@@ -2073,7 +2137,7 @@ async function main() {
2073
2137
  deliveryBundle: existingBundle
2074
2138
  });
2075
2139
  const deliveryService = new TaskDeliveryService();
2076
- const analysisDocService = new FeishuAnalysisDocService({ refStore: store });
2140
+ const analysisDocService = new FeishuAnalysisDocService({ config, refStore: store });
2077
2141
  const publicationService = new TaskPublicationService(logger);
2078
2142
  const deliveryDispatcher = new TaskDeliveryDispatcher(config);
2079
2143
  let deliveryBundle = mergeReplayBundle(await deliveryService.buildBundle({
@@ -2708,7 +2772,7 @@ async function main() {
2708
2772
  const skillSyncStatus = await logger.readSkillSyncSnapshot();
2709
2773
  const evolutionStatus = await logger.readEvolutionSnapshot();
2710
2774
  const repoMemoryStatus = await logger.readRepoMemorySnapshot();
2711
- const feishuStatus = await buildFeishuDoctorStatus();
2775
+ const feishuStatus = await buildFeishuDoctorStatus(config);
2712
2776
  const preflight = await new CodexPreflight(config).run();
2713
2777
  const skillSummary = await inspectBuiltinSkills(config, skillSyncService);
2714
2778
  const repoMemorySummary = await new RepoMemoryService(config).readDoctorSummary();
@@ -3086,7 +3150,7 @@ function resolveTimeoutKind(run, config, waitingForFirstSignal, elapsedMs, idleM
3086
3150
  if (elapsedMs >= config.runtime.runTimeoutMs) {
3087
3151
  return "hard_timeout";
3088
3152
  }
3089
- if (waitingForFirstSignal && elapsedMs >= config.runtime.startupTimeoutMs) {
3153
+ if (waitingForFirstSignal && idleMs >= config.runtime.startupTimeoutMs) {
3090
3154
  return "startup_timeout";
3091
3155
  }
3092
3156
  if (!waitingForFirstSignal && idleMs >= config.runtime.idleTimeoutMs) {