@h-rig/runtime 0.0.6-alpha.10 → 0.0.6-alpha.12

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.
@@ -2951,45 +2951,6 @@ async function readSourceAwareTaskStatus(projectRoot, taskId, options = {}) {
2951
2951
  return null;
2952
2952
  }
2953
2953
  }
2954
- function updateGithubIssueTaskBySourceIssueId(sourceIssueId, taskId, update, options = {}) {
2955
- const parsed = sourceIssueId?.trim().match(/^([^/]+)\/([^#]+)#(\d+)$/);
2956
- if (!parsed || parsed[3] !== taskId) {
2957
- return false;
2958
- }
2959
- applyGithubIssueUpdate(options.ghBinary ?? "gh", options.spawn ?? spawnSync, parsed[3], {
2960
- sourceIssueId: sourceIssueId.trim(),
2961
- taskSource: { kind: "github-issues", owner: parsed[1], repo: parsed[2] }
2962
- }, update);
2963
- return true;
2964
- }
2965
- function updateSourceAwareTaskConfigTask(projectRoot, taskId, update, options = {}) {
2966
- const configPath = options.configPath ?? resolve13(projectRoot, ".rig", "task-config.json");
2967
- const rawEntry = readRawTaskEntry(configPath, taskId);
2968
- if (!rawEntry) {
2969
- const configuredFilesPath = readConfiguredFilesTaskSourcePath(projectRoot);
2970
- return configuredFilesPath ? updateFileBackedTask(projectRoot, configuredFilesPath, taskId, update) : false;
2971
- }
2972
- const metadata = readMaterializedTaskMetadata(rawEntry);
2973
- const source = metadata.taskSource;
2974
- if (source?.kind === "github-issues") {
2975
- applyGithubIssueUpdate(options.ghBinary ?? "gh", options.spawn ?? spawnSync, taskId, metadata, update);
2976
- return true;
2977
- }
2978
- if (source?.kind === "files" && source.path) {
2979
- return updateFileBackedTask(projectRoot, source.path, taskId, update);
2980
- }
2981
- if (source?.kind && source.kind !== "files" && source.kind !== "legacy-task-config") {
2982
- return false;
2983
- }
2984
- if (!source && options.allowLocalTaskConfigStatusFallback === false) {
2985
- return false;
2986
- }
2987
- if (typeof update.status !== "string" || update.status.trim().length === 0) {
2988
- return false;
2989
- }
2990
- writeLegacyTaskStatus(configPath, taskId, update.status);
2991
- return true;
2992
- }
2993
2954
  function readMaterializedTaskMetadata(entry) {
2994
2955
  const rawRig = entry._rig;
2995
2956
  if (!isPlainRecord2(rawRig)) {
@@ -3063,46 +3024,6 @@ function stripLegacyTaskConfigMetadata2(raw) {
3063
3024
  const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
3064
3025
  return tasks;
3065
3026
  }
3066
- function writeLegacyTaskStatus(configPath, taskId, status) {
3067
- const rawConfig = readRawTaskConfig(configPath);
3068
- if (!rawConfig) {
3069
- return;
3070
- }
3071
- const entry = rawConfig[taskId];
3072
- if (!isPlainRecord2(entry)) {
3073
- return;
3074
- }
3075
- entry.status = status;
3076
- writeFileSync5(configPath, `${JSON.stringify(rawConfig, null, 2)}
3077
- `, "utf8");
3078
- }
3079
- function updateFileBackedTask(projectRoot, sourcePath, taskId, update) {
3080
- const directory = resolve13(projectRoot, sourcePath);
3081
- const file = findFileBackedTaskFile(directory, taskId);
3082
- if (!file) {
3083
- return false;
3084
- }
3085
- const raw = JSON.parse(readFileSync6(file, "utf8"));
3086
- if (!isPlainRecord2(raw)) {
3087
- return false;
3088
- }
3089
- if (update.status)
3090
- raw.status = update.status;
3091
- if (update.title !== undefined)
3092
- raw.title = update.title;
3093
- if (update.body !== undefined)
3094
- raw.body = update.body;
3095
- if (update.comment?.trim()) {
3096
- const existing = Array.isArray(raw.comments) ? raw.comments : [];
3097
- raw.comments = [
3098
- ...existing,
3099
- { body: update.comment, createdAt: new Date().toISOString(), source: "rig" }
3100
- ];
3101
- }
3102
- writeFileSync5(file, `${JSON.stringify(raw, null, 2)}
3103
- `, "utf8");
3104
- return true;
3105
- }
3106
3027
  function listFileBackedTasks(projectRoot, sourcePath) {
3107
3028
  const directory = resolve13(projectRoot, sourcePath);
3108
3029
  if (!existsSync13(directory)) {
@@ -3170,26 +3091,6 @@ function readGithubIssueTask(bin, spawnFn, id, metadata, rawEntry) {
3170
3091
  ], spawnFn);
3171
3092
  return githubIssueToTask(issue, source, rawEntry);
3172
3093
  }
3173
- function applyGithubIssueUpdate(bin, spawnFn, id, metadata, update) {
3174
- const source = requireGithubIssueSource(metadata, id);
3175
- const repo = `${source.owner}/${source.repo}`;
3176
- if (update.status) {
3177
- applyGithubIssueStatus(bin, repo, spawnFn, id, update.status);
3178
- }
3179
- if (typeof update.comment === "string" && update.comment.trim().length > 0) {
3180
- runGhVoid(bin, ["issue", "comment", String(id), "--repo", repo, "--body", update.comment], spawnFn);
3181
- }
3182
- const editArgs = ["issue", "edit", String(id), "--repo", repo];
3183
- if (typeof update.title === "string" && update.title.trim().length > 0) {
3184
- editArgs.push("--title", update.title.trim());
3185
- }
3186
- if (typeof update.body === "string") {
3187
- editArgs.push("--body", update.body);
3188
- }
3189
- if (editArgs.length > 5) {
3190
- runGhVoid(bin, editArgs, spawnFn);
3191
- }
3192
- }
3193
3094
  function requireGithubIssueSource(metadata, id) {
3194
3095
  const source = metadata.taskSource;
3195
3096
  if (source?.kind === "github-issues" && source.owner && source.repo) {
@@ -3249,73 +3150,6 @@ function githubStatusFor(issue) {
3249
3150
  return "cancelled";
3250
3151
  return "open";
3251
3152
  }
3252
- function applyGithubIssueStatus(bin, repo, spawnFn, id, status) {
3253
- if (status === "closed") {
3254
- runGhVoid(bin, ["issue", "close", String(id), "--repo", repo], spawnFn);
3255
- return;
3256
- }
3257
- const targetLabel = statusLabelFor(status);
3258
- for (const label of STATUS_LABELS) {
3259
- if (targetLabel !== null && label === targetLabel) {
3260
- continue;
3261
- }
3262
- try {
3263
- runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, "--remove-label", label], spawnFn);
3264
- } catch {}
3265
- }
3266
- if (targetLabel !== null) {
3267
- try {
3268
- runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, "--add-label", targetLabel], spawnFn);
3269
- } catch (error) {
3270
- const message = error instanceof Error ? error.message : String(error);
3271
- if (!/not found/i.test(message)) {
3272
- throw error;
3273
- }
3274
- ensureStatusLabel(bin, repo, spawnFn, targetLabel);
3275
- runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, "--add-label", targetLabel], spawnFn);
3276
- }
3277
- }
3278
- }
3279
- function statusLabelFor(status) {
3280
- switch (status) {
3281
- case "in_progress":
3282
- return "in-progress";
3283
- case "blocked":
3284
- return "blocked";
3285
- case "ready":
3286
- return "ready";
3287
- case "under_review":
3288
- return "under-review";
3289
- case "failed":
3290
- return "failed";
3291
- case "cancelled":
3292
- return "cancelled";
3293
- case "open":
3294
- return null;
3295
- default:
3296
- throw new Error(`unsupported status: ${status}`);
3297
- }
3298
- }
3299
- function ensureStatusLabel(bin, repo, spawnFn, label) {
3300
- try {
3301
- runGhVoid(bin, [
3302
- "label",
3303
- "create",
3304
- label,
3305
- "--repo",
3306
- repo,
3307
- "--color",
3308
- "6f42c1",
3309
- "--description",
3310
- "Task status managed by Rig"
3311
- ], spawnFn);
3312
- } catch (error) {
3313
- const message = error instanceof Error ? error.message : String(error);
3314
- if (!/already exists/i.test(message)) {
3315
- throw error;
3316
- }
3317
- }
3318
- }
3319
3153
  function selectedGitHubEnv() {
3320
3154
  const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || "";
3321
3155
  return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
@@ -3331,10 +3165,6 @@ function runGh(bin, args, spawnFn) {
3331
3165
  }
3332
3166
  return JSON.parse(res.stdout);
3333
3167
  }
3334
- function runGhVoid(bin, args, spawnFn) {
3335
- const res = spawnFn(bin, [...args], ghSpawnOptions());
3336
- assertGhSuccess(args, res);
3337
- }
3338
3168
  function assertGhSuccess(args, res) {
3339
3169
  if (res.error) {
3340
3170
  const msg = res.error.message ?? String(res.error);
@@ -3372,19 +3202,6 @@ function isPlainRecord2(candidate) {
3372
3202
  function hasRunnableTaskSource(source) {
3373
3203
  return Boolean(source && typeof source === "object" && !Array.isArray(source));
3374
3204
  }
3375
- function cleanString(value) {
3376
- return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
3377
- }
3378
- function taskIdFromSourceIssueId(value) {
3379
- const raw = cleanString(value);
3380
- if (!raw)
3381
- return null;
3382
- const issueNumber = raw.match(/#([^#\s]+)$/)?.[1];
3383
- return issueNumber ?? raw;
3384
- }
3385
- function resolveSourceTaskId(taskId, sourceTask) {
3386
- return cleanString(sourceTask?.id) ?? taskIdFromSourceIssueId(sourceTask?.sourceIssueId) ?? taskIdFromSourceIssueId(sourceTask?.source_issue_id) ?? taskId;
3387
- }
3388
3205
  async function getPluginTask(projectRoot, taskId) {
3389
3206
  const ctx = await buildPluginHostContext(projectRoot);
3390
3207
  const [source] = ctx?.taskSourceRegistry.list() ?? [];
@@ -3409,91 +3226,6 @@ async function readConfiguredTaskSourceTask(projectRoot, taskId) {
3409
3226
  task
3410
3227
  };
3411
3228
  }
3412
- async function updatePluginTaskSourceTask(projectRoot, taskId, update) {
3413
- const ctx = await buildPluginHostContext(projectRoot);
3414
- const [source] = ctx?.taskSourceRegistry.list() ?? [];
3415
- if (!hasRunnableTaskSource(source)) {
3416
- return ctx ? { taskId, updated: false, source: "none", sourceKind: null, status: null } : null;
3417
- }
3418
- if (source.updateTask) {
3419
- await source.updateTask(taskId, update);
3420
- } else if (update.status && source.updateStatus) {
3421
- await source.updateStatus(taskId, update.status);
3422
- } else {
3423
- return {
3424
- taskId,
3425
- updated: false,
3426
- source: "plugin",
3427
- sourceKind: source.kind,
3428
- status: null
3429
- };
3430
- }
3431
- const status = source.get ? (await source.get(taskId))?.status ?? update.status ?? null : update.status ?? null;
3432
- return {
3433
- taskId,
3434
- updated: true,
3435
- source: "plugin",
3436
- sourceKind: source.kind,
3437
- status
3438
- };
3439
- }
3440
- async function updateConfiguredTaskSourceTask(projectRoot, input) {
3441
- const taskId = resolveSourceTaskId(input.taskId, input.sourceTask);
3442
- let pluginResult = null;
3443
- try {
3444
- pluginResult = await updatePluginTaskSourceTask(projectRoot, taskId, input.update);
3445
- } catch (error) {
3446
- const fallbackUpdated = updateSourceAwareTaskConfigTask(projectRoot, taskId, input.update, {
3447
- allowLocalTaskConfigStatusFallback: false
3448
- });
3449
- if (!fallbackUpdated) {
3450
- throw error;
3451
- }
3452
- return {
3453
- taskId,
3454
- updated: true,
3455
- source: "compat",
3456
- sourceKind: null,
3457
- status: await readSourceAwareTaskStatus(projectRoot, taskId)
3458
- };
3459
- }
3460
- if (pluginResult) {
3461
- return pluginResult;
3462
- }
3463
- const updated = updateSourceAwareTaskConfigTask(projectRoot, taskId, input.update);
3464
- return {
3465
- taskId,
3466
- updated,
3467
- source: updated ? "compat" : "none",
3468
- sourceKind: null,
3469
- status: await readSourceAwareTaskStatus(projectRoot, taskId)
3470
- };
3471
- }
3472
- function buildTaskRunLifecycleComment(input) {
3473
- const lines = [
3474
- "<!-- rig:status-comment -->",
3475
- `### Rig status: ${input.status}`,
3476
- "",
3477
- input.summary,
3478
- "",
3479
- `- Run: ${input.runId}`,
3480
- `- Status: ${input.status}`
3481
- ];
3482
- if (input.errorText?.trim()) {
3483
- lines.push(`- Error: ${input.errorText.trim()}`);
3484
- }
3485
- if (input.runtimeWorkspace?.trim()) {
3486
- lines.push(`- Runtime workspace: ${input.runtimeWorkspace.trim()}`);
3487
- }
3488
- if (input.logsDir?.trim()) {
3489
- lines.push(`- Logs: ${input.logsDir.trim()}`);
3490
- }
3491
- if (input.sessionDir?.trim()) {
3492
- lines.push(`- Session: ${input.sessionDir.trim()}`);
3493
- }
3494
- return lines.join(`
3495
- `);
3496
- }
3497
3229
 
3498
3230
  // packages/runtime/src/control-plane/native/task-state.ts
3499
3231
  import { existsSync as existsSync17, readFileSync as readFileSync8, readdirSync as readdirSync2, statSync as statSync4, writeFileSync as writeFileSync6 } from "fs";
@@ -9319,15 +9051,13 @@ async function runAgentWrapper(options = {}) {
9319
9051
  throw error;
9320
9052
  }
9321
9053
  const serverManagedRun = Boolean(process.env.RIG_SERVER_RUN_ID?.trim());
9322
- if (exitCode === 0 && serverManagedRun) {
9323
- await updateTaskSourceAfterRun(monorepoRoot, taskId, runtime);
9324
- }
9325
9054
  const taskClosed = await isTaskClosed(monorepoRoot, taskId);
9326
9055
  const finalExitCode = resolveFinalProviderExitCode({
9327
9056
  providerExitCode: exitCode,
9328
9057
  taskClosed,
9329
9058
  serverManagedRun
9330
9059
  });
9060
+ const handoffRequired = exitCode !== 0 || !taskClosed && !serverManagedRun;
9331
9061
  emitWrapperEvent("provider.completed", {
9332
9062
  provider,
9333
9063
  runtimeId: runtime.id,
@@ -9336,24 +9066,21 @@ async function runAgentWrapper(options = {}) {
9336
9066
  providerExitCode: exitCode,
9337
9067
  taskClosed,
9338
9068
  serverManagedRun,
9339
- handoffRequired: exitCode !== 0 || !taskClosed
9069
+ handoffRequired
9340
9070
  });
9341
- if (exitCode !== 0 || !taskClosed) {
9071
+ if (handoffRequired) {
9342
9072
  recordRuntimeHandoff(projectRoot, runtime, taskId, exitCode);
9343
9073
  }
9344
- if (exitCode === 0 && !taskClosed && serverManagedRun) {
9345
- console.error(`[rig-agent] Server-managed task run cannot finish successfully while ${taskId} is still open.`);
9346
- }
9347
9074
  return finalExitCode;
9348
9075
  }
9349
9076
  function resolveFinalProviderExitCode(input) {
9350
9077
  if (input.providerExitCode !== 0) {
9351
9078
  return input.providerExitCode;
9352
9079
  }
9353
- if (input.taskClosed) {
9080
+ if (input.taskClosed || input.serverManagedRun) {
9354
9081
  return 0;
9355
9082
  }
9356
- return input.serverManagedRun ? 2 : 0;
9083
+ return 0;
9357
9084
  }
9358
9085
  function shouldBypassProviderSandboxOnPlatform(provider, platform) {
9359
9086
  if (platform !== "darwin") {
@@ -9584,41 +9311,6 @@ async function readPluginTaskStatus(projectRoot, taskId) {
9584
9311
  return readSourceAwareTaskStatus(projectRoot, taskId);
9585
9312
  }
9586
9313
  }
9587
- async function updateTaskSourceAfterRun(projectRoot, taskId, runtime) {
9588
- const comment = buildTaskRunLifecycleComment({
9589
- runId: process.env.RIG_SERVER_RUN_ID || "(unknown)",
9590
- status: "closed",
9591
- summary: "Rig task run completed and closed this task.",
9592
- runtimeWorkspace: runtime.workspaceDir,
9593
- logsDir: runtime.logsDir,
9594
- sessionDir: runtime.sessionDir
9595
- });
9596
- try {
9597
- const result = await updateConfiguredTaskSourceTask(projectRoot, {
9598
- taskId,
9599
- update: { status: "closed", comment }
9600
- });
9601
- if (result.updated) {
9602
- return;
9603
- }
9604
- throw new Error(result.source === "plugin" || result.sourceKind ? `configured task source${result.sourceKind ? ` (${result.sourceKind})` : ""} does not support lifecycle updates` : "no source-aware task source is configured");
9605
- } catch (error) {
9606
- let fallbackUpdated = false;
9607
- try {
9608
- const sourceIssueId = loadRuntimeContextFromEnv()?.sourceTask?.sourceIssueId;
9609
- fallbackUpdated = updateGithubIssueTaskBySourceIssueId(sourceIssueId, taskId, { status: "closed", comment });
9610
- fallbackUpdated = updateSourceAwareTaskConfigTask(projectRoot, taskId, { status: "closed", comment }) || fallbackUpdated;
9611
- } catch (fallbackError) {
9612
- console.error(`[rig-agent] Source-aware compatibility update also failed for ${taskId}: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`);
9613
- }
9614
- const message = `[rig-agent] Failed to update task source for ${taskId}${fallbackUpdated ? "; applied source-aware compatibility update" : ""}: ${error instanceof Error ? error.message : String(error)}`;
9615
- if (fallbackUpdated) {
9616
- console.log(message);
9617
- } else {
9618
- console.error(message);
9619
- }
9620
- }
9621
- }
9622
9314
  function recordRuntimeHandoff(hostProjectRoot, runtime, taskId, exitCode) {
9623
9315
  const handoffDir = resolve35(hostProjectRoot, ".rig/runtime/handoffs");
9624
9316
  mkdirSync19(handoffDir, { recursive: true });
@@ -7278,8 +7278,9 @@ function defaultPrCloseoutLine(taskId, repoNameWithOwner) {
7278
7278
  const sourceIssueId = loadRuntimeContextFromEnv()?.sourceTask?.sourceIssueId;
7279
7279
  if (sourceIssueId) {
7280
7280
  const match = sourceIssueId.match(/^([^#]+)#(\d+)$/);
7281
- if (match) {
7282
- const [, sourceRepo, issueNumber] = match;
7281
+ if (match?.[1] && match[2]) {
7282
+ const sourceRepo = match[1];
7283
+ const issueNumber = match[2];
7283
7284
  return sourceRepo.toLowerCase() === repoNameWithOwner.toLowerCase() ? `Closes #${issueNumber}` : `Closes ${sourceRepo}#${issueNumber}`;
7284
7285
  }
7285
7286
  }
@@ -9319,15 +9319,13 @@ async function runAgentWrapper(options = {}) {
9319
9319
  throw error;
9320
9320
  }
9321
9321
  const serverManagedRun = Boolean(process.env.RIG_SERVER_RUN_ID?.trim());
9322
- if (exitCode === 0 && serverManagedRun) {
9323
- await updateTaskSourceAfterRun(monorepoRoot, taskId, runtime);
9324
- }
9325
9322
  const taskClosed = await isTaskClosed(monorepoRoot, taskId);
9326
9323
  const finalExitCode = resolveFinalProviderExitCode({
9327
9324
  providerExitCode: exitCode,
9328
9325
  taskClosed,
9329
9326
  serverManagedRun
9330
9327
  });
9328
+ const handoffRequired = exitCode !== 0 || !taskClosed && !serverManagedRun;
9331
9329
  emitWrapperEvent("provider.completed", {
9332
9330
  provider,
9333
9331
  runtimeId: runtime.id,
@@ -9336,24 +9334,21 @@ async function runAgentWrapper(options = {}) {
9336
9334
  providerExitCode: exitCode,
9337
9335
  taskClosed,
9338
9336
  serverManagedRun,
9339
- handoffRequired: exitCode !== 0 || !taskClosed
9337
+ handoffRequired
9340
9338
  });
9341
- if (exitCode !== 0 || !taskClosed) {
9339
+ if (handoffRequired) {
9342
9340
  recordRuntimeHandoff(projectRoot, runtime, taskId, exitCode);
9343
9341
  }
9344
- if (exitCode === 0 && !taskClosed && serverManagedRun) {
9345
- console.error(`[rig-agent] Server-managed task run cannot finish successfully while ${taskId} is still open.`);
9346
- }
9347
9342
  return finalExitCode;
9348
9343
  }
9349
9344
  function resolveFinalProviderExitCode(input) {
9350
9345
  if (input.providerExitCode !== 0) {
9351
9346
  return input.providerExitCode;
9352
9347
  }
9353
- if (input.taskClosed) {
9348
+ if (input.taskClosed || input.serverManagedRun) {
9354
9349
  return 0;
9355
9350
  }
9356
- return input.serverManagedRun ? 2 : 0;
9351
+ return 0;
9357
9352
  }
9358
9353
  function shouldBypassProviderSandboxOnPlatform(provider, platform) {
9359
9354
  if (platform !== "darwin") {
@@ -9587,8 +9582,8 @@ async function readPluginTaskStatus(projectRoot, taskId) {
9587
9582
  async function updateTaskSourceAfterRun(projectRoot, taskId, runtime) {
9588
9583
  const comment = buildTaskRunLifecycleComment({
9589
9584
  runId: process.env.RIG_SERVER_RUN_ID || "(unknown)",
9590
- status: "closed",
9591
- summary: "Rig task run completed and closed this task.",
9585
+ status: "completed",
9586
+ summary: "Rig task run completed; source closeout waits for approved PR merge.",
9592
9587
  runtimeWorkspace: runtime.workspaceDir,
9593
9588
  logsDir: runtime.logsDir,
9594
9589
  sessionDir: runtime.sessionDir
@@ -9596,7 +9591,7 @@ async function updateTaskSourceAfterRun(projectRoot, taskId, runtime) {
9596
9591
  try {
9597
9592
  const result = await updateConfiguredTaskSourceTask(projectRoot, {
9598
9593
  taskId,
9599
- update: { status: "closed", comment }
9594
+ update: { status: "completed", comment }
9600
9595
  });
9601
9596
  if (result.updated) {
9602
9597
  return;
@@ -9606,8 +9601,8 @@ async function updateTaskSourceAfterRun(projectRoot, taskId, runtime) {
9606
9601
  let fallbackUpdated = false;
9607
9602
  try {
9608
9603
  const sourceIssueId = loadRuntimeContextFromEnv()?.sourceTask?.sourceIssueId;
9609
- fallbackUpdated = updateGithubIssueTaskBySourceIssueId(sourceIssueId, taskId, { status: "closed", comment });
9610
- fallbackUpdated = updateSourceAwareTaskConfigTask(projectRoot, taskId, { status: "closed", comment }) || fallbackUpdated;
9604
+ fallbackUpdated = updateGithubIssueTaskBySourceIssueId(sourceIssueId, taskId, { status: "completed", comment });
9605
+ fallbackUpdated = updateSourceAwareTaskConfigTask(projectRoot, taskId, { status: "completed", comment }) || fallbackUpdated;
9611
9606
  } catch (fallbackError) {
9612
9607
  console.error(`[rig-agent] Source-aware compatibility update also failed for ${taskId}: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`);
9613
9608
  }