@scotthuang/agent-knock-knock 0.13.2 → 0.13.3
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 +22 -0
- package/README.md +17 -4
- package/dist/src/claude-terminal-agent-adapter.js +4 -0
- package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
- package/dist/src/cli-core.js +9 -1
- package/dist/src/cli-core.js.map +1 -1
- package/dist/src/codex-terminal-agent-adapter.js +4 -0
- package/dist/src/codex-terminal-agent-adapter.js.map +1 -1
- package/dist/src/herdr-terminal-control-provider.js +6 -1
- package/dist/src/herdr-terminal-control-provider.js.map +1 -1
- package/dist/src/host-adapter.d.ts +1 -1
- package/dist/src/host-adapter.js +1 -1
- package/dist/src/host-bridge-tools.js +2 -2
- package/dist/src/managed-session.js +2 -18
- package/dist/src/managed-session.js.map +1 -1
- package/dist/src/native-thread-lifecycle-cli-adapter.d.ts +13 -0
- package/dist/src/native-thread-lifecycle-cli-adapter.js +578 -2
- package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-command-adapter.js +325 -35
- package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-helpers.d.ts +27 -0
- package/dist/src/openclaw-plugin-helpers.js +682 -2
- package/dist/src/openclaw-plugin-helpers.js.map +1 -1
- package/dist/src/openclaw-plugin-schemas.d.ts +53 -0
- package/dist/src/openclaw-plugin-schemas.js +53 -0
- package/dist/src/openclaw-plugin-schemas.js.map +1 -1
- package/dist/src/terminal-action-projection.d.ts +83 -1
- package/dist/src/terminal-action-projection.js +174 -0
- package/dist/src/terminal-action-projection.js.map +1 -1
- package/dist/src/terminal-agent-adapter.d.ts +7 -0
- package/dist/src/terminal-agent-adapter.js +8 -0
- package/dist/src/terminal-agent-adapter.js.map +1 -1
- package/dist/src/terminal-agent-bridge.d.ts +29 -0
- package/dist/src/terminal-agent-bridge.js +580 -14
- package/dist/src/terminal-agent-bridge.js.map +1 -1
- package/dist/src/terminal-control-ref.d.ts +23 -0
- package/dist/src/terminal-control-ref.js +32 -0
- package/dist/src/terminal-control-ref.js.map +1 -1
- package/dist/src/terminal-list-cli-adapter.js +324 -16
- package/dist/src/terminal-list-cli-adapter.js.map +1 -1
- package/dist/src/terminal-list-renderer.d.ts +1 -0
- package/dist/src/terminal-list-renderer.js +87 -3
- package/dist/src/terminal-list-renderer.js.map +1 -1
- package/dist/src/terminal-model-control.d.ts +312 -0
- package/dist/src/terminal-model-control.js +1595 -0
- package/dist/src/terminal-model-control.js.map +1 -0
- package/dist/src/terminal-runtime-cli-adapter.d.ts +2 -0
- package/dist/src/terminal-runtime-cli-adapter.js +59 -17
- package/dist/src/terminal-runtime-cli-adapter.js.map +1 -1
- package/docs/quickstart-herdr.md +1 -1
- package/docs/quickstart-tmux.md +27 -2
- package/openclaw.plugin.json +13 -1
- package/package.json +1 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +54 -11
|
@@ -5,6 +5,8 @@ import { hasCanonicalTerminalEndpoint, sameTerminalControlIncarnation, terminalC
|
|
|
5
5
|
import { CLAUDE_INJECTED_PASTE_FRAME_PROFILE, claudeInjectedPasteProofEligible, revalidateClaudeComposerProof, settleClaudeInjectedPasteComposer } from "./claude-injected-paste-proof.js";
|
|
6
6
|
import { TERMINAL_INTERACTION_SCHEMA, TERMINAL_INTERACTION_VERSION, validateAnyTerminalInteractionResponse, validateTerminalInteractionProjection } from "./terminal-interaction-protocol.js";
|
|
7
7
|
import { captureTerminalInteraction } from "./terminal-interaction-core.js";
|
|
8
|
+
import { inspectNativeQuestionnaire } from "./terminal-questionnaire-adapter.js";
|
|
9
|
+
import { discoverTerminalModelOptions, inspectTerminalModelControlResidual, observeTerminalModelControl, planTerminalModelControl, probeTerminalModelControl, repairTerminalModelControlResidual, switchTerminalModel } from "./terminal-model-control.js";
|
|
8
10
|
// Verified Codex profiles through 0.154.0 keep Enter in paste/newline mode for
|
|
9
11
|
// 120ms after burst input. Cross that boundary rather than landing on it, and
|
|
10
12
|
// also require observable composer stability instead of treating this delay
|
|
@@ -22,6 +24,11 @@ const CODEX_MULTILINE_SETTLE_SCROLLBACK_LINES = 240;
|
|
|
22
24
|
const CODEX_COMPOSER_MARKER = /^[›»](?:\s|$)/u;
|
|
23
25
|
const CODEX_COMPOSER_FOOTER = /^(?:gpt-[\w.-]+(?:\s|$)|[-\w.]+ default ·)/u;
|
|
24
26
|
const CODEX_COMPLETE_COMPOSER_FOOTER = /^(?:gpt-[\w.-]+(?:\s+\S+)?|[-\w.]+ default)\s+·\s+\S.*$/u;
|
|
27
|
+
const CODEX_MODEL_CONTROL_SLASH_POPUP_BY_PROFILE = {
|
|
28
|
+
"codex-model-control-0.154.0": [
|
|
29
|
+
" /model choose what model and reasoning effort to use"
|
|
30
|
+
]
|
|
31
|
+
};
|
|
25
32
|
// Keep every exact slash-completion shape closed per behavior profile so a
|
|
26
33
|
// version adding another matching command cannot silently become an authorized
|
|
27
34
|
// native command surface.
|
|
@@ -647,6 +654,367 @@ export class TerminalAgentBridge {
|
|
|
647
654
|
};
|
|
648
655
|
}
|
|
649
656
|
}
|
|
657
|
+
async modelOptions(agent, terminalControl, agentVersion, plan, options = {}) {
|
|
658
|
+
const adapter = this.registry.require(agent);
|
|
659
|
+
assertTerminalModelControlPlan(adapter, terminalControl, agentVersion, plan);
|
|
660
|
+
assertTerminalMutationCapabilities({
|
|
661
|
+
provider: this.terminalProvider,
|
|
662
|
+
terminal: this.terminalProvider.endpoint(terminalControl),
|
|
663
|
+
semantic: ["send_keys", "screen_status"],
|
|
664
|
+
transport: [
|
|
665
|
+
"stable_resource_resolution",
|
|
666
|
+
"screen_capture",
|
|
667
|
+
"ansi_capture",
|
|
668
|
+
"text_delivery",
|
|
669
|
+
"key_delivery"
|
|
670
|
+
]
|
|
671
|
+
});
|
|
672
|
+
const result = await discoverTerminalModelOptions({
|
|
673
|
+
agent,
|
|
674
|
+
agentVersion,
|
|
675
|
+
plan,
|
|
676
|
+
terminalControl,
|
|
677
|
+
ports: this.modelControlPorts(adapter, plan, options),
|
|
678
|
+
initialResidual: options.initialResidual
|
|
679
|
+
});
|
|
680
|
+
return {
|
|
681
|
+
terminalControl: result.terminalControl,
|
|
682
|
+
catalog: result.catalog
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
async setModel(agent, terminalControl, agentVersion, plan, expectedCatalogFingerprint, request, options = {}) {
|
|
686
|
+
const adapter = this.registry.require(agent);
|
|
687
|
+
assertTerminalModelControlPlan(adapter, terminalControl, agentVersion, plan);
|
|
688
|
+
assertTerminalMutationCapabilities({
|
|
689
|
+
provider: this.terminalProvider,
|
|
690
|
+
terminal: this.terminalProvider.endpoint(terminalControl),
|
|
691
|
+
semantic: ["send_keys", "screen_status"],
|
|
692
|
+
transport: [
|
|
693
|
+
"stable_resource_resolution",
|
|
694
|
+
"screen_capture",
|
|
695
|
+
"ansi_capture",
|
|
696
|
+
"text_delivery",
|
|
697
|
+
"key_delivery"
|
|
698
|
+
]
|
|
699
|
+
});
|
|
700
|
+
const result = await switchTerminalModel({
|
|
701
|
+
agent,
|
|
702
|
+
agentVersion,
|
|
703
|
+
plan,
|
|
704
|
+
terminalControl,
|
|
705
|
+
expectedCatalogFingerprint,
|
|
706
|
+
request,
|
|
707
|
+
ports: this.modelControlPorts(adapter, plan, options)
|
|
708
|
+
});
|
|
709
|
+
return {
|
|
710
|
+
...result,
|
|
711
|
+
terminalControl: result.terminalControl
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
async inspectModelControlResidual(agent, terminalControl, agentVersion, plan, options = {}) {
|
|
715
|
+
const adapter = this.registry.require(agent);
|
|
716
|
+
assertTerminalModelControlPlan(adapter, terminalControl, agentVersion, plan);
|
|
717
|
+
assertTerminalMutationCapabilities({
|
|
718
|
+
provider: this.terminalProvider,
|
|
719
|
+
terminal: this.terminalProvider.endpoint(terminalControl),
|
|
720
|
+
semantic: ["screen_status"],
|
|
721
|
+
transport: ["stable_resource_resolution", "screen_capture", "ansi_capture"]
|
|
722
|
+
});
|
|
723
|
+
return inspectTerminalModelControlResidual({
|
|
724
|
+
plan,
|
|
725
|
+
terminalControl,
|
|
726
|
+
ports: this.modelControlPorts(adapter, plan, options)
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
async repairModelControlResidual(agent, terminalControl, agentVersion, plan, expectedResidualFingerprint, options = {}) {
|
|
730
|
+
const adapter = this.registry.require(agent);
|
|
731
|
+
assertTerminalModelControlPlan(adapter, terminalControl, agentVersion, plan);
|
|
732
|
+
assertTerminalMutationCapabilities({
|
|
733
|
+
provider: this.terminalProvider,
|
|
734
|
+
terminal: this.terminalProvider.endpoint(terminalControl),
|
|
735
|
+
semantic: ["send_keys", "screen_status"],
|
|
736
|
+
transport: [
|
|
737
|
+
"stable_resource_resolution",
|
|
738
|
+
"screen_capture",
|
|
739
|
+
"ansi_capture",
|
|
740
|
+
"key_delivery"
|
|
741
|
+
]
|
|
742
|
+
});
|
|
743
|
+
const result = await repairTerminalModelControlResidual({
|
|
744
|
+
plan,
|
|
745
|
+
terminalControl,
|
|
746
|
+
expectedResidualFingerprint,
|
|
747
|
+
ports: this.modelControlPorts(adapter, plan, options)
|
|
748
|
+
});
|
|
749
|
+
return {
|
|
750
|
+
...result,
|
|
751
|
+
terminalControl: result.terminalControl
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
modelControlPorts(adapter, plan, options) {
|
|
755
|
+
if (!options.beforeInput) {
|
|
756
|
+
throw new Error("terminal model control requires current-snapshot authority before every input");
|
|
757
|
+
}
|
|
758
|
+
// `claude agents` deliberately reports the same interactive process as
|
|
759
|
+
// `waiting` / `dialog open` while Claude's native model picker is open.
|
|
760
|
+
// Keep that lifecycle relaxation private to this model-control operation:
|
|
761
|
+
// a capture may cross the state boundary read-only, but a terminal key is
|
|
762
|
+
// authorized only by the immediately preceding, exact profiled picker
|
|
763
|
+
// frame. The permit is one-shot so no later transport can inherit it.
|
|
764
|
+
let claudeModelDialogInputPermit;
|
|
765
|
+
let codexModelInputPermit;
|
|
766
|
+
const consumeCodexModelInputPermit = () => {
|
|
767
|
+
const permit = codexModelInputPermit;
|
|
768
|
+
codexModelInputPermit = undefined;
|
|
769
|
+
return permit;
|
|
770
|
+
};
|
|
771
|
+
const capture = async (input) => {
|
|
772
|
+
claudeModelDialogInputPermit = undefined;
|
|
773
|
+
codexModelInputPermit = undefined;
|
|
774
|
+
const original = input.terminalControl;
|
|
775
|
+
const verified = await this.verifyModelControlTerminalIdentity(adapter, plan, original, options.runtime, { claudeModelDialog: "allow" });
|
|
776
|
+
const styledScreen = await this.terminalProvider.capture(this.terminalProvider.endpoint(verified), { scrollbackLines: 160, preserveEscapes: true });
|
|
777
|
+
const screen = stripTerminalEscapeSequences(styledScreen);
|
|
778
|
+
const inspection = adapter.inspectScreen({
|
|
779
|
+
screen,
|
|
780
|
+
runtime: options.runtime
|
|
781
|
+
});
|
|
782
|
+
const modelObservation = adapter.observeModelControl?.(plan, screen);
|
|
783
|
+
const exactCodexModelSurface = adapter.agent === "codex" &&
|
|
784
|
+
modelObservation !== undefined &&
|
|
785
|
+
modelObservation.state !== "none" &&
|
|
786
|
+
modelObservation.state !== "ambiguous";
|
|
787
|
+
const exactClaudeModelDialog = adapter.agent === "claude" &&
|
|
788
|
+
modelObservation?.state === "claude_model_picker";
|
|
789
|
+
const reverified = await this.verifyModelControlTerminalIdentity(adapter, plan, verified, options.runtime, {
|
|
790
|
+
claudeModelDialog: exactClaudeModelDialog
|
|
791
|
+
? "require"
|
|
792
|
+
: "forbid"
|
|
793
|
+
});
|
|
794
|
+
if (!sameTerminalControlIdentity(verified, reverified)) {
|
|
795
|
+
throw new Error("terminal control identity changed across model-control capture");
|
|
796
|
+
}
|
|
797
|
+
if (exactClaudeModelDialog) {
|
|
798
|
+
claudeModelDialogInputPermit = {
|
|
799
|
+
terminalControl: reverified,
|
|
800
|
+
fingerprint: modelObservation.fingerprint
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
const codexComposer = adapter.agent === "codex"
|
|
804
|
+
? currentCodexComposerCapture(styledScreen, input.expectedComposer ?? "", false, false, input.expectedComposer === plan.command
|
|
805
|
+
? CODEX_MODEL_CONTROL_SLASH_POPUP_BY_PROFILE[plan.behaviorProfile]
|
|
806
|
+
: undefined)
|
|
807
|
+
: undefined;
|
|
808
|
+
let codexInputBlocked = false;
|
|
809
|
+
if (adapter.agent === "codex") {
|
|
810
|
+
try {
|
|
811
|
+
const questionnaire = inspectNativeQuestionnaire({
|
|
812
|
+
agent: "codex",
|
|
813
|
+
version: "0.154.0",
|
|
814
|
+
screen: styledScreen
|
|
815
|
+
});
|
|
816
|
+
const asyncQuestionMode = inspectCodexAsyncQuestionInputMode(styledScreen);
|
|
817
|
+
codexInputBlocked = !exactCodexModelSurface && (questionnaire.status !== "none" ||
|
|
818
|
+
codexActiveWriterViewerVisible(styledScreen) ||
|
|
819
|
+
asyncQuestionMode === "expanded" ||
|
|
820
|
+
asyncQuestionMode === "ambiguous");
|
|
821
|
+
}
|
|
822
|
+
catch {
|
|
823
|
+
codexInputBlocked = !exactCodexModelSurface;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
const exactEmptyComposer = adapter.agent === "codex"
|
|
827
|
+
? codexComposer?.state === "exact_empty"
|
|
828
|
+
: isExactClaudeIdleComposer(screen);
|
|
829
|
+
const claudeProfiledCommand = adapter.agent === "claude" &&
|
|
830
|
+
input.expectedComposer === plan.command &&
|
|
831
|
+
exactClaudeModelControlComposerCapture(screen, plan, input.expectedComposer) !== undefined;
|
|
832
|
+
const claudeBareCommand = adapter.agent === "claude" &&
|
|
833
|
+
input.expectedComposer !== undefined &&
|
|
834
|
+
exactTerminalComposerCapture("claude", screen, input.expectedComposer) !== undefined;
|
|
835
|
+
const exactCommandComposer = input.expectedComposer !== undefined && (adapter.agent === "codex"
|
|
836
|
+
? codexComposer?.state === "exact_draft"
|
|
837
|
+
: claudeProfiledCommand || claudeBareCommand);
|
|
838
|
+
const exactCommandReady = adapter.agent === "codex"
|
|
839
|
+
? codexComposer?.state === "exact_draft" &&
|
|
840
|
+
codexComposer.profiledSlashPopup === true
|
|
841
|
+
: claudeProfiledCommand;
|
|
842
|
+
const exactBareCommand = adapter.agent === "codex"
|
|
843
|
+
? codexComposer?.state === "exact_draft" &&
|
|
844
|
+
codexComposer.bareCommand === true
|
|
845
|
+
: claudeBareCommand && !claudeProfiledCommand;
|
|
846
|
+
if (adapter.agent === "codex") {
|
|
847
|
+
const exactModelSurface = modelObservation &&
|
|
848
|
+
modelObservation.state !== "none" &&
|
|
849
|
+
modelObservation.state !== "ambiguous"
|
|
850
|
+
? {
|
|
851
|
+
kind: "model_surface",
|
|
852
|
+
fingerprint: modelObservation.fingerprint
|
|
853
|
+
}
|
|
854
|
+
: undefined;
|
|
855
|
+
const exactComposerSurface = codexComposer?.state === "exact_empty"
|
|
856
|
+
? {
|
|
857
|
+
kind: "composer_empty",
|
|
858
|
+
fingerprint: codexComposer.digest
|
|
859
|
+
}
|
|
860
|
+
: codexComposer?.state === "exact_draft"
|
|
861
|
+
? {
|
|
862
|
+
kind: "composer_command",
|
|
863
|
+
fingerprint: codexComposer.digest
|
|
864
|
+
}
|
|
865
|
+
: undefined;
|
|
866
|
+
const proof = exactModelSurface ?? exactComposerSurface;
|
|
867
|
+
if (proof) {
|
|
868
|
+
codexModelInputPermit = {
|
|
869
|
+
terminalControl: reverified,
|
|
870
|
+
expectedComposer: input.expectedComposer,
|
|
871
|
+
...proof
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
return {
|
|
876
|
+
terminalControl: reverified,
|
|
877
|
+
screen,
|
|
878
|
+
// The exact profiled picker is the current input owner. Generic
|
|
879
|
+
// lifecycle/questionnaire detectors may still see stale transcript
|
|
880
|
+
// markers behind that overlay, so keep their result from vetoing the
|
|
881
|
+
// model-control state machine. Unknown or partial pickers receive no
|
|
882
|
+
// such override and remain fail-closed.
|
|
883
|
+
activityState: exactCodexModelSurface
|
|
884
|
+
? "unknown"
|
|
885
|
+
: inspection.activity.state,
|
|
886
|
+
approvalBlocked: exactCodexModelSurface
|
|
887
|
+
? false
|
|
888
|
+
: inspection.approval.blocked,
|
|
889
|
+
exactEmptyComposer,
|
|
890
|
+
exactCommandReady,
|
|
891
|
+
exactCommandComposer,
|
|
892
|
+
exactBareCommand,
|
|
893
|
+
...(codexComposer?.state === "exact_draft"
|
|
894
|
+
? { exactCommandFingerprint: codexComposer.digest }
|
|
895
|
+
: {}),
|
|
896
|
+
inputBlocked: codexInputBlocked
|
|
897
|
+
};
|
|
898
|
+
};
|
|
899
|
+
return {
|
|
900
|
+
beforeInput: options.beforeInput,
|
|
901
|
+
loadCodexCatalog: options.loadCodexCatalog,
|
|
902
|
+
capture,
|
|
903
|
+
sendText: async (control, text) => {
|
|
904
|
+
claudeModelDialogInputPermit = undefined;
|
|
905
|
+
if (adapter.agent === "codex") {
|
|
906
|
+
const permit = consumeCodexModelInputPermit();
|
|
907
|
+
if (!permit || permit.kind !== "composer_empty") {
|
|
908
|
+
throw new Error("Codex /model text requires one fresh exact empty-composer permit");
|
|
909
|
+
}
|
|
910
|
+
const fresh = await capture({
|
|
911
|
+
terminalControl: control,
|
|
912
|
+
expectedComposer: permit.expectedComposer
|
|
913
|
+
});
|
|
914
|
+
const freshPermit = consumeCodexModelInputPermit();
|
|
915
|
+
if (!freshPermit ||
|
|
916
|
+
freshPermit.kind !== permit.kind ||
|
|
917
|
+
freshPermit.fingerprint !== permit.fingerprint ||
|
|
918
|
+
!sameTerminalControlIdentity(permit.terminalControl, freshPermit.terminalControl) ||
|
|
919
|
+
fresh.inputBlocked === true ||
|
|
920
|
+
fresh.approvalBlocked ||
|
|
921
|
+
fresh.activityState === "working" ||
|
|
922
|
+
fresh.activityState === "awaiting_approval" ||
|
|
923
|
+
!fresh.exactEmptyComposer) {
|
|
924
|
+
throw new Error("the exact Codex empty composer changed before /model text delivery");
|
|
925
|
+
}
|
|
926
|
+
await this.terminalProvider.sendText(this.terminalProvider.endpoint(freshPermit.terminalControl), text);
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
const verified = await this.verifyTerminalIdentity(adapter.agent, control, options.runtime);
|
|
930
|
+
await this.terminalProvider.sendText(this.terminalProvider.endpoint(verified), text);
|
|
931
|
+
},
|
|
932
|
+
sendKeys: async (control, keys) => {
|
|
933
|
+
const requestedControl = control;
|
|
934
|
+
if (adapter.agent === "codex") {
|
|
935
|
+
const permit = consumeCodexModelInputPermit();
|
|
936
|
+
claudeModelDialogInputPermit = undefined;
|
|
937
|
+
if (!permit || !sameTerminalControlIdentity(permit.terminalControl, requestedControl)) {
|
|
938
|
+
throw new Error("Codex model-control key requires one fresh exact input-surface permit");
|
|
939
|
+
}
|
|
940
|
+
const fresh = await capture({
|
|
941
|
+
terminalControl: requestedControl,
|
|
942
|
+
expectedComposer: permit.expectedComposer
|
|
943
|
+
});
|
|
944
|
+
const freshPermit = consumeCodexModelInputPermit();
|
|
945
|
+
if (!freshPermit ||
|
|
946
|
+
freshPermit.kind !== permit.kind ||
|
|
947
|
+
freshPermit.fingerprint !== permit.fingerprint ||
|
|
948
|
+
!sameTerminalControlIdentity(permit.terminalControl, freshPermit.terminalControl) ||
|
|
949
|
+
fresh.inputBlocked === true ||
|
|
950
|
+
fresh.approvalBlocked ||
|
|
951
|
+
fresh.activityState === "working" ||
|
|
952
|
+
fresh.activityState === "awaiting_approval") {
|
|
953
|
+
throw new Error("the exact Codex model-control input surface changed before key dispatch");
|
|
954
|
+
}
|
|
955
|
+
await this.terminalProvider.sendKeys(this.terminalProvider.endpoint(freshPermit.terminalControl), keys);
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
const permit = claudeModelDialogInputPermit;
|
|
959
|
+
claudeModelDialogInputPermit = undefined;
|
|
960
|
+
const requireClaudeModelDialog = permit !== undefined &&
|
|
961
|
+
sameTerminalControlIdentity(permit.terminalControl, requestedControl);
|
|
962
|
+
const verified = await this.verifyModelControlTerminalIdentity(adapter, plan, requestedControl, options.runtime, {
|
|
963
|
+
claudeModelDialog: requireClaudeModelDialog
|
|
964
|
+
? "require"
|
|
965
|
+
: "forbid"
|
|
966
|
+
});
|
|
967
|
+
if (requireClaudeModelDialog) {
|
|
968
|
+
const styledScreen = await this.terminalProvider.capture(this.terminalProvider.endpoint(verified), { scrollbackLines: 160, preserveEscapes: true });
|
|
969
|
+
const screen = stripTerminalEscapeSequences(styledScreen);
|
|
970
|
+
const inspection = adapter.inspectScreen({
|
|
971
|
+
screen,
|
|
972
|
+
runtime: options.runtime
|
|
973
|
+
});
|
|
974
|
+
const observation = adapter.observeModelControl?.(plan, screen);
|
|
975
|
+
if (inspection.approval.blocked ||
|
|
976
|
+
inspection.activity.state === "working" ||
|
|
977
|
+
observation?.state !== "claude_model_picker" ||
|
|
978
|
+
observation.fingerprint !== permit?.fingerprint) {
|
|
979
|
+
throw new Error("the exact Claude model picker changed before key dispatch");
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
await this.terminalProvider.sendKeys(this.terminalProvider.endpoint(verified), keys);
|
|
983
|
+
},
|
|
984
|
+
sleep: this.sleep
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
async verifyModelControlTerminalIdentity(adapter, plan, terminalControl, runtime, options) {
|
|
988
|
+
const canUseClaudeModelDialog = adapter.agent === "claude" &&
|
|
989
|
+
plan.behaviorProfile === "claude-model-control-2.1.266" &&
|
|
990
|
+
runtime?.requireExactClaudeAgentRow === true &&
|
|
991
|
+
runtime.exactClaudeAgentState === "idle";
|
|
992
|
+
if (options.claudeModelDialog === "require") {
|
|
993
|
+
if (!canUseClaudeModelDialog) {
|
|
994
|
+
throw new Error("Claude model-dialog identity cannot be required outside its exact profile");
|
|
995
|
+
}
|
|
996
|
+
return this.verifyTerminalIdentity(adapter.agent, terminalControl, { ...runtime, exactClaudeAgentState: "status_dialog" });
|
|
997
|
+
}
|
|
998
|
+
try {
|
|
999
|
+
return await this.verifyTerminalIdentity(adapter.agent, terminalControl, runtime);
|
|
1000
|
+
}
|
|
1001
|
+
catch (idleError) {
|
|
1002
|
+
if (options.claudeModelDialog !== "allow" ||
|
|
1003
|
+
!canUseClaudeModelDialog) {
|
|
1004
|
+
throw idleError;
|
|
1005
|
+
}
|
|
1006
|
+
try {
|
|
1007
|
+
return await this.verifyTerminalIdentity(adapter.agent, terminalControl, { ...runtime, exactClaudeAgentState: "status_dialog" });
|
|
1008
|
+
}
|
|
1009
|
+
catch {
|
|
1010
|
+
// Preserve the original current-snapshot failure. Both attempts use
|
|
1011
|
+
// the same PID, process incarnation, native Session, cwd, and terminal
|
|
1012
|
+
// control fence; the second changes only the exact allowed agents row
|
|
1013
|
+
// state from idle to `waiting` / `dialog open`.
|
|
1014
|
+
throw idleError;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
650
1018
|
async send(agent, terminalControl, text, options = {}) {
|
|
651
1019
|
const adapter = this.registry.require(agent);
|
|
652
1020
|
const multiline = /[\r\n]/u.test(text.trimEnd());
|
|
@@ -2755,9 +3123,80 @@ function exactClaudeComposerFrame(screen) {
|
|
|
2755
3123
|
.filter((line) => line.trim().length > 0)
|
|
2756
3124
|
};
|
|
2757
3125
|
}
|
|
3126
|
+
function exactClaudeModelControlComposerCapture(screen, plan, expectedText) {
|
|
3127
|
+
if (plan.behaviorProfile !== "claude-model-control-2.1.266" ||
|
|
3128
|
+
expectedText !== plan.command ||
|
|
3129
|
+
expectedText !== "/model")
|
|
3130
|
+
return undefined;
|
|
3131
|
+
const frame = exactClaudeComposerFrame(screen);
|
|
3132
|
+
if (!frame ||
|
|
3133
|
+
frame.composerRows.length !== 1 ||
|
|
3134
|
+
frame.composerRows[0].replace(/^\s*❯\s?/u, "").trimEnd() !== expectedText ||
|
|
3135
|
+
frame.trailing.length > 2 ||
|
|
3136
|
+
!claudeNativeInspectionTrailingIsFooter(frame.trailing))
|
|
3137
|
+
return undefined;
|
|
3138
|
+
const beforeComposer = frame.lines.slice(0, frame.openIndex);
|
|
3139
|
+
while (beforeComposer.length > 0 &&
|
|
3140
|
+
beforeComposer.at(-1)?.trim() === "") {
|
|
3141
|
+
beforeComposer.pop();
|
|
3142
|
+
}
|
|
3143
|
+
let popupStart = beforeComposer.length;
|
|
3144
|
+
while (popupStart > 0) {
|
|
3145
|
+
const line = beforeComposer[popupStart - 1];
|
|
3146
|
+
if (/^\s*(?:(?:❯|›)\s*)?\/[a-z][a-z0-9-]*(?:\s{2,}|\s*$)/iu.test(line) ||
|
|
3147
|
+
/^\s{2,}\S/u.test(line)) {
|
|
3148
|
+
popupStart -= 1;
|
|
3149
|
+
continue;
|
|
3150
|
+
}
|
|
3151
|
+
break;
|
|
3152
|
+
}
|
|
3153
|
+
const popupRows = beforeComposer.slice(popupStart);
|
|
3154
|
+
if (popupRows.length === 0 || popupRows.length > 32)
|
|
3155
|
+
return undefined;
|
|
3156
|
+
const suggestions = [];
|
|
3157
|
+
for (const [lineIndex, line] of popupRows.entries()) {
|
|
3158
|
+
const match = /^\s*(?:(❯|›)\s*)?(\/[a-z][a-z0-9-]*)(?:\s{2,}|\s*$)(.*)$/iu
|
|
3159
|
+
.exec(line);
|
|
3160
|
+
if (match) {
|
|
3161
|
+
suggestions.push({
|
|
3162
|
+
lineIndex,
|
|
3163
|
+
selected: Boolean(match[1]),
|
|
3164
|
+
command: match[2],
|
|
3165
|
+
normalized: `${match[2]} ${match[3]}`.trim().replace(/\s+/gu, " ")
|
|
3166
|
+
});
|
|
3167
|
+
continue;
|
|
3168
|
+
}
|
|
3169
|
+
if (suggestions.length === 0 || !/^\s{2,}\S/u.test(line)) {
|
|
3170
|
+
return undefined;
|
|
3171
|
+
}
|
|
3172
|
+
const previous = suggestions.at(-1);
|
|
3173
|
+
previous.normalized = `${previous.normalized} ${line.trim()}`
|
|
3174
|
+
.replace(/\s+/gu, " ");
|
|
3175
|
+
}
|
|
3176
|
+
if (suggestions.length === 0 || suggestions[0].lineIndex !== 0) {
|
|
3177
|
+
return undefined;
|
|
3178
|
+
}
|
|
3179
|
+
const selected = suggestions.filter((row) => row.selected);
|
|
3180
|
+
if (selected.length > 1 ||
|
|
3181
|
+
selected.length === 1 && selected[0].lineIndex !== 0 ||
|
|
3182
|
+
suggestions.slice(1).some((row) => row.command === "/model"))
|
|
3183
|
+
return undefined;
|
|
3184
|
+
const first = suggestions[0].normalized;
|
|
3185
|
+
if (!/^\/model Set the AI model for Claude Code \(currently [A-Za-z0-9][A-Za-z0-9._:/+\[\]\- ]{0,127}\)$/u.test(first)) {
|
|
3186
|
+
return undefined;
|
|
3187
|
+
}
|
|
3188
|
+
return {
|
|
3189
|
+
digest: createHash("sha256")
|
|
3190
|
+
.update(frame.lines.slice(frame.openIndex).join("\n"))
|
|
3191
|
+
.digest("hex")
|
|
3192
|
+
};
|
|
3193
|
+
}
|
|
2758
3194
|
function claudeNativeInspectionTrailingIsFooter(lines) {
|
|
2759
|
-
return lines.length <= 2 && lines.every(
|
|
2760
|
-
|
|
3195
|
+
return lines.length <= 2 && lines.every(isClaudeNativeComposerFooterLine);
|
|
3196
|
+
}
|
|
3197
|
+
function isClaudeNativeComposerFooterLine(line) {
|
|
3198
|
+
return /^\s*(?:[⏵⏴⏸]{1,2}|\?)\s*.*(?:manual mode|shift\+tab|accept edits|bypass permissions|for shortcuts|← for agents)/iu
|
|
3199
|
+
.test(line);
|
|
2761
3200
|
}
|
|
2762
3201
|
function nativeInspectionScreenFingerprint(screen) {
|
|
2763
3202
|
return `sha256:${createHash("sha256").update(screen).digest("hex")}`;
|
|
@@ -3143,7 +3582,7 @@ export function inspectCodexAsyncQuestionInputMode(styledScreen) {
|
|
|
3143
3582
|
* Classify only the bottom live Codex composer region. A matching transcript
|
|
3144
3583
|
* prompt elsewhere in scrollback is deliberately ignored.
|
|
3145
3584
|
*/
|
|
3146
|
-
function currentCodexComposerCapture(styledScreen, expectedText, allowOpaqueLargePastePlaceholder = false, classifyOpaqueLargePasteAsDifferent = false) {
|
|
3585
|
+
function currentCodexComposerCapture(styledScreen, expectedText, allowOpaqueLargePastePlaceholder = false, classifyOpaqueLargePasteAsDifferent = false, exactSlashPopupRows) {
|
|
3147
3586
|
const screen = stripTerminalEscapeSequences(styledScreen);
|
|
3148
3587
|
const lines = screen.replace(/\r\n?/gu, "\n").split("\n");
|
|
3149
3588
|
while (lines.length > 0 && lines.at(-1)?.trim().length === 0) {
|
|
@@ -3185,10 +3624,29 @@ function currentCodexComposerCapture(styledScreen, expectedText, allowOpaqueLarg
|
|
|
3185
3624
|
if (positivelyEmpty) {
|
|
3186
3625
|
return { state: "exact_empty", digest };
|
|
3187
3626
|
}
|
|
3188
|
-
|
|
3627
|
+
const expectedComparable = composerComparableText(expectedText);
|
|
3628
|
+
const exactStyledProfiledCommand = exactSlashPopupRows !== undefined &&
|
|
3629
|
+
exactCodexStyledCommandComposerCapture(styledScreen, expectedText) !== undefined;
|
|
3630
|
+
const exactFooterlessProfiledCommand = footerIndex < 0 &&
|
|
3631
|
+
exactStyledProfiledCommand;
|
|
3632
|
+
// Codex 0.154 replaces the ordinary model/cwd footer with its slash
|
|
3633
|
+
// completion surface. Real terminal renderers may retain one or more blank
|
|
3634
|
+
// layout rows between the Composer and that surface. The exact styled
|
|
3635
|
+
// command is sufficient only for reversible cleanup; Enter additionally
|
|
3636
|
+
// requires the unique, ordered, version-profiled completion rows below.
|
|
3637
|
+
const popupRows = region.slice(1).map((row) => row.trimEnd());
|
|
3638
|
+
const exactPopupLayout = exactSlashPopupRows !== undefined && (JSON.stringify(popupRows) === JSON.stringify(exactSlashPopupRows) ||
|
|
3639
|
+
popupRows.length === exactSlashPopupRows.length + 1 &&
|
|
3640
|
+
popupRows[0] === "" &&
|
|
3641
|
+
JSON.stringify(popupRows.slice(1)) ===
|
|
3642
|
+
JSON.stringify(exactSlashPopupRows));
|
|
3643
|
+
const exactProfiledSlashPopup = exactSlashPopupRows !== undefined &&
|
|
3644
|
+
composerComparableText(bodyRows[0] ?? "").trimEnd() ===
|
|
3645
|
+
expectedComparable &&
|
|
3646
|
+
exactPopupLayout;
|
|
3647
|
+
if (footerIndex < 0 && !exactFooterlessProfiledCommand) {
|
|
3189
3648
|
return undefined;
|
|
3190
3649
|
}
|
|
3191
|
-
const expectedComparable = composerComparableText(expectedText);
|
|
3192
3650
|
const expectedCharacterCount = Array.from(expectedText).length;
|
|
3193
3651
|
const comparable = composerComparableText(bodyRows.join("\n"));
|
|
3194
3652
|
const opaqueLargePastePlaceholder = /^\[Pasted Content \d+ chars\]$/u.test(comparable);
|
|
@@ -3197,17 +3655,67 @@ function currentCodexComposerCapture(styledScreen, expectedText, allowOpaqueLarg
|
|
|
3197
3655
|
? { state: "different_draft", digest }
|
|
3198
3656
|
: undefined;
|
|
3199
3657
|
}
|
|
3200
|
-
|
|
3658
|
+
// Herdr's ANSI visible buffer preserves Codex's fixed-width Composer paint:
|
|
3659
|
+
// the typed command is followed by layout padding through the viewport edge.
|
|
3660
|
+
// Accept that padding only when the closed 0.154 profile, full-row
|
|
3661
|
+
// background paint, exact command text, and complete model/cwd footer all
|
|
3662
|
+
// agree. Plain padded text remains untrusted.
|
|
3663
|
+
const exactVisibleDraft = footerIndex >= 0 && (terminalComposerRowsMatchExpected(bodyRows, expectedComparable) ||
|
|
3664
|
+
exactStyledProfiledCommand);
|
|
3201
3665
|
const exactLargePastePlaceholder = allowOpaqueLargePastePlaceholder &&
|
|
3202
3666
|
expectedCharacterCount > CODEX_LARGE_PASTE_CHAR_THRESHOLD &&
|
|
3203
3667
|
comparable === composerComparableText(`[Pasted Content ${expectedCharacterCount} chars]`);
|
|
3204
|
-
if (exactVisibleDraft ||
|
|
3205
|
-
|
|
3668
|
+
if (exactVisibleDraft ||
|
|
3669
|
+
exactLargePastePlaceholder ||
|
|
3670
|
+
exactProfiledSlashPopup ||
|
|
3671
|
+
exactFooterlessProfiledCommand) {
|
|
3672
|
+
return {
|
|
3673
|
+
state: "exact_draft",
|
|
3674
|
+
digest,
|
|
3675
|
+
...(exactProfiledSlashPopup ? { profiledSlashPopup: true } : {}),
|
|
3676
|
+
...(exactVisibleDraft && footerIndex >= 0
|
|
3677
|
+
? { bareCommand: true }
|
|
3678
|
+
: {})
|
|
3679
|
+
};
|
|
3206
3680
|
}
|
|
3207
3681
|
return comparable.length > 0
|
|
3208
3682
|
? { state: "different_draft", digest }
|
|
3209
3683
|
: undefined;
|
|
3210
3684
|
}
|
|
3685
|
+
/**
|
|
3686
|
+
* Prove the live footerless Codex slash Composer without trusting transcript
|
|
3687
|
+
* text alone. The 0.154 TUI paints this row across the current viewport while
|
|
3688
|
+
* its completion popup replaces the ordinary model/cwd footer.
|
|
3689
|
+
*/
|
|
3690
|
+
function exactCodexStyledCommandComposerCapture(styledScreen, expectedText) {
|
|
3691
|
+
const rows = styledScreen.replace(/\r\n?/gu, "\n").split("\n");
|
|
3692
|
+
const viewportColumns = inferCodexVisibleViewportColumns(styledScreen);
|
|
3693
|
+
if (!viewportColumns)
|
|
3694
|
+
return undefined;
|
|
3695
|
+
let composerLine;
|
|
3696
|
+
for (let index = rows.length - 1; index >= 0; index -= 1) {
|
|
3697
|
+
const plain = stripTerminalEscapeSequences(rows[index]);
|
|
3698
|
+
if (CODEX_COMPOSER_MARKER.test(plain)) {
|
|
3699
|
+
composerLine = rows[index];
|
|
3700
|
+
break;
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
if (!composerLine)
|
|
3704
|
+
return undefined;
|
|
3705
|
+
const plain = stripTerminalEscapeSequences(composerLine);
|
|
3706
|
+
const exactCommand = plain.trimEnd().replace(/^[›»]\s?/u, "") ===
|
|
3707
|
+
expectedText;
|
|
3708
|
+
const spansViewport = Array.from(plain).length === viewportColumns &&
|
|
3709
|
+
plain.endsWith(" ");
|
|
3710
|
+
const hasBackground = [...composerLine.matchAll(/\x1b\[([0-9;]*)m/gu)]
|
|
3711
|
+
.some((match) => match[1].split(";").map(Number).includes(48));
|
|
3712
|
+
if (!exactCommand || !spansViewport || !hasBackground) {
|
|
3713
|
+
return undefined;
|
|
3714
|
+
}
|
|
3715
|
+
return {
|
|
3716
|
+
digest: createHash("sha256").update(composerLine).digest("hex")
|
|
3717
|
+
};
|
|
3718
|
+
}
|
|
3211
3719
|
function exactCodexComposerCapture(screen, expectedText) {
|
|
3212
3720
|
const lines = screen.replace(/\r\n?/gu, "\n").split("\n");
|
|
3213
3721
|
const expectedComparable = composerComparableText(expectedText);
|
|
@@ -3624,6 +4132,9 @@ export function captureTerminalInteractionRuntimeOffer(input) {
|
|
|
3624
4132
|
input.approvalBlocked === true) {
|
|
3625
4133
|
return undefined;
|
|
3626
4134
|
}
|
|
4135
|
+
if (exactCurrentModelControlSurface(input.agent, agentVersion, input.screen)) {
|
|
4136
|
+
return undefined;
|
|
4137
|
+
}
|
|
3627
4138
|
const coreOffer = captureTerminalInteraction({
|
|
3628
4139
|
subject,
|
|
3629
4140
|
agent: input.agent,
|
|
@@ -3651,6 +4162,21 @@ export function captureTerminalInteractionRuntimeOffer(input) {
|
|
|
3651
4162
|
nativeInspection: coreOffer.nativeInspection
|
|
3652
4163
|
};
|
|
3653
4164
|
}
|
|
4165
|
+
function exactCurrentModelControlSurface(agent, agentVersion, screen) {
|
|
4166
|
+
if (!agentVersion)
|
|
4167
|
+
return false;
|
|
4168
|
+
try {
|
|
4169
|
+
const capabilities = probeTerminalModelControl(agent, agentVersion);
|
|
4170
|
+
if (capabilities.status !== "supported")
|
|
4171
|
+
return false;
|
|
4172
|
+
const observation = observeTerminalModelControl(planTerminalModelControl(capabilities), screen);
|
|
4173
|
+
return observation.state !== "none" &&
|
|
4174
|
+
observation.state !== "ambiguous";
|
|
4175
|
+
}
|
|
4176
|
+
catch {
|
|
4177
|
+
return false;
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
3654
4180
|
function terminalInteractionTrustedWatchIdentity(terminalControl, runtime, evidence) {
|
|
3655
4181
|
if (runtime.interactionSubject?.kind !== "terminal_watch" ||
|
|
3656
4182
|
!evidence ||
|
|
@@ -3934,7 +4460,18 @@ async function dispatchTerminalInteractionAnswer(input) {
|
|
|
3934
4460
|
throw new TerminalInteractionInputNotStartedError("terminal interaction answer does not have a closed adapter action");
|
|
3935
4461
|
}
|
|
3936
4462
|
function statusFromInspection(adapter, terminalControl, inspection, options = {}) {
|
|
3937
|
-
const
|
|
4463
|
+
const exactModelSurface = options.screen !== undefined &&
|
|
4464
|
+
exactCurrentModelControlSurface(adapter.agent, options.runtime?.agentVersion, options.screen);
|
|
4465
|
+
const modelSurfaceReason = exactModelSurface
|
|
4466
|
+
? `exact ${adapter.displayName} native model-control surface is open`
|
|
4467
|
+
: undefined;
|
|
4468
|
+
const approval = exactModelSurface
|
|
4469
|
+
? {
|
|
4470
|
+
blocked: false,
|
|
4471
|
+
approvable: false,
|
|
4472
|
+
reason: modelSurfaceReason
|
|
4473
|
+
}
|
|
4474
|
+
: dispatchableApprovalInspection(adapter, inspection.approval);
|
|
3938
4475
|
const fingerprint = terminalApprovalFingerprint(adapter.agent, terminalControl, {
|
|
3939
4476
|
...inspection,
|
|
3940
4477
|
approval
|
|
@@ -3952,7 +4489,7 @@ function statusFromInspection(adapter, terminalControl, inspection, options = {}
|
|
|
3952
4489
|
}] : [];
|
|
3953
4490
|
})
|
|
3954
4491
|
: [];
|
|
3955
|
-
const interaction = options.screen === undefined
|
|
4492
|
+
const interaction = options.screen === undefined || exactModelSurface
|
|
3956
4493
|
? undefined
|
|
3957
4494
|
: captureTerminalInteractionRuntimeOffer({
|
|
3958
4495
|
agent: adapter.agent,
|
|
@@ -3968,10 +4505,10 @@ function statusFromInspection(adapter, terminalControl, inspection, options = {}
|
|
|
3968
4505
|
agent: adapter.agent,
|
|
3969
4506
|
reachable: true,
|
|
3970
4507
|
capabilities: adapter.capabilities,
|
|
3971
|
-
activity_state: inspection.activity.state,
|
|
3972
|
-
activity_reason: inspection.activity.reason,
|
|
3973
|
-
screen_state: inspection.activity.state,
|
|
3974
|
-
screen_reason: inspection.activity.reason,
|
|
4508
|
+
activity_state: exactModelSurface ? "unknown" : inspection.activity.state,
|
|
4509
|
+
activity_reason: modelSurfaceReason ?? inspection.activity.reason,
|
|
4510
|
+
screen_state: exactModelSurface ? "unknown" : inspection.activity.state,
|
|
4511
|
+
screen_reason: modelSurfaceReason ?? inspection.activity.reason,
|
|
3975
4512
|
approval_state: {
|
|
3976
4513
|
scanned: true,
|
|
3977
4514
|
blocked: approval.blocked,
|
|
@@ -4116,4 +4653,33 @@ function unsupportedScreenStatus(adapter, terminalControl) {
|
|
|
4116
4653
|
capability_limitation: reason
|
|
4117
4654
|
};
|
|
4118
4655
|
}
|
|
4656
|
+
function assertTerminalModelControlPlan(adapter, terminalControl, agentVersion, plan) {
|
|
4657
|
+
if (!terminalControl.capabilities.includes("send_keys") ||
|
|
4658
|
+
!terminalControl.capabilities.includes("screen_status")) {
|
|
4659
|
+
throw new Error("terminal model control requires exact screen-status and key-delivery capabilities");
|
|
4660
|
+
}
|
|
4661
|
+
const capabilities = adapter.probeModelControl?.(agentVersion);
|
|
4662
|
+
if (capabilities?.status !== "supported" ||
|
|
4663
|
+
capabilities.modelSelection !== true ||
|
|
4664
|
+
capabilities.reasoningEffortSelection !== true ||
|
|
4665
|
+
capabilities.agentVersion !== agentVersion) {
|
|
4666
|
+
throw new Error(capabilities?.reason ??
|
|
4667
|
+
`${adapter.displayName} has no verified model-control profile`);
|
|
4668
|
+
}
|
|
4669
|
+
const expected = adapter.planModelControl?.(capabilities);
|
|
4670
|
+
if (!expected ||
|
|
4671
|
+
expected.command !== "/model" ||
|
|
4672
|
+
expected.requiresIdle !== true ||
|
|
4673
|
+
expected.requiresExactEmptyComposer !== true ||
|
|
4674
|
+
JSON.stringify(expected) !== JSON.stringify(plan)) {
|
|
4675
|
+
throw new Error("the terminal adapter did not produce the exact current model-control plan");
|
|
4676
|
+
}
|
|
4677
|
+
if (adapter.agent === "codex"
|
|
4678
|
+
? plan.behaviorProfile !== "codex-model-control-0.154.0" ||
|
|
4679
|
+
plan.scope !== "current_and_new_sessions"
|
|
4680
|
+
: plan.behaviorProfile !== "claude-model-control-2.1.266" ||
|
|
4681
|
+
plan.scope !== "current_session") {
|
|
4682
|
+
throw new Error("refusing an unprofiled terminal model-control plan");
|
|
4683
|
+
}
|
|
4684
|
+
}
|
|
4119
4685
|
//# sourceMappingURL=terminal-agent-bridge.js.map
|