120 lines
2.8 KiB
TypeScript
120 lines
2.8 KiB
TypeScript
// src/permissions/mode.ts
|
|
// Permission mode management (default, acceptEdits, plan, bypassPermissions)
|
|
|
|
export type PermissionMode =
|
|
| "default"
|
|
| "acceptEdits"
|
|
| "plan"
|
|
| "bypassPermissions";
|
|
|
|
// Use globalThis to ensure singleton across bundle
|
|
// This prevents Bun's bundler from creating duplicate instances of the mode manager
|
|
const MODE_KEY = Symbol.for("@letta/permissionMode");
|
|
|
|
type GlobalWithMode = typeof globalThis & {
|
|
[key: symbol]: PermissionMode;
|
|
};
|
|
|
|
function getGlobalMode(): PermissionMode {
|
|
const global = globalThis as GlobalWithMode;
|
|
if (!global[MODE_KEY]) {
|
|
global[MODE_KEY] = "default";
|
|
}
|
|
return global[MODE_KEY];
|
|
}
|
|
|
|
function setGlobalMode(value: PermissionMode): void {
|
|
const global = globalThis as GlobalWithMode;
|
|
global[MODE_KEY] = value;
|
|
}
|
|
|
|
/**
|
|
* Permission mode state for the current session.
|
|
* Set via CLI --permission-mode flag or settings.json defaultMode.
|
|
*/
|
|
class PermissionModeManager {
|
|
private get currentMode(): PermissionMode {
|
|
return getGlobalMode();
|
|
}
|
|
|
|
private set currentMode(value: PermissionMode) {
|
|
setGlobalMode(value);
|
|
}
|
|
|
|
/**
|
|
* Set the permission mode for this session
|
|
*/
|
|
setMode(mode: PermissionMode): void {
|
|
this.currentMode = mode;
|
|
}
|
|
|
|
/**
|
|
* Get the current permission mode
|
|
*/
|
|
getMode(): PermissionMode {
|
|
return this.currentMode;
|
|
}
|
|
|
|
/**
|
|
* Check if a tool should be auto-allowed based on current mode
|
|
* Returns null if mode doesn't apply to this tool
|
|
*/
|
|
checkModeOverride(toolName: string): "allow" | "deny" | null {
|
|
switch (this.currentMode) {
|
|
case "bypassPermissions":
|
|
// Auto-allow everything (except explicit deny rules checked earlier)
|
|
return "allow";
|
|
|
|
case "acceptEdits":
|
|
// Auto-allow edit tools: Write, Edit, MultiEdit, NotebookEdit
|
|
if (["Write", "Edit", "MultiEdit", "NotebookEdit"].includes(toolName)) {
|
|
return "allow";
|
|
}
|
|
return null;
|
|
|
|
case "plan": {
|
|
// Read-only mode: allow analysis tools, deny modification tools
|
|
const allowedInPlan = [
|
|
"Read",
|
|
"Glob",
|
|
"Grep",
|
|
"NotebookRead",
|
|
"TodoWrite",
|
|
];
|
|
const deniedInPlan = [
|
|
"Write",
|
|
"Edit",
|
|
"NotebookEdit",
|
|
"Bash",
|
|
"WebFetch",
|
|
];
|
|
|
|
if (allowedInPlan.includes(toolName)) {
|
|
return "allow";
|
|
}
|
|
if (deniedInPlan.includes(toolName)) {
|
|
return "deny";
|
|
}
|
|
return null;
|
|
}
|
|
|
|
case "default":
|
|
// No mode overrides, use normal permission flow
|
|
return null;
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset to default mode
|
|
*/
|
|
reset(): void {
|
|
this.currentMode = "default";
|
|
}
|
|
}
|
|
|
|
// Singleton instance
|
|
export const permissionMode = new PermissionModeManager();
|