@scotthuang/agent-knock-knock 0.12.17 → 0.12.18

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 (55) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/README.md +11 -11
  3. package/dist/src/claude-lifecycle-compatibility.d.ts +9 -0
  4. package/dist/src/claude-lifecycle-compatibility.js +50 -5
  5. package/dist/src/claude-lifecycle-compatibility.js.map +1 -1
  6. package/dist/src/claude-local-transcript-provider.js +5 -6
  7. package/dist/src/claude-local-transcript-provider.js.map +1 -1
  8. package/dist/src/claude-terminal-agent-adapter.js +38 -30
  9. package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
  10. package/dist/src/codex-lifecycle-compatibility.d.ts +19 -0
  11. package/dist/src/codex-lifecycle-compatibility.js +37 -0
  12. package/dist/src/codex-lifecycle-compatibility.js.map +1 -1
  13. package/dist/src/codex-store-adapter.js +5 -6
  14. package/dist/src/codex-store-adapter.js.map +1 -1
  15. package/dist/src/codex-terminal-agent-adapter.js +46 -28
  16. package/dist/src/codex-terminal-agent-adapter.js.map +1 -1
  17. package/dist/src/doctor-capabilities.d.ts +4 -3
  18. package/dist/src/doctor-capabilities.js +26 -6
  19. package/dist/src/doctor-capabilities.js.map +1 -1
  20. package/dist/src/install-doctor-command-adapter.js +1 -1
  21. package/dist/src/install-doctor-command-adapter.js.map +1 -1
  22. package/dist/src/native-thread-lifecycle-cli-adapter.js +17 -2
  23. package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -1
  24. package/dist/src/native-thread-transition-application.js +9 -3
  25. package/dist/src/native-thread-transition-application.js.map +1 -1
  26. package/dist/src/openclaw-plugin-command-adapter.js +13 -11
  27. package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
  28. package/dist/src/openclaw-plugin-helpers.js +22 -1
  29. package/dist/src/openclaw-plugin-helpers.js.map +1 -1
  30. package/dist/src/openclaw-plugin-schemas.js +2 -2
  31. package/dist/src/openclaw-plugin-schemas.js.map +1 -1
  32. package/dist/src/terminal-action-projection.js +1 -1
  33. package/dist/src/terminal-action-projection.js.map +1 -1
  34. package/dist/src/terminal-agent-adapter.d.ts +10 -2
  35. package/dist/src/terminal-agent-adapter.js.map +1 -1
  36. package/dist/src/terminal-agent-bridge.d.ts +16 -17
  37. package/dist/src/terminal-agent-bridge.js +180 -217
  38. package/dist/src/terminal-agent-bridge.js.map +1 -1
  39. package/dist/src/terminal-command-cli-adapter.js +12 -4
  40. package/dist/src/terminal-command-cli-adapter.js.map +1 -1
  41. package/dist/src/terminal-delegate-cli-adapter.js +4 -3
  42. package/dist/src/terminal-delegate-cli-adapter.js.map +1 -1
  43. package/dist/src/terminal-dispatch-execution.d.ts +1 -0
  44. package/dist/src/terminal-dispatch-execution.js.map +1 -1
  45. package/dist/src/terminal-list-cli-adapter.js +24 -10
  46. package/dist/src/terminal-list-cli-adapter.js.map +1 -1
  47. package/dist/src/terminal-list-renderer.js +21 -9
  48. package/dist/src/terminal-list-renderer.js.map +1 -1
  49. package/dist/src/terminal-watch-cli-adapter.js +27 -0
  50. package/dist/src/terminal-watch-cli-adapter.js.map +1 -1
  51. package/docs/quickstart-herdr.md +2 -2
  52. package/docs/quickstart-tmux.md +4 -4
  53. package/openclaw.plugin.json +1 -1
  54. package/package.json +1 -1
  55. package/templates/openclaw-skills/agent-knock-knock/SKILL.md +12 -10
@@ -40,6 +40,10 @@ const CODEX_NATIVE_STATUS_POPUP_BY_PROFILE = {
40
40
  "codex-tui-0.149.1": [
41
41
  " /status show current session configuration and token usage",
42
42
  " /statusline configure which items appear in the status line"
43
+ ],
44
+ "codex-tui-generic-v1": [
45
+ " /status show current session configuration and token usage",
46
+ " /statusline configure which items appear in the status line"
43
47
  ]
44
48
  };
45
49
  // Codex's verified `/status` profiles are exercised against its canonical
@@ -52,7 +56,8 @@ const CODEX_NATIVE_STATUS_MIN_VIEWPORT_BY_PROFILE = {
52
56
  "codex-tui-0.146.1": 80,
53
57
  "codex-tui-0.147.0": 80,
54
58
  "codex-tui-0.148.0": 80,
55
- "codex-tui-0.149.1": 80
59
+ "codex-tui-0.149.1": 80,
60
+ "codex-tui-generic-v1": 80
56
61
  };
57
62
  const CLAUDE_NATIVE_STATUS_POPUP_BY_PROFILE = {
58
63
  "claude-code-2.1.218-native-status": [
@@ -72,6 +77,12 @@ const CLAUDE_NATIVE_STATUS_POPUP_BY_PROFILE = {
72
77
  "/statusline Set up Claude Code's status line UI",
73
78
  "/ide Manage IDE integrations and show status",
74
79
  "/usage Show session cost, plan usage, and activity stats"
80
+ ],
81
+ "claude-code-unverified-native-status-v1": [
82
+ "/status Show Claude Code status including version, model, account, API connectivity, and tool statuses",
83
+ "/statusline Set up Claude Code's status line UI",
84
+ "/ide Manage IDE integrations and show status",
85
+ "/usage Show session cost, plan usage, and activity stats"
75
86
  ]
76
87
  };
77
88
  const CLAUDE_NATIVE_STATUS_SETTLE_BY_PROFILE = {
@@ -86,6 +97,10 @@ const CLAUDE_NATIVE_STATUS_SETTLE_BY_PROFILE = {
86
97
  "claude-code-2.1.237-native-status": {
87
98
  minimumStableMs: 80,
88
99
  maximumSettleMs: 5_000
100
+ },
101
+ "claude-code-unverified-native-status-v1": {
102
+ minimumStableMs: 80,
103
+ maximumSettleMs: 5_000
89
104
  }
90
105
  };
91
106
  const CODEX_LARGE_PASTE_CHAR_THRESHOLD = 1_000;
@@ -166,10 +181,16 @@ export class NativeInspectionDismissalError extends Error {
166
181
  function terminalSendComposerRequirements(agent, multiline, options) {
167
182
  const exactEmptyRetryAuthority = options.requireExactEmptyComposerAfterBeforeText;
168
183
  const requireExactEmptyComposerBeforeText = options.requireExactEmptyComposerBeforeText === true;
169
- const requireExactComposer = (agent === "codex" && multiline) ||
170
- options.requireExactComposerBeforeEnter === true ||
171
- requireExactEmptyComposerBeforeText ||
172
- exactEmptyRetryAuthority !== undefined;
184
+ const userExplicitEnterAfterTextWithoutComposerVeto = options.userExplicitEnterAfterTextWithoutComposerVeto === true;
185
+ if (userExplicitEnterAfterTextWithoutComposerVeto &&
186
+ (agent !== "codex" || !requireExactEmptyComposerBeforeText)) {
187
+ throw new TerminalInputNotStartedError("composer-veto-free Enter requires Codex and an exact-empty pre-text user-explicit boundary");
188
+ }
189
+ const requireExactComposer = !userExplicitEnterAfterTextWithoutComposerVeto &&
190
+ ((agent === "codex" && multiline) ||
191
+ options.requireExactComposerBeforeEnter === true ||
192
+ requireExactEmptyComposerBeforeText ||
193
+ exactEmptyRetryAuthority !== undefined);
173
194
  if (exactEmptyRetryAuthority !== undefined &&
174
195
  (agent !== "codex" ||
175
196
  options.beforeText === undefined ||
@@ -182,6 +203,39 @@ function terminalSendComposerRequirements(agent, multiline, options) {
182
203
  requireExactComposer
183
204
  };
184
205
  }
206
+ async function recordTerminalSendStage(options, event, preserveUserInput, previousError) {
207
+ try {
208
+ await options.onTransportStage?.(event);
209
+ return previousError;
210
+ }
211
+ catch (error) {
212
+ if (!preserveUserInput)
213
+ throw error;
214
+ return previousError ?? error;
215
+ }
216
+ }
217
+ async function runTerminalSendBeforeEnter(options, context, preserveUserInput, previousError) {
218
+ try {
219
+ await options.beforeEnter?.(context);
220
+ return previousError;
221
+ }
222
+ catch (error) {
223
+ if (!preserveUserInput)
224
+ throw error;
225
+ return previousError ?? error;
226
+ }
227
+ }
228
+ async function waitForUserExplicitCodexEnter(input) {
229
+ if (!input.enabled)
230
+ return;
231
+ await input.sleep(Math.max(0, CODEX_PASTE_ENTER_SETTLE_MS -
232
+ (input.nowMs() - input.textInjectedAt)));
233
+ }
234
+ function throwPostTextHookFailureAfterUserEnter(enabled, error) {
235
+ if (!enabled || error === undefined)
236
+ return;
237
+ throw new TerminalEnterDispatchReservedError("user-explicit Codex Enter was dispatched but post-text bookkeeping failed; do not retry", { cause: error });
238
+ }
185
239
  export class TerminalAgentBridge {
186
240
  registry;
187
241
  terminalProvider;
@@ -457,17 +511,21 @@ export class TerminalAgentBridge {
457
511
  }
458
512
  throw error;
459
513
  }
514
+ const textInjectedAt = this.nowMs();
515
+ const preserveUserInput = options.userExplicitEnterAfterTextWithoutComposerVeto === true;
516
+ let postTextHookError;
460
517
  let verifiedForEnter;
461
518
  try {
462
- await options.onTransportStage?.({
519
+ postTextHookError = await recordTerminalSendStage(options, {
463
520
  stage: "text_injected",
464
521
  agent: adapter.agent,
465
522
  terminalControl: verifiedForText,
466
523
  multiline
467
- });
468
- // Text and Enter are separate tmux operations. Revalidate between them
469
- // and never submit after identity or composer drift. Every failure in
470
- // this region is typed proof that this invocation did not call C-m.
524
+ }, preserveUserInput);
525
+ // Text and Enter are separate transport operations. Autonomous sends
526
+ // retain their exact Composer proof. A user-explicit managed Codex Send
527
+ // crosses only the native paste-suppression window, then keeps terminal
528
+ // identity as its final physical boundary.
471
529
  if (adapter.agent === "codex" && requireExactComposer) {
472
530
  verifiedForEnter = await this.settleCodexMultilineComposer(adapter, terminalControl, normalized, options.runtime, {
473
531
  allowOpaqueLargePastePlaceholder: exactEmptyRetryAuthority === undefined,
@@ -478,14 +536,20 @@ export class TerminalAgentBridge {
478
536
  verifiedForEnter = await this.verifyExactComposerBeforeEnter(adapter, terminalControl, normalized, options.runtime, options.allowWorkingComposerForUserExplicit === true);
479
537
  }
480
538
  else {
539
+ await waitForUserExplicitCodexEnter({
540
+ enabled: preserveUserInput,
541
+ textInjectedAt,
542
+ nowMs: this.nowMs,
543
+ sleep: this.sleep
544
+ });
481
545
  verifiedForEnter = await this.verifyTerminalIdentity(adapter.agent, terminalControl, options.runtime);
482
546
  }
483
- await options.beforeEnter?.({
547
+ postTextHookError = await runTerminalSendBeforeEnter(options, {
484
548
  agent: adapter.agent,
485
549
  terminalControl: verifiedForEnter,
486
550
  multiline,
487
551
  text: normalized
488
- });
552
+ }, preserveUserInput, postTextHookError);
489
553
  // `beforeEnter` may await Store and terminal identity fences. The human
490
554
  // can still edit the composer or change the native thread while that
491
555
  // callback is pending, so recapture before attempting Enter.
@@ -517,12 +581,13 @@ export class TerminalAgentBridge {
517
581
  // pre-Enter proof: a thrown key-delivery error may still have emitted C-m.
518
582
  try {
519
583
  await this.terminalProvider.sendKeys(this.terminalProvider.endpoint(verifiedForEnter), ["C-m"]);
520
- await options.onTransportStage?.({
584
+ postTextHookError = await recordTerminalSendStage(options, {
521
585
  stage: "enter_dispatched",
522
586
  agent: adapter.agent,
523
587
  terminalControl: verifiedForEnter,
524
588
  multiline
525
- });
589
+ }, preserveUserInput, postTextHookError);
590
+ throwPostTextHookFailureAfterUserEnter(preserveUserInput, postTextHookError);
526
591
  }
527
592
  catch (error) {
528
593
  if (exactEmptyRetryAuthority) {
@@ -564,9 +629,10 @@ export class TerminalAgentBridge {
564
629
  return this.settleCodexComposerObservation(adapter, terminalControl, normalized, options.runtime, { minimumStableMs: CODEX_MULTILINE_SETTLE_POLL_MS });
565
630
  }
566
631
  /**
567
- * Honor one explicit user Send against the current Codex composer.
568
- * Empty composers receive the request, exact drafts receive Enter only, and
569
- * every other stable draft is cleared and replaced by the requested text.
632
+ * Honor one explicit user Send against the current Codex terminal. The
633
+ * rendered Composer is not authority: clear the current draft once, inject
634
+ * the user's request, cross Codex's paste-suppression window, and press Enter
635
+ * once. Approval/modal and terminal identity remain hard pre-mutation gates.
570
636
  * This primitive is intentionally not used by autonomous managed sends.
571
637
  */
572
638
  async sendUserExplicitCodex(terminalControl, text, options) {
@@ -584,6 +650,7 @@ export class TerminalAgentBridge {
584
650
  transport: [
585
651
  "stable_resource_resolution",
586
652
  "screen_capture",
653
+ "text_delivery",
587
654
  "key_delivery"
588
655
  ]
589
656
  });
@@ -591,189 +658,50 @@ export class TerminalAgentBridge {
591
658
  catch (error) {
592
659
  throw new TerminalInputNotStartedError(error instanceof Error ? error.message : String(error), { cause: error });
593
660
  }
594
- const observation = await this.settleCodexComposerObservation(adapter, terminalControl, normalized, options.runtime, {
595
- // The same-draft branch may press Enter without any preceding text
596
- // delivery of its own, so its initial stable proof must itself cross
597
- // Codex's paste/newline suppression window.
598
- minimumStableMs: CODEX_PASTE_ENTER_SETTLE_MS,
599
- classifyOpaqueLargePasteAsDifferent: true,
600
- allowWorkingComposer: true
601
- });
602
- if (observation.state !== "exact_empty" &&
603
- observation.state !== "exact_draft" &&
604
- observation.state !== "different_draft") {
605
- const reason = "reason" in observation
606
- ? observation.reason
607
- : `Codex composer is ${observation.state}`;
608
- throw new TerminalInputNotStartedError(`the explicit user Send has no stable mutable Codex composer: ${reason}`);
609
- }
610
- const reservationContext = {
611
- terminalControl: observation.terminalControl,
612
- text: normalized,
613
- composerDigest: observation.digest,
614
- composerState: observation.state
615
- };
616
661
  const notStarted = (error, prefix) => error instanceof TerminalInputNotStartedError
617
662
  ? error
618
663
  : new TerminalInputNotStartedError(`${prefix ? `${prefix}: ` : ""}${error instanceof Error ? error.message : String(error)}`, { cause: error });
619
664
  const uncertain = (message, error) => error instanceof TerminalEnterDispatchReservedError
620
665
  ? error
621
666
  : new TerminalEnterDispatchReservedError(message, { cause: error });
622
- const revalidateBeforeFirstMutation = async (expectedState) => {
667
+ const captureSafePrompt = async (control) => {
623
668
  try {
624
- await options.beforeMutationReservation(reservationContext);
625
- const finalComposer = await this.captureCodexComposerSnapshot(adapter, observation.terminalControl, normalized, options.runtime, {
626
- classifyOpaqueLargePasteAsDifferent: true,
627
- allowWorkingComposer: true
669
+ const captured = await this.captureInspection(adapter, control, {
670
+ runtime: options.runtime,
671
+ // Approval/modal authority belongs to the current viewport. Do
672
+ // not let historical Esc instructions in scrollback veto Send.
673
+ scrollbackLines: 0
628
674
  });
629
- if (finalComposer.state !== expectedState ||
630
- finalComposer.digest !== observation.digest ||
631
- !sameTerminalControlIdentity(observation.terminalControl, finalComposer.terminalControl)) {
632
- const reason = "reason" in finalComposer
633
- ? finalComposer.reason
634
- : `Codex composer is ${finalComposer.state}`;
635
- throw new Error(`the Codex composer changed before explicit user Send: ${reason}`);
675
+ if (captured.inspection.approval.blocked ||
676
+ captured.inspection.activity.state === "awaiting_approval" ||
677
+ codexBlockingModalVisible(captured.screen)) {
678
+ throw new Error("the explicit user Send is blocked by a Codex approval or modal prompt");
636
679
  }
637
- return finalComposer.terminalControl;
638
- }
639
- catch (error) {
640
- throw notStarted(error);
641
- }
642
- };
643
- const dispatchEnter = async (control, outcomeLabel, mutationAlreadyStarted) => {
644
- let endpoint;
645
- try {
646
- endpoint = this.terminalProvider.endpoint(control);
647
- }
648
- catch (error) {
649
- if (!mutationAlreadyStarted)
650
- throw notStarted(error);
651
- throw uncertain(`${outcomeLabel} Enter endpoint is unresolved after terminal input; do not retry automatically`, error);
652
- }
653
- try {
654
- await this.terminalProvider.sendKeys(endpoint, ["C-m"]);
655
- }
656
- catch (error) {
657
- if (!mutationAlreadyStarted &&
658
- error instanceof TerminalControlInputNotSentError) {
659
- throw notStarted(error);
680
+ const verified = await this.verifyTerminalIdentity(adapter.agent, captured.terminalControl, options.runtime);
681
+ if (!sameTerminalControlIdentity(captured.terminalControl, verified)) {
682
+ throw new Error("terminal identity changed across the explicit Send approval scan");
660
683
  }
661
- throw uncertain(`${outcomeLabel} Enter outcome is uncertain; do not retry automatically`, error);
662
- }
663
- try {
664
- await options.onTransportStage?.({
665
- stage: "enter_dispatched",
666
- agent: adapter.agent,
667
- terminalControl: control,
668
- multiline
669
- });
670
- return control;
684
+ return verified;
671
685
  }
672
686
  catch (error) {
673
- throw uncertain(`${outcomeLabel} Enter was dispatched but its durable acknowledgement failed; do not retry automatically`, error);
687
+ throw notStarted(error);
674
688
  }
675
689
  };
676
- const injectAndSubmit = async (control, mutationAlreadyStarted) => {
677
- let endpoint;
678
- try {
679
- endpoint = this.terminalProvider.endpoint(control);
680
- }
681
- catch (error) {
682
- if (!mutationAlreadyStarted)
683
- throw notStarted(error);
684
- throw uncertain("explicit Codex replacement endpoint is unresolved after clearing the prior draft; do not retry automatically", error);
685
- }
686
- try {
687
- await this.terminalProvider.sendText(endpoint, normalized);
688
- }
689
- catch (error) {
690
- if (!mutationAlreadyStarted &&
691
- error instanceof TerminalControlInputNotSentError) {
692
- throw notStarted(error);
693
- }
694
- throw uncertain(mutationAlreadyStarted
695
- ? "explicit Codex replacement text outcome is uncertain after clearing the prior draft; do not retry automatically"
696
- : "explicit Codex text outcome is uncertain; do not retry automatically", error);
697
- }
698
- let exactDraftControl;
699
- try {
700
- await options.onTransportStage?.({
701
- stage: "text_injected",
702
- agent: adapter.agent,
703
- terminalControl: control,
704
- multiline
705
- });
706
- // This text was injected from an exact-empty composer under the same
707
- // terminal lock. Codex may render a large paste only as its exact
708
- // character-count placeholder, which is sufficient for this freshly
709
- // injected request but never for classifying a pre-existing draft.
710
- exactDraftControl = await this.settleCodexMultilineComposer(adapter, control, normalized, options.runtime, {
711
- allowOpaqueLargePastePlaceholder: true,
712
- allowWorkingComposer: true
713
- });
714
- }
715
- catch (error) {
716
- throw uncertain("explicit Codex text was injected but its Enter outcome is unresolved; do not retry automatically", error);
717
- }
718
- return dispatchEnter(exactDraftControl, "explicit Codex Send", true);
690
+ const initiallySafe = await captureSafePrompt(terminalControl);
691
+ const reservationContext = {
692
+ terminalControl: initiallySafe,
693
+ text: normalized
719
694
  };
720
- if (observation.state === "exact_draft") {
721
- const verifiedForEnter = await revalidateBeforeFirstMutation("exact_draft");
722
- const entered = await dispatchEnter(verifiedForEnter, "explicit Codex existing-draft submission", false);
723
- return {
724
- stage: "enter_dispatched",
725
- terminalControl: entered,
726
- disposition: "submitted_existing_draft",
727
- clearCount: 0,
728
- textInjectionCount: 0,
729
- enterCount: 1
730
- };
731
- }
732
- if (observation.state === "exact_empty") {
733
- try {
734
- assertTerminalMutationCapabilities({
735
- provider: this.terminalProvider,
736
- terminal: this.terminalProvider.endpoint(observation.terminalControl),
737
- semantic: ["send_keys", "screen_status"],
738
- transport: [
739
- "stable_resource_resolution",
740
- "screen_capture",
741
- "text_delivery",
742
- "key_delivery"
743
- ]
744
- });
745
- }
746
- catch (error) {
747
- throw notStarted(error);
748
- }
749
- const verifiedForText = await revalidateBeforeFirstMutation("exact_empty");
750
- const entered = await injectAndSubmit(verifiedForText, false);
751
- return {
752
- stage: "enter_dispatched",
753
- terminalControl: entered,
754
- disposition: "injected_empty_composer",
755
- clearCount: 0,
756
- textInjectionCount: 1,
757
- enterCount: 1
758
- };
759
- }
760
695
  try {
761
- assertTerminalMutationCapabilities({
762
- provider: this.terminalProvider,
763
- terminal: this.terminalProvider.endpoint(observation.terminalControl),
764
- semantic: ["send_keys", "screen_status"],
765
- transport: [
766
- "stable_resource_resolution",
767
- "screen_capture",
768
- "text_delivery",
769
- "key_delivery"
770
- ]
771
- });
696
+ await options.beforeMutationReservation(reservationContext);
772
697
  }
773
698
  catch (error) {
774
699
  throw notStarted(error);
775
700
  }
776
- const verifiedForClear = await revalidateBeforeFirstMutation("different_draft");
701
+ // The reservation callback may await Store or terminal locks. Recapture
702
+ // only approval/modal and identity authority immediately before the first
703
+ // physical mutation; Composer visibility and contents are never a veto.
704
+ const verifiedForClear = await captureSafePrompt(initiallySafe);
777
705
  let clearEndpoint;
778
706
  try {
779
707
  clearEndpoint = this.terminalProvider.endpoint(verifiedForClear);
@@ -790,31 +718,84 @@ export class TerminalAgentBridge {
790
718
  }
791
719
  throw uncertain("explicit Codex draft-clear outcome is uncertain; do not retry automatically", error);
792
720
  }
793
- let emptyControl;
721
+ let postMutationHookError;
794
722
  try {
795
- await options.onComposerClearDispatched?.(reservationContext);
796
- const empty = await this.settleCodexComposerObservation(adapter, verifiedForClear, normalized, options.runtime, {
797
- minimumStableMs: CODEX_MULTILINE_SETTLE_POLL_MS,
798
- requiredState: "exact_empty",
799
- classifyOpaqueLargePasteAsDifferent: true,
800
- allowWorkingComposer: true
723
+ await options.onComposerClearDispatched?.({
724
+ terminalControl: verifiedForClear,
725
+ text: normalized
801
726
  });
802
- if (empty.state !== "exact_empty") {
803
- const reason = "reason" in empty
804
- ? empty.reason
805
- : `Codex composer is ${empty.state}`;
806
- throw new Error(`Codex composer did not become empty after explicit replacement clear: ${reason}`);
727
+ }
728
+ catch (error) {
729
+ postMutationHookError = error;
730
+ }
731
+ let verifiedForText;
732
+ try {
733
+ verifiedForText = await this.verifyTerminalIdentity(adapter.agent, verifiedForClear, options.runtime);
734
+ if (!sameTerminalControlIdentity(verifiedForClear, verifiedForText)) {
735
+ throw new Error("terminal identity changed after clearing the Composer");
736
+ }
737
+ await this.terminalProvider.sendText(this.terminalProvider.endpoint(verifiedForText), normalized);
738
+ }
739
+ catch (error) {
740
+ throw uncertain("explicit Codex replacement text outcome is uncertain after clearing the prior draft; do not retry automatically", error);
741
+ }
742
+ const textInjectedAt = this.nowMs();
743
+ try {
744
+ try {
745
+ await options.onTransportStage?.({
746
+ stage: "text_injected",
747
+ agent: adapter.agent,
748
+ terminalControl: verifiedForText,
749
+ multiline
750
+ });
751
+ }
752
+ catch (error) {
753
+ postMutationHookError ??= error;
754
+ }
755
+ await this.sleep(Math.max(0, CODEX_PASTE_ENTER_SETTLE_MS - (this.nowMs() - textInjectedAt)));
756
+ }
757
+ catch (error) {
758
+ throw uncertain("explicit Codex text was injected but its Enter outcome is unresolved; do not retry automatically", error);
759
+ }
760
+ let verifiedForEnter;
761
+ try {
762
+ verifiedForEnter = await this.verifyTerminalIdentity(adapter.agent, verifiedForText, options.runtime);
763
+ if (!sameTerminalControlIdentity(verifiedForText, verifiedForEnter)) {
764
+ throw new Error("terminal identity changed before explicit Send Enter");
765
+ }
766
+ }
767
+ catch (error) {
768
+ throw uncertain("explicit Codex Enter endpoint is unresolved after terminal input; do not retry automatically", error);
769
+ }
770
+ try {
771
+ await this.terminalProvider.sendKeys(this.terminalProvider.endpoint(verifiedForEnter), ["C-m"]);
772
+ }
773
+ catch (error) {
774
+ throw uncertain("explicit Codex Send Enter outcome is uncertain; do not retry automatically", error);
775
+ }
776
+ try {
777
+ try {
778
+ await options.onTransportStage?.({
779
+ stage: "enter_dispatched",
780
+ agent: adapter.agent,
781
+ terminalControl: verifiedForEnter,
782
+ multiline
783
+ });
784
+ }
785
+ catch (error) {
786
+ postMutationHookError ??= error;
787
+ }
788
+ if (postMutationHookError !== undefined) {
789
+ throw postMutationHookError;
807
790
  }
808
- emptyControl = empty.terminalControl;
809
791
  }
810
792
  catch (error) {
811
- throw uncertain("explicit Codex composer was cleared but replacement did not start; do not retry automatically", error);
793
+ throw uncertain("explicit Codex Send Enter was dispatched but its post-mutation acknowledgement failed; do not retry automatically", error);
812
794
  }
813
- const entered = await injectAndSubmit(emptyControl, true);
814
795
  return {
815
796
  stage: "enter_dispatched",
816
- terminalControl: entered,
817
- disposition: "replaced_existing_draft",
797
+ terminalControl: verifiedForEnter,
798
+ disposition: "replaced_current_composer",
818
799
  clearCount: 1,
819
800
  textInjectionCount: 1,
820
801
  enterCount: 1
@@ -2208,7 +2189,7 @@ function assertClosedStatusInspectionPlan(adapter, terminalControl, plan) {
2208
2189
  plan.composer.maximumSettleMs < plan.composer.minimumStableMs ||
2209
2190
  plan.expectedResult.kind !== "native_status" ||
2210
2191
  !exactPresentation) {
2211
- throw new Error("refusing a non-closed or unverified native inspection plan");
2192
+ throw new Error("refusing a non-closed native inspection plan");
2212
2193
  }
2213
2194
  }
2214
2195
  function assertClosedNativeInspectionDismissal(plan) {
@@ -2216,7 +2197,7 @@ function assertClosedNativeInspectionDismissal(plan) {
2216
2197
  plan.expectedResult.dismissal?.expected !== "idle_empty_composer" ||
2217
2198
  JSON.stringify(plan.expectedResult.dismissal.keys) !==
2218
2199
  JSON.stringify(["Escape"])) {
2219
- throw new Error("native inspection has no closed verified modal dismissal plan");
2200
+ throw new Error("native inspection has no closed modal dismissal plan");
2220
2201
  }
2221
2202
  }
2222
2203
  function assertNativeInspectionComposerSafe(inspection, displayName = "terminal agent") {
@@ -2471,24 +2452,6 @@ export function exactCodexReadyStyledComposerCapture(screen) {
2471
2452
  digest: createHash("sha256").update(composerLine).digest("hex")
2472
2453
  };
2473
2454
  }
2474
- /**
2475
- * Closed list-time proof that the bottom Codex composer is either exactly
2476
- * empty or contains one stable replaceable draft. The draft text is never
2477
- * returned; the request-aware classification is repeated under the Send lock.
2478
- */
2479
- export function codexUserExplicitComposerCapture(screen) {
2480
- // NUL cannot be represented by the terminal composer, so every visible
2481
- // nonempty value classifies as replaceable without exposing its contents.
2482
- const composer = currentCodexComposerCapture(screen, "\0", false, true);
2483
- if (!composer)
2484
- return undefined;
2485
- return {
2486
- state: composer.state === "exact_empty"
2487
- ? "exact_empty"
2488
- : "stable_nonempty",
2489
- digest: composer.digest
2490
- };
2491
- }
2492
2455
  /**
2493
2456
  * Infer a viewport only from fixed-width visible-buffer rows. Trimmed captures
2494
2457
  * deliberately return undefined: a short content row is not proof of a short