@gajae-code/coding-agent 0.15.5 → 0.15.6

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 (155) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/dist/types/async/job-manager.d.ts +83 -0
  3. package/dist/types/cli/auth-gateway-cli.d.ts +17 -0
  4. package/dist/types/cli/model-presets-cli.d.ts +9 -0
  5. package/dist/types/cli/update-cli.d.ts +2 -0
  6. package/dist/types/commands/auth-gateway.d.ts +3 -0
  7. package/dist/types/commands/gc.d.ts +13 -0
  8. package/dist/types/commands/model-presets.d.ts +21 -0
  9. package/dist/types/config/autorouting-tier-map.d.ts +96 -0
  10. package/dist/types/config/file-lock.d.ts +49 -2
  11. package/dist/types/config/internal/model-preset-registry-test-state.d.ts +4 -0
  12. package/dist/types/config/internal/model-preset-registry-test-support.d.ts +3 -0
  13. package/dist/types/config/keybindings.d.ts +3 -2
  14. package/dist/types/config/model-preset-registry.d.ts +368 -0
  15. package/dist/types/config/model-profile-activation.d.ts +39 -0
  16. package/dist/types/config/model-profiles.d.ts +5 -3
  17. package/dist/types/config/model-registry.d.ts +11 -5
  18. package/dist/types/config/settings-schema.d.ts +9 -0
  19. package/dist/types/coordinator-mcp/model-preset.d.ts +7 -5
  20. package/dist/types/coordinator-mcp/projection-scan.d.ts +46 -0
  21. package/dist/types/extensibility/extensions/types.d.ts +2 -5
  22. package/dist/types/gjc-runtime/empty-delete-gc.d.ts +58 -0
  23. package/dist/types/gjc-runtime/gc-runtime.d.ts +2 -0
  24. package/dist/types/gjc-runtime/session-state-lock.d.ts +35 -6
  25. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +6 -0
  26. package/dist/types/index.d.ts +1 -0
  27. package/dist/types/main.d.ts +3 -1
  28. package/dist/types/modes/components/jobs-overlay-model.d.ts +4 -2
  29. package/dist/types/modes/jobs-observer.d.ts +36 -0
  30. package/dist/types/sdk/broker/broker.d.ts +3 -0
  31. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +3 -0
  32. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +4 -0
  33. package/dist/types/sdk/client/discovery.d.ts +1 -0
  34. package/dist/types/sdk/host/model-pin.d.ts +7 -2
  35. package/dist/types/sdk/host/sdk-run-capability.d.ts +1 -0
  36. package/dist/types/sdk/host/session-runtime.d.ts +15 -1
  37. package/dist/types/sdk/prompt-deadline-manager.d.ts +11 -1
  38. package/dist/types/sdk/protocol/adapter-validation.d.ts +2 -0
  39. package/dist/types/sdk/router/session-router.d.ts +9 -0
  40. package/dist/types/session/account-inventory.d.ts +1 -0
  41. package/dist/types/session/agent-session.d.ts +19 -7
  42. package/dist/types/session/fold-coordinator.d.ts +201 -0
  43. package/dist/types/session/sdk-run-capability-internal.d.ts +7 -0
  44. package/dist/types/session/sdk-run-capability.d.ts +2 -0
  45. package/dist/types/telemetry/control.d.ts +11 -0
  46. package/dist/types/telemetry/events.d.ts +20 -0
  47. package/dist/types/telemetry/index.d.ts +3 -0
  48. package/dist/types/telemetry/transport.d.ts +22 -0
  49. package/dist/types/tools/bash-interactive.d.ts +23 -0
  50. package/dist/types/tools/index.d.ts +6 -5
  51. package/dist/types/web/search/providers/base.d.ts +9 -4
  52. package/dist/types/web/search/providers/xai.d.ts +2 -1
  53. package/dist/types/web/search/types.d.ts +14 -0
  54. package/package.json +10 -7
  55. package/scripts/generate-hotkeys-docs.ts +4 -0
  56. package/scripts/generate-sdk-operation-inventory.ts +3 -3
  57. package/src/async/job-manager.ts +671 -39
  58. package/src/cli/auth-broker-cli.ts +40 -12
  59. package/src/cli/auth-gateway-cli.ts +273 -97
  60. package/src/cli/config-cli.ts +2 -2
  61. package/src/cli/fast-help.ts +3 -0
  62. package/src/cli/model-presets-cli.ts +100 -0
  63. package/src/cli/update-cli.ts +40 -5
  64. package/src/cli.ts +6 -2
  65. package/src/commands/auth-gateway.ts +12 -8
  66. package/src/commands/gc.ts +19 -0
  67. package/src/commands/model-presets.ts +24 -0
  68. package/src/config/autorouting-tier-map.ts +32 -0
  69. package/src/config/file-lock-gc.ts +9 -4
  70. package/src/config/file-lock.ts +775 -59
  71. package/src/config/internal/model-preset-registry-test-state.ts +28 -0
  72. package/src/config/internal/model-preset-registry-test-support.ts +11 -0
  73. package/src/config/keybindings.ts +6 -2
  74. package/src/config/model-preset-registry.ts +2799 -0
  75. package/src/config/model-profile-activation.ts +115 -28
  76. package/src/config/model-profile-contract.ts +4 -1
  77. package/src/config/model-profiles.ts +51 -9
  78. package/src/config/model-registry.ts +1021 -122
  79. package/src/config/settings-schema.ts +11 -0
  80. package/src/config/settings.ts +6 -1
  81. package/src/coordinator-mcp/model-preset.ts +32 -17
  82. package/src/coordinator-mcp/projection-scan.ts +571 -0
  83. package/src/coordinator-mcp/server.ts +19 -24
  84. package/src/coordinator-mcp/session-reaper.ts +9 -3
  85. package/src/extensibility/extensions/loader.ts +0 -1
  86. package/src/extensibility/extensions/types.ts +2 -3
  87. package/src/extensibility/gjc-plugins/runtime-adapters.ts +1 -0
  88. package/src/gjc-runtime/empty-delete-gc.ts +301 -0
  89. package/src/gjc-runtime/gc-render.ts +29 -0
  90. package/src/gjc-runtime/gc-runtime.ts +174 -11
  91. package/src/gjc-runtime/session-state-lock.ts +1242 -141
  92. package/src/gjc-runtime/session-state-sidecar.ts +76 -3
  93. package/src/index.ts +1 -0
  94. package/src/internal-urls/docs-index.generated.ts +9 -8
  95. package/src/lsp/config.ts +4 -3
  96. package/src/lsp/path-trust.ts +12 -6
  97. package/src/main.ts +9 -2
  98. package/src/modes/acp/acp-agent.ts +11 -5
  99. package/src/modes/components/jobs-overlay-model.ts +99 -16
  100. package/src/modes/components/model-selector.ts +158 -18
  101. package/src/modes/components/status-line/segments.ts +6 -1
  102. package/src/modes/controllers/extension-ui-controller.ts +1 -1
  103. package/src/modes/controllers/input-controller.ts +23 -6
  104. package/src/modes/interactive-mode.ts +14 -1
  105. package/src/modes/jobs-observer.ts +149 -16
  106. package/src/modes/runtime-init.ts +1 -1
  107. package/src/modes/utils/injected-user-submission.ts +15 -1
  108. package/src/prompts/tools/bash.md +2 -2
  109. package/src/prompts/tools/fold-receipt.md +3 -0
  110. package/src/prompts/tools/job.md +1 -1
  111. package/src/sdk/acp/adapter.ts +17 -2
  112. package/src/sdk/broker/broker.ts +48 -21
  113. package/src/sdk/broker/lifecycle.ts +32 -6
  114. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  115. package/src/sdk/bus/chat-daemon-runtime.ts +11 -1
  116. package/src/sdk/bus/kind-aware-reconciliation.ts +36 -6
  117. package/src/sdk/bus/prompt-reconciliation.ts +28 -4
  118. package/src/sdk/bus/slack-conversation.ts +5 -1
  119. package/src/sdk/cli/session-cli.ts +12 -1
  120. package/src/sdk/client/discovery.ts +3 -1
  121. package/src/sdk/host/control/dispatch.ts +3 -0
  122. package/src/sdk/host/model-pin.ts +185 -25
  123. package/src/sdk/host/sdk-run-capability.ts +1 -0
  124. package/src/sdk/host/session-runtime.test.ts +371 -37
  125. package/src/sdk/host/session-runtime.ts +364 -52
  126. package/src/sdk/prompt-deadline-manager.ts +16 -1
  127. package/src/sdk/protocol/adapter-validation.ts +27 -0
  128. package/src/sdk/protocol/operation-inventory.generated.json +2 -2
  129. package/src/sdk/protocol/operation-registry.ts +4 -1
  130. package/src/sdk/router/session-router.ts +228 -96
  131. package/src/sdk/session.ts +83 -12
  132. package/src/session/account-inventory.ts +27 -11
  133. package/src/session/agent-session.ts +678 -103
  134. package/src/session/fold-coordinator.ts +433 -0
  135. package/src/session/sdk-run-capability-internal.ts +15 -0
  136. package/src/session/sdk-run-capability.ts +2 -0
  137. package/src/session/session-manager.ts +5 -4
  138. package/src/setup/provider-presets.json +1 -1
  139. package/src/slash-commands/helpers/credential-switch.ts +6 -1
  140. package/src/task/executor.ts +19 -3
  141. package/src/telemetry/control.ts +26 -0
  142. package/src/telemetry/events.ts +142 -0
  143. package/src/telemetry/index.ts +3 -0
  144. package/src/telemetry/transport.ts +87 -0
  145. package/src/tools/bash-interactive.ts +182 -85
  146. package/src/tools/bash.ts +772 -67
  147. package/src/tools/index.ts +7 -5
  148. package/src/tools/tool-catalog.generated.ts +1 -1
  149. package/src/web/search/provider.ts +120 -11
  150. package/src/web/search/providers/anthropic.ts +89 -17
  151. package/src/web/search/providers/base.ts +9 -4
  152. package/src/web/search/providers/gemini.ts +87 -15
  153. package/src/web/search/providers/openai-compatible.ts +23 -8
  154. package/src/web/search/providers/xai.ts +98 -12
  155. package/src/web/search/types.ts +16 -0
@@ -13,6 +13,8 @@ import {
13
13
  unregisterOwnedRegistration,
14
14
  } from "../../session/terminal-abort";
15
15
  import { Broker } from "../broker/broker";
16
+ import { createKindAwareReconciliation } from "../bus/kind-aware-reconciliation";
17
+ import { createPromptReconciliation } from "../bus/prompt-reconciliation";
16
18
  import { createReconciliationStore } from "../bus/reconciliation-store";
17
19
  import { CursorRegistry, QueryHandlers, RevisionStore } from "./query";
18
20
  import {
@@ -146,6 +148,76 @@ async function queryGoalState(
146
148
  return response.page?.items[0];
147
149
  }
148
150
 
151
+ test("native prompt reconciliation fails closed for an explicitly empty assistant result", () => {
152
+ const reconciliation = createPromptReconciliation();
153
+ const correlation = { commandId: "native-empty-command", turnId: "native-empty-turn" };
154
+ reconciliation.admit("native-empty-ref");
155
+ reconciliation.noteAccepted(correlation, "native-empty-ref");
156
+ reconciliation.noteTransition(correlation, { type: "agent_end", finalText: "" });
157
+ expect(reconciliation.lookup({ clientRef: "native-empty-ref" })).toMatchObject({
158
+ status: "failed",
159
+ error: { code: "prompt_failed" },
160
+ receiptState: "missing",
161
+ });
162
+ reconciliation.noteTransition(correlation, { type: "agent_end", finalText: "late text" });
163
+ expect(reconciliation.lookup({ clientRef: "native-empty-ref" })).toMatchObject({ status: "failed" });
164
+ });
165
+
166
+ test("broker reconciliation fails closed for an empty prompt and persists the first terminal result", async () => {
167
+ let records: unknown[] = [];
168
+ const store = {
169
+ load: async () => records,
170
+ transact: async (mutator: (current: never[]) => never[]) => {
171
+ records = mutator(records as never);
172
+ },
173
+ } as never;
174
+ const reconciliation = createKindAwareReconciliation({ store });
175
+ const correlation = { commandId: "broker-empty-command", turnId: "broker-empty-turn" };
176
+ await reconciliation.noteAccepted("prompt", correlation, "broker-empty-ref");
177
+ await reconciliation.noteTransition("prompt", correlation, {
178
+ type: "agent_end",
179
+ content: { version: 1, type: "text", text: "", byteLength: 0, truncated: false },
180
+ });
181
+ const settled = reconciliation.lookupResult("prompt", { clientRef: "broker-empty-ref" });
182
+ expect(settled).toMatchObject({ status: "failed", error: { code: "prompt_failed" } });
183
+ await reconciliation.noteTransition("prompt", correlation, {
184
+ type: "agent_end",
185
+ content: { version: 1, type: "text", text: "late text", byteLength: 9, truncated: false },
186
+ });
187
+ const reloaded = createKindAwareReconciliation({ store });
188
+ await reloaded.hydrateFromStore();
189
+ expect(reloaded.lookupResult("prompt", { clientRef: "broker-empty-ref" })).toMatchObject({
190
+ status: "failed",
191
+ error: { code: "prompt_failed" },
192
+ });
193
+ const noOpCorrelation = { commandId: "broker-noop-command", turnId: "broker-noop-turn" };
194
+ await reconciliation.noteAccepted("prompt", noOpCorrelation, "broker-noop-ref");
195
+ await reconciliation.finalizeOutcome(
196
+ "prompt",
197
+ noOpCorrelation,
198
+ { kind: "stopped", reason: "cancelled", provenance: "client_cancel" },
199
+ undefined,
200
+ "",
201
+ );
202
+ expect(reconciliation.lookupResult("prompt", { clientRef: "broker-noop-ref" })).toMatchObject({
203
+ status: "terminal_ok",
204
+ });
205
+ });
206
+
207
+ test("session-host prompt reconciliation fails closed when the accepted result is empty", async () => {
208
+ const reconciliation = createInvocationReconciliation();
209
+ const correlation = { commandId: "host-empty-command", turnId: "host-empty-turn" };
210
+ await reconciliation.noteAccepted("prompt", correlation, "host-empty-ref");
211
+ await reconciliation.noteTransition("prompt", correlation, {
212
+ type: "agent_end",
213
+ content: { version: 1, type: "text", text: "", byteLength: 0, truncated: false },
214
+ });
215
+ expect(reconciliation.lookup("prompt", { clientRef: "host-empty-ref" })).toMatchObject({
216
+ status: "failed",
217
+ error: { code: "prompt_failed" },
218
+ });
219
+ });
220
+
149
221
  describe("SDK goal snapshot lifecycle", () => {
150
222
  test("recovers an active nonzero-activity goal from the durable session projection", async () => {
151
223
  const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-sdk-goal-recovery-"));
@@ -405,7 +477,10 @@ test("durable reload keeps agent_end terminal across a paused successor transiti
405
477
  const correlation = { commandId: "durable-race-command", turnId: "durable-race-turn" };
406
478
  await reconciliation.noteAccepted("prompt", correlation, "durable-race-ref");
407
479
  pauseWrites = 2;
408
- const terminal = reconciliation.noteTransition("prompt", correlation, { type: "agent_end" });
480
+ const terminal = reconciliation.noteTransition("prompt", correlation, {
481
+ type: "agent_end",
482
+ content: { version: 1, type: "text", text: "completed", byteLength: 9, truncated: false },
483
+ });
409
484
  await writeStarted[0]!.promise;
410
485
  // A successor lifecycle event arrives while the terminal write is held. It
411
486
  // must observe the staged terminal record and never resurrect in_flight state.
@@ -455,7 +530,10 @@ test("agent_end is not swallowed when deadline persistence fails", async () => {
455
530
  message: "deadline",
456
531
  });
457
532
  await persistStarted.promise;
458
- const terminal = reconciliation.noteTransition("prompt", correlation, { type: "agent_end" });
533
+ const terminal = reconciliation.noteTransition("prompt", correlation, {
534
+ type: "agent_end",
535
+ content: { version: 1, type: "text", text: "completed", byteLength: 9, truncated: false },
536
+ });
459
537
  releasePersist.resolve();
460
538
  await expect(deadline).rejects.toThrow("held store failed");
461
539
  await terminal;
@@ -523,13 +601,86 @@ test("agent_end upgrades a durable deadline terminal when it races a successful
523
601
  code: "prompt_deadline_exceeded",
524
602
  message: "deadline",
525
603
  });
526
- await reconciliation.noteTransition("prompt", correlation, { type: "agent_end" });
604
+ await reconciliation.noteTransition("prompt", correlation, {
605
+ type: "agent_end",
606
+ content: { version: 1, type: "text", text: "completed", byteLength: 9, truncated: false },
607
+ });
527
608
  const reloaded = createInvocationReconciliation({ store });
528
609
  await reloaded.hydrate();
529
610
  expect(reloaded.lookup("prompt", { clientRef: "upgrade-race-ref" })).toMatchObject({ status: "terminal_ok" });
530
611
  });
531
612
 
532
- test("failed agent_end upgrade persistence retains the deadline record for retry", async () => {
613
+ test("an empty late agent_end cannot upgrade a durable prompt deadline failure", async () => {
614
+ let records: unknown[] = [];
615
+ const store = {
616
+ path: null,
617
+ load: async () => records,
618
+ transact: async (mutator: (current: never[]) => never[]) => {
619
+ records = mutator(records as never);
620
+ },
621
+ } as never;
622
+ const reconciliation = createInvocationReconciliation({ store });
623
+ const correlation = { commandId: "empty-deadline-command", turnId: "empty-deadline-turn" };
624
+ await reconciliation.noteAccepted("prompt", correlation, "empty-deadline-ref");
625
+ await reconciliation.finalizeOutcome("prompt", correlation, {
626
+ kind: "failed",
627
+ code: "prompt_deadline_exceeded",
628
+ message: "deadline",
629
+ });
630
+ await reconciliation.noteTransition("prompt", correlation, {
631
+ type: "agent_end",
632
+ content: { version: 1, type: "text", text: "", byteLength: 0, truncated: false },
633
+ });
634
+ expect(reconciliation.lookup("prompt", { clientRef: "empty-deadline-ref" })).toMatchObject({
635
+ status: "failed",
636
+ error: { code: "prompt_deadline_exceeded" },
637
+ });
638
+ const reloaded = createInvocationReconciliation({ store });
639
+ await reloaded.hydrate();
640
+ expect(reloaded.lookup("prompt", { clientRef: "empty-deadline-ref" })).toMatchObject({
641
+ status: "failed",
642
+ error: { code: "prompt_deadline_exceeded" },
643
+ });
644
+ });
645
+
646
+ test("whitespace and provider failures stay actionable across a late empty agent_end", async () => {
647
+ let records: unknown[] = [];
648
+ const store = {
649
+ path: null,
650
+ load: async () => records,
651
+ transact: async (mutator: (current: never[]) => never[]) => {
652
+ records = mutator(records as never);
653
+ },
654
+ } as never;
655
+ const reconciliation = createInvocationReconciliation({ store });
656
+ const whitespace = { commandId: "whitespace-command", turnId: "whitespace-turn" };
657
+ await reconciliation.noteAccepted("prompt", whitespace, "whitespace-ref");
658
+ await reconciliation.noteTransition("prompt", whitespace, {
659
+ type: "agent_end",
660
+ content: { version: 1, type: "text", text: " \t\n ", byteLength: 4, truncated: false },
661
+ });
662
+ expect(reconciliation.lookup("prompt", { clientRef: "whitespace-ref" })).toMatchObject({
663
+ status: "failed",
664
+ error: { code: "prompt_failed", message: "Prompt submission failed." },
665
+ });
666
+
667
+ const providerFailure = { commandId: "provider-command", turnId: "provider-turn" };
668
+ await reconciliation.noteAccepted("prompt", providerFailure, "provider-ref");
669
+ await reconciliation.noteTransition("prompt", providerFailure, {
670
+ type: "agent_failed",
671
+ error: Object.assign(new Error("provider leaked detail"), { code: "provider_unavailable" }),
672
+ });
673
+ await reconciliation.noteTransition("prompt", providerFailure, {
674
+ type: "agent_end",
675
+ content: { version: 1, type: "text", text: "", byteLength: 0, truncated: false },
676
+ });
677
+ expect(reconciliation.lookup("prompt", { clientRef: "provider-ref" })).toMatchObject({
678
+ status: "failed",
679
+ error: { code: "provider_unavailable", message: "Prompt submission failed." },
680
+ });
681
+ });
682
+
683
+ test("an empty prompt terminal remains failed after a late real failure", async () => {
533
684
  let records: unknown[] = [];
534
685
  let writes = 0;
535
686
  const store = {
@@ -550,9 +701,12 @@ test("failed agent_end upgrade persistence retains the deadline record for retry
550
701
  code: "prompt_deadline_exceeded",
551
702
  message: "deadline",
552
703
  });
553
- await expect(reconciliation.noteTransition("prompt", correlation, { type: "agent_end" })).rejects.toThrow(
554
- "upgrade persist failed",
555
- );
704
+ await expect(
705
+ reconciliation.noteTransition("prompt", correlation, {
706
+ type: "agent_end",
707
+ content: { version: 1, type: "text", text: "completed", byteLength: 9, truncated: false },
708
+ }),
709
+ ).rejects.toThrow("upgrade persist failed");
556
710
  expect(reconciliation.lookup("prompt", { clientRef: "upgrade-failure-ref" })).toMatchObject({
557
711
  status: "failed",
558
712
  error: { code: "prompt_deadline_exceeded" },
@@ -563,17 +717,132 @@ test("failed agent_end upgrade persistence retains the deadline record for retry
563
717
  status: "failed",
564
718
  error: { code: "prompt_deadline_exceeded" },
565
719
  });
566
- await reconciliation.noteTransition("prompt", correlation, { type: "agent_end" });
720
+ await reconciliation.noteTransition("prompt", correlation, {
721
+ type: "agent_end",
722
+ content: { version: 1, type: "text", text: "completed", byteLength: 9, truncated: false },
723
+ });
567
724
  const reloaded = createInvocationReconciliation({ store });
568
725
  await reloaded.hydrate();
569
726
  expect(reloaded.lookup("prompt", { clientRef: "upgrade-failure-ref" })).toMatchObject({ status: "terminal_ok" });
570
727
  });
571
728
 
729
+ test("late cancellation and non-text activity upgrade deadline terminals", async () => {
730
+ const makeStore = () => {
731
+ let records: unknown[] = [];
732
+ return {
733
+ path: null,
734
+ load: async () => records,
735
+ transact: async (mutator: (current: never[]) => never[]) => {
736
+ records = mutator(records as never);
737
+ },
738
+ } as never;
739
+ };
740
+ const cancelled = createInvocationReconciliation({ store: makeStore() });
741
+ const cancelledCorrelation = { commandId: "late-cancel-command", turnId: "late-cancel-turn" };
742
+ await cancelled.noteAccepted("prompt", cancelledCorrelation, "late-cancel-ref");
743
+ await cancelled.finalizeOutcome("prompt", cancelledCorrelation, {
744
+ kind: "failed",
745
+ code: "prompt_deadline_exceeded",
746
+ message: "deadline",
747
+ });
748
+ await cancelled.noteTransition("prompt", cancelledCorrelation, {
749
+ type: "agent_end",
750
+ outcome: { kind: "stopped", reason: "cancelled", provenance: "client_cancel" },
751
+ });
752
+ expect(cancelled.lookup("prompt", { clientRef: "late-cancel-ref" })).toMatchObject({ status: "terminal_ok" });
753
+
754
+ const activity = createInvocationReconciliation({ store: makeStore() });
755
+ const activityCorrelation = { commandId: "late-activity-command", turnId: "late-activity-turn" };
756
+ await activity.noteAccepted("prompt", activityCorrelation, "late-activity-ref");
757
+ await activity.finalizeOutcome("prompt", activityCorrelation, {
758
+ kind: "failed",
759
+ code: "prompt_deadline_exceeded",
760
+ message: "deadline",
761
+ });
762
+ await activity.noteTransition("prompt", activityCorrelation, { type: "agent_end", hasActivity: true });
763
+ expect(activity.lookup("prompt", { clientRef: "late-activity-ref" })).toMatchObject({ status: "terminal_ok" });
764
+ });
765
+ test("finalization without an explicit outcome follows the noteTransition evidence predicate", async () => {
766
+ const makeLocalStore = () => {
767
+ let records: unknown[] = [];
768
+ return {
769
+ path: null,
770
+ load: async () => records,
771
+ transact: async (mutator: (current: never[]) => never[]) => {
772
+ records = mutator(records as never);
773
+ },
774
+ } as never;
775
+ };
776
+ const contentBearing = createInvocationReconciliation({ store: makeLocalStore() });
777
+ const contentCorrelation = { commandId: "final-content-command", turnId: "final-content-turn" };
778
+ await contentBearing.noteAccepted("prompt", contentCorrelation, "final-content-ref");
779
+ await contentBearing.finalizeOutcome("prompt", contentCorrelation, undefined, undefined, undefined, {
780
+ content: { version: 1, type: "text", text: "completed", byteLength: 9, truncated: false },
781
+ });
782
+ expect(contentBearing.lookup("prompt", { clientRef: "final-content-ref" })).toMatchObject({
783
+ status: "terminal_ok",
784
+ });
785
+
786
+ const activityBearing = createInvocationReconciliation({ store: makeLocalStore() });
787
+ const activityCorrelation = { commandId: "final-activity-command", turnId: "final-activity-turn" };
788
+ await activityBearing.noteAccepted("prompt", activityCorrelation, "final-activity-ref");
789
+ await activityBearing.finalizeOutcome("prompt", activityCorrelation, undefined, undefined, undefined, {
790
+ hasActivity: true,
791
+ });
792
+ expect(activityBearing.lookup("prompt", { clientRef: "final-activity-ref" })).toMatchObject({
793
+ status: "terminal_ok",
794
+ });
795
+
796
+ const stoppedOutcome = createInvocationReconciliation({ store: makeLocalStore() });
797
+ const stoppedCorrelation = { commandId: "final-stopped-command", turnId: "final-stopped-turn" };
798
+ await stoppedOutcome.noteAccepted("prompt", stoppedCorrelation, "final-stopped-ref");
799
+ await stoppedOutcome.finalizeOutcome("prompt", stoppedCorrelation, undefined, undefined, undefined, {
800
+ outcomeKind: "stopped",
801
+ });
802
+ expect(stoppedOutcome.lookup("prompt", { clientRef: "final-stopped-ref" })).toMatchObject({
803
+ status: "terminal_ok",
804
+ });
805
+
806
+ const evidenceFree = createInvocationReconciliation({ store: makeLocalStore() });
807
+ const emptyCorrelation = { commandId: "final-empty-command", turnId: "final-empty-turn" };
808
+ await evidenceFree.noteAccepted("prompt", emptyCorrelation, "final-empty-ref");
809
+ await evidenceFree.finalizeOutcome("prompt", emptyCorrelation);
810
+ expect(evidenceFree.lookup("prompt", { clientRef: "final-empty-ref" })).toMatchObject({
811
+ status: "failed",
812
+ error: { code: "prompt_failed" },
813
+ });
814
+ const legacyArg5 = createInvocationReconciliation({ store: makeLocalStore() });
815
+ const legacyCorrelation = { commandId: "final-legacy-command", turnId: "final-legacy-turn" };
816
+ await legacyArg5.noteAccepted("prompt", legacyCorrelation, "final-legacy-ref");
817
+ await legacyArg5.finalizeOutcome("prompt", legacyCorrelation, undefined, undefined, {
818
+ content: { version: 1, type: "text", text: "legacy", byteLength: 6, truncated: false },
819
+ });
820
+ expect(legacyArg5.lookup("prompt", { clientRef: "final-legacy-ref" })).toMatchObject({
821
+ status: "terminal_ok",
822
+ });
823
+
824
+ const explicitFailure = createInvocationReconciliation({ store: makeLocalStore() });
825
+ const failureCorrelation = { commandId: "final-failure-command", turnId: "final-failure-turn" };
826
+ await explicitFailure.noteAccepted("prompt", failureCorrelation, "final-failure-ref");
827
+ await explicitFailure.finalizeOutcome("prompt", failureCorrelation, {
828
+ kind: "failed",
829
+ code: "provider_rejected",
830
+ message: "provider rejected",
831
+ });
832
+ expect(explicitFailure.lookup("prompt", { clientRef: "final-failure-ref" })).toMatchObject({
833
+ status: "failed",
834
+ error: { code: "provider_rejected" },
835
+ });
836
+ });
837
+
572
838
  test("a reason attached after a prompt settled is never replaced by a later failure", async () => {
573
839
  const reconciliation = createInvocationReconciliation();
574
840
  const correlation = { commandId: "late-reason-command", turnId: "late-reason-turn" };
575
841
  await reconciliation.noteAccepted("prompt", correlation, "late-reason-ref");
576
- await reconciliation.noteTransition("prompt", correlation, { type: "agent_end" });
842
+ await reconciliation.noteTransition("prompt", correlation, {
843
+ type: "agent_end",
844
+ content: { version: 1, type: "text", text: "completed", byteLength: 9, truncated: false },
845
+ });
577
846
  const claimed = reconciliation.lookup("prompt", { clientRef: "late-reason-ref" }) as Record<string, unknown>;
578
847
  expect(claimed).toEqual({
579
848
  status: "terminal_ok",
@@ -649,7 +918,10 @@ test("retains terminal host reconciliation records past the 15-minute window", a
649
918
  reconciliation.admit("skill", "aged-skill-ref");
650
919
  await reconciliation.noteAccepted("prompt", promptCorrelation, "aged-ref");
651
920
  await reconciliation.noteAccepted("skill", skillCorrelation, "aged-skill-ref");
652
- await reconciliation.noteTransition("prompt", promptCorrelation, { type: "agent_end" });
921
+ await reconciliation.noteTransition("prompt", promptCorrelation, {
922
+ type: "agent_end",
923
+ content: { version: 1, type: "text", text: "completed", byteLength: 9, truncated: false },
924
+ });
653
925
  await reconciliation.noteTransition("skill", skillCorrelation, { type: "agent_end" });
654
926
 
655
927
  clock += 15 * 60_000 + 1;
@@ -679,7 +951,10 @@ test("capacity eviction still releases the clientRef and reports honest unknown"
679
951
  const correlation = { commandId: `capacity-command-${index}`, turnId: `capacity-turn-${index}` };
680
952
  reconciliation.admit("prompt", `capacity-ref-${index}`);
681
953
  await reconciliation.noteAccepted("prompt", correlation, `capacity-ref-${index}`);
682
- await reconciliation.noteTransition("prompt", correlation, { type: "agent_end" });
954
+ await reconciliation.noteTransition("prompt", correlation, {
955
+ type: "agent_end",
956
+ content: { version: 1, type: "text", text: "completed", byteLength: 9, truncated: false },
957
+ });
683
958
  }
684
959
  expect(reconciliation.lookup("prompt", { clientRef: "capacity-ref-1" })).toEqual({
685
960
  status: "unknown",
@@ -1870,10 +2145,10 @@ describe("SessionSdkSessionRuntime", () => {
1870
2145
  prompt("conn-b", "stale-b");
1871
2146
  await waitResponse("stale-b");
1872
2147
  // A later AGENT-INITIATED turn starts: the pending queue is empty, so
1873
- // the owner stays conn-a (B's stale connection is not associated).
2148
+ // the predecessor's conn-a owner is cleared and no SDK client owns it.
1874
2149
  await handlers.get("agent_start")?.({ type: "agent_start" }, ctx);
1875
- // B's abort must NOT stop the agent-initiated turn.
1876
- transport.feed("conn-b", {
2150
+ // A's stale owner must NOT stop the agent-initiated turn.
2151
+ transport.feed("conn-a", {
1877
2152
  type: "control_request",
1878
2153
  id: "stale-owner-abort",
1879
2154
  operation: "turn.abort",
@@ -1987,6 +2262,40 @@ describe("SessionSdkSessionRuntime", () => {
1987
2262
  }
1988
2263
  });
1989
2264
 
2265
+ test("agent-initiated successor activity cannot renew a stale predecessor deadline", async () => {
2266
+ const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-sdk-stale-deadline-owner-"));
2267
+ try {
2268
+ const harness = await invocationHarness("stale-deadline-owner", cwd, {
2269
+ settings: {
2270
+ get: (key: string) =>
2271
+ key === "sdk.promptDeadlineMs" ? 25 : key === "sdk.promptMaxRuntimeMs" ? 60_000 : undefined,
2272
+ } as unknown as Settings,
2273
+ sendUserMessage: async (_content, options) => {
2274
+ await options?.onPreflightAcceptCommit?.();
2275
+ await neverSettlingPromise();
2276
+ },
2277
+ });
2278
+ const prompt = await harness.control("turn.prompt", { text: "predecessor" });
2279
+ expect(prompt.ok).toBe(true);
2280
+ const ids = { commandId: prompt.result?.commandId, turnId: prompt.result?.turnId };
2281
+ await harness.emit("agent_start");
2282
+ // Empty drain represents an agent-initiated successor with no SDK owner.
2283
+ await harness.emit("agent_start");
2284
+ for (let i = 0; i < 3; i += 1) {
2285
+ await harness.emit("tool_execution_start");
2286
+ await Bun.sleep(15);
2287
+ }
2288
+ expect(await settledStatus(harness, "turn.prompt_status", ids)).toMatchObject({
2289
+ status: "failed",
2290
+ error: { code: "prompt_deadline_exceeded" },
2291
+ });
2292
+ await harness.stop();
2293
+ } finally {
2294
+ await Bun.sleep(50);
2295
+ await rm(cwd, { recursive: true, force: true });
2296
+ }
2297
+ });
2298
+
1990
2299
  test("SDK-only host retires an accepted-then-failed submission so a later agent-initiated turn is not mis-owned", async () => {
1991
2300
  // An idle submission is accepted (pending entry pushed) and then REJECTS
1992
2301
  // before agent_start (e.g. the busy retry expires). The failed entry must
@@ -2985,7 +3294,7 @@ async function invocationHarness(
2985
3294
  sessionId: string,
2986
3295
  cwd: string,
2987
3296
  hooks: {
2988
- sendUserMessage?: (content: unknown, options?: PreflightHooks & { deliverAs?: string }) => Promise<void>;
3297
+ sendUserMessage?: (content: unknown, options?: PreflightHooks & { deliverAs?: string }) => Promise<unknown>;
2989
3298
  invokeSkill?: (name: string, args?: string, options?: PreflightHooks) => Promise<unknown>;
2990
3299
  abort?: () => void;
2991
3300
  isIdle?: () => boolean;
@@ -3010,7 +3319,10 @@ async function invocationHarness(
3010
3319
  on(event: string, handler: (event: unknown, ctx: unknown) => Promise<void> | void) {
3011
3320
  handlers.set(event, handler);
3012
3321
  },
3013
- sendUserMessage: hooks.sendUserMessage ?? (async () => {}),
3322
+ sendUserMessage: async (content: unknown, options?: PreflightHooks & { deliverAs?: string }) => {
3323
+ const result = await hooks.sendUserMessage?.(content, options);
3324
+ return result === undefined ? "completed" : result;
3325
+ },
3014
3326
  } as unknown as ExtensionAPI;
3015
3327
  const interceptorStore = hooks.persistInterceptor
3016
3328
  ? createInterceptorReconciliationStore(
@@ -3202,13 +3514,19 @@ async function settledStatus(
3202
3514
  name: string,
3203
3515
  input: Record<string, unknown>,
3204
3516
  ): Promise<NonNullable<ResponseFrame["result"]>> {
3205
- for (let attempt = 0; attempt < 200; attempt += 1) {
3517
+ // Wall-clock budget instead of a fixed poll count: CI runners can starve the
3518
+ // event loop long enough to exhaust 200 ~1ms polls before a 25ms deadline
3519
+ // timer is dispatched, failing a correct implementation on timing alone.
3520
+ // The contract is unchanged — the status must still reach a terminal
3521
+ // reconciliation state with the asserted shape within a bounded horizon.
3522
+ const budgetEndsAt = Date.now() + 10_000;
3523
+ for (;;) {
3206
3524
  const frame = await harness.query(name, input);
3207
3525
  const result = frame.result;
3208
3526
  if (result && (result.status === "failed" || result.status === "terminal_ok")) return result;
3527
+ if (Date.now() > budgetEndsAt) throw new Error(`${name} never reported a terminal reconciliation status`);
3209
3528
  await Bun.sleep(1);
3210
3529
  }
3211
- throw new Error(`${name} never reported a terminal reconciliation status`);
3212
3530
  }
3213
3531
 
3214
3532
  function neverSettlingPromise(): Promise<void> {
@@ -3429,7 +3747,10 @@ describe("post-acceptance invocation terminalization", () => {
3429
3747
  },
3430
3748
  ],
3431
3749
  });
3432
- expect(await settledStatus(harness, "turn.prompt_status", ids)).toMatchObject({ status: "terminal_ok" });
3750
+ expect(await settledStatus(harness, "turn.prompt_status", ids)).toMatchObject({
3751
+ status: "failed",
3752
+ error: { code: "prompt_failed" },
3753
+ });
3433
3754
  await harness.stop();
3434
3755
  } finally {
3435
3756
  await Bun.sleep(50);
@@ -3460,7 +3781,10 @@ describe("post-acceptance invocation terminalization", () => {
3460
3781
  },
3461
3782
  ],
3462
3783
  });
3463
- expect(await settledStatus(harness, "turn.prompt_status", ids)).toMatchObject({ status: "terminal_ok" });
3784
+ expect(await settledStatus(harness, "turn.prompt_status", ids)).toMatchObject({
3785
+ status: "failed",
3786
+ error: { code: "prompt_failed" },
3787
+ });
3464
3788
  await harness.stop();
3465
3789
  } finally {
3466
3790
  await Bun.sleep(50);
@@ -3489,7 +3813,8 @@ describe("post-acceptance invocation terminalization", () => {
3489
3813
  });
3490
3814
  await harness.emit("agent_end", event);
3491
3815
  expect(await settledStatus(harness, "turn.prompt_status", ids)).toMatchObject({
3492
- status: "terminal_ok",
3816
+ status: "failed",
3817
+ error: { code: "prompt_failed" },
3493
3818
  });
3494
3819
  await harness.stop();
3495
3820
  } finally {
@@ -3530,7 +3855,9 @@ describe("post-acceptance invocation terminalization", () => {
3530
3855
  error: { code: "provider_http_402" },
3531
3856
  });
3532
3857
  expect((await harness.query("turn.prompt_status", replacementIds)).result?.status).toBe("in_flight");
3533
- await harness.emit("agent_end", { messages: [{ role: "assistant", stopReason: "stop" }] });
3858
+ await harness.emit("agent_end", {
3859
+ messages: [{ role: "assistant", stopReason: "stop", content: "completed" }],
3860
+ });
3534
3861
  expect(await settledStatus(harness, "turn.prompt_status", replacementIds)).toMatchObject({
3535
3862
  status: "terminal_ok",
3536
3863
  });
@@ -3656,7 +3983,9 @@ describe("post-acceptance invocation terminalization", () => {
3656
3983
  expect(successor.ok).toBe(true);
3657
3984
  const successorIds = { commandId: successor.result?.commandId, turnId: successor.result?.turnId };
3658
3985
  await harness.emit("agent_start");
3659
- await harness.emit("agent_end", { messages: [{ role: "assistant", stopReason: "stop" }] });
3986
+ await harness.emit("agent_end", {
3987
+ messages: [{ role: "assistant", stopReason: "stop", content: "completed" }],
3988
+ });
3660
3989
  secondInflight.resolve();
3661
3990
  expect(await settledStatus(harness, "turn.prompt_status", successorIds)).toMatchObject({
3662
3991
  status: "terminal_ok",
@@ -3789,7 +4118,9 @@ describe("post-acceptance invocation terminalization", () => {
3789
4118
  expect(successor.ok).toBe(true);
3790
4119
  const successorIds = { commandId: successor.result?.commandId, turnId: successor.result?.turnId };
3791
4120
  await harness.emit("agent_start");
3792
- await harness.emit("agent_end", { messages: [{ role: "assistant", stopReason: "stop" }] });
4121
+ await harness.emit("agent_end", {
4122
+ messages: [{ role: "assistant", stopReason: "stop", content: "completed" }],
4123
+ });
3793
4124
  secondInflight.resolve();
3794
4125
  expect(await settledStatus(harness, "turn.prompt_status", successorIds)).toMatchObject({
3795
4126
  status: "terminal_ok",
@@ -3938,7 +4269,7 @@ describe("post-acceptance invocation terminalization", () => {
3938
4269
  status: "failed",
3939
4270
  error: { code: "aborted" },
3940
4271
  });
3941
- await harness.emit("agent_end");
4272
+ await harness.emit("agent_end", { messages: [{ role: "assistant", content: "completed" }] });
3942
4273
  expect(await settledStatus(harness, "turn.prompt_status", secondIds)).toMatchObject({
3943
4274
  status: "terminal_ok",
3944
4275
  });
@@ -3989,8 +4320,8 @@ describe("post-acceptance invocation terminalization", () => {
3989
4320
  expect(await harness.query("turn.prompt_status", successorIds)).toMatchObject({
3990
4321
  result: { status: expect.stringMatching(/accepted|in_flight/) },
3991
4322
  });
3992
- await harness.emit("agent_end");
3993
- await harness.emit("agent_end");
4323
+ await harness.emit("agent_end", { messages: [{ role: "assistant", content: "completed" }] });
4324
+ await harness.emit("agent_end", { messages: [{ role: "assistant", content: "completed" }] });
3994
4325
  expect(await settledStatus(harness, "turn.prompt_status", successorIds)).toMatchObject({
3995
4326
  status: "terminal_ok",
3996
4327
  });
@@ -4078,7 +4409,7 @@ describe("post-acceptance invocation terminalization", () => {
4078
4409
  expect(accepted.ok).toBe(true);
4079
4410
  const successorIds = { commandId: accepted.result?.commandId, turnId: accepted.result?.turnId };
4080
4411
  await harness.emit("agent_start");
4081
- await harness.emit("agent_end");
4412
+ await harness.emit("agent_end", { messages: [{ role: "assistant", content: "completed" }] });
4082
4413
  expect(await settledStatus(harness, "turn.prompt_status", successorIds)).toMatchObject({
4083
4414
  status: "terminal_ok",
4084
4415
  });
@@ -4277,7 +4608,7 @@ describe("post-acceptance invocation terminalization", () => {
4277
4608
  const accepted = await harness.control("turn.prompt", { text: "hello" });
4278
4609
  const { commandId, turnId } = accepted.result ?? {};
4279
4610
  await harness.emit("agent_start");
4280
- await harness.emit("agent_end");
4611
+ await harness.emit("agent_end", { messages: [{ role: "assistant", content: "completed" }] });
4281
4612
  const claimed = await settledStatus(harness, "turn.prompt_status", { commandId, turnId });
4282
4613
  expect(claimed).toMatchObject({ status: "terminal_ok" });
4283
4614
  inflight.reject(Object.assign(new Error("late provider failure"), { code: "upstream_error" }));
@@ -4445,7 +4776,7 @@ describe("accepted-control zero-execution bound (#4668)", () => {
4445
4776
  const first = await harness.control("turn.prompt", { text: "first" });
4446
4777
  expect(first.ok).toBe(true);
4447
4778
  await harness.emit("agent_start");
4448
- await harness.emit("agent_end");
4779
+ await harness.emit("agent_end", { messages: [{ role: "assistant", content: "completed" }] });
4449
4780
  const followUpB = await harness.control("turn.follow_up", { text: "b" });
4450
4781
  const followUpC = await harness.control("turn.follow_up", { text: "c" });
4451
4782
  promoted[0]?.({ startsOwnRun: true });
@@ -4593,7 +4924,7 @@ describe("accepted-control zero-execution bound (#4668)", () => {
4593
4924
  }
4594
4925
  const midRun = await harness.query("turn.prompt_status", ids);
4595
4926
  expect(midRun.result?.status).not.toBe("failed");
4596
- await harness.emit("agent_end");
4927
+ await harness.emit("agent_end", { messages: [{ role: "assistant", content: "completed" }] });
4597
4928
  const final = await harness.query("turn.prompt_status", ids);
4598
4929
  expect(final.result?.status).toBe("terminal_ok");
4599
4930
  await harness.stop();
@@ -4688,7 +5019,7 @@ describe("accepted-control zero-execution bound (#4668)", () => {
4688
5019
  // The consuming run ends. The consumed submission must terminalize
4689
5020
  // WITH it (terminal_ok, never a fabricated prompt_deadline_exceeded):
4690
5021
  // with the bug it would still be parked in pending as merely accepted.
4691
- await handlers.get("agent_end")?.({}, ctx);
5022
+ await handlers.get("agent_end")?.({ messages: [{ role: "assistant", content: "completed" }] }, ctx);
4692
5023
  const statusOf = async (ids: { commandId?: string; turnId?: string }, frameId: string) => {
4693
5024
  transport.feed("conn-a", {
4694
5025
  type: "query_request",
@@ -4766,7 +5097,7 @@ describe("accepted-control zero-execution bound (#4668)", () => {
4766
5097
  expect(midRun.result?.status).not.toBe("terminal_ok");
4767
5098
  expect(midRun.result?.status).not.toBe("failed");
4768
5099
  // The in-flight run ends: the diverted correlation terminalizes with it.
4769
- await harness.emit("agent_end");
5100
+ await harness.emit("agent_end", { messages: [{ role: "assistant", content: "completed" }] });
4770
5101
  expect(await settledStatus(harness, "turn.prompt_status", idsRaced)).toMatchObject({
4771
5102
  status: "terminal_ok",
4772
5103
  });
@@ -4825,7 +5156,7 @@ describe("accepted-control zero-execution bound (#4668)", () => {
4825
5156
  expect(queued.result?.status).not.toBe("failed");
4826
5157
  // Real consumption re-leases and attaches to the in-flight run.
4827
5158
  consumed?.({ startsOwnRun: false });
4828
- await harness.emit("agent_end");
5159
+ await harness.emit("agent_end", { messages: [{ role: "assistant", content: "completed" }] });
4829
5160
  expect(await settledStatus(harness, "turn.prompt_status", idsRaced)).toMatchObject({
4830
5161
  status: "terminal_ok",
4831
5162
  });
@@ -5265,6 +5596,7 @@ describe("accepted-control zero-execution bound (#4668)", () => {
5265
5596
  const acceptedA = (await waitFrame("settle-early-a")) as { result?: { commandId?: string; turnId?: string } };
5266
5597
  const idsA = { commandId: acceptedA.result?.commandId, turnId: acceptedA.result?.turnId };
5267
5598
  const statusDeadline = Date.now() + 15_000;
5599
+ let settledStatus: string | undefined;
5268
5600
  for (;;) {
5269
5601
  transport.feed("conn-a", {
5270
5602
  type: "query_request",
@@ -5276,10 +5608,12 @@ describe("accepted-control zero-execution bound (#4668)", () => {
5276
5608
  result?: { status?: string };
5277
5609
  };
5278
5610
  transport.sent.splice(transport.sent.indexOf(statusFrame), 1);
5279
- if (statusFrame.result?.status === "terminal_ok") break;
5611
+ settledStatus = statusFrame.result?.status;
5612
+ if (statusFrame.result?.status === "terminal_ok" || statusFrame.result?.status === "failed") break;
5280
5613
  if (Date.now() > statusDeadline) throw new Error("settled prompt never reported terminal_ok");
5281
5614
  await Bun.sleep(20);
5282
5615
  }
5616
+ expect(settledStatus).toBe("failed");
5283
5617
  // conn-b's prompt is accepted and hangs; its run then starts.
5284
5618
  transport.feed("conn-b", {
5285
5619
  type: "control_request",
@@ -5954,7 +6288,7 @@ test("SDK-only host lets every connection whose follow-up was promoted abort the
5954
6288
  Promise.resolve(options?.onPreflightAcceptCommit?.()).then(() => {
5955
6289
  if (options?.onQueuedPromoted) promoted.push(options.onQueuedPromoted);
5956
6290
  options?.onPreflightAccepted?.();
5957
- return {};
6291
+ return "completed";
5958
6292
  }),
5959
6293
  } as unknown as ExtensionAPI;
5960
6294
  const transport = memoryTransport();
@@ -6042,7 +6376,7 @@ test("SDK-only host lets every connection whose follow-up was promoted abort the
6042
6376
  // invocation left the remaining follow-ups durably accepted — their
6043
6377
  // result lookups would never complete and a restart would report them
6044
6378
  // failed even though they ran in the shared turn.
6045
- await handlers.get("agent_end")?.({ type: "agent_end" }, ctx);
6379
+ await handlers.get("agent_end")?.({ type: "agent_end", stopReason: "cancelled", messages: [] }, ctx);
6046
6380
  {
6047
6381
  const terminalDeadline = Date.now() + 15_000;
6048
6382
  const promptStatuses = () =>