feat: add OpenAI OAuth, strict model overrides, and Gmail pull mode

This commit is contained in:
William Valentin
2026-02-13 14:55:40 -08:00
parent 8f644d5e25
commit 955b9e28e0
50 changed files with 5955 additions and 160 deletions
+15
View File
@@ -72,4 +72,19 @@ describe('HookEngine', () => {
expect(result.approved).toBe(false);
expect(result.reason).toBe('Too dangerous');
});
it('uses interactive confirmer when set (no pending queue)', async () => {
const engine = new HookEngine({ confirm: ['shell.*'], log: [], silent: [] });
const confirmer = vi.fn(async () => ({ approved: true }));
engine.setInteractiveConfirmer(confirmer);
const result = await engine.requestConfirmation('shell.exec', { cmd: 'ls' });
expect(result.approved).toBe(true);
expect(engine.getPendingConfirmations()).toHaveLength(0);
expect(confirmer).toHaveBeenCalledOnce();
expect(confirmer).toHaveBeenCalledWith(expect.objectContaining({
tool: 'shell.exec',
args: { cmd: 'ls' },
}));
});
});
+20
View File
@@ -1,16 +1,32 @@
import { randomUUID } from 'crypto';
import type { HookAction, HookResult, PendingConfirmation, HookConfig } from './types.js';
export type InteractiveConfirmer = (pending: {
id: string;
tool: string;
args: Record<string, unknown>;
}) => Promise<HookResult>;
export class HookEngine {
private confirmPatterns: RegExp[];
private logPatterns: RegExp[];
private pendingConfirmations: Map<string, PendingConfirmation> = new Map();
private interactiveConfirmer?: InteractiveConfirmer;
constructor(config: HookConfig) {
this.confirmPatterns = config.confirm.map(p => this.patternToRegex(p));
this.logPatterns = config.log.map(p => this.patternToRegex(p));
}
/**
* Optional interactive confirmation handler.
* When set, confirmation requests are handled immediately (no pending queue).
* Useful for CLI/TUI environments where we can prompt the user inline.
*/
setInteractiveConfirmer(confirmer: InteractiveConfirmer | undefined): void {
this.interactiveConfirmer = confirmer;
}
private patternToRegex(pattern: string): RegExp {
const escaped = pattern
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
@@ -31,6 +47,10 @@ export class HookEngine {
async requestConfirmation(tool: string, args: Record<string, unknown>): Promise<HookResult> {
const id = randomUUID();
if (this.interactiveConfirmer) {
return await this.interactiveConfirmer({ id, tool, args });
}
return new Promise((resolve) => {
const pending: PendingConfirmation = {
id,