@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.
Files changed (54) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +17 -4
  3. package/dist/src/claude-terminal-agent-adapter.js +4 -0
  4. package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
  5. package/dist/src/cli-core.js +9 -1
  6. package/dist/src/cli-core.js.map +1 -1
  7. package/dist/src/codex-terminal-agent-adapter.js +4 -0
  8. package/dist/src/codex-terminal-agent-adapter.js.map +1 -1
  9. package/dist/src/herdr-terminal-control-provider.js +6 -1
  10. package/dist/src/herdr-terminal-control-provider.js.map +1 -1
  11. package/dist/src/host-adapter.d.ts +1 -1
  12. package/dist/src/host-adapter.js +1 -1
  13. package/dist/src/host-bridge-tools.js +2 -2
  14. package/dist/src/managed-session.js +2 -18
  15. package/dist/src/managed-session.js.map +1 -1
  16. package/dist/src/native-thread-lifecycle-cli-adapter.d.ts +13 -0
  17. package/dist/src/native-thread-lifecycle-cli-adapter.js +578 -2
  18. package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -1
  19. package/dist/src/openclaw-plugin-command-adapter.js +325 -35
  20. package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
  21. package/dist/src/openclaw-plugin-helpers.d.ts +27 -0
  22. package/dist/src/openclaw-plugin-helpers.js +682 -2
  23. package/dist/src/openclaw-plugin-helpers.js.map +1 -1
  24. package/dist/src/openclaw-plugin-schemas.d.ts +53 -0
  25. package/dist/src/openclaw-plugin-schemas.js +53 -0
  26. package/dist/src/openclaw-plugin-schemas.js.map +1 -1
  27. package/dist/src/terminal-action-projection.d.ts +83 -1
  28. package/dist/src/terminal-action-projection.js +174 -0
  29. package/dist/src/terminal-action-projection.js.map +1 -1
  30. package/dist/src/terminal-agent-adapter.d.ts +7 -0
  31. package/dist/src/terminal-agent-adapter.js +8 -0
  32. package/dist/src/terminal-agent-adapter.js.map +1 -1
  33. package/dist/src/terminal-agent-bridge.d.ts +29 -0
  34. package/dist/src/terminal-agent-bridge.js +580 -14
  35. package/dist/src/terminal-agent-bridge.js.map +1 -1
  36. package/dist/src/terminal-control-ref.d.ts +23 -0
  37. package/dist/src/terminal-control-ref.js +32 -0
  38. package/dist/src/terminal-control-ref.js.map +1 -1
  39. package/dist/src/terminal-list-cli-adapter.js +324 -16
  40. package/dist/src/terminal-list-cli-adapter.js.map +1 -1
  41. package/dist/src/terminal-list-renderer.d.ts +1 -0
  42. package/dist/src/terminal-list-renderer.js +87 -3
  43. package/dist/src/terminal-list-renderer.js.map +1 -1
  44. package/dist/src/terminal-model-control.d.ts +312 -0
  45. package/dist/src/terminal-model-control.js +1595 -0
  46. package/dist/src/terminal-model-control.js.map +1 -0
  47. package/dist/src/terminal-runtime-cli-adapter.d.ts +2 -0
  48. package/dist/src/terminal-runtime-cli-adapter.js +59 -17
  49. package/dist/src/terminal-runtime-cli-adapter.js.map +1 -1
  50. package/docs/quickstart-herdr.md +1 -1
  51. package/docs/quickstart-tmux.md +27 -2
  52. package/openclaw.plugin.json +13 -1
  53. package/package.json +1 -1
  54. package/templates/openclaw-skills/agent-knock-knock/SKILL.md +54 -11
@@ -239,6 +239,43 @@ function parseAkkLifecycleCommand(action, rest) {
239
239
  }
240
240
  return { action: "list-resumable-threads", terminalId };
241
241
  }
242
+ if (action === "models" || action === "model-options") {
243
+ const usage = "Usage: /akk models <exact-terminal-id>";
244
+ const { token: terminalId, rest: extra } = takeRequiredToken(rest, usage);
245
+ assertExactTerminalId(terminalId, usage);
246
+ if (extra.trim()) {
247
+ throw new Error(usage);
248
+ }
249
+ return { action: "model-options", terminalId };
250
+ }
251
+ if (action === "repair-model-control") {
252
+ const usage = "Usage: /akk repair-model-control <exact-terminal-id>";
253
+ const { token: terminalId, rest: extra } = takeRequiredToken(rest, usage);
254
+ assertExactTerminalId(terminalId, usage);
255
+ if (extra.trim()) {
256
+ throw new Error(usage);
257
+ }
258
+ return { action: "repair-model-control", terminalId };
259
+ }
260
+ if (action === "set-model") {
261
+ const usage = "Usage: /akk set-model <exact-terminal-id> <advertised-model-id> " +
262
+ "<advertised-reasoning-effort>";
263
+ const { token: terminalId, rest: modelInput } = takeRequiredToken(rest, usage);
264
+ assertExactTerminalId(terminalId, usage);
265
+ const { token: model, rest: effortInput } = takeRequiredToken(modelInput, usage);
266
+ const { token: reasoningEffort, rest: extra } = takeRequiredToken(effortInput, usage);
267
+ if (extra.trim() ||
268
+ !isAkkModelSemanticIdentifier(model, 160) ||
269
+ !/^[a-z][a-z0-9_-]{0,63}$/u.test(reasoningEffort)) {
270
+ throw new Error(usage);
271
+ }
272
+ return {
273
+ action: "set-model",
274
+ terminalId,
275
+ model,
276
+ reasoningEffort
277
+ };
278
+ }
242
279
  if (action === "new-thread" || action === "clear-thread") {
243
280
  const usage = `Usage: /akk ${action} <exact-terminal-id>`;
244
281
  const { token: terminalId, rest: extra } = takeRequiredToken(rest, usage);
@@ -378,6 +415,9 @@ export function akkUsageText() {
378
415
  "/akk watch <exact-terminal-id>",
379
416
  "/akk unwatch <watch-id>",
380
417
  "/akk threads <exact-terminal-id>",
418
+ "/akk models <exact-terminal-id>",
419
+ "/akk repair-model-control <exact-terminal-id>",
420
+ "/akk set-model <exact-terminal-id> <advertised-model-id> <advertised-reasoning-effort>",
381
421
  "/akk new-thread <exact-terminal-id>",
382
422
  "/akk clear-thread <exact-terminal-id>",
383
423
  "/akk resume-thread <exact-terminal-id> [uuid|previous|number|@short-id]",
@@ -385,7 +425,10 @@ export function akkUsageText() {
385
425
  "/akk status [turn-selector|terminal-watch-id]",
386
426
  "/akk respond <turn-selector>: <answer>",
387
427
  "/akk approve <turn-selector> [approve_once|reject]",
388
- "/akk cancel <turn-selector>"
428
+ "/akk cancel <turn-selector>",
429
+ "Model control targets one exact physical pane; Codex rollout attribution and identify-foreground are not prerequisites.",
430
+ "Repair-model-control is advertised only for an exact stale Codex /model Composer surface or open native picker; it clears no other draft, never presses Enter, and never submits or selects anything.",
431
+ "Models normally requires an idle, empty, prompt-free UI with no active Turn; for an exactly advertised Codex 0.154 /model residue it may continue that residue into read-only catalog discovery. Successful discovery consumes any residual-entry authority, restores the exact empty UI, and issues a fresh ordinary one-shot set-model offer; set-model accepts only that offer. If the result is uncertain, inspect the pane and do not retry automatically."
389
432
  ].join("\n");
390
433
  }
391
434
  export function formatAkkListCommandResult(result) {
@@ -485,6 +528,500 @@ export function formatAkkListCommandResult(result) {
485
528
  : [])
486
529
  ].join("\n");
487
530
  }
531
+ /**
532
+ * Keep every structured controller-Host List result below its model-output
533
+ * budget without weakening the private action-authority path.
534
+ *
535
+ * The CLI remains the complete operator/debug contract. The controller receives a
536
+ * terminal-first projection: current resource identities and state, action
537
+ * names, and only the dynamic semantic inputs needed to invoke those actions.
538
+ * Static explanations live in the bundled agent-knock-knock skill.
539
+ */
540
+ export function compactAkkListModelProjection(value) {
541
+ const result = recordValue(value) ?? {};
542
+ const actionContracts = recordValue(result.action_contracts);
543
+ const diagnostics = compactAkkListDiagnostics(result);
544
+ const recovery = compactAkkListControlState(result.recovery);
545
+ return {
546
+ projection: {
547
+ schema: "agent-knock-knock/host-list-compact",
548
+ version: 1,
549
+ skill: "agent-knock-knock",
550
+ ...(finiteNumber(actionContracts?.version) !== undefined
551
+ ? { action_contract_version: finiteNumber(actionContracts?.version) }
552
+ : {})
553
+ },
554
+ terminals: arrayValue(result.terminals).map(compactAkkListTerminal),
555
+ terminal_watches: arrayValue(result.terminal_watches)
556
+ .map(compactAkkListWatch),
557
+ unavailable_managed_turns: arrayValue(result.unavailable_managed_turns)
558
+ .map((turn) => compactAkkListTurn(turn, true)),
559
+ ...(recovery ? { recovery } : {}),
560
+ ...(Object.keys(diagnostics).length > 0 ? { diagnostics } : {})
561
+ };
562
+ }
563
+ function compactAkkListTerminal(terminal) {
564
+ const output = compactAkkListScalars(terminal, [
565
+ "id",
566
+ "short_ref",
567
+ "source",
568
+ "agent",
569
+ "agent_version",
570
+ "pid",
571
+ "cwd",
572
+ "process_state",
573
+ "screen_state",
574
+ "activity_state",
575
+ "durable_activity_state",
576
+ "native_identity_state",
577
+ "native_agent_session_id",
578
+ "management_state",
579
+ "handoff_state",
580
+ "confidence"
581
+ ]);
582
+ const workspace = nonEmptyString(terminal.workspace);
583
+ if (workspace && workspace !== nonEmptyString(terminal.cwd)) {
584
+ output.workspace = workspace;
585
+ }
586
+ const command = nonEmptyString(terminal.command);
587
+ if (command)
588
+ output.command = truncateText(command, 160);
589
+ compactAkkListNested(output, "terminal_control", terminal.terminal_control, ["kind", "target"]);
590
+ compactAkkListNested(output, "approval_state", terminal.approval_state, [
591
+ "scanned", "blocked", "approvable", "state", "kind", "decision_mode"
592
+ ]);
593
+ const features = compactAkkListFeatures(terminal);
594
+ if (Object.keys(features).length > 0)
595
+ output.features = features;
596
+ const managed = compactAkkListManaged(terminal.managed);
597
+ if (managed)
598
+ output.managed = managed;
599
+ const managementConflict = compactAkkListControlState(terminal.management_conflict);
600
+ if (managementConflict)
601
+ output.management_conflict = managementConflict;
602
+ const managementUnavailable = recordValue(terminal.management_unavailable);
603
+ if (managementUnavailable)
604
+ output.management_unavailable = true;
605
+ const handoffDecision = compactAkkListHandoffDecision(terminal.handoff_decision);
606
+ if (handoffDecision)
607
+ output.handoff_decision = handoffDecision;
608
+ const blockingTurns = arrayValue(terminal.blocking_turns)
609
+ .map(compactAkkListBlockingTurn);
610
+ if (blockingTurns.length > 0)
611
+ output.blocking_turns = blockingTurns;
612
+ const managedRecord = recordValue(terminal.managed);
613
+ const currentTurn = recordValue(managedRecord?.current_turn);
614
+ Object.assign(output, compactAkkListActions(terminal.available_actions, {
615
+ terminal_id: terminal.id,
616
+ conversation_id: terminal.id,
617
+ session_id: managedRecord?.session_id,
618
+ turn_id: currentTurn?.turn_id ?? currentTurn?.conversation_id ??
619
+ currentTurn?.id
620
+ }));
621
+ addAkkListWarningFlags(output, terminal);
622
+ return output;
623
+ }
624
+ function compactAkkListFeatures(terminal) {
625
+ const output = {};
626
+ const lifecycle = recordValue(terminal.native_thread_lifecycle);
627
+ const inspection = recordValue(terminal.native_inspection);
628
+ const modelControl = recordValue(terminal.model_control);
629
+ if (typeof lifecycle?.status === "string") {
630
+ output.native_threads = lifecycle.status;
631
+ }
632
+ if (typeof inspection?.status === "string") {
633
+ output.native_inspection = inspection.status;
634
+ }
635
+ if (typeof modelControl?.status === "string") {
636
+ const model = compactAkkListScalars(modelControl, ["status", "scope"]);
637
+ output.model_control = Object.keys(model).length === 1
638
+ ? model.status
639
+ : model;
640
+ }
641
+ return output;
642
+ }
643
+ function compactAkkListManaged(value) {
644
+ const managed = recordValue(value);
645
+ if (!managed)
646
+ return undefined;
647
+ const output = compactAkkListScalars(managed, [
648
+ "session_id",
649
+ "session_short_ref",
650
+ "native_thread_id",
651
+ "binding_status",
652
+ "turn_count",
653
+ "hidden_turn_count",
654
+ "session_count"
655
+ ]);
656
+ const currentTurn = recordValue(managed.current_turn);
657
+ const recentTurn = recordValue(managed.recent_turn);
658
+ output.current_turn = currentTurn ? compactAkkListTurn(currentTurn) : null;
659
+ output.recent_turn = recentTurn ? compactAkkListTurn(recentTurn) : null;
660
+ const history = arrayValue(managed.history)
661
+ .map((turn) => compactAkkListTurn(turn));
662
+ if (history.length > 0)
663
+ output.history = history;
664
+ return output;
665
+ }
666
+ function compactAkkListTurn(turn, standalone = false) {
667
+ const output = compactAkkListScalars(turn, [
668
+ "short_ref",
669
+ "status",
670
+ "lifecycle_state",
671
+ "session_id",
672
+ "turn_id",
673
+ "updated_at",
674
+ "response_rounds_used"
675
+ ]);
676
+ const turnId = nonEmptyString(turn.turn_id ?? turn.conversation_id ?? turn.id);
677
+ if (turnId)
678
+ output.turn_id = turnId;
679
+ if (standalone) {
680
+ Object.assign(output, compactAkkListScalars(turn, [
681
+ "source", "agent", "workspace", "created_at"
682
+ ]));
683
+ }
684
+ compactAkkListNested(output, "approval_state", turn.approval_state, [
685
+ "scanned", "blocked", "approvable", "state", "kind", "decision_mode"
686
+ ]);
687
+ const interaction = compactAkkListInteraction(turn.interaction_state);
688
+ if (interaction)
689
+ output.interaction_state = interaction;
690
+ const terminalAvailability = compactAkkListControlState(turn.terminal_availability);
691
+ if (terminalAvailability) {
692
+ output.terminal_availability = terminalAvailability;
693
+ }
694
+ else if (typeof turn.terminal_availability === "string" ||
695
+ typeof turn.terminal_availability === "boolean") {
696
+ output.terminal_availability = turn.terminal_availability;
697
+ }
698
+ Object.assign(output, compactAkkListActions(turn.available_actions, {
699
+ session_id: turn.session_id,
700
+ turn_id: turnId,
701
+ conversation_id: turnId
702
+ }));
703
+ addAkkListWarningFlags(output, turn);
704
+ return output;
705
+ }
706
+ function compactAkkListWatch(watch) {
707
+ const output = compactAkkListScalars(watch, [
708
+ "watch_id",
709
+ "short_ref",
710
+ "source",
711
+ "watch_mode",
712
+ "confidence",
713
+ "interaction_policy",
714
+ "agent",
715
+ "terminal_id",
716
+ "native_thread_id",
717
+ "workspace",
718
+ "status",
719
+ "activity_state",
720
+ "created_at",
721
+ "deadline_at",
722
+ "updated_at",
723
+ "last_activity_at"
724
+ ]);
725
+ compactAkkListNested(output, "capabilities", watch.capabilities, [
726
+ "interaction_notify", "interaction_respond"
727
+ ]);
728
+ compactAkkListNested(output, "callback", watch.callback, [
729
+ "pending", "delivered", "failed", "superseded", "last_error_code"
730
+ ]);
731
+ const interaction = compactAkkListInteraction(watch.interaction_state);
732
+ if (interaction)
733
+ output.interaction_state = interaction;
734
+ const settlement = recordValue(watch.settlement);
735
+ if (settlement) {
736
+ const compactSettlement = compactAkkListScalars(settlement, [
737
+ "kind", "observed_at", "reason_code", "completion_id",
738
+ "completion_timestamp"
739
+ ]);
740
+ if (Object.keys(compactSettlement).length > 0) {
741
+ output.settlement = compactSettlement;
742
+ }
743
+ }
744
+ Object.assign(output, compactAkkListActions(watch.available_actions, {
745
+ terminal_id: watch.terminal_id,
746
+ watch_id: watch.watch_id
747
+ }));
748
+ addAkkListWarningFlags(output, watch);
749
+ return output;
750
+ }
751
+ function compactAkkListInteraction(value) {
752
+ const interaction = recordValue(value);
753
+ if (!interaction)
754
+ return undefined;
755
+ const output = compactAkkListScalars(interaction, [
756
+ "state", "response_kind", "current_step", "total_steps",
757
+ "response_authority"
758
+ ]);
759
+ compactAkkListNested(output, "capabilities", interaction.capabilities, [
760
+ "respond", "single_select", "multi_select", "free_text", "confirm"
761
+ ]);
762
+ return Object.keys(output).length > 0 ? output : undefined;
763
+ }
764
+ function compactAkkListActions(value, parentIds = {}) {
765
+ const actions = recordValue(value);
766
+ if (!actions)
767
+ return {};
768
+ const availableActions = {};
769
+ const actionInputs = {};
770
+ for (const [name, actionValue] of Object.entries(actions)) {
771
+ const action = recordValue(actionValue);
772
+ if (!action)
773
+ continue;
774
+ availableActions[name] = true;
775
+ const input = compactAkkListActionInput(name, action, parentIds);
776
+ if (Object.keys(input).length > 0)
777
+ actionInputs[name] = input;
778
+ }
779
+ return {
780
+ ...(Object.keys(availableActions).length > 0
781
+ ? { available_actions: availableActions }
782
+ : {}),
783
+ ...(Object.keys(actionInputs).length > 0
784
+ ? { action_inputs: actionInputs }
785
+ : {})
786
+ };
787
+ }
788
+ function compactAkkListActionInput(name, action, parentIds = {}) {
789
+ const output = {};
790
+ const tool = nonEmptyString(action.tool);
791
+ const expectedTool = name === "retry_submission"
792
+ ? "agent_knock_knock_send"
793
+ : `agent_knock_knock_${name}`;
794
+ if (tool && tool !== expectedTool)
795
+ output.tool = tool;
796
+ const args = compactAkkListActionArguments(action.arguments, name, parentIds);
797
+ if (Object.keys(args).length > 0)
798
+ output.arguments = args;
799
+ const missingRequired = stringArrayValue(action.missing_required)
800
+ .filter((field) => !isAkkModelFacingPrivateAuthorityField(field));
801
+ if (missingRequired.length > 0) {
802
+ output.missing_required = missingRequired;
803
+ }
804
+ for (const key of ["scope", "decision_mode"]) {
805
+ const item = action[key];
806
+ if (typeof item === "string" ||
807
+ typeof item === "boolean" ||
808
+ typeof item === "number")
809
+ output[key] = item;
810
+ }
811
+ for (const key of ["decisions", "choices"]) {
812
+ const values = stringArrayValue(action[key]);
813
+ if (values.length > 0)
814
+ output[key] = values;
815
+ }
816
+ return output;
817
+ }
818
+ function compactAkkListActionArguments(value, actionName, parentIds) {
819
+ const args = recordValue(value);
820
+ if (!args)
821
+ return {};
822
+ const output = {};
823
+ for (const [key, item] of Object.entries(args)) {
824
+ if (isAkkModelFacingPrivateAuthorityField(key) ||
825
+ ["request", "text", "body", "description", "instruction", "use"]
826
+ .includes(key))
827
+ continue;
828
+ const compact = compactAkkListSemanticValue(item, 0);
829
+ if (compact !== undefined)
830
+ output[key] = compact;
831
+ }
832
+ const selector = nonEmptyString(output.selector);
833
+ if (actionName === "send" &&
834
+ selector &&
835
+ /^terminal:v[0-9]+:\S+$/u.test(selector)) {
836
+ delete output.selector;
837
+ output.terminal_id = selector;
838
+ }
839
+ const conversationId = nonEmptyString(output.conversation_id);
840
+ if (actionName === "approve" &&
841
+ conversationId &&
842
+ /^terminal:v[0-9]+:\S+$/u.test(conversationId)) {
843
+ delete output.conversation_id;
844
+ output.terminal_id = conversationId;
845
+ }
846
+ for (const key of [
847
+ "terminal_id", "session_id", "turn_id", "conversation_id", "watch_id"
848
+ ]) {
849
+ if (output[key] !== undefined && output[key] === parentIds[key]) {
850
+ delete output[key];
851
+ }
852
+ }
853
+ return output;
854
+ }
855
+ function compactAkkListSemanticValue(value, depth) {
856
+ if (value === null ||
857
+ typeof value === "string" ||
858
+ typeof value === "boolean" ||
859
+ (typeof value === "number" && Number.isFinite(value)))
860
+ return value;
861
+ if (depth >= 3)
862
+ return undefined;
863
+ if (Array.isArray(value)) {
864
+ return value.slice(0, 32).flatMap((item) => {
865
+ const compact = compactAkkListSemanticValue(item, depth + 1);
866
+ return compact === undefined ? [] : [compact];
867
+ });
868
+ }
869
+ const record = recordValue(value);
870
+ if (!record)
871
+ return undefined;
872
+ const output = {};
873
+ for (const [key, item] of Object.entries(record)) {
874
+ if (isAkkModelFacingPrivateAuthorityField(key))
875
+ continue;
876
+ const compact = compactAkkListSemanticValue(item, depth + 1);
877
+ if (compact !== undefined)
878
+ output[key] = compact;
879
+ }
880
+ return output;
881
+ }
882
+ function compactAkkListHandoffDecision(value) {
883
+ const decision = recordValue(value);
884
+ if (!decision)
885
+ return undefined;
886
+ const output = compactAkkListScalars(decision, [
887
+ "kind", "source_session_id", "source_turn_id"
888
+ ]);
889
+ const choices = recordValue(decision.choices);
890
+ if (choices) {
891
+ const compactChoices = {};
892
+ for (const [name, choiceValue] of Object.entries(choices)) {
893
+ const choice = recordValue(choiceValue);
894
+ if (!choice)
895
+ continue;
896
+ const action = compactAkkListNamedAction(choice.action);
897
+ compactChoices[name] = action ? { action } : true;
898
+ }
899
+ if (Object.keys(compactChoices).length > 0)
900
+ output.choices = compactChoices;
901
+ }
902
+ return Object.keys(output).length > 0 ? output : undefined;
903
+ }
904
+ function compactAkkListBlockingTurn(value) {
905
+ const output = compactAkkListScalars(value, [
906
+ "session_id", "turn_id", "status"
907
+ ]);
908
+ const recoveryAction = compactAkkListNamedAction(value.recovery_action);
909
+ if (recoveryAction)
910
+ output.recovery_action = recoveryAction;
911
+ return output;
912
+ }
913
+ function compactAkkListNamedAction(value) {
914
+ const action = recordValue(value);
915
+ if (!action)
916
+ return undefined;
917
+ const tool = nonEmptyString(action.tool);
918
+ const name = tool?.replace(/^agent_knock_knock_/u, "") ?? "action";
919
+ const input = compactAkkListActionInput(name, action);
920
+ return {
921
+ name,
922
+ ...(Object.keys(input).length > 0 ? input : {})
923
+ };
924
+ }
925
+ function compactAkkListControlState(value) {
926
+ const record = recordValue(value);
927
+ if (!record)
928
+ return undefined;
929
+ const output = {};
930
+ for (const [key, item] of Object.entries(record)) {
931
+ if (isAkkModelFacingPrivateAuthorityField(key) ||
932
+ /(?:reason|recovery|instruction|description|evidence|path)$/iu.test(key))
933
+ continue;
934
+ const keep = /(?:^|_)(?:id|state|status|kind|mode|scope|code|count|owner)$/u
935
+ .test(key) || typeof item === "boolean" || typeof item === "number";
936
+ if (!keep)
937
+ continue;
938
+ const compact = compactAkkListSemanticValue(item, 0);
939
+ if (compact !== undefined)
940
+ output[key] = compact;
941
+ }
942
+ return Object.keys(output).length > 0 ? output : undefined;
943
+ }
944
+ function compactAkkListDiagnostics(result) {
945
+ const output = {};
946
+ compactAkkListNested(output, "store", result.store, [
947
+ "status", "readable", "writable", "upgradeable", "format_version",
948
+ "writer_protocol"
949
+ ]);
950
+ const reconciliation = recordValue(result.reconciliation);
951
+ if (reconciliation) {
952
+ const compact = compactAkkListDiagnosticRecord(reconciliation);
953
+ if (Object.keys(compact).length > 0)
954
+ output.reconciliation = compact;
955
+ }
956
+ const scan = recordValue(result.terminal_scan);
957
+ if (scan) {
958
+ const compact = compactAkkListDiagnosticRecord(scan);
959
+ if (Array.isArray(scan.agents)) {
960
+ compact.agents = scan.agents.filter((item) => typeof item === "string");
961
+ }
962
+ if (scan.diagnostics !== undefined)
963
+ compact.provider_diagnostics = true;
964
+ if (Object.keys(compact).length > 0)
965
+ output.terminal_scan = compact;
966
+ }
967
+ return output;
968
+ }
969
+ function compactAkkListDiagnosticRecord(value) {
970
+ const output = {};
971
+ for (const [key, item] of Object.entries(value)) {
972
+ if (key === "reason" ||
973
+ key === "diagnostics" ||
974
+ /(?:^|_)(?:path|dir)$/u.test(key))
975
+ continue;
976
+ if (typeof item === "boolean" ||
977
+ (typeof item === "number" && Number.isFinite(item)) ||
978
+ (typeof item === "string" && key !== "error"))
979
+ output[key] = item;
980
+ if (key === "error" && typeof item === "string") {
981
+ output.error = truncateText(item, 300);
982
+ }
983
+ if (Array.isArray(item) && key === "errors") {
984
+ output.error_count = item.length;
985
+ }
986
+ }
987
+ if (nonEmptyString(value.reason) &&
988
+ !["ok", "healthy", "compatible", "disabled"]
989
+ .includes(nonEmptyString(value.status) ?? "")) {
990
+ output.message = truncateText(value.reason, 300);
991
+ }
992
+ return output;
993
+ }
994
+ function compactAkkListScalars(value, keys) {
995
+ const output = {};
996
+ for (const key of keys) {
997
+ const item = value[key];
998
+ if (item === null ||
999
+ typeof item === "string" ||
1000
+ typeof item === "boolean" ||
1001
+ (typeof item === "number" && Number.isFinite(item)))
1002
+ output[key] = item;
1003
+ }
1004
+ return output;
1005
+ }
1006
+ function compactAkkListNested(output, key, value, keys) {
1007
+ const record = recordValue(value);
1008
+ if (!record)
1009
+ return;
1010
+ const compact = compactAkkListScalars(record, keys);
1011
+ if (Object.keys(compact).length > 0)
1012
+ output[key] = compact;
1013
+ }
1014
+ function addAkkListWarningFlags(output, value) {
1015
+ const compatibilityWarnings = [
1016
+ nonEmptyString(value.compatibility_warning),
1017
+ ...stringArrayValue(value.compatibility_warnings)
1018
+ ].filter((item) => Boolean(item));
1019
+ if (compatibilityWarnings.length > 0)
1020
+ output.compatibility_warning = true;
1021
+ const warnings = stringArrayValue(value.warnings);
1022
+ if (warnings.length > 0)
1023
+ output.warning_count = warnings.length;
1024
+ }
488
1025
  export function formatAkkWatchCommandResult(result) {
489
1026
  const watch = terminalWatchRecord(result);
490
1027
  return [
@@ -720,6 +1257,108 @@ export function formatAkkThreadsCommandResult(result) {
720
1257
  "Listing or switching native threads does not create an AKK Turn."
721
1258
  ].join("\n");
722
1259
  }
1260
+ export function formatAkkModelOptionsCommandResult(result) {
1261
+ const terminalId = nonEmptyString(result.terminal_id) ?? "unknown";
1262
+ const scope = nonEmptyString(result.scope) ?? "unknown";
1263
+ const current = recordValue(result.current) ?? {};
1264
+ const models = arrayValue(result.models);
1265
+ const modelLines = models.slice(0, 50).map((entry) => {
1266
+ const id = nonEmptyString(entry.id) ?? "unknown";
1267
+ const label = nonEmptyString(entry.label);
1268
+ const efforts = stringArrayValue(entry.reasoning_efforts);
1269
+ return `- ${id}${label && label !== id ? ` (${label})` : ""}` +
1270
+ `${efforts.length > 0 ? ` | reasoning: ${efforts.join(", ")}` : ""}`;
1271
+ });
1272
+ return [
1273
+ `AKK model options (${models.length} models):`,
1274
+ `terminal: ${terminalId}`,
1275
+ `agent: ${nonEmptyString(result.agent) ?? "unknown"}`,
1276
+ `scope: ${scope}`,
1277
+ `current model: ${nonEmptyString(current.model) ?? "unknown"}`,
1278
+ `current reasoning: ${nonEmptyString(current.reasoning_effort) ?? "unobserved"}`,
1279
+ ...compatibilityWarningLines(result),
1280
+ ...(modelLines.length > 0 ? ["models:", ...modelLines] : ["models: none"]),
1281
+ scope === "current_and_new_sessions"
1282
+ ? "Codex applies this selection to the current session and persists the selected model and ordinary efforts (including max) for new sessions; ultra applies only now and uses a native non-ultra future fallback."
1283
+ : "Claude Code applies this selection only to the current session; new-session defaults are unchanged.",
1284
+ `next: /akk set-model ${terminalId} <advertised-model-id> <advertised-reasoning-effort>`,
1285
+ "This successful catalog result has restored an exact empty Composer and carries a fresh ordinary private offer for one set-model attempt in the current controller. Residual-entry authority was consumed by model-options and is never reusable by set-model. A Codex rollout and identify-foreground are not prerequisites. Refresh /akk models after any terminal change or failed attempt."
1286
+ ].join("\n");
1287
+ }
1288
+ export function formatAkkSetModelCommandResult(result) {
1289
+ const outcome = nonEmptyString(result.outcome) ?? "uncertain";
1290
+ const requested = recordValue(result.requested) ?? {};
1291
+ const effective = recordValue(result.effective) ?? {};
1292
+ const newSessionDefaults = recordValue(result.new_session_defaults) ?? {};
1293
+ const uncertain = outcome === "uncertain" || result.do_not_retry === true;
1294
+ const defaultsChanged = typeof result.defaults_changed === "boolean"
1295
+ ? result.defaults_changed ? "yes" : "no"
1296
+ : "unknown";
1297
+ const requestedUltra = requested.reasoning_effort === "ultra";
1298
+ return [
1299
+ outcome === "changed"
1300
+ ? "AKK changed and verified the coding-agent model."
1301
+ : outcome === "already_effective"
1302
+ ? "AKK verified that the requested model was already effective."
1303
+ : "AKK could not prove the complete model-change postcondition.",
1304
+ `terminal: ${nonEmptyString(result.terminal_id) ?? "unknown"}`,
1305
+ `scope: ${nonEmptyString(result.scope) ?? "unknown"}`,
1306
+ `requested model: ${nonEmptyString(requested.model) ?? "unknown"}`,
1307
+ `requested reasoning: ${nonEmptyString(requested.reasoning_effort) ?? "unchanged/default"}`,
1308
+ `effective model: ${nonEmptyString(effective.model) ?? "unproven"}`,
1309
+ `effective reasoning: ${nonEmptyString(effective.reasoning_effort) ?? "unproven"}`,
1310
+ `new-session default changed: ${defaultsChanged}`,
1311
+ ...(nonEmptyString(newSessionDefaults.model)
1312
+ ? [`new-session default model: ${nonEmptyString(newSessionDefaults.model)}`]
1313
+ : []),
1314
+ ...(nonEmptyString(newSessionDefaults.reasoning_effort)
1315
+ ? [`new-session default reasoning: ${nonEmptyString(newSessionDefaults.reasoning_effort)}`]
1316
+ : requestedUltra && result.scope === "current_and_new_sessions" && outcome === "changed"
1317
+ ? ["new-session default reasoning: native non-ultra fallback (exact value is not exposed by this Codex TUI)"]
1318
+ : []),
1319
+ ...compatibilityWarningLines(result),
1320
+ ...(nonEmptyString(result.reason)
1321
+ ? [
1322
+ `reason: ${sanitizeAkkModelFacingDiagnosticText(nonEmptyString(result.reason) ?? "")}`
1323
+ ]
1324
+ : []),
1325
+ uncertain
1326
+ ? "Next: do not retry automatically; inspect the shared pane, then refresh /akk models before any deliberate new attempt."
1327
+ : "No task, approval, questionnaire response, or AKK Turn was created."
1328
+ ].join("\n");
1329
+ }
1330
+ export function formatAkkRepairModelControlCommandResult(result) {
1331
+ const outcome = nonEmptyString(result.outcome) ?? "uncertain";
1332
+ const repaired = outcome === "repaired";
1333
+ const uncertain = outcome === "uncertain" || result.do_not_retry === true;
1334
+ return [
1335
+ repaired
1336
+ ? "AKK cleared and verified the exact stale Codex /model surface."
1337
+ : "AKK could not prove the model-control repair postcondition.",
1338
+ `terminal: ${nonEmptyString(result.terminal_id) ?? "unknown"}`,
1339
+ `composer postcondition: ${nonEmptyString(result.composer_postcondition) ?? "unproven"}`,
1340
+ `terminal input attempted: ${result.terminal_input_attempted === true ? "yes" : "no"}`,
1341
+ ...compatibilityWarningLines(result),
1342
+ ...(nonEmptyString(result.reason)
1343
+ ? [
1344
+ `reason: ${sanitizeAkkModelFacingDiagnosticText(nonEmptyString(result.reason) ?? "")}`
1345
+ ]
1346
+ : []),
1347
+ uncertain
1348
+ ? "Next: do not retry automatically; inspect the shared pane and refresh /akk list."
1349
+ : "Next: refresh /akk list, then run /akk models before any deliberate set-model attempt.",
1350
+ "This repair never submits a task, chooses a model, approves a prompt, or creates an AKK Turn."
1351
+ ].join("\n");
1352
+ }
1353
+ export function isAkkRepairModelControlSuccess(result) {
1354
+ const record = recordValue(result);
1355
+ return record?.outcome === "repaired" || record?.outcome === "already_empty";
1356
+ }
1357
+ export function isAkkSetModelSuccess(result) {
1358
+ const record = recordValue(result);
1359
+ return record?.outcome === "changed" ||
1360
+ record?.outcome === "already_effective";
1361
+ }
723
1362
  export function formatAkkThreadTransitionCommandResult(result) {
724
1363
  if (!isAkkThreadTransitionSuccess(result)) {
725
1364
  const status = nonEmptyString(result.status) ?? "unknown";
@@ -805,6 +1444,34 @@ export function buildAkkCommandCliArgs(command, config, context = {}) {
805
1444
  "--terminal",
806
1445
  command.terminalId
807
1446
  ], ["--store-dir", storeDir], ["--codex-home", codexHome], ["--selection-scope", nonEmptyString(context.selectionScope)]);
1447
+ case "model-options":
1448
+ return withOptionalArgs([
1449
+ "model-options",
1450
+ "--terminal",
1451
+ command.terminalId,
1452
+ "--expected-binding-token",
1453
+ requiredExpectedBindingToken(context.expectedBindingToken)
1454
+ ], ["--store-dir", storeDir], ["--codex-home", codexHome]);
1455
+ case "repair-model-control":
1456
+ return withOptionalArgs([
1457
+ "repair-model-control",
1458
+ "--terminal",
1459
+ command.terminalId,
1460
+ "--expected-binding-token",
1461
+ requiredExpectedBindingToken(context.expectedBindingToken)
1462
+ ], ["--store-dir", storeDir], ["--codex-home", codexHome]);
1463
+ case "set-model":
1464
+ return withOptionalArgs([
1465
+ "set-model",
1466
+ "--terminal",
1467
+ command.terminalId,
1468
+ "--expected-binding-token",
1469
+ requiredExpectedBindingToken(context.expectedBindingToken),
1470
+ "--expected-catalog-fingerprint",
1471
+ requiredExpectedCatalogFingerprint(context.expectedCatalogFingerprint),
1472
+ "--model",
1473
+ command.model
1474
+ ], ["--reasoning-effort", command.reasoningEffort], ["--store-dir", storeDir], ["--codex-home", codexHome]);
808
1475
  case "new-thread":
809
1476
  return withOptionalArgs([
810
1477
  "new-thread",
@@ -944,6 +1611,10 @@ function assertExactTerminalId(value, usage) {
944
1611
  throw new Error(`${usage}; use the exact terminal_id returned by /akk list`);
945
1612
  }
946
1613
  }
1614
+ function isAkkModelSemanticIdentifier(value, maxLength) {
1615
+ return value.length <= maxLength &&
1616
+ /^[A-Za-z0-9][A-Za-z0-9._:/+\-]*$/u.test(value);
1617
+ }
947
1618
  function isExactNativeThreadId(value) {
948
1619
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(value);
949
1620
  }
@@ -980,6 +1651,13 @@ function requiredCandidateToken(value) {
980
1651
  }
981
1652
  return token;
982
1653
  }
1654
+ function requiredExpectedCatalogFingerprint(value) {
1655
+ const fingerprint = nonEmptyString(value);
1656
+ if (!fingerprint) {
1657
+ throw new Error("expected catalog fingerprint is required; refresh model options before switching the model");
1658
+ }
1659
+ return fingerprint;
1660
+ }
983
1661
  function requiredSelectionScope(value) {
984
1662
  const scope = nonEmptyString(value);
985
1663
  if (!scope) {
@@ -1126,7 +1804,9 @@ function formatAvailableActions(label, resource) {
1126
1804
  "list_resumable_threads",
1127
1805
  "new_thread",
1128
1806
  "resume_thread",
1129
- "native_inspect"
1807
+ "native_inspect",
1808
+ "model_options",
1809
+ "repair_model_control"
1130
1810
  ]);
1131
1811
  const names = Object.keys(actions)
1132
1812
  .filter((name) => displayedActions.has(name))