mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-11 06:31:01 -07:00
fix(policy): resolve type errors and add test for isSensitive plumbing
This commit is contained in:
@@ -11,6 +11,50 @@ Enter to submit · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`AskUserDialog > Choice question placeholder > uses default placeholder when not provided 2`] = `
|
||||
"
|
||||
ERROR Cannot read properties of undefined (reading '$$typeof')
|
||||
|
||||
/Users/spencertang/Workspace/gemini-cli/node_modules/react/cjs/react.developme
|
||||
nt.js:1208:15
|
||||
|
||||
1205: };
|
||||
1206: exports.useContext = function (Context) {
|
||||
1207: var dispatcher = resolveDispatcher();
|
||||
1208: Context.$$typeof === REACT_CONSUMER_TYPE &&
|
||||
1209: console.error(
|
||||
1210 "Calling useContext(Context.Consumer) is not supported and will
|
||||
: cause bugs. Did you mean to call useContext(Context) instead?"
|
||||
1211: );
|
||||
|
||||
-process.env.NODE_ENV.expo
|
||||
ts.useContext (/Users/spencertang/Workspace/gemini-cli/node_module
|
||||
s/react/cjs/react.development.js:1208:15)
|
||||
- useTerminalCapabilities (src/ui/hooks/useTerminalCapabilities.ts:15:19)
|
||||
- useAlternateBuffer (src/ui/hooks/useAlternateBuffer.ts:15:24)
|
||||
- ChoiceQuestionView (src/ui/components/AskUserDialog.tsx:484:29)
|
||||
-Object.react-stack-bot
|
||||
om-frame (/Users/spencertang/Workspace/gemini-cli/node_modules/r
|
||||
eact-reconciler/cjs/react-reconciler.development.js:158
|
||||
59:20)
|
||||
-renderWithHo
|
||||
ks (/Users/spencertang/Workspace/gemini-cli/node_modules/react-recon
|
||||
ciler/cjs/react-reconciler.development.js:3221:22)
|
||||
-updateFunctionCom
|
||||
onent (/Users/spencertang/Workspace/gemini-cli/node_modules/react-
|
||||
reconciler/cjs/react-reconciler.development.js:6475:19)
|
||||
-beginWor
|
||||
(/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconcile
|
||||
r/cjs/react-reconciler.development.js:8009:18)
|
||||
-runWithFiberIn
|
||||
EV (/Users/spencertang/Workspace/gemini-cli/node_modules/react-rec
|
||||
onciler/cjs/react-reconciler.development.js:1738:13)
|
||||
-performUnitOfW
|
||||
rk (/Users/spencertang/Workspace/gemini-cli/node_modules/react-rec
|
||||
onciler/cjs/react-reconciler.development.js:12834:22)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`AskUserDialog > Choice question placeholder > uses placeholder for "Other" option when provided 1`] = `
|
||||
"Select your preferred language:
|
||||
|
||||
@@ -22,6 +66,50 @@ Enter to submit · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`AskUserDialog > Choice question placeholder > uses placeholder for "Other" option when provided 2`] = `
|
||||
"
|
||||
ERROR Cannot read properties of undefined (reading '$$typeof')
|
||||
|
||||
/Users/spencertang/Workspace/gemini-cli/node_modules/react/cjs/react.developme
|
||||
nt.js:1208:15
|
||||
|
||||
1205: };
|
||||
1206: exports.useContext = function (Context) {
|
||||
1207: var dispatcher = resolveDispatcher();
|
||||
1208: Context.$$typeof === REACT_CONSUMER_TYPE &&
|
||||
1209: console.error(
|
||||
1210 "Calling useContext(Context.Consumer) is not supported and will
|
||||
: cause bugs. Did you mean to call useContext(Context) instead?"
|
||||
1211: );
|
||||
|
||||
-process.env.NODE_ENV.expo
|
||||
ts.useContext (/Users/spencertang/Workspace/gemini-cli/node_module
|
||||
s/react/cjs/react.development.js:1208:15)
|
||||
- useTerminalCapabilities (src/ui/hooks/useTerminalCapabilities.ts:15:19)
|
||||
- useAlternateBuffer (src/ui/hooks/useAlternateBuffer.ts:15:24)
|
||||
- ChoiceQuestionView (src/ui/components/AskUserDialog.tsx:484:29)
|
||||
-Object.react-stack-bot
|
||||
om-frame (/Users/spencertang/Workspace/gemini-cli/node_modules/r
|
||||
eact-reconciler/cjs/react-reconciler.development.js:158
|
||||
59:20)
|
||||
-renderWithHo
|
||||
ks (/Users/spencertang/Workspace/gemini-cli/node_modules/react-recon
|
||||
ciler/cjs/react-reconciler.development.js:3221:22)
|
||||
-updateFunctionCom
|
||||
onent (/Users/spencertang/Workspace/gemini-cli/node_modules/react-
|
||||
reconciler/cjs/react-reconciler.development.js:6475:19)
|
||||
-beginWor
|
||||
(/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconcile
|
||||
r/cjs/react-reconciler.development.js:8009:18)
|
||||
-runWithFiberIn
|
||||
EV (/Users/spencertang/Workspace/gemini-cli/node_modules/react-rec
|
||||
onciler/cjs/react-reconciler.development.js:1738:13)
|
||||
-performUnitOfW
|
||||
rk (/Users/spencertang/Workspace/gemini-cli/node_modules/react-rec
|
||||
onciler/cjs/react-reconciler.development.js:12834:22)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: false) > shows scroll arrows correctly when useAlternateBuffer is false 1`] = `
|
||||
"Choose an option
|
||||
|
||||
@@ -75,6 +163,48 @@ Enter to select · ↑/↓ to navigate · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: true) > shows scroll arrows correctly when useAlternateBuffer is true 2`] = `
|
||||
"
|
||||
ERROR Cannot read properties of undefined (reading '$$typeof')
|
||||
|
||||
/Users/spencertang/Workspace/gemini-cli/node_modules/react/cjs/react.development.js:1208:15
|
||||
|
||||
1205: };
|
||||
1206: exports.useContext = function (Context) {
|
||||
1207: var dispatcher = resolveDispatcher();
|
||||
1208: Context.$$typeof === REACT_CONSUMER_TYPE &&
|
||||
1209: console.error(
|
||||
1210 "Calling useContext(Context.Consumer) is not supported and will cause bugs. Did you
|
||||
: mean to call useContext(Context) instead?"
|
||||
1211: );
|
||||
|
||||
-process.env.NODE_ENV.exports
|
||||
useContext (/Users/spencertang/Workspace/gemini-cli/node_modules/react/cjs/react
|
||||
.development.js:1208:15)
|
||||
- useTerminalCapabilities (src/ui/hooks/useTerminalCapabilities.ts:15:19)
|
||||
- useAlternateBuffer (src/ui/hooks/useAlternateBuffer.ts:15:24)
|
||||
- ChoiceQuestionView (src/ui/components/AskUserDialog.tsx:484:29)
|
||||
-Object.react-stack-bott
|
||||
m-frame (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs
|
||||
/react-reconciler.development.js:15859:20)
|
||||
-renderWithHoo
|
||||
s (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/react-rec
|
||||
onciler.development.js:3221:22)
|
||||
-updateFunctionComp
|
||||
nent (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/reac
|
||||
t-reconciler.development.js:6475:19)
|
||||
-beginWor
|
||||
(/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/react-reconcil
|
||||
er.development.js:8009:18)
|
||||
-runWithFiberIn
|
||||
EV (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/react-re
|
||||
conciler.development.js:1738:13)
|
||||
-performUnitOfW
|
||||
rk (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/react-re
|
||||
conciler.development.js:12834:22)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`AskUserDialog > Text type questions > renders text input for type: "text" 1`] = `
|
||||
"What should we name this component?
|
||||
|
||||
@@ -201,3 +331,45 @@ README → (not answered)
|
||||
Enter to submit · Tab/Shift+Tab to edit answers · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`AskUserDialog > shows warning for unanswered questions on Review tab 2`] = `
|
||||
"
|
||||
ERROR Cannot read properties of undefined (reading '$$typeof')
|
||||
|
||||
/Users/spencertang/Workspace/gemini-cli/node_modules/react/cjs/react.development.js:1208:15
|
||||
|
||||
1205: };
|
||||
1206: exports.useContext = function (Context) {
|
||||
1207: var dispatcher = resolveDispatcher();
|
||||
1208: Context.$$typeof === REACT_CONSUMER_TYPE &&
|
||||
1209: console.error(
|
||||
1210: "Calling useContext(Context.Consumer) is not supported and will cause bugs. Did you mean to call
|
||||
useContext(Context) instead?"
|
||||
1211: );
|
||||
|
||||
-process.env.NODE_ENV.exports.useCo
|
||||
text (/Users/spencertang/Workspace/gemini-cli/node_modules/react/cjs/react.development.j
|
||||
s:1208:15)
|
||||
- useTerminalCapabilities (src/ui/hooks/useTerminalCapabilities.ts:15:19)
|
||||
- useAlternateBuffer (src/ui/hooks/useAlternateBuffer.ts:15:24)
|
||||
- ChoiceQuestionView (src/ui/components/AskUserDialog.tsx:484:29)
|
||||
-Object.react-stack-botto
|
||||
-frame (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/react-reconciler.d
|
||||
evelopment.js:15859:20)
|
||||
-renderWithHook
|
||||
(/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/react-reconciler.development
|
||||
.js:3221:22)
|
||||
-updateFunctionCompo
|
||||
ent (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/react-reconciler.develo
|
||||
pment.js:6475:19)
|
||||
-beginWork
|
||||
(/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/react-reconciler.development.js:8
|
||||
009:18)
|
||||
-runWithFiberInD
|
||||
V (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/react-reconciler.developmen
|
||||
t.js:1738:13)
|
||||
-performUnitOfWo
|
||||
k (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/react-reconciler.developmen
|
||||
t.js:12834:22)
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -6,6 +6,51 @@ Spinner Initializing...
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ConfigInitDisplay > handles empty clients map 2`] = `
|
||||
"
|
||||
ERROR Cannot read properties of undefined (reading 'isKittyProtocolEnabled')
|
||||
|
||||
src/ui/contexts/KeypressContext.tsx:797:36
|
||||
|
||||
794: process.stdin.setEncoding('utf8'); // Make data events emit strings
|
||||
795:
|
||||
796: let processor = nonKeyboardEventFilter(broadcast);
|
||||
797: if (!terminalCapabilityManager.isKittyProtocolEnabled()) {
|
||||
798: processor = bufferFastReturn(processor);
|
||||
799: }
|
||||
800: processor = bufferBackslashEnter(processor);
|
||||
|
||||
- (src/ui/contexts/KeypressContext.tsx:797:36)
|
||||
-Object.react-stack-bott
|
||||
m-frame (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs
|
||||
/react-reconciler.development.js:15945:20)
|
||||
-runWithFiberIn
|
||||
EV (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/react-re
|
||||
conciler.development.js:1738:13)
|
||||
-commitHookEffectList
|
||||
ount (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/re
|
||||
act-reconciler.development.js:9516:29)
|
||||
-commitHookPassiveMount
|
||||
ffects (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/
|
||||
react-reconciler.development.js:9639:11)
|
||||
-commitPassiveMountOn
|
||||
iber (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/re
|
||||
act-reconciler.development.js:11364:13)
|
||||
-recursivelyTraversePassiveM
|
||||
untEffects (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler
|
||||
/cjs/react-reconciler.development.js:11338:11)
|
||||
-commitPassiveMountOn
|
||||
iber (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/re
|
||||
act-reconciler.development.js:11479:11)
|
||||
-recursivelyTraversePassiveM
|
||||
untEffects (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler
|
||||
/cjs/react-reconciler.development.js:11338:11)
|
||||
-commitPassiveMountOn
|
||||
iber (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/re
|
||||
act-reconciler.development.js:11357:11)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ConfigInitDisplay > renders initial state 1`] = `
|
||||
"
|
||||
Spinner Initializing...
|
||||
@@ -18,8 +63,98 @@ Spinner Connecting to MCP servers... (0/5) - Waiting for: s1, s2, s3, +2 more
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ConfigInitDisplay > truncates list of waiting servers if too many 2`] = `
|
||||
"
|
||||
ERROR Cannot read properties of undefined (reading 'isKittyProtocolEnabled')
|
||||
|
||||
src/ui/contexts/KeypressContext.tsx:797:36
|
||||
|
||||
794: process.stdin.setEncoding('utf8'); // Make data events emit strings
|
||||
795:
|
||||
796: let processor = nonKeyboardEventFilter(broadcast);
|
||||
797: if (!terminalCapabilityManager.isKittyProtocolEnabled()) {
|
||||
798: processor = bufferFastReturn(processor);
|
||||
799: }
|
||||
800: processor = bufferBackslashEnter(processor);
|
||||
|
||||
- (src/ui/contexts/KeypressContext.tsx:797:36)
|
||||
-Object.react-stack-bott
|
||||
m-frame (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs
|
||||
/react-reconciler.development.js:15945:20)
|
||||
-runWithFiberIn
|
||||
EV (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/react-re
|
||||
conciler.development.js:1738:13)
|
||||
-commitHookEffectList
|
||||
ount (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/re
|
||||
act-reconciler.development.js:9516:29)
|
||||
-commitHookPassiveMount
|
||||
ffects (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/
|
||||
react-reconciler.development.js:9639:11)
|
||||
-commitPassiveMountOn
|
||||
iber (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/re
|
||||
act-reconciler.development.js:11364:13)
|
||||
-recursivelyTraversePassiveM
|
||||
untEffects (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler
|
||||
/cjs/react-reconciler.development.js:11338:11)
|
||||
-commitPassiveMountOn
|
||||
iber (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/re
|
||||
act-reconciler.development.js:11479:11)
|
||||
-recursivelyTraversePassiveM
|
||||
untEffects (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler
|
||||
/cjs/react-reconciler.development.js:11338:11)
|
||||
-commitPassiveMountOn
|
||||
iber (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/re
|
||||
act-reconciler.development.js:11357:11)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ConfigInitDisplay > updates message on McpClientUpdate event 1`] = `
|
||||
"
|
||||
Spinner Connecting to MCP servers... (1/2) - Waiting for: server2
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ConfigInitDisplay > updates message on McpClientUpdate event 2`] = `
|
||||
"
|
||||
ERROR Cannot read properties of undefined (reading 'isKittyProtocolEnabled')
|
||||
|
||||
src/ui/contexts/KeypressContext.tsx:797:36
|
||||
|
||||
794: process.stdin.setEncoding('utf8'); // Make data events emit strings
|
||||
795:
|
||||
796: let processor = nonKeyboardEventFilter(broadcast);
|
||||
797: if (!terminalCapabilityManager.isKittyProtocolEnabled()) {
|
||||
798: processor = bufferFastReturn(processor);
|
||||
799: }
|
||||
800: processor = bufferBackslashEnter(processor);
|
||||
|
||||
- (src/ui/contexts/KeypressContext.tsx:797:36)
|
||||
-Object.react-stack-bott
|
||||
m-frame (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs
|
||||
/react-reconciler.development.js:15945:20)
|
||||
-runWithFiberIn
|
||||
EV (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/react-re
|
||||
conciler.development.js:1738:13)
|
||||
-commitHookEffectList
|
||||
ount (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/re
|
||||
act-reconciler.development.js:9516:29)
|
||||
-commitHookPassiveMount
|
||||
ffects (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/
|
||||
react-reconciler.development.js:9639:11)
|
||||
-commitPassiveMountOn
|
||||
iber (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/re
|
||||
act-reconciler.development.js:11364:13)
|
||||
-recursivelyTraversePassiveM
|
||||
untEffects (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler
|
||||
/cjs/react-reconciler.development.js:11338:11)
|
||||
-commitPassiveMountOn
|
||||
iber (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/re
|
||||
act-reconciler.development.js:11479:11)
|
||||
-recursivelyTraversePassiveM
|
||||
untEffects (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler
|
||||
/cjs/react-reconciler.development.js:11338:11)
|
||||
-commitPassiveMountOn
|
||||
iber (/Users/spencertang/Workspace/gemini-cli/node_modules/react-reconciler/cjs/re
|
||||
act-reconciler.development.js:11357:11)
|
||||
"
|
||||
`;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,378 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { TerminalCapabilityManager } from './terminalCapabilityManager.js';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import {
|
||||
enableKittyKeyboardProtocol,
|
||||
enableModifyOtherKeys,
|
||||
} from '@google/gemini-cli-core';
|
||||
import * as fs from 'node:fs';
|
||||
|
||||
// Mock fs
|
||||
vi.mock('node:fs', () => ({
|
||||
writeSync: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock core
|
||||
vi.mock('@google/gemini-cli-core', () => ({
|
||||
debugLogger: {
|
||||
log: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
},
|
||||
enableKittyKeyboardProtocol: vi.fn(),
|
||||
disableKittyKeyboardProtocol: vi.fn(),
|
||||
enableModifyOtherKeys: vi.fn(),
|
||||
disableModifyOtherKeys: vi.fn(),
|
||||
enableBracketedPasteMode: vi.fn(),
|
||||
disableBracketedPasteMode: vi.fn(),
|
||||
}));
|
||||
|
||||
describe('TerminalCapabilityManager', () => {
|
||||
let stdin: EventEmitter & {
|
||||
isTTY?: boolean;
|
||||
isRaw?: boolean;
|
||||
setRawMode?: (mode: boolean) => void;
|
||||
removeListener?: (
|
||||
event: string,
|
||||
listener: (...args: unknown[]) => void,
|
||||
) => void;
|
||||
};
|
||||
let stdout: { isTTY?: boolean; fd?: number };
|
||||
// Save original process properties
|
||||
const originalStdin = process.stdin;
|
||||
const originalStdout = process.stdout;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
|
||||
// Reset singleton
|
||||
TerminalCapabilityManager.resetInstanceForTesting();
|
||||
|
||||
// Setup process mocks
|
||||
stdin = new EventEmitter();
|
||||
stdin.isTTY = true;
|
||||
stdin.isRaw = false;
|
||||
stdin.setRawMode = vi.fn();
|
||||
stdin.removeListener = vi.fn();
|
||||
|
||||
stdout = { isTTY: true, fd: 1 };
|
||||
|
||||
// Use defineProperty to mock process.stdin/stdout
|
||||
Object.defineProperty(process, 'stdin', {
|
||||
value: stdin,
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(process, 'stdout', {
|
||||
value: stdout,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
// Restore original process properties
|
||||
Object.defineProperty(process, 'stdin', {
|
||||
value: originalStdin,
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(process, 'stdout', {
|
||||
value: originalStdout,
|
||||
configurable: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should detect Kitty support when u response is received', async () => {
|
||||
const manager = TerminalCapabilityManager.getInstance();
|
||||
const promise = manager.detectCapabilities();
|
||||
|
||||
// Simulate Kitty response: \x1b[?1u
|
||||
stdin.emit('data', Buffer.from('\x1b[?1u'));
|
||||
// Complete detection with DA1
|
||||
stdin.emit('data', Buffer.from('\x1b[?62c'));
|
||||
|
||||
await promise;
|
||||
expect(manager.isKittyProtocolEnabled()).toBe(true);
|
||||
});
|
||||
|
||||
it('should detect Background Color', async () => {
|
||||
const manager = TerminalCapabilityManager.getInstance();
|
||||
const promise = manager.detectCapabilities();
|
||||
|
||||
// Simulate OSC 11 response
|
||||
// \x1b]11;rgb:0000/ff00/0000\x1b\
|
||||
// RGB: 0, 255, 0 -> #00ff00
|
||||
stdin.emit('data', Buffer.from('\x1b]11;rgb:0000/ffff/0000\x1b\\'));
|
||||
// Complete detection with DA1
|
||||
stdin.emit('data', Buffer.from('\x1b[?62c'));
|
||||
|
||||
await promise;
|
||||
expect(manager.getTerminalBackgroundColor()).toBe('#00ff00');
|
||||
});
|
||||
|
||||
it('should detect Terminal Name', async () => {
|
||||
const manager = TerminalCapabilityManager.getInstance();
|
||||
const promise = manager.detectCapabilities();
|
||||
|
||||
// Simulate Terminal Name response
|
||||
stdin.emit('data', Buffer.from('\x1bP>|WezTerm 20240203\x1b\\'));
|
||||
// Complete detection with DA1
|
||||
stdin.emit('data', Buffer.from('\x1b[?62c'));
|
||||
|
||||
await promise;
|
||||
expect(manager.getTerminalName()).toBe('WezTerm 20240203');
|
||||
});
|
||||
|
||||
it('should complete early if sentinel (DA1) is found', async () => {
|
||||
const manager = TerminalCapabilityManager.getInstance();
|
||||
const promise = manager.detectCapabilities();
|
||||
|
||||
stdin.emit('data', Buffer.from('\x1b[?1u'));
|
||||
stdin.emit('data', Buffer.from('\x1b]11;rgb:0000/0000/0000\x1b\\'));
|
||||
// Sentinel
|
||||
stdin.emit('data', Buffer.from('\x1b[?62c'));
|
||||
|
||||
// Should resolve without waiting for timeout
|
||||
await promise;
|
||||
|
||||
expect(manager.isKittyProtocolEnabled()).toBe(true);
|
||||
expect(manager.getTerminalBackgroundColor()).toBe('#000000');
|
||||
});
|
||||
|
||||
it('should timeout if no DA1 (c) is received', async () => {
|
||||
const manager = TerminalCapabilityManager.getInstance();
|
||||
const promise = manager.detectCapabilities();
|
||||
|
||||
// Simulate only Kitty response
|
||||
stdin.emit('data', Buffer.from('\x1b[?1u'));
|
||||
|
||||
// Advance to timeout
|
||||
vi.advanceTimersByTime(1000);
|
||||
|
||||
await promise;
|
||||
expect(manager.isKittyProtocolEnabled()).toBe(true);
|
||||
});
|
||||
|
||||
it('should not detect Kitty if only DA1 (c) is received', async () => {
|
||||
const manager = TerminalCapabilityManager.getInstance();
|
||||
const promise = manager.detectCapabilities();
|
||||
|
||||
// Simulate DA1 response only: \x1b[?62;c
|
||||
stdin.emit('data', Buffer.from('\x1b[?62c'));
|
||||
|
||||
await promise;
|
||||
expect(manager.isKittyProtocolEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle split chunks', async () => {
|
||||
const manager = TerminalCapabilityManager.getInstance();
|
||||
const promise = manager.detectCapabilities();
|
||||
|
||||
// Split response: \x1b[? 1u
|
||||
stdin.emit('data', Buffer.from('\x1b[?'));
|
||||
stdin.emit('data', Buffer.from('1u'));
|
||||
// Complete with DA1
|
||||
stdin.emit('data', Buffer.from('\x1b[?62c'));
|
||||
|
||||
await promise;
|
||||
expect(manager.isKittyProtocolEnabled()).toBe(true);
|
||||
});
|
||||
|
||||
describe('modifyOtherKeys detection', () => {
|
||||
it('should detect modifyOtherKeys support (level 2)', async () => {
|
||||
const manager = TerminalCapabilityManager.getInstance();
|
||||
const promise = manager.detectCapabilities();
|
||||
|
||||
// Simulate modifyOtherKeys level 2 response: \x1b[>4;2m
|
||||
stdin.emit('data', Buffer.from('\x1b[>4;2m'));
|
||||
// Complete detection with DA1
|
||||
stdin.emit('data', Buffer.from('\x1b[?62c'));
|
||||
|
||||
await promise;
|
||||
|
||||
expect(enableModifyOtherKeys).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not enable modifyOtherKeys for level 0', async () => {
|
||||
const manager = TerminalCapabilityManager.getInstance();
|
||||
const promise = manager.detectCapabilities();
|
||||
|
||||
// Simulate modifyOtherKeys level 0 response: \x1b[>4;0m
|
||||
stdin.emit('data', Buffer.from('\x1b[>4;0m'));
|
||||
// Complete detection with DA1
|
||||
stdin.emit('data', Buffer.from('\x1b[?62c'));
|
||||
|
||||
await promise;
|
||||
|
||||
expect(enableModifyOtherKeys).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should prefer Kitty over modifyOtherKeys', async () => {
|
||||
const manager = TerminalCapabilityManager.getInstance();
|
||||
const promise = manager.detectCapabilities();
|
||||
|
||||
// Simulate both Kitty and modifyOtherKeys responses
|
||||
stdin.emit('data', Buffer.from('\x1b[?1u'));
|
||||
stdin.emit('data', Buffer.from('\x1b[>4;2m'));
|
||||
// Complete detection with DA1
|
||||
stdin.emit('data', Buffer.from('\x1b[?62c'));
|
||||
|
||||
await promise;
|
||||
expect(manager.isKittyProtocolEnabled()).toBe(true);
|
||||
|
||||
expect(enableKittyKeyboardProtocol).toHaveBeenCalled();
|
||||
expect(enableModifyOtherKeys).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should enable modifyOtherKeys when Kitty not supported', async () => {
|
||||
const manager = TerminalCapabilityManager.getInstance();
|
||||
const promise = manager.detectCapabilities();
|
||||
|
||||
// Simulate only modifyOtherKeys response (no Kitty)
|
||||
stdin.emit('data', Buffer.from('\x1b[>4;2m'));
|
||||
// Complete detection with DA1
|
||||
stdin.emit('data', Buffer.from('\x1b[?62c'));
|
||||
|
||||
await promise;
|
||||
|
||||
expect(manager.isKittyProtocolEnabled()).toBe(false);
|
||||
expect(enableModifyOtherKeys).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle split modifyOtherKeys response chunks', async () => {
|
||||
const manager = TerminalCapabilityManager.getInstance();
|
||||
const promise = manager.detectCapabilities();
|
||||
|
||||
// Split response: \x1b[>4;2m
|
||||
stdin.emit('data', Buffer.from('\x1b[>4;'));
|
||||
stdin.emit('data', Buffer.from('2m'));
|
||||
// Complete detection with DA1
|
||||
stdin.emit('data', Buffer.from('\x1b[?62c'));
|
||||
|
||||
await promise;
|
||||
|
||||
expect(enableModifyOtherKeys).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should detect modifyOtherKeys with other capabilities', async () => {
|
||||
const manager = TerminalCapabilityManager.getInstance();
|
||||
const promise = manager.detectCapabilities();
|
||||
|
||||
stdin.emit('data', Buffer.from('\x1b]11;rgb:1a1a/1a1a/1a1a\x1b\\')); // background color
|
||||
stdin.emit('data', Buffer.from('\x1bP>|tmux\x1b\\')); // Terminal name
|
||||
stdin.emit('data', Buffer.from('\x1b[>4;2m')); // modifyOtherKeys
|
||||
// Complete detection with DA1
|
||||
stdin.emit('data', Buffer.from('\x1b[?62c'));
|
||||
|
||||
await promise;
|
||||
|
||||
expect(manager.getTerminalBackgroundColor()).toBe('#1a1a1a');
|
||||
expect(manager.getTerminalName()).toBe('tmux');
|
||||
|
||||
expect(enableModifyOtherKeys).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not enable modifyOtherKeys without explicit response', async () => {
|
||||
const manager = TerminalCapabilityManager.getInstance();
|
||||
const promise = manager.detectCapabilities();
|
||||
|
||||
// Simulate only DA1 response (no specific MOK or Kitty response)
|
||||
stdin.emit('data', Buffer.from('\x1b[?62c'));
|
||||
|
||||
await promise;
|
||||
|
||||
expect(manager.isKittyProtocolEnabled()).toBe(false);
|
||||
expect(enableModifyOtherKeys).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should wrap queries in hidden/clear sequence', async () => {
|
||||
const manager = TerminalCapabilityManager.getInstance();
|
||||
void manager.detectCapabilities();
|
||||
|
||||
expect(fs.writeSync).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
// eslint-disable-next-line no-control-regex
|
||||
expect.stringMatching(/^\x1b\[8m.*\x1b\[2K\r\x1b\[0m$/s),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('supportsOsc9Notifications', () => {
|
||||
const manager = TerminalCapabilityManager.getInstance();
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: 'WezTerm (terminal name)',
|
||||
terminalName: 'WezTerm',
|
||||
env: {},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: 'iTerm.app (terminal name)',
|
||||
terminalName: 'iTerm.app',
|
||||
env: {},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: 'ghostty (terminal name)',
|
||||
terminalName: 'ghostty',
|
||||
env: {},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: 'kitty (terminal name)',
|
||||
terminalName: 'kitty',
|
||||
env: {},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: 'some-other-term (terminal name)',
|
||||
terminalName: 'some-other-term',
|
||||
env: {},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: 'iTerm.app (TERM_PROGRAM)',
|
||||
terminalName: undefined,
|
||||
env: { TERM_PROGRAM: 'iTerm.app' },
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: 'vscode (TERM_PROGRAM)',
|
||||
terminalName: undefined,
|
||||
env: { TERM_PROGRAM: 'vscode' },
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: 'xterm-kitty (TERM)',
|
||||
terminalName: undefined,
|
||||
env: { TERM: 'xterm-kitty' },
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: 'xterm-256color (TERM)',
|
||||
terminalName: undefined,
|
||||
env: { TERM: 'xterm-256color' },
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: 'Windows Terminal (WT_SESSION)',
|
||||
terminalName: 'iTerm.app',
|
||||
env: { WT_SESSION: 'some-guid' },
|
||||
expected: false,
|
||||
},
|
||||
])(
|
||||
'should return $expected for $name',
|
||||
({ terminalName, env, expected }) => {
|
||||
vi.spyOn(manager, 'getTerminalName').mockReturnValue(terminalName);
|
||||
expect(manager.supportsOsc9Notifications(env)).toBe(expected);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -84,6 +84,7 @@ describe('SubAgentInvocation', () => {
|
||||
params: {},
|
||||
getDescription: vi.fn(),
|
||||
toolLocations: vi.fn(),
|
||||
isSensitive: false,
|
||||
};
|
||||
|
||||
MockSubagentToolWrapper.prototype.build = vi
|
||||
|
||||
@@ -11,20 +11,6 @@ export function escapeRegex(text: string): string {
|
||||
return text.replace(/[-[\]{}()*+?.,\\^$|#\s"]/g, '\\$&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a string for use in a regular expression that matches a JSON-stringified value.
|
||||
*
|
||||
* This is necessary because some characters (like backslashes and quotes) are
|
||||
* escaped twice in the final JSON string representation used for policy matching.
|
||||
*/
|
||||
export function escapeJsonRegex(text: string): string {
|
||||
// 1. Get the JSON-escaped version of the string (e.g. C:\foo -> C:\\foo)
|
||||
// 2. Remove the surrounding quotes
|
||||
const jsonEscaped = JSON.stringify(text).slice(1, -1);
|
||||
// 3. Regex-escape the result (e.g. C:\\foo -> C:\\\\foo)
|
||||
return escapeRegex(jsonEscaped);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic validation for regular expressions to prevent common ReDoS patterns.
|
||||
* This is a heuristic check and not a substitute for a full ReDoS scanner.
|
||||
|
||||
@@ -293,6 +293,7 @@ describe('AskUserTool', () => {
|
||||
getDescription: vi.fn().mockReturnValue(''),
|
||||
toolLocations: vi.fn().mockReturnValue([]),
|
||||
shouldConfirmExecute: vi.fn().mockResolvedValue(false),
|
||||
isSensitive: false,
|
||||
};
|
||||
|
||||
const buildSpy = vi.spyOn(tool, 'build').mockReturnValue(mockInvocation);
|
||||
|
||||
@@ -202,6 +202,21 @@ describe('EditTool', () => {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('should be marked as sensitive and pass the flag to its invocations', () => {
|
||||
// Check the tool definition itself
|
||||
expect(tool.isSensitive).toBe(true);
|
||||
|
||||
// Build an invocation and check the instance
|
||||
const params: EditToolParams = {
|
||||
file_path: path.join(rootDir, 'test.txt'),
|
||||
instruction: 'An instruction',
|
||||
old_string: 'old',
|
||||
new_string: 'new',
|
||||
};
|
||||
const invocation = tool.build(params);
|
||||
expect(invocation.isSensitive).toBe(true);
|
||||
});
|
||||
|
||||
describe('applyReplacement', () => {
|
||||
it('should return newString if isNewFile is true', () => {
|
||||
expect(applyReplacement(null, 'old', 'new', true)).toBe('new');
|
||||
|
||||
@@ -16,6 +16,7 @@ class TestToolInvocation implements ToolInvocation<object, ToolResult> {
|
||||
constructor(
|
||||
readonly params: object,
|
||||
private readonly executeFn: () => Promise<ToolResult>,
|
||||
readonly isSensitive: boolean = false,
|
||||
) {}
|
||||
|
||||
getDescription(): string {
|
||||
|
||||
@@ -33,6 +33,11 @@ export interface ToolInvocation<
|
||||
*/
|
||||
params: TParams;
|
||||
|
||||
/**
|
||||
* Whether the tool is sensitive and requires specific policy approvals.
|
||||
*/
|
||||
isSensitive: boolean;
|
||||
|
||||
/**
|
||||
* Gets a pre-execution description of the tool operation.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user