@dexto/tui 1.13.4 → 1.14.0

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 (72) hide show
  1. package/dist/agent-backend.cjs +2 -0
  2. package/dist/agent-backend.d.ts +2 -0
  3. package/dist/agent-backend.d.ts.map +1 -1
  4. package/dist/agent-backend.js +2 -0
  5. package/dist/components/ApprovalPrompt.cjs +19 -17
  6. package/dist/components/ApprovalPrompt.d.ts +2 -1
  7. package/dist/components/ApprovalPrompt.d.ts.map +1 -1
  8. package/dist/components/ApprovalPrompt.js +19 -17
  9. package/dist/components/ApprovalPrompt.test.cjs +119 -0
  10. package/dist/components/ApprovalPrompt.test.d.ts +2 -0
  11. package/dist/components/ApprovalPrompt.test.d.ts.map +1 -0
  12. package/dist/components/ApprovalPrompt.test.js +120 -0
  13. package/dist/components/Footer.cjs +5 -1
  14. package/dist/components/Footer.d.ts.map +1 -1
  15. package/dist/components/Footer.js +5 -1
  16. package/dist/components/Footer.test.cjs +47 -0
  17. package/dist/components/Footer.test.d.ts +2 -0
  18. package/dist/components/Footer.test.d.ts.map +1 -0
  19. package/dist/components/Footer.test.js +46 -0
  20. package/dist/components/modes/AlternateBufferCLI.cjs +3 -1
  21. package/dist/components/modes/AlternateBufferCLI.d.ts.map +1 -1
  22. package/dist/components/modes/AlternateBufferCLI.js +3 -1
  23. package/dist/components/modes/StaticCLI.cjs +3 -1
  24. package/dist/components/modes/StaticCLI.d.ts.map +1 -1
  25. package/dist/components/modes/StaticCLI.js +3 -1
  26. package/dist/components/overlays/PermissionsSelector.cjs +139 -0
  27. package/dist/components/overlays/PermissionsSelector.d.ts +16 -0
  28. package/dist/components/overlays/PermissionsSelector.d.ts.map +1 -0
  29. package/dist/components/overlays/PermissionsSelector.js +115 -0
  30. package/dist/components/overlays/PermissionsSelector.test.cjs +105 -0
  31. package/dist/components/overlays/PermissionsSelector.test.d.ts +2 -0
  32. package/dist/components/overlays/PermissionsSelector.test.d.ts.map +1 -0
  33. package/dist/components/overlays/PermissionsSelector.test.js +104 -0
  34. package/dist/containers/InputContainer.cjs +108 -28
  35. package/dist/containers/InputContainer.d.ts +1 -0
  36. package/dist/containers/InputContainer.d.ts.map +1 -1
  37. package/dist/containers/InputContainer.js +108 -28
  38. package/dist/containers/OverlayContainer.cjs +15 -0
  39. package/dist/containers/OverlayContainer.d.ts.map +1 -1
  40. package/dist/containers/OverlayContainer.js +17 -0
  41. package/dist/hooks/useAgentEvents.cjs +6 -3
  42. package/dist/hooks/useAgentEvents.d.ts.map +1 -1
  43. package/dist/hooks/useAgentEvents.js +6 -3
  44. package/dist/hooks/useCLIState.cjs +28 -8
  45. package/dist/hooks/useCLIState.d.ts +1 -0
  46. package/dist/hooks/useCLIState.d.ts.map +1 -1
  47. package/dist/hooks/useCLIState.js +28 -8
  48. package/dist/hooks/useCLIState.test.cjs +583 -0
  49. package/dist/hooks/useCLIState.test.d.ts +2 -0
  50. package/dist/hooks/useCLIState.test.d.ts.map +1 -0
  51. package/dist/hooks/useCLIState.test.js +582 -0
  52. package/dist/index.d.cts +2 -0
  53. package/dist/interactive-commands/commands.test.cjs +6 -0
  54. package/dist/interactive-commands/commands.test.js +6 -0
  55. package/dist/interactive-commands/tool-commands.cjs +7 -0
  56. package/dist/interactive-commands/tool-commands.d.ts.map +1 -1
  57. package/dist/interactive-commands/tool-commands.js +7 -0
  58. package/dist/services/processStream.cjs +4 -2
  59. package/dist/services/processStream.d.ts.map +1 -1
  60. package/dist/services/processStream.js +4 -2
  61. package/dist/services/processStream.test.cjs +63 -0
  62. package/dist/services/processStream.test.js +63 -0
  63. package/dist/state/types.d.ts +1 -1
  64. package/dist/state/types.d.ts.map +1 -1
  65. package/dist/utils/approvalScope.cjs +35 -0
  66. package/dist/utils/approvalScope.d.ts +3 -0
  67. package/dist/utils/approvalScope.d.ts.map +1 -0
  68. package/dist/utils/approvalScope.js +11 -0
  69. package/dist/utils/commandOverlays.cjs +1 -0
  70. package/dist/utils/commandOverlays.d.ts.map +1 -1
  71. package/dist/utils/commandOverlays.js +1 -0
  72. package/package.json +5 -5
@@ -64,7 +64,8 @@ function AlternateBufferCLI({
64
64
  inputService,
65
65
  buffer,
66
66
  overlayContainerRef,
67
- visibleMessages
67
+ visibleMessages,
68
+ isHydratingHistory
68
69
  } = useCLIState({
69
70
  agent,
70
71
  initialSessionId,
@@ -283,6 +284,7 @@ function AlternateBufferCLI({
283
284
  ui,
284
285
  session,
285
286
  initialPrompt,
287
+ isHydratingHistory,
286
288
  approval,
287
289
  steerMessages,
288
290
  queuedMessages,
@@ -74,7 +74,8 @@ function StaticCLI({
74
74
  inputService,
75
75
  buffer,
76
76
  overlayContainerRef,
77
- visibleMessages
77
+ visibleMessages,
78
+ isHydratingHistory
78
79
  } = (0, import_useCLIState.useCLIState)({
79
80
  agent,
80
81
  initialSessionId,
@@ -246,6 +247,7 @@ function StaticCLI({
246
247
  ui,
247
248
  session,
248
249
  initialPrompt,
250
+ isHydratingHistory,
249
251
  approval,
250
252
  steerMessages,
251
253
  queuedMessages,
@@ -1 +1 @@
1
- {"version":3,"file":"StaticCLI.d.ts","sourceRoot":"","sources":["../../../src/components/modes/StaticCLI.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAM9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAsBxD,UAAU,cAAc;IACpB,KAAK,EAAE,eAAe,CAAC;IACvB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,EAAE,WAAW,CAAC;IACzB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAgB,SAAS,CAAC,EACtB,KAAK,EACL,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,cAAc,EACd,wBAAgC,EAChC,YAAmB,GACtB,EAAE,cAAc,2CAyShB"}
1
+ {"version":3,"file":"StaticCLI.d.ts","sourceRoot":"","sources":["../../../src/components/modes/StaticCLI.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAM9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAsBxD,UAAU,cAAc;IACpB,KAAK,EAAE,eAAe,CAAC;IACvB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,EAAE,WAAW,CAAC;IACzB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAgB,SAAS,CAAC,EACtB,KAAK,EACL,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,cAAc,EACd,wBAAgC,EAChC,YAAmB,GACtB,EAAE,cAAc,2CA2ShB"}
@@ -51,7 +51,8 @@ function StaticCLI({
51
51
  inputService,
52
52
  buffer,
53
53
  overlayContainerRef,
54
- visibleMessages
54
+ visibleMessages,
55
+ isHydratingHistory
55
56
  } = useCLIState({
56
57
  agent,
57
58
  initialSessionId,
@@ -223,6 +224,7 @@ function StaticCLI({
223
224
  ui,
224
225
  session,
225
226
  initialPrompt,
227
+ isHydratingHistory,
226
228
  approval,
227
229
  steerMessages,
228
230
  queuedMessages,
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var PermissionsSelector_exports = {};
20
+ __export(PermissionsSelector_exports, {
21
+ PermissionsSelector: () => PermissionsSelector
22
+ });
23
+ module.exports = __toCommonJS(PermissionsSelector_exports);
24
+ var import_jsx_runtime = require("react/jsx-runtime");
25
+ var import_react = require("react");
26
+ var import_ink = require("ink");
27
+ var import_approvalScope = require("../../utils/approvalScope.js");
28
+ var import_BaseSelector = require("../base/BaseSelector.js");
29
+ const PermissionsSelector = (0, import_react.forwardRef)(
30
+ function PermissionsSelector2({ agent, sessionId, ui, setUi, onClose }, ref) {
31
+ const selectorRef = (0, import_react.useRef)(null);
32
+ const [grants, setGrants] = (0, import_react.useState)([]);
33
+ const [selectedIndex, setSelectedIndex] = (0, import_react.useState)(
34
+ ui.bypassPermissions ? 2 : ui.autoApproveEdits ? 1 : 0
35
+ );
36
+ const [isLoading, setIsLoading] = (0, import_react.useState)(true);
37
+ const [error, setError] = (0, import_react.useState)(null);
38
+ const mode = ui.bypassPermissions ? "auto" : ui.autoApproveEdits ? "edits" : "defaults";
39
+ (0, import_react.useImperativeHandle)(
40
+ ref,
41
+ () => ({
42
+ handleInput: (input, key) => selectorRef.current?.handleInput(input, key) ?? false
43
+ }),
44
+ []
45
+ );
46
+ (0, import_react.useEffect)(() => {
47
+ let active = true;
48
+ async function load() {
49
+ try {
50
+ const approvals = sessionId && agent.getSessionApprovals ? await agent.getSessionApprovals(sessionId) : [];
51
+ if (active) setGrants(approvals);
52
+ } catch (cause) {
53
+ if (active) setError(cause instanceof Error ? cause.message : String(cause));
54
+ } finally {
55
+ if (active) setIsLoading(false);
56
+ }
57
+ }
58
+ void load();
59
+ return () => {
60
+ active = false;
61
+ };
62
+ }, [agent, sessionId]);
63
+ const options = [
64
+ { kind: "mode", value: "defaults", label: "Agent defaults" },
65
+ { kind: "mode", value: "edits", label: "Accept edits for this session" },
66
+ { kind: "mode", value: "auto", label: "Auto-approve for this session" },
67
+ ...grants.map(
68
+ (approval) => ({
69
+ kind: "grant",
70
+ approval,
71
+ label: `Revoke ${(0, import_approvalScope.formatApprovalScope)(approval)}`
72
+ })
73
+ )
74
+ ];
75
+ async function select(option) {
76
+ if (option.kind === "mode") {
77
+ setUi((previous) => ({
78
+ ...previous,
79
+ autoApproveEdits: option.value === "edits",
80
+ bypassPermissions: option.value === "auto",
81
+ planModeActive: false,
82
+ planModeInitialized: false
83
+ }));
84
+ onClose();
85
+ return;
86
+ }
87
+ if (!sessionId || !agent.revokeSessionApproval) return;
88
+ setIsLoading(true);
89
+ setError(null);
90
+ try {
91
+ await agent.revokeSessionApproval(sessionId, option.approval);
92
+ setGrants(
93
+ (previous) => previous.filter(
94
+ (grant) => grant.kind !== option.approval.kind || grant.value !== option.approval.value
95
+ )
96
+ );
97
+ setSelectedIndex(0);
98
+ } catch (cause) {
99
+ setError(cause instanceof Error ? cause.message : String(cause));
100
+ } finally {
101
+ setIsLoading(false);
102
+ }
103
+ }
104
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "column", children: [
105
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "gray", children: [
106
+ "Agent policy:",
107
+ " ",
108
+ agent.getEffectiveConfig(sessionId ?? void 0).permissions.mode
109
+ ] }),
110
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: "gray", children: "Mode applies to this TUI session. Remembered grants also apply when this conversation resumes." }),
111
+ error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: "red", children: error }),
112
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
113
+ import_BaseSelector.BaseSelector,
114
+ {
115
+ ref: selectorRef,
116
+ items: options,
117
+ isVisible: true,
118
+ isLoading,
119
+ selectedIndex,
120
+ onSelectIndex: setSelectedIndex,
121
+ onSelect: (option) => {
122
+ void select(option);
123
+ },
124
+ onClose,
125
+ title: "Session permissions",
126
+ formatItem: (option, selected) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: selected ? "cyan" : "white", children: [
127
+ option.kind === "mode" && option.value === mode ? "\u25CF " : " ",
128
+ option.label
129
+ ] })
130
+ }
131
+ ),
132
+ grants.length === 0 && !isLoading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: "gray", children: "No remembered user grants." })
133
+ ] });
134
+ }
135
+ );
136
+ // Annotate the CommonJS export names for ESM import in node:
137
+ 0 && (module.exports = {
138
+ PermissionsSelector
139
+ });
@@ -0,0 +1,16 @@
1
+ import type { TuiAgentBackend } from '../../agent-backend.js';
2
+ import type { UIState } from '../../state/types.js';
3
+ import type { Key } from '../../hooks/useInputOrchestrator.js';
4
+ interface PermissionsSelectorProps {
5
+ agent: TuiAgentBackend;
6
+ sessionId: string | null;
7
+ ui: UIState;
8
+ setUi: React.Dispatch<React.SetStateAction<UIState>>;
9
+ onClose: () => void;
10
+ }
11
+ export interface PermissionsSelectorHandle {
12
+ handleInput: (input: string, key: Key) => boolean;
13
+ }
14
+ export declare const PermissionsSelector: import("react").ForwardRefExoticComponent<PermissionsSelectorProps & import("react").RefAttributes<PermissionsSelectorHandle>>;
15
+ export {};
16
+ //# sourceMappingURL=PermissionsSelector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PermissionsSelector.d.ts","sourceRoot":"","sources":["../../../src/components/overlays/PermissionsSelector.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,qCAAqC,CAAC;AAS/D,UAAU,wBAAwB;IAC9B,KAAK,EAAE,eAAe,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACrD,OAAO,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,yBAAyB;IACtC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACrD;AAED,eAAO,MAAM,mBAAmB,gIAyH/B,CAAC"}
@@ -0,0 +1,115 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
3
+ import { Box, Text } from "ink";
4
+ import { formatApprovalScope } from "../../utils/approvalScope.js";
5
+ import { BaseSelector } from "../base/BaseSelector.js";
6
+ const PermissionsSelector = forwardRef(
7
+ function PermissionsSelector2({ agent, sessionId, ui, setUi, onClose }, ref) {
8
+ const selectorRef = useRef(null);
9
+ const [grants, setGrants] = useState([]);
10
+ const [selectedIndex, setSelectedIndex] = useState(
11
+ ui.bypassPermissions ? 2 : ui.autoApproveEdits ? 1 : 0
12
+ );
13
+ const [isLoading, setIsLoading] = useState(true);
14
+ const [error, setError] = useState(null);
15
+ const mode = ui.bypassPermissions ? "auto" : ui.autoApproveEdits ? "edits" : "defaults";
16
+ useImperativeHandle(
17
+ ref,
18
+ () => ({
19
+ handleInput: (input, key) => selectorRef.current?.handleInput(input, key) ?? false
20
+ }),
21
+ []
22
+ );
23
+ useEffect(() => {
24
+ let active = true;
25
+ async function load() {
26
+ try {
27
+ const approvals = sessionId && agent.getSessionApprovals ? await agent.getSessionApprovals(sessionId) : [];
28
+ if (active) setGrants(approvals);
29
+ } catch (cause) {
30
+ if (active) setError(cause instanceof Error ? cause.message : String(cause));
31
+ } finally {
32
+ if (active) setIsLoading(false);
33
+ }
34
+ }
35
+ void load();
36
+ return () => {
37
+ active = false;
38
+ };
39
+ }, [agent, sessionId]);
40
+ const options = [
41
+ { kind: "mode", value: "defaults", label: "Agent defaults" },
42
+ { kind: "mode", value: "edits", label: "Accept edits for this session" },
43
+ { kind: "mode", value: "auto", label: "Auto-approve for this session" },
44
+ ...grants.map(
45
+ (approval) => ({
46
+ kind: "grant",
47
+ approval,
48
+ label: `Revoke ${formatApprovalScope(approval)}`
49
+ })
50
+ )
51
+ ];
52
+ async function select(option) {
53
+ if (option.kind === "mode") {
54
+ setUi((previous) => ({
55
+ ...previous,
56
+ autoApproveEdits: option.value === "edits",
57
+ bypassPermissions: option.value === "auto",
58
+ planModeActive: false,
59
+ planModeInitialized: false
60
+ }));
61
+ onClose();
62
+ return;
63
+ }
64
+ if (!sessionId || !agent.revokeSessionApproval) return;
65
+ setIsLoading(true);
66
+ setError(null);
67
+ try {
68
+ await agent.revokeSessionApproval(sessionId, option.approval);
69
+ setGrants(
70
+ (previous) => previous.filter(
71
+ (grant) => grant.kind !== option.approval.kind || grant.value !== option.approval.value
72
+ )
73
+ );
74
+ setSelectedIndex(0);
75
+ } catch (cause) {
76
+ setError(cause instanceof Error ? cause.message : String(cause));
77
+ } finally {
78
+ setIsLoading(false);
79
+ }
80
+ }
81
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
82
+ /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
83
+ "Agent policy:",
84
+ " ",
85
+ agent.getEffectiveConfig(sessionId ?? void 0).permissions.mode
86
+ ] }),
87
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Mode applies to this TUI session. Remembered grants also apply when this conversation resumes." }),
88
+ error && /* @__PURE__ */ jsx(Text, { color: "red", children: error }),
89
+ /* @__PURE__ */ jsx(
90
+ BaseSelector,
91
+ {
92
+ ref: selectorRef,
93
+ items: options,
94
+ isVisible: true,
95
+ isLoading,
96
+ selectedIndex,
97
+ onSelectIndex: setSelectedIndex,
98
+ onSelect: (option) => {
99
+ void select(option);
100
+ },
101
+ onClose,
102
+ title: "Session permissions",
103
+ formatItem: (option, selected) => /* @__PURE__ */ jsxs(Text, { color: selected ? "cyan" : "white", children: [
104
+ option.kind === "mode" && option.value === mode ? "\u25CF " : " ",
105
+ option.label
106
+ ] })
107
+ }
108
+ ),
109
+ grants.length === 0 && !isLoading && /* @__PURE__ */ jsx(Text, { color: "gray", children: "No remembered user grants." })
110
+ ] });
111
+ }
112
+ );
113
+ export {
114
+ PermissionsSelector
115
+ };
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ var import_react = require("react");
3
+ var import_node_stream = require("node:stream");
4
+ var import_ink = require("ink");
5
+ var import_vitest = require("vitest");
6
+ var import_PermissionsSelector = require("./PermissionsSelector.js");
7
+ var import_initialState = require("../../state/initialState.js");
8
+ const key = {
9
+ upArrow: false,
10
+ downArrow: false,
11
+ leftArrow: false,
12
+ rightArrow: false,
13
+ pageUp: false,
14
+ pageDown: false,
15
+ return: false,
16
+ escape: false,
17
+ ctrl: false,
18
+ shift: false,
19
+ meta: false,
20
+ tab: false,
21
+ backspace: false,
22
+ delete: false,
23
+ paste: false
24
+ };
25
+ async function openPermissions() {
26
+ const stdout = Object.assign(new import_node_stream.PassThrough(), { columns: 120, rows: 40, isTTY: false });
27
+ let output = "";
28
+ stdout.on("data", (chunk) => {
29
+ output += String(chunk);
30
+ });
31
+ const agent = {
32
+ getSessionApprovals: import_vitest.vi.fn().mockResolvedValue([{ kind: "action", value: "bash:git status *" }]),
33
+ revokeSessionApproval: import_vitest.vi.fn().mockResolvedValue(void 0),
34
+ getEffectiveConfig: () => ({ permissions: { mode: "manual" } })
35
+ };
36
+ const ref = (0, import_react.createRef)();
37
+ let ui = (0, import_initialState.createInitialState)().ui;
38
+ const onClose = import_vitest.vi.fn();
39
+ const view = (0, import_ink.render)(
40
+ (0, import_react.createElement)(import_PermissionsSelector.PermissionsSelector, {
41
+ ref,
42
+ agent,
43
+ sessionId: "a",
44
+ ui,
45
+ setUi: (update) => {
46
+ ui = typeof update === "function" ? update(ui) : update;
47
+ },
48
+ onClose
49
+ }),
50
+ {
51
+ stdout,
52
+ debug: true,
53
+ patchConsole: false,
54
+ exitOnCtrlC: false
55
+ }
56
+ );
57
+ await import_vitest.vi.waitFor(() => (0, import_vitest.expect)(output).toContain("Revoke git status *"));
58
+ return {
59
+ agent,
60
+ onClose,
61
+ getUi: () => ui,
62
+ getOutput: () => output,
63
+ async press(pressed) {
64
+ ref.current?.handleInput("", { ...key, ...pressed });
65
+ await new Promise((resolve) => setTimeout(resolve, 20));
66
+ },
67
+ close() {
68
+ view.unmount();
69
+ view.cleanup();
70
+ }
71
+ };
72
+ }
73
+ (0, import_vitest.describe)("PermissionsSelector", () => {
74
+ (0, import_vitest.it)("lets a user choose session auto-approval without changing saved grants", async () => {
75
+ const screen = await openPermissions();
76
+ try {
77
+ await screen.press({ downArrow: true });
78
+ await screen.press({ downArrow: true });
79
+ await screen.press({ return: true });
80
+ (0, import_vitest.expect)(screen.getUi().bypassPermissions).toBe(true);
81
+ (0, import_vitest.expect)(screen.getUi().autoApproveEdits).toBe(false);
82
+ (0, import_vitest.expect)(screen.onClose).toHaveBeenCalledOnce();
83
+ (0, import_vitest.expect)(screen.agent.revokeSessionApproval).not.toHaveBeenCalled();
84
+ } finally {
85
+ screen.close();
86
+ }
87
+ });
88
+ (0, import_vitest.it)("lets a user revoke a displayed grant and keeps the selector open", async () => {
89
+ const screen = await openPermissions();
90
+ try {
91
+ for (let index = 0; index < 3; index++) await screen.press({ downArrow: true });
92
+ await screen.press({ return: true });
93
+ (0, import_vitest.expect)(screen.agent.revokeSessionApproval).toHaveBeenCalledWith("a", {
94
+ kind: "action",
95
+ value: "bash:git status *"
96
+ });
97
+ await import_vitest.vi.waitFor(
98
+ () => (0, import_vitest.expect)(screen.getOutput()).toContain("No remembered user grants.")
99
+ );
100
+ (0, import_vitest.expect)(screen.onClose).not.toHaveBeenCalled();
101
+ } finally {
102
+ screen.close();
103
+ }
104
+ });
105
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=PermissionsSelector.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PermissionsSelector.test.d.ts","sourceRoot":"","sources":["../../../src/components/overlays/PermissionsSelector.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,104 @@
1
+ import { createElement, createRef } from "react";
2
+ import { PassThrough } from "node:stream";
3
+ import { render } from "ink";
4
+ import { describe, expect, it, vi } from "vitest";
5
+ import { PermissionsSelector } from "./PermissionsSelector.js";
6
+ import { createInitialState } from "../../state/initialState.js";
7
+ const key = {
8
+ upArrow: false,
9
+ downArrow: false,
10
+ leftArrow: false,
11
+ rightArrow: false,
12
+ pageUp: false,
13
+ pageDown: false,
14
+ return: false,
15
+ escape: false,
16
+ ctrl: false,
17
+ shift: false,
18
+ meta: false,
19
+ tab: false,
20
+ backspace: false,
21
+ delete: false,
22
+ paste: false
23
+ };
24
+ async function openPermissions() {
25
+ const stdout = Object.assign(new PassThrough(), { columns: 120, rows: 40, isTTY: false });
26
+ let output = "";
27
+ stdout.on("data", (chunk) => {
28
+ output += String(chunk);
29
+ });
30
+ const agent = {
31
+ getSessionApprovals: vi.fn().mockResolvedValue([{ kind: "action", value: "bash:git status *" }]),
32
+ revokeSessionApproval: vi.fn().mockResolvedValue(void 0),
33
+ getEffectiveConfig: () => ({ permissions: { mode: "manual" } })
34
+ };
35
+ const ref = createRef();
36
+ let ui = createInitialState().ui;
37
+ const onClose = vi.fn();
38
+ const view = render(
39
+ createElement(PermissionsSelector, {
40
+ ref,
41
+ agent,
42
+ sessionId: "a",
43
+ ui,
44
+ setUi: (update) => {
45
+ ui = typeof update === "function" ? update(ui) : update;
46
+ },
47
+ onClose
48
+ }),
49
+ {
50
+ stdout,
51
+ debug: true,
52
+ patchConsole: false,
53
+ exitOnCtrlC: false
54
+ }
55
+ );
56
+ await vi.waitFor(() => expect(output).toContain("Revoke git status *"));
57
+ return {
58
+ agent,
59
+ onClose,
60
+ getUi: () => ui,
61
+ getOutput: () => output,
62
+ async press(pressed) {
63
+ ref.current?.handleInput("", { ...key, ...pressed });
64
+ await new Promise((resolve) => setTimeout(resolve, 20));
65
+ },
66
+ close() {
67
+ view.unmount();
68
+ view.cleanup();
69
+ }
70
+ };
71
+ }
72
+ describe("PermissionsSelector", () => {
73
+ it("lets a user choose session auto-approval without changing saved grants", async () => {
74
+ const screen = await openPermissions();
75
+ try {
76
+ await screen.press({ downArrow: true });
77
+ await screen.press({ downArrow: true });
78
+ await screen.press({ return: true });
79
+ expect(screen.getUi().bypassPermissions).toBe(true);
80
+ expect(screen.getUi().autoApproveEdits).toBe(false);
81
+ expect(screen.onClose).toHaveBeenCalledOnce();
82
+ expect(screen.agent.revokeSessionApproval).not.toHaveBeenCalled();
83
+ } finally {
84
+ screen.close();
85
+ }
86
+ });
87
+ it("lets a user revoke a displayed grant and keeps the selector open", async () => {
88
+ const screen = await openPermissions();
89
+ try {
90
+ for (let index = 0; index < 3; index++) await screen.press({ downArrow: true });
91
+ await screen.press({ return: true });
92
+ expect(screen.agent.revokeSessionApproval).toHaveBeenCalledWith("a", {
93
+ kind: "action",
94
+ value: "bash:git status *"
95
+ });
96
+ await vi.waitFor(
97
+ () => expect(screen.getOutput()).toContain("No remembered user grants.")
98
+ );
99
+ expect(screen.onClose).not.toHaveBeenCalled();
100
+ } finally {
101
+ screen.close();
102
+ }
103
+ });
104
+ });