@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.
- package/dist/agent-backend.cjs +2 -0
- package/dist/agent-backend.d.ts +2 -0
- package/dist/agent-backend.d.ts.map +1 -1
- package/dist/agent-backend.js +2 -0
- package/dist/components/ApprovalPrompt.cjs +19 -17
- package/dist/components/ApprovalPrompt.d.ts +2 -1
- package/dist/components/ApprovalPrompt.d.ts.map +1 -1
- package/dist/components/ApprovalPrompt.js +19 -17
- package/dist/components/ApprovalPrompt.test.cjs +119 -0
- package/dist/components/ApprovalPrompt.test.d.ts +2 -0
- package/dist/components/ApprovalPrompt.test.d.ts.map +1 -0
- package/dist/components/ApprovalPrompt.test.js +120 -0
- package/dist/components/Footer.cjs +5 -1
- package/dist/components/Footer.d.ts.map +1 -1
- package/dist/components/Footer.js +5 -1
- package/dist/components/Footer.test.cjs +47 -0
- package/dist/components/Footer.test.d.ts +2 -0
- package/dist/components/Footer.test.d.ts.map +1 -0
- package/dist/components/Footer.test.js +46 -0
- package/dist/components/modes/AlternateBufferCLI.cjs +3 -1
- package/dist/components/modes/AlternateBufferCLI.d.ts.map +1 -1
- package/dist/components/modes/AlternateBufferCLI.js +3 -1
- package/dist/components/modes/StaticCLI.cjs +3 -1
- package/dist/components/modes/StaticCLI.d.ts.map +1 -1
- package/dist/components/modes/StaticCLI.js +3 -1
- package/dist/components/overlays/PermissionsSelector.cjs +139 -0
- package/dist/components/overlays/PermissionsSelector.d.ts +16 -0
- package/dist/components/overlays/PermissionsSelector.d.ts.map +1 -0
- package/dist/components/overlays/PermissionsSelector.js +115 -0
- package/dist/components/overlays/PermissionsSelector.test.cjs +105 -0
- package/dist/components/overlays/PermissionsSelector.test.d.ts +2 -0
- package/dist/components/overlays/PermissionsSelector.test.d.ts.map +1 -0
- package/dist/components/overlays/PermissionsSelector.test.js +104 -0
- package/dist/containers/InputContainer.cjs +108 -28
- package/dist/containers/InputContainer.d.ts +1 -0
- package/dist/containers/InputContainer.d.ts.map +1 -1
- package/dist/containers/InputContainer.js +108 -28
- package/dist/containers/OverlayContainer.cjs +15 -0
- package/dist/containers/OverlayContainer.d.ts.map +1 -1
- package/dist/containers/OverlayContainer.js +17 -0
- package/dist/hooks/useAgentEvents.cjs +6 -3
- package/dist/hooks/useAgentEvents.d.ts.map +1 -1
- package/dist/hooks/useAgentEvents.js +6 -3
- package/dist/hooks/useCLIState.cjs +28 -8
- package/dist/hooks/useCLIState.d.ts +1 -0
- package/dist/hooks/useCLIState.d.ts.map +1 -1
- package/dist/hooks/useCLIState.js +28 -8
- package/dist/hooks/useCLIState.test.cjs +583 -0
- package/dist/hooks/useCLIState.test.d.ts +2 -0
- package/dist/hooks/useCLIState.test.d.ts.map +1 -0
- package/dist/hooks/useCLIState.test.js +582 -0
- package/dist/index.d.cts +2 -0
- package/dist/interactive-commands/commands.test.cjs +6 -0
- package/dist/interactive-commands/commands.test.js +6 -0
- package/dist/interactive-commands/tool-commands.cjs +7 -0
- package/dist/interactive-commands/tool-commands.d.ts.map +1 -1
- package/dist/interactive-commands/tool-commands.js +7 -0
- package/dist/services/processStream.cjs +4 -2
- package/dist/services/processStream.d.ts.map +1 -1
- package/dist/services/processStream.js +4 -2
- package/dist/services/processStream.test.cjs +63 -0
- package/dist/services/processStream.test.js +63 -0
- package/dist/state/types.d.ts +1 -1
- package/dist/state/types.d.ts.map +1 -1
- package/dist/utils/approvalScope.cjs +35 -0
- package/dist/utils/approvalScope.d.ts +3 -0
- package/dist/utils/approvalScope.d.ts.map +1 -0
- package/dist/utils/approvalScope.js +11 -0
- package/dist/utils/commandOverlays.cjs +1 -0
- package/dist/utils/commandOverlays.d.ts.map +1 -1
- package/dist/utils/commandOverlays.js +1 -0
- 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,
|
|
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 @@
|
|
|
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
|
+
});
|