@koda-sl/baker-bridge 0.108.0-dev.f7c15fa76 → 0.109.0-dev.88120240

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 (52) hide show
  1. package/README.md +22 -21
  2. package/dist/hono/agent-session.d.ts +1 -0
  3. package/dist/hono/agent-session.d.ts.map +1 -1
  4. package/dist/hono/agent-session.js +13 -2
  5. package/dist/hono/agent-session.js.map +1 -1
  6. package/dist/hono/agent-session.test.js +37 -0
  7. package/dist/hono/agent-session.test.js.map +1 -1
  8. package/dist/hono/agent-tasks.d.ts.map +1 -1
  9. package/dist/hono/agent-tasks.js +7 -3
  10. package/dist/hono/agent-tasks.js.map +1 -1
  11. package/dist/hono/agent-tasks.test.js +15 -0
  12. package/dist/hono/agent-tasks.test.js.map +1 -1
  13. package/dist/hono/agent.d.ts.map +1 -1
  14. package/dist/hono/agent.js +12 -9
  15. package/dist/hono/agent.js.map +1 -1
  16. package/dist/hono/attachments.d.ts +6 -1
  17. package/dist/hono/attachments.d.ts.map +1 -1
  18. package/dist/hono/attachments.js +9 -4
  19. package/dist/hono/attachments.js.map +1 -1
  20. package/dist/hono/attachments.test.js +5 -0
  21. package/dist/hono/attachments.test.js.map +1 -1
  22. package/dist/hono/bridge-job.d.ts +31 -12
  23. package/dist/hono/bridge-job.d.ts.map +1 -1
  24. package/dist/hono/bridge-job.js +130 -62
  25. package/dist/hono/bridge-job.js.map +1 -1
  26. package/dist/hono/bridge-job.test.js +227 -146
  27. package/dist/hono/bridge-job.test.js.map +1 -1
  28. package/dist/hono/session-continuity.d.ts +0 -1
  29. package/dist/hono/session-continuity.d.ts.map +1 -1
  30. package/dist/hono/session-continuity.js +2 -5
  31. package/dist/hono/session-continuity.js.map +1 -1
  32. package/dist/hono/types.d.ts +7 -6
  33. package/dist/hono/types.d.ts.map +1 -1
  34. package/dist/hono/workspace-settle.d.ts +20 -0
  35. package/dist/hono/workspace-settle.d.ts.map +1 -0
  36. package/dist/hono/workspace-settle.js +46 -0
  37. package/dist/hono/workspace-settle.js.map +1 -0
  38. package/dist/review/digest.d.ts +7 -1
  39. package/dist/review/digest.d.ts.map +1 -1
  40. package/dist/review/digest.js +80 -16
  41. package/dist/review/digest.js.map +1 -1
  42. package/dist/review/digest.test.js +83 -0
  43. package/dist/review/digest.test.js.map +1 -1
  44. package/package.json +2 -1
  45. package/dist/hono/main-sync.d.ts +0 -41
  46. package/dist/hono/main-sync.d.ts.map +0 -1
  47. package/dist/hono/main-sync.js +0 -390
  48. package/dist/hono/main-sync.js.map +0 -1
  49. package/dist/hono/main-sync.test.d.ts +0 -2
  50. package/dist/hono/main-sync.test.d.ts.map +0 -1
  51. package/dist/hono/main-sync.test.js +0 -266
  52. package/dist/hono/main-sync.test.js.map +0 -1
@@ -29,6 +29,7 @@ import { createBakerUiServer, normalizeTagInputToolInput, sanitizeTagChanges, TA
29
29
  import { blockingToolDelegated, toolRunAsCommand } from "./tool-in-shell.js";
30
30
  import { recordTurnEvent } from "./turn-events.js";
31
31
  import { addQueryModelUsage, queryModelUsage, turnModelUsageList } from "./turn-model-usage.js";
32
+ import { settleWorkspaceAfterTasks } from "./workspace-settle.js";
32
33
  export function parsePlanDecision(value) {
33
34
  if (!value || typeof value !== "object") {
34
35
  return null;
@@ -151,10 +152,11 @@ const BASE_DISALLOWED_TOOLS = [
151
152
  // not asked to fund a research fleet. Ordinary fan-out is unaffected:
152
153
  // `Task` stays, so the scaffold's subagents still run.
153
154
  "Workflow",
154
- // Cross-session messaging. A Runtime holds exactly one session, so
155
- // there is never a peer to reach; every call is a dead end that costs a
156
- // round trip to discover.
157
- "SendMessage",
155
+ // NOT listed: `SendMessage`. It is how the model follows up with a
156
+ // subagent it already ran, and the CLI tells it to on every Agent result.
157
+ // The continued subagent runs in the background, but it announces itself
158
+ // with `task_started` / `background_tasks_changed`, so the turn stays open
159
+ // until it finishes (`createTurnEndController`).
158
160
  // A Session review may not change anything. Removing the definitions is
159
161
  // the second of three layers — the key it carries is read-scoped and its
160
162
  // Bash calls go through `reviewBashPolicy` — because each fails
@@ -498,9 +500,9 @@ function sdkResultError(msg) {
498
500
  // the whole turn; the deny THRESHOLD below still guards a one-off end-of-turn race.
499
501
  // The trailing colon matters: the CLI always emits the envelope followed by the
500
502
  // error that caused it ("…failed: Error: Stream closed", "…failed: AbortError: …").
501
- // Prose that merely NAMES the failure quotes it without one, which is how the
502
- // scaffold's guard-background-commands hook describes what it prevents — and its
503
- // block is an is_error tool_result, so a colon-less match counted a healthy
503
+ // Prose that merely NAMES the failure quotes it without one, which is how a
504
+ // hook explaining a block quotes it (the scaffold's former background-subagent
505
+ // block did) — and a hook block is an is_error tool_result, so a colon-less match counted a healthy
504
506
  // blocked spawn as a deny and could trip the breaker on a healthy turn.
505
507
  const CONTROL_CHANNEL_DENY_MARKER = "Tool permission request failed:";
506
508
  // Hook failures are reported through the same is_error tool_result channel but
@@ -663,9 +665,27 @@ export function createStallDetector(input) {
663
665
  awaitingModel = (msg.type === "user" || msg.type === "stream_event") && pendingToolUses.size === 0;
664
666
  },
665
667
  stalled: () => stalled,
668
+ hasPendingToolUses: () => pendingToolUses.size > 0,
666
669
  dispose: () => clearInterval(timer),
667
670
  };
668
671
  }
672
+ // A query's cost and `modelUsage` are cumulative — on every result message and
673
+ // on the CLI's usage reply alike — so the latest reading replaces the last one
674
+ // rather than adding to it. It is folded into the turn totals when the query
675
+ // settles (see `runQueryOnce`). A spuriously empty later reading never wipes a
676
+ // real earlier one.
677
+ function recordCumulativeUsage(state, reading) {
678
+ const own = ownQueryUsage(reading, state.queryUsageBaseline);
679
+ state.observedCostUsd = own.costUsd;
680
+ const snapshot = queryModelUsage(own.modelUsage);
681
+ if (snapshot.size > 0) {
682
+ state.queryModelUsage = snapshot;
683
+ }
684
+ return own.costUsd;
685
+ }
686
+ // How long ending a turn waits on the CLI's usage reply before going ahead
687
+ // without it. Billing a turn late is recoverable; hanging one is not.
688
+ const USAGE_READING_TIMEOUT_MS = 5_000;
669
689
  // Decides when the turn is really over and the CLI may be told to exit.
670
690
  //
671
691
  // The prompt rides a held-open input stream (see `createHeldOpenQuery` in
@@ -687,7 +707,10 @@ export function createStallDetector(input) {
687
707
  // events must not, or the end would never re-arm. After input ends, an exit
688
708
  // guard closes the query if the CLI neither speaks nor exits, so a wedged
689
709
  // process cannot hang the turn until the session watchdog.
690
- function createTurnEndController(job, query, closeQuery, timings, onTurnEvent) {
710
+ function createTurnEndController(job, query, closeQuery, timings, onTurnEvent,
711
+ // Runs once, before input ends on a turn that ran tasks: a task can settle
712
+ // without waking the model, so the last result may not include what it spent.
713
+ beforeEndInput) {
691
714
  const inFlightTasks = new Set();
692
715
  // The background_tasks_changed LEVEL signal, tracked separately from the
693
716
  // edge-paired set above: a foreground command the CLI auto-backgrounds emits
@@ -700,15 +723,52 @@ function createTurnEndController(job, query, closeQuery, timings, onTurnEvent) {
700
723
  let cleanResultSeen = false;
701
724
  let lastWasTaskLifecycle = false;
702
725
  let inputEnded = false;
726
+ // Between deciding to end and ending, while the usage reading is in flight.
727
+ // Anything that would have cancelled a pending end — model activity, a task
728
+ // starting — is recorded here and abandons the end instead.
729
+ let ending;
730
+ // The query stopped being drained (a Stop, a crash): a reading answering
731
+ // after that has no turn left to end.
732
+ let disposed = false;
703
733
  let settleTimer;
704
734
  let exitGuard;
705
735
  const endInput = () => {
706
736
  settleTimer = undefined;
707
737
  // Fire-time guard: a task that started during the settle window keeps the
708
738
  // channel open; a later settle re-arms the end.
709
- if (inputEnded || hasInFlightWork()) {
739
+ if (inputEnded || ending || hasInFlightWork()) {
710
740
  return;
711
741
  }
742
+ if (sawAnyTask && beforeEndInput) {
743
+ const attempt = { interrupted: false };
744
+ ending = attempt;
745
+ let timeout;
746
+ const timedOut = new Promise((resolveTimeout) => {
747
+ timeout = setTimeout(resolveTimeout, USAGE_READING_TIMEOUT_MS);
748
+ timeout.unref?.();
749
+ });
750
+ void Promise.race([beforeEndInput().catch(() => undefined), timedOut]).finally(() => {
751
+ clearTimeout(timeout);
752
+ ending = undefined;
753
+ if (disposed) {
754
+ return;
755
+ }
756
+ // Re-check at the moment input would end, as the fire-time guard above
757
+ // does: a wake or a task that landed during the reading keeps the
758
+ // channel open, and the woken turn's own result or settle ends it.
759
+ if (attempt.interrupted || hasInFlightWork()) {
760
+ if (cleanResultSeen && !hasInFlightWork()) {
761
+ scheduleEnd();
762
+ }
763
+ return;
764
+ }
765
+ finishEndInput();
766
+ });
767
+ return;
768
+ }
769
+ finishEndInput();
770
+ };
771
+ const finishEndInput = () => {
712
772
  inputEnded = true;
713
773
  query.endInput?.();
714
774
  onTurnEvent({
@@ -778,15 +838,21 @@ function createTurnEndController(job, query, closeQuery, timings, onTurnEvent) {
778
838
  clearTimeout(settleTimer);
779
839
  settleTimer = undefined;
780
840
  }
841
+ if (ending && (isModelActivity || hasInFlightWork())) {
842
+ ending.interrupted = true;
843
+ }
781
844
  if (exitGuard) {
782
845
  exitGuard.refresh();
783
846
  }
784
847
  },
785
- maybeEndAfter: (msg) => {
848
+ maybeEndAfter: (msg, refused) => {
786
849
  if (inputEnded) {
787
850
  return;
788
851
  }
789
- if (msg.type === "result" && !msg.is_error) {
852
+ // A refusal's error result is not thrown (the refusal already decided how
853
+ // the turn ends), so it has to end input like a clean one or the CLI idles
854
+ // on stdin until the session watchdog.
855
+ if (msg.type === "result" && (!msg.is_error || refused)) {
790
856
  cleanResultSeen = true;
791
857
  if (hasInFlightWork()) {
792
858
  console.error(`[BridgeJob] result received with ${inFlightTasks.size} task(s) and ${backgroundTaskCount} background command(s) in flight — holding the turn open (thread ${job.threadId})`);
@@ -803,7 +869,10 @@ function createTurnEndController(job, query, closeQuery, timings, onTurnEvent) {
803
869
  scheduleEnd();
804
870
  }
805
871
  },
872
+ hasInFlightWork,
873
+ ranTasks: () => sawAnyTask,
806
874
  dispose: () => {
875
+ disposed = true;
807
876
  if (settleTimer) {
808
877
  clearTimeout(settleTimer);
809
878
  settleTimer = undefined;
@@ -895,7 +964,7 @@ export class BridgeJobExecutor {
895
964
  readUsageBaseline;
896
965
  stalledCliExitMs;
897
966
  readFlowTree;
898
- mainSync;
967
+ settleWorkspace;
899
968
  constructor(dependencies) {
900
969
  this.sdk = dependencies.sdk;
901
970
  this.relay = dependencies.relay;
@@ -914,9 +983,9 @@ export class BridgeJobExecutor {
914
983
  dependencies.readUsageBaseline ??
915
984
  ((sessionId) => readUsageBaseline(sessionId, process.env.CLAUDE_CONFIG_DIR || resolve(process.env.HOME || homedir(), ".claude")));
916
985
  this.stalledCliExitMs = dependencies.stalledCliExitMs ?? STALLED_CLI_EXIT_MS;
986
+ this.settleWorkspace = dependencies.settleWorkspace ?? (() => settleWorkspaceAfterTasks());
917
987
  this.readFlowTree =
918
988
  dependencies.readFlowTree ?? ((flowSlug) => readRawFlowTree(bridgeRuntimeFiles.flowsDir(), flowSlug));
919
- this.mainSync = dependencies.mainSync;
920
989
  }
921
990
  async execute(job, controls) {
922
991
  const state = createBridgeJobState();
@@ -929,10 +998,12 @@ export class BridgeJobExecutor {
929
998
  return { kind: "cancelled", costUsd: 0, model: state.model };
930
999
  }
931
1000
  const loaded = await this.loadTrustedBoundary(job, controls);
1001
+ if (loaded?.kind !== "clean") {
1002
+ request = await this.withContinuedAttachments(job, request);
1003
+ }
932
1004
  attempt = prepareSessionAttempt({
933
1005
  requestPrompt: request.prompt,
934
1006
  reminder: request.reminder,
935
- recoveryReminder: request.recoveryReminder,
936
1007
  history: job.history,
937
1008
  loaded,
938
1009
  leadingCommand: request.leadingCommand,
@@ -984,34 +1055,17 @@ export class BridgeJobExecutor {
984
1055
  // skill) must stay the leading token, so they skip speaker attribution and
985
1056
  // reference context — otherwise the prepended lines push the command off the
986
1057
  // front and it lands as ordinary message text the model just echoes.
987
- const bridgeCommand = verbatimSlashCommand(job.prompt);
988
- const verbatim = bridgeCommand ??
1058
+ const verbatim = verbatimSlashCommand(job.prompt) ??
989
1059
  leadingSkillCommand(job.prompt, (await readSessionSkills(bridgeRuntimeFiles.projectRoot())) ?? []);
990
1060
  let requestPrompt = verbatim ?? applyCurrentSenderAttribution(job.prompt, job.currentSender);
991
- // Publish and compact keep the workspace as it is (Publish rebases onto main
992
- // itself). A skill reads and edits the workspace, so it is brought level
993
- // first — but a note beside the command would push it off the front, so
994
- // the thread hears about it on its next turn.
995
- const notes = bridgeCommand === null
996
- ? ((await this.mainSync?.beforeTurn(job.threadId, job.publishedElsewhere ?? [], {
997
- withNote: verbatim === null,
998
- })) ?? {})
999
- : {};
1000
- const viewing = verbatim === null ? currentReferenceReminder(job.currentReference) : undefined;
1001
- const reminder = joinReminders(notes.resumed, viewing);
1002
- const recoveryReminder = joinReminders(notes.recovered, viewing);
1061
+ const reminder = verbatim === null ? currentReferenceReminder(job.currentReference) : undefined;
1003
1062
  if (job.attachments && job.attachments.length > 0) {
1004
- requestPrompt += await this.prepareAttachments(job.attachments);
1063
+ requestPrompt += await this.prepareAttachments(job.attachments, "message");
1005
1064
  }
1006
1065
  if (!controls.isActive()) {
1007
1066
  return null;
1008
1067
  }
1009
- return {
1010
- prompt: requestPrompt,
1011
- ...(reminder ? { reminder } : {}),
1012
- ...(recoveryReminder ? { recoveryReminder } : {}),
1013
- leadingCommand: verbatim !== null,
1014
- };
1068
+ return { prompt: requestPrompt, ...(reminder ? { reminder } : {}), leadingCommand: verbatim !== null };
1015
1069
  }
1016
1070
  /**
1017
1071
  * The reviewer's prompt: the brief Convex wrote, with the evidence block this
@@ -1210,6 +1264,18 @@ export class BridgeJobExecutor {
1210
1264
  });
1211
1265
  }
1212
1266
  }
1267
+ /**
1268
+ * The files of the request a late reply continues, for a turn that does not
1269
+ * resume the Session that received them with the request. Its history names
1270
+ * them but cannot open them.
1271
+ */
1272
+ async withContinuedAttachments(job, request) {
1273
+ if (!job.continuedAttachments?.length || request.leadingCommand) {
1274
+ return request;
1275
+ }
1276
+ const suffix = await this.prepareAttachments(job.continuedAttachments, "continued_request");
1277
+ return { ...request, prompt: request.prompt + suffix };
1278
+ }
1213
1279
  async invalidResumeFallbackAttempt(job, controls, request, attempt, state, error) {
1214
1280
  if (!attempt.usedBoundary ||
1215
1281
  state.sawUserVisibleSdkMessage ||
@@ -1218,6 +1284,7 @@ export class BridgeJobExecutor {
1218
1284
  return null;
1219
1285
  }
1220
1286
  await this.sessionStore.clear(job.threadId, { runToken: controls.runToken, reason: "sdk_error" });
1287
+ const rebuilt = await this.withContinuedAttachments(job, request);
1221
1288
  void this.relay.contextSource({ threadId: job.threadId, turnId: job.turnId }, "rehydrated_invalid_resume", {
1222
1289
  historyEntryCount: job.history?.entries.length ?? 0,
1223
1290
  omittedEntryCount: job.history?.omittedEntryCount ?? 0,
@@ -1226,12 +1293,12 @@ export class BridgeJobExecutor {
1226
1293
  });
1227
1294
  // A leading command stays bare here too: a history preamble in front of it
1228
1295
  // would turn it into plain text (see `prepareSessionAttempt`).
1229
- if (request.leadingCommand) {
1230
- return { prompt: request.prompt, attemptedRehydration: false, resumeOptions: {} };
1296
+ if (rebuilt.leadingCommand) {
1297
+ return { prompt: rebuilt.prompt, attemptedRehydration: false, resumeOptions: {} };
1231
1298
  }
1232
1299
  return {
1233
- prompt: buildRehydratedPrompt(request.prompt, job.history),
1234
- ...(request.recoveryReminder ? { reminder: request.recoveryReminder } : {}),
1300
+ prompt: buildRehydratedPrompt(rebuilt.prompt, job.history),
1301
+ ...(rebuilt.reminder ? { reminder: rebuilt.reminder } : {}),
1235
1302
  attemptedRehydration: true,
1236
1303
  resumeOptions: {},
1237
1304
  };
@@ -1286,11 +1353,6 @@ export class BridgeJobExecutor {
1286
1353
  const malformed = refuseMalformedCall(toolName, input, job.prompt, userTurnsBefore(job));
1287
1354
  if (malformed)
1288
1355
  return Promise.resolve({ behavior: "deny", message: malformed });
1289
- const clash = toolName === "Write" && typeof input?.file_path === "string"
1290
- ? (this.mainSync?.refuseWrite(job.threadId, input.file_path) ?? null)
1291
- : null;
1292
- if (clash)
1293
- return Promise.resolve({ behavior: "deny", message: clash });
1294
1356
  // Cast what is owed FIRST, whatever comes next — including another create. Checked
1295
1357
  // the other way round, a second create (the agent retrying after a rejected flag,
1296
1358
  // which is what happens in practice) would overwrite the handle and the first
@@ -1516,7 +1578,19 @@ export class BridgeJobExecutor {
1516
1578
  // in-progress session.
1517
1579
  async drainQuery(job, controls, state, query, closeQuery) {
1518
1580
  const stallDetector = this.createStallDetector(job, controls, closeQuery);
1519
- const turnEnd = createTurnEndController(job, query, closeQuery, { settleMs: this.turnEndSettleMs, exitGuardMs: this.turnEndExitGuardMs }, this.recordTurnEvent);
1581
+ // What the query had spent when its turn ended, read from the CLI — set
1582
+ // only when a task ran, and higher than the last result's figure when a
1583
+ // task kept working after it.
1584
+ let settledCostUsd = 0;
1585
+ // A reading that answers after the query drained (past its timeout) belongs
1586
+ // to a query already folded into the turn — and a retry reuses `state`.
1587
+ let drained = false;
1588
+ const turnEnd = createTurnEndController(job, query, closeQuery, { settleMs: this.turnEndSettleMs, exitGuardMs: this.turnEndExitGuardMs }, this.recordTurnEvent, async () => {
1589
+ const reading = await query.usage?.();
1590
+ if (reading && !drained) {
1591
+ settledCostUsd = recordCumulativeUsage(state, reading);
1592
+ }
1593
+ });
1520
1594
  let costUsd = 0;
1521
1595
  // Errors thrown while HANDLING a message (control-channel breaker, error
1522
1596
  // results) are real verdicts and must surface as-is; only errors from the
@@ -1526,6 +1600,7 @@ export class BridgeJobExecutor {
1526
1600
  for await (const msg of query) {
1527
1601
  stallDetector.observeMessage(msg);
1528
1602
  turnEnd.observeMessage(msg);
1603
+ controls.setWorkInFlight?.(stallDetector.hasPendingToolUses() || turnEnd.hasInFlightWork());
1529
1604
  try {
1530
1605
  costUsd = await this.consumeSdkMessage(job, controls, state, msg, costUsd);
1531
1606
  }
@@ -1533,7 +1608,7 @@ export class BridgeJobExecutor {
1533
1608
  consumeError = true;
1534
1609
  throw error;
1535
1610
  }
1536
- turnEnd.maybeEndAfter(msg);
1611
+ turnEnd.maybeEndAfter(msg, state.refusedWithoutFallback === true);
1537
1612
  if (!controls.isActive()) {
1538
1613
  closeQuery();
1539
1614
  break;
@@ -1548,6 +1623,8 @@ export class BridgeJobExecutor {
1548
1623
  throw error;
1549
1624
  }
1550
1625
  finally {
1626
+ drained = true;
1627
+ controls.setWorkInFlight?.(false);
1551
1628
  stallDetector.dispose();
1552
1629
  turnEnd.dispose();
1553
1630
  await this.openPendingCastingAtTurnEnd(job, controls);
@@ -1555,7 +1632,11 @@ export class BridgeJobExecutor {
1555
1632
  if (stallDetector.stalled() && controls.isActive()) {
1556
1633
  throw new StreamStallError();
1557
1634
  }
1558
- return costUsd;
1635
+ // Not for a Session review: it runs with every hook off and may not change anything.
1636
+ if (turnEnd.ranTasks() && controls.isActive() && !job.review) {
1637
+ await this.settleWorkspace();
1638
+ }
1639
+ return Math.max(costUsd, settledCostUsd);
1559
1640
  }
1560
1641
  /**
1561
1642
  * Open a casting the turn recorded but never reached.
@@ -1648,17 +1729,7 @@ export class BridgeJobExecutor {
1648
1729
  }
1649
1730
  trackMainRequestUsage(state, msg);
1650
1731
  if (msg.type === "result") {
1651
- const own = ownQueryUsage({ totalCostUsd: msg.total_cost_usd, modelUsage: msg.modelUsage }, state.queryUsageBaseline);
1652
- state.observedCostUsd = own.costUsd;
1653
- // A query's result `modelUsage` is cumulative (like `total_cost_usd`, which
1654
- // we overwrite above), so keep the latest snapshot rather than summing —
1655
- // it is folded into the turn totals when the query settles (see
1656
- // `runQueryOnce`). Guard against a spuriously empty later result wiping a
1657
- // real earlier one.
1658
- const snapshot = queryModelUsage(own.modelUsage);
1659
- if (snapshot.size > 0) {
1660
- state.queryModelUsage = snapshot;
1661
- }
1732
+ const ownCostUsd = recordCumulativeUsage(state, { totalCostUsd: msg.total_cost_usd, modelUsage: msg.modelUsage });
1662
1733
  await this.publishContextUsage(job, state, msg.modelUsage);
1663
1734
  // What a Session review returns. The SDK enforces the JSON schema, so this
1664
1735
  // either parses against the findings contract or the review failed to
@@ -1675,7 +1746,7 @@ export class BridgeJobExecutor {
1675
1746
  await this.clearIfDirty(job, controls, state, "result_error");
1676
1747
  throw resultError;
1677
1748
  }
1678
- return own.costUsd;
1749
+ return ownCostUsd;
1679
1750
  }
1680
1751
  if (isUserVisibleSdkMessage(msg)) {
1681
1752
  state.sawUserVisibleSdkMessage = true;
@@ -2288,7 +2359,4 @@ function userTurnsBefore(job) {
2288
2359
  .trim();
2289
2360
  return said || undefined;
2290
2361
  }
2291
- function joinReminders(...blocks) {
2292
- return blocks.filter((block) => block !== undefined).join("\n\n") || undefined;
2293
- }
2294
2362
  //# sourceMappingURL=bridge-job.js.map