@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.
- package/CHANGELOG.md +17 -1
- package/README.md +11 -11
- package/dist/src/claude-lifecycle-compatibility.d.ts +9 -0
- package/dist/src/claude-lifecycle-compatibility.js +50 -5
- package/dist/src/claude-lifecycle-compatibility.js.map +1 -1
- package/dist/src/claude-local-transcript-provider.js +5 -6
- package/dist/src/claude-local-transcript-provider.js.map +1 -1
- package/dist/src/claude-terminal-agent-adapter.js +38 -30
- package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
- package/dist/src/codex-lifecycle-compatibility.d.ts +19 -0
- package/dist/src/codex-lifecycle-compatibility.js +37 -0
- package/dist/src/codex-lifecycle-compatibility.js.map +1 -1
- package/dist/src/codex-store-adapter.js +5 -6
- package/dist/src/codex-store-adapter.js.map +1 -1
- package/dist/src/codex-terminal-agent-adapter.js +46 -28
- package/dist/src/codex-terminal-agent-adapter.js.map +1 -1
- package/dist/src/doctor-capabilities.d.ts +4 -3
- package/dist/src/doctor-capabilities.js +26 -6
- package/dist/src/doctor-capabilities.js.map +1 -1
- package/dist/src/install-doctor-command-adapter.js +1 -1
- package/dist/src/install-doctor-command-adapter.js.map +1 -1
- package/dist/src/native-thread-lifecycle-cli-adapter.js +17 -2
- package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -1
- package/dist/src/native-thread-transition-application.js +9 -3
- package/dist/src/native-thread-transition-application.js.map +1 -1
- package/dist/src/openclaw-plugin-command-adapter.js +13 -11
- package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-helpers.js +22 -1
- package/dist/src/openclaw-plugin-helpers.js.map +1 -1
- package/dist/src/openclaw-plugin-schemas.js +2 -2
- package/dist/src/openclaw-plugin-schemas.js.map +1 -1
- package/dist/src/terminal-action-projection.js +1 -1
- package/dist/src/terminal-action-projection.js.map +1 -1
- package/dist/src/terminal-agent-adapter.d.ts +10 -2
- package/dist/src/terminal-agent-adapter.js.map +1 -1
- package/dist/src/terminal-agent-bridge.d.ts +16 -17
- package/dist/src/terminal-agent-bridge.js +180 -217
- package/dist/src/terminal-agent-bridge.js.map +1 -1
- package/dist/src/terminal-command-cli-adapter.js +12 -4
- package/dist/src/terminal-command-cli-adapter.js.map +1 -1
- package/dist/src/terminal-delegate-cli-adapter.js +4 -3
- package/dist/src/terminal-delegate-cli-adapter.js.map +1 -1
- package/dist/src/terminal-dispatch-execution.d.ts +1 -0
- package/dist/src/terminal-dispatch-execution.js.map +1 -1
- package/dist/src/terminal-list-cli-adapter.js +24 -10
- package/dist/src/terminal-list-cli-adapter.js.map +1 -1
- package/dist/src/terminal-list-renderer.js +21 -9
- package/dist/src/terminal-list-renderer.js.map +1 -1
- package/dist/src/terminal-watch-cli-adapter.js +27 -0
- package/dist/src/terminal-watch-cli-adapter.js.map +1 -1
- package/docs/quickstart-herdr.md +2 -2
- package/docs/quickstart-tmux.md +4 -4
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- 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
|
|
170
|
-
|
|
171
|
-
requireExactEmptyComposerBeforeText
|
|
172
|
-
|
|
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
|
|
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
|
|
469
|
-
//
|
|
470
|
-
//
|
|
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
|
|
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
|
|
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
|
|
568
|
-
*
|
|
569
|
-
*
|
|
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
|
|
667
|
+
const captureSafePrompt = async (control) => {
|
|
623
668
|
try {
|
|
624
|
-
await
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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 (
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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
|
-
|
|
638
|
-
|
|
639
|
-
|
|
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
|
-
|
|
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
|
|
687
|
+
throw notStarted(error);
|
|
674
688
|
}
|
|
675
689
|
};
|
|
676
|
-
const
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
721
|
+
let postMutationHookError;
|
|
794
722
|
try {
|
|
795
|
-
await options.onComposerClearDispatched?.(
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
requiredState: "exact_empty",
|
|
799
|
-
classifyOpaqueLargePasteAsDifferent: true,
|
|
800
|
-
allowWorkingComposer: true
|
|
723
|
+
await options.onComposerClearDispatched?.({
|
|
724
|
+
terminalControl: verifiedForClear,
|
|
725
|
+
text: normalized
|
|
801
726
|
});
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
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
|
|
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:
|
|
817
|
-
disposition: "
|
|
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
|
|
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
|
|
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
|