overarch-linux-arm64 0.8.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 (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +214 -0
  3. package/dist/bin/overarch.d.ts +2 -0
  4. package/dist/bin/overarch.js +178 -0
  5. package/dist/src/agent/context.d.ts +14 -0
  6. package/dist/src/agent/context.js +200 -0
  7. package/dist/src/agent/context_compressor.d.ts +21 -0
  8. package/dist/src/agent/context_compressor.js +90 -0
  9. package/dist/src/agent/manifest.d.ts +22 -0
  10. package/dist/src/agent/manifest.js +131 -0
  11. package/dist/src/agent/planner.d.ts +38 -0
  12. package/dist/src/agent/planner.js +118 -0
  13. package/dist/src/agent/runtime.d.ts +59 -0
  14. package/dist/src/agent/runtime.js +400 -0
  15. package/dist/src/cli/approval.d.ts +13 -0
  16. package/dist/src/cli/approval.js +71 -0
  17. package/dist/src/cli/args.d.ts +15 -0
  18. package/dist/src/cli/args.js +47 -0
  19. package/dist/src/cli/commands/audit.d.ts +10 -0
  20. package/dist/src/cli/commands/audit.js +79 -0
  21. package/dist/src/cli/commands/companion.d.ts +1 -0
  22. package/dist/src/cli/commands/companion.js +143 -0
  23. package/dist/src/cli/commands/config.d.ts +1 -0
  24. package/dist/src/cli/commands/config.js +135 -0
  25. package/dist/src/cli/commands/doctor.d.ts +1 -0
  26. package/dist/src/cli/commands/doctor.js +217 -0
  27. package/dist/src/cli/commands/init.d.ts +1 -0
  28. package/dist/src/cli/commands/init.js +134 -0
  29. package/dist/src/cli/commands/memory.d.ts +2 -0
  30. package/dist/src/cli/commands/memory.js +98 -0
  31. package/dist/src/cli/commands/onboarding.d.ts +3 -0
  32. package/dist/src/cli/commands/onboarding.js +223 -0
  33. package/dist/src/cli/commands/sessions.d.ts +1 -0
  34. package/dist/src/cli/commands/sessions.js +22 -0
  35. package/dist/src/cli/commands/status.d.ts +1 -0
  36. package/dist/src/cli/commands/status.js +37 -0
  37. package/dist/src/cli/commands/update.d.ts +9 -0
  38. package/dist/src/cli/commands/update.js +105 -0
  39. package/dist/src/cli/repl.d.ts +3 -0
  40. package/dist/src/cli/repl.js +821 -0
  41. package/dist/src/cli/ui.d.ts +53 -0
  42. package/dist/src/cli/ui.js +215 -0
  43. package/dist/src/companion/petdex_adapter.d.ts +31 -0
  44. package/dist/src/companion/petdex_adapter.js +171 -0
  45. package/dist/src/companion/petdex_bridge.d.ts +65 -0
  46. package/dist/src/companion/petdex_bridge.js +356 -0
  47. package/dist/src/companion/types.d.ts +57 -0
  48. package/dist/src/companion/types.js +11 -0
  49. package/dist/src/config/index.d.ts +24 -0
  50. package/dist/src/config/index.js +237 -0
  51. package/dist/src/config/types.d.ts +58 -0
  52. package/dist/src/config/types.js +7 -0
  53. package/dist/src/core/diagnostics.d.ts +48 -0
  54. package/dist/src/core/diagnostics.js +115 -0
  55. package/dist/src/core/index.d.ts +23 -0
  56. package/dist/src/core/index.js +23 -0
  57. package/dist/src/core/mission.d.ts +55 -0
  58. package/dist/src/core/mission.js +173 -0
  59. package/dist/src/index.d.ts +1 -0
  60. package/dist/src/index.js +1 -0
  61. package/dist/src/learning/memory_manager.d.ts +26 -0
  62. package/dist/src/learning/memory_manager.js +211 -0
  63. package/dist/src/learning/types.d.ts +54 -0
  64. package/dist/src/learning/types.js +1 -0
  65. package/dist/src/mcp/registry.d.ts +17 -0
  66. package/dist/src/mcp/registry.js +97 -0
  67. package/dist/src/mcp/types.d.ts +13 -0
  68. package/dist/src/mcp/types.js +1 -0
  69. package/dist/src/providers/anthropic.d.ts +10 -0
  70. package/dist/src/providers/anthropic.js +95 -0
  71. package/dist/src/providers/base.d.ts +15 -0
  72. package/dist/src/providers/base.js +108 -0
  73. package/dist/src/providers/factory.d.ts +28 -0
  74. package/dist/src/providers/factory.js +142 -0
  75. package/dist/src/providers/gemini.d.ts +10 -0
  76. package/dist/src/providers/gemini.js +97 -0
  77. package/dist/src/providers/openai.d.ts +9 -0
  78. package/dist/src/providers/openai.js +85 -0
  79. package/dist/src/providers/openrouter.d.ts +5 -0
  80. package/dist/src/providers/openrouter.js +14 -0
  81. package/dist/src/providers/registry.d.ts +19 -0
  82. package/dist/src/providers/registry.js +209 -0
  83. package/dist/src/providers/types.d.ts +59 -0
  84. package/dist/src/providers/types.js +1 -0
  85. package/dist/src/providers/validator.d.ts +24 -0
  86. package/dist/src/providers/validator.js +161 -0
  87. package/dist/src/security/audit.d.ts +56 -0
  88. package/dist/src/security/audit.js +154 -0
  89. package/dist/src/security/guardrails.d.ts +9 -0
  90. package/dist/src/security/guardrails.js +32 -0
  91. package/dist/src/security/permissions.d.ts +12 -0
  92. package/dist/src/security/permissions.js +68 -0
  93. package/dist/src/security/policy.d.ts +220 -0
  94. package/dist/src/security/policy.js +720 -0
  95. package/dist/src/session/manager.d.ts +22 -0
  96. package/dist/src/session/manager.js +172 -0
  97. package/dist/src/session/types.d.ts +36 -0
  98. package/dist/src/session/types.js +1 -0
  99. package/dist/src/tools/checkpoint.d.ts +26 -0
  100. package/dist/src/tools/checkpoint.js +160 -0
  101. package/dist/src/tools/diagnostics.d.ts +2 -0
  102. package/dist/src/tools/diagnostics.js +125 -0
  103. package/dist/src/tools/diff.d.ts +14 -0
  104. package/dist/src/tools/diff.js +124 -0
  105. package/dist/src/tools/filesystem.d.ts +2 -0
  106. package/dist/src/tools/filesystem.js +224 -0
  107. package/dist/src/tools/git.d.ts +2 -0
  108. package/dist/src/tools/git.js +138 -0
  109. package/dist/src/tools/markdown.d.ts +33 -0
  110. package/dist/src/tools/markdown.js +190 -0
  111. package/dist/src/tools/project.d.ts +2 -0
  112. package/dist/src/tools/project.js +63 -0
  113. package/dist/src/tools/registry.d.ts +14 -0
  114. package/dist/src/tools/registry.js +230 -0
  115. package/dist/src/tools/search.d.ts +2 -0
  116. package/dist/src/tools/search.js +136 -0
  117. package/dist/src/tools/shell.d.ts +2 -0
  118. package/dist/src/tools/shell.js +74 -0
  119. package/dist/src/tools/symbols.d.ts +30 -0
  120. package/dist/src/tools/symbols.js +155 -0
  121. package/dist/src/tools/types.d.ts +28 -0
  122. package/dist/src/tools/types.js +1 -0
  123. package/dist/src/tools/web_search.d.ts +14 -0
  124. package/dist/src/tools/web_search.js +154 -0
  125. package/install.sh +29 -0
  126. package/overarch +15 -0
  127. package/package.json +28 -0
@@ -0,0 +1,24 @@
1
+ import { Message } from './types.js';
2
+ export interface ValidatedMessagePayload {
3
+ role: 'system' | 'user' | 'assistant' | 'tool';
4
+ content: string | null;
5
+ tool_call_id?: string;
6
+ tool_calls?: Array<{
7
+ id: string;
8
+ type: 'function';
9
+ function: {
10
+ name: string;
11
+ arguments: string;
12
+ };
13
+ }>;
14
+ }
15
+ /**
16
+ * Pre-flight message validator and healer for LLM APIs (especially OpenAI / OpenRouter).
17
+ * Guarantees that every assistant tool call is paired with a corresponding tool result message,
18
+ * and fixes orphaned calls or results that would cause provider 400 Bad Request errors.
19
+ */
20
+ export declare function validateAndHealMessages(messages: Message[]): Message[];
21
+ /**
22
+ * Serializes Message array into strict OpenAI / OpenRouter Chat Completion format.
23
+ */
24
+ export declare function formatForOpenAI(messages: Message[]): ValidatedMessagePayload[];
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Pre-flight message validator and healer for LLM APIs (especially OpenAI / OpenRouter).
3
+ * Guarantees that every assistant tool call is paired with a corresponding tool result message,
4
+ * and fixes orphaned calls or results that would cause provider 400 Bad Request errors.
5
+ */
6
+ export function validateAndHealMessages(messages) {
7
+ if (!messages || messages.length === 0) {
8
+ return [];
9
+ }
10
+ const healed = [];
11
+ const pendingToolCalls = new Map(); // id -> toolName
12
+ for (let i = 0; i < messages.length; i++) {
13
+ const rawMsg = messages[i];
14
+ const role = rawMsg.role;
15
+ const content = rawMsg.content || '';
16
+ const toolCallId = rawMsg.toolCallId || rawMsg.tool_call_id;
17
+ const toolCalls = rawMsg.toolCalls || rawMsg.tool_calls;
18
+ if (role === 'assistant') {
19
+ // If we had pending tool calls from a previous assistant message that were never answered,
20
+ // heal them by inserting synthesized tool result messages before continuing
21
+ if (pendingToolCalls.size > 0) {
22
+ for (const [orphanId, orphanName] of pendingToolCalls.entries()) {
23
+ healed.push({
24
+ role: 'tool',
25
+ toolCallId: orphanId,
26
+ content: JSON.stringify({ success: false, error: `Tool call '${orphanName}' (${orphanId}) had no returned result.` })
27
+ });
28
+ }
29
+ pendingToolCalls.clear();
30
+ }
31
+ if (toolCalls && Array.isArray(toolCalls) && toolCalls.length > 0) {
32
+ const normalizedToolCalls = toolCalls.map((tc, idx) => {
33
+ const id = tc.id || `call_${Date.now()}_${idx}`;
34
+ const name = tc.name || tc.function?.name || 'unknown_tool';
35
+ let args = tc.arguments;
36
+ if (typeof args === 'string') {
37
+ try {
38
+ args = JSON.parse(args);
39
+ }
40
+ catch {
41
+ args = {};
42
+ }
43
+ }
44
+ pendingToolCalls.set(id, name);
45
+ return { id, name, arguments: args || {} };
46
+ });
47
+ healed.push({
48
+ role: 'assistant',
49
+ content,
50
+ toolCalls: normalizedToolCalls
51
+ });
52
+ }
53
+ else {
54
+ healed.push({
55
+ role: 'assistant',
56
+ content
57
+ });
58
+ }
59
+ }
60
+ else if (role === 'tool') {
61
+ const validId = toolCallId || `call_unpaired_${Date.now()}`;
62
+ if (pendingToolCalls.has(validId)) {
63
+ pendingToolCalls.delete(validId);
64
+ healed.push({
65
+ role: 'tool',
66
+ toolCallId: validId,
67
+ content
68
+ });
69
+ }
70
+ else if (pendingToolCalls.size > 0) {
71
+ // Match first available pending tool call if ID mismatched due to corruption
72
+ const firstPendingId = pendingToolCalls.keys().next().value;
73
+ if (firstPendingId) {
74
+ pendingToolCalls.delete(firstPendingId);
75
+ healed.push({
76
+ role: 'tool',
77
+ toolCallId: firstPendingId,
78
+ content
79
+ });
80
+ }
81
+ }
82
+ else {
83
+ // Orphaned tool response with no preceding tool call.
84
+ // Convert to a system/user observation so providers don't reject it with missing tool_call_id
85
+ healed.push({
86
+ role: 'user',
87
+ content: `[Previous Tool Observation]: ${content}`
88
+ });
89
+ }
90
+ }
91
+ else {
92
+ // System or User message.
93
+ // If there are pending tool calls, heal them first!
94
+ if (pendingToolCalls.size > 0) {
95
+ for (const [orphanId, orphanName] of pendingToolCalls.entries()) {
96
+ healed.push({
97
+ role: 'tool',
98
+ toolCallId: orphanId,
99
+ content: JSON.stringify({ success: false, error: `Tool call '${orphanName}' (${orphanId}) was interrupted.` })
100
+ });
101
+ }
102
+ pendingToolCalls.clear();
103
+ }
104
+ healed.push({
105
+ role: role,
106
+ content
107
+ });
108
+ }
109
+ }
110
+ // Trailing check: if the very last message was an assistant tool call, synthesize results
111
+ if (pendingToolCalls.size > 0) {
112
+ for (const [orphanId, orphanName] of pendingToolCalls.entries()) {
113
+ healed.push({
114
+ role: 'tool',
115
+ toolCallId: orphanId,
116
+ content: JSON.stringify({ success: false, error: `Tool call '${orphanName}' (${orphanId}) awaiting completion.` })
117
+ });
118
+ }
119
+ pendingToolCalls.clear();
120
+ }
121
+ return healed;
122
+ }
123
+ /**
124
+ * Serializes Message array into strict OpenAI / OpenRouter Chat Completion format.
125
+ */
126
+ export function formatForOpenAI(messages) {
127
+ const healed = validateAndHealMessages(messages);
128
+ return healed.map((m) => {
129
+ if (m.role === 'tool') {
130
+ return {
131
+ role: 'tool',
132
+ tool_call_id: m.toolCallId || 'call_fallback',
133
+ content: m.content
134
+ };
135
+ }
136
+ if (m.role === 'assistant') {
137
+ if (m.toolCalls && m.toolCalls.length > 0) {
138
+ return {
139
+ role: 'assistant',
140
+ content: m.content || null,
141
+ tool_calls: m.toolCalls.map((tc) => ({
142
+ id: tc.id,
143
+ type: 'function',
144
+ function: {
145
+ name: tc.name,
146
+ arguments: JSON.stringify(tc.arguments || {})
147
+ }
148
+ }))
149
+ };
150
+ }
151
+ return {
152
+ role: 'assistant',
153
+ content: m.content || ''
154
+ };
155
+ }
156
+ return {
157
+ role: m.role,
158
+ content: m.content || ''
159
+ };
160
+ });
161
+ }
@@ -0,0 +1,56 @@
1
+ import { Capability, RiskLevel } from './policy.js';
2
+ export interface AuditRecord {
3
+ id: string;
4
+ timestamp: string;
5
+ action: string;
6
+ capability: Capability;
7
+ riskLevel: RiskLevel;
8
+ decision: 'ALLOWED' | 'DENIED' | 'APPROVED' | 'ESCALATED' | 'STOPPED';
9
+ outcome: 'SUCCESS' | 'FAILED' | 'BLOCKED' | 'PENDING';
10
+ reason?: string;
11
+ params?: Record<string, any>;
12
+ durationMs?: number;
13
+ }
14
+ export interface AuditFilterOptions {
15
+ limit?: number;
16
+ riskLevel?: RiskLevel;
17
+ decision?: string;
18
+ action?: string;
19
+ capability?: Capability;
20
+ since?: string;
21
+ }
22
+ export declare class AuditLogger {
23
+ private logPath;
24
+ private static instance;
25
+ constructor(customPath?: string);
26
+ static getInstance(customPath?: string): AuditLogger;
27
+ getLogPath(): string;
28
+ setLogPath(newPath: string): void;
29
+ /**
30
+ * Append an entry to the append-only JSONL audit log.
31
+ */
32
+ log(entry: {
33
+ action: string;
34
+ capability: Capability;
35
+ riskLevel: RiskLevel;
36
+ decision: 'ALLOWED' | 'DENIED' | 'APPROVED' | 'ESCALATED' | 'STOPPED';
37
+ outcome: 'SUCCESS' | 'FAILED' | 'BLOCKED' | 'PENDING';
38
+ reason?: string;
39
+ params?: Record<string, any>;
40
+ durationMs?: number;
41
+ }): AuditRecord;
42
+ /**
43
+ * Read and filter entries from the audit log.
44
+ */
45
+ readEntries(options?: AuditFilterOptions): AuditRecord[];
46
+ /**
47
+ * Export audit log to a target file in JSON, JSONL, or CSV format.
48
+ */
49
+ exportEntries(destinationPath: string, format?: 'json' | 'jsonl' | 'csv'): string;
50
+ /**
51
+ * Clear audit log (primarily for testing).
52
+ */
53
+ clear(): void;
54
+ private sanitizeParams;
55
+ }
56
+ export declare const auditLogger: AuditLogger;
@@ -0,0 +1,154 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as os from 'node:os';
4
+ import * as crypto from 'node:crypto';
5
+ export class AuditLogger {
6
+ logPath;
7
+ static instance = null;
8
+ constructor(customPath) {
9
+ if (customPath) {
10
+ this.logPath = customPath;
11
+ }
12
+ else {
13
+ const workspaceDir = path.join(process.cwd(), '.overarch');
14
+ if (fs.existsSync(workspaceDir)) {
15
+ this.logPath = path.join(workspaceDir, 'audit.jsonl');
16
+ }
17
+ else {
18
+ const globalDir = path.join(os.homedir(), '.overarch');
19
+ if (!fs.existsSync(globalDir)) {
20
+ try {
21
+ fs.mkdirSync(globalDir, { recursive: true });
22
+ }
23
+ catch { }
24
+ }
25
+ this.logPath = path.join(globalDir, 'audit.jsonl');
26
+ }
27
+ }
28
+ }
29
+ static getInstance(customPath) {
30
+ if (!AuditLogger.instance || customPath) {
31
+ AuditLogger.instance = new AuditLogger(customPath);
32
+ }
33
+ return AuditLogger.instance;
34
+ }
35
+ getLogPath() {
36
+ return this.logPath;
37
+ }
38
+ setLogPath(newPath) {
39
+ this.logPath = newPath;
40
+ }
41
+ /**
42
+ * Append an entry to the append-only JSONL audit log.
43
+ */
44
+ log(entry) {
45
+ const record = {
46
+ id: crypto.randomBytes(8).toString('hex'),
47
+ timestamp: new Date().toISOString(),
48
+ action: entry.action,
49
+ capability: entry.capability,
50
+ riskLevel: entry.riskLevel,
51
+ decision: entry.decision,
52
+ outcome: entry.outcome,
53
+ reason: entry.reason,
54
+ params: entry.params ? this.sanitizeParams(entry.params) : undefined,
55
+ durationMs: entry.durationMs,
56
+ };
57
+ try {
58
+ const dir = path.dirname(this.logPath);
59
+ if (!fs.existsSync(dir)) {
60
+ fs.mkdirSync(dir, { recursive: true });
61
+ }
62
+ fs.appendFileSync(this.logPath, JSON.stringify(record) + '\n', 'utf8');
63
+ }
64
+ catch (err) {
65
+ // Append-only logging fails closed without crashing runtime
66
+ console.error(`[Overarch Audit] Failed to append audit log: ${err}`);
67
+ }
68
+ return record;
69
+ }
70
+ /**
71
+ * Read and filter entries from the audit log.
72
+ */
73
+ readEntries(options = {}) {
74
+ if (!fs.existsSync(this.logPath)) {
75
+ return [];
76
+ }
77
+ try {
78
+ const content = fs.readFileSync(this.logPath, 'utf8');
79
+ const lines = content.split('\n').filter(line => line.trim().length > 0);
80
+ const records = [];
81
+ for (const line of lines) {
82
+ try {
83
+ const rec = JSON.parse(line);
84
+ if (options.riskLevel && rec.riskLevel !== options.riskLevel)
85
+ continue;
86
+ if (options.decision && rec.decision.toLowerCase() !== options.decision.toLowerCase())
87
+ continue;
88
+ if (options.action && !rec.action.toLowerCase().includes(options.action.toLowerCase()))
89
+ continue;
90
+ if (options.capability && rec.capability !== options.capability)
91
+ continue;
92
+ if (options.since && new Date(rec.timestamp) < new Date(options.since))
93
+ continue;
94
+ records.push(rec);
95
+ }
96
+ catch { }
97
+ }
98
+ if (options.limit && options.limit > 0) {
99
+ return records.slice(-options.limit);
100
+ }
101
+ return records;
102
+ }
103
+ catch {
104
+ return [];
105
+ }
106
+ }
107
+ /**
108
+ * Export audit log to a target file in JSON, JSONL, or CSV format.
109
+ */
110
+ exportEntries(destinationPath, format = 'jsonl') {
111
+ const entries = this.readEntries();
112
+ const destDir = path.dirname(destinationPath);
113
+ if (!fs.existsSync(destDir)) {
114
+ fs.mkdirSync(destDir, { recursive: true });
115
+ }
116
+ if (format === 'json') {
117
+ fs.writeFileSync(destinationPath, JSON.stringify(entries, null, 2), 'utf8');
118
+ }
119
+ else if (format === 'csv') {
120
+ const header = 'id,timestamp,action,capability,riskLevel,decision,outcome,reason\n';
121
+ const rows = entries.map(e => `"${e.id}","${e.timestamp}","${e.action}","${e.capability}","${e.riskLevel}","${e.decision}","${e.outcome}","${(e.reason || '').replace(/"/g, '""')}"`).join('\n');
122
+ fs.writeFileSync(destinationPath, header + rows + '\n', 'utf8');
123
+ }
124
+ else {
125
+ const content = entries.map(e => JSON.stringify(e)).join('\n') + (entries.length ? '\n' : '');
126
+ fs.writeFileSync(destinationPath, content, 'utf8');
127
+ }
128
+ return destinationPath;
129
+ }
130
+ /**
131
+ * Clear audit log (primarily for testing).
132
+ */
133
+ clear() {
134
+ if (fs.existsSync(this.logPath)) {
135
+ fs.writeFileSync(this.logPath, '', 'utf8');
136
+ }
137
+ }
138
+ sanitizeParams(params) {
139
+ const sanitized = {};
140
+ for (const [key, val] of Object.entries(params)) {
141
+ if (/key|secret|token|password|auth/i.test(key)) {
142
+ sanitized[key] = '[REDACTED]';
143
+ }
144
+ else if (typeof val === 'string' && val.length > 200) {
145
+ sanitized[key] = val.slice(0, 197) + '...';
146
+ }
147
+ else {
148
+ sanitized[key] = val;
149
+ }
150
+ }
151
+ return sanitized;
152
+ }
153
+ }
154
+ export const auditLogger = AuditLogger.getInstance();
@@ -0,0 +1,9 @@
1
+ export declare function validatePath(filepath: string, basePath: string): boolean;
2
+ export declare const DANGEROUS_COMMANDS: string[];
3
+ export declare function isCommandDangerous(command: string): {
4
+ dangerous: boolean;
5
+ reason?: string;
6
+ };
7
+ export declare function abort(): void;
8
+ export declare function isAborted(): boolean;
9
+ export declare function resetAbort(): void;
@@ -0,0 +1,32 @@
1
+ import * as path from 'node:path';
2
+ export function validatePath(filepath, basePath) {
3
+ const resolvedPath = path.resolve(basePath, filepath);
4
+ const resolvedBase = path.resolve(basePath);
5
+ return resolvedPath.startsWith(resolvedBase);
6
+ }
7
+ export const DANGEROUS_COMMANDS = [
8
+ 'rm -rf /',
9
+ 'format',
10
+ ':(){:|:&};:',
11
+ 'dd if=',
12
+ 'mkfs',
13
+ '> /dev/sda'
14
+ ];
15
+ export function isCommandDangerous(command) {
16
+ for (const pattern of DANGEROUS_COMMANDS) {
17
+ if (command.includes(pattern)) {
18
+ return { dangerous: true, reason: `Matches dangerous pattern: ${pattern}` };
19
+ }
20
+ }
21
+ return { dangerous: false };
22
+ }
23
+ let aborted = false;
24
+ export function abort() {
25
+ aborted = true;
26
+ }
27
+ export function isAborted() {
28
+ return aborted;
29
+ }
30
+ export function resetAbort() {
31
+ aborted = false;
32
+ }
@@ -0,0 +1,12 @@
1
+ import { PermissionTier, PermissionConfig } from '../config/types.js';
2
+ import { ToolDefinition } from '../tools/types.js';
3
+ import * as readline from 'node:readline/promises';
4
+ export declare function classifyAction(tool: ToolDefinition): PermissionTier;
5
+ export declare function checkPermission(tool: ToolDefinition, params: Record<string, unknown>, config: PermissionConfig, options?: {
6
+ autoApprove?: boolean;
7
+ rl?: readline.Interface;
8
+ }): Promise<{
9
+ allowed: boolean;
10
+ reason?: string;
11
+ }>;
12
+ export declare function formatPermissionPrompt(tool: ToolDefinition, params: Record<string, unknown>): string;
@@ -0,0 +1,68 @@
1
+ import { PermissionTier } from '../config/types.js';
2
+ import * as readline from 'node:readline/promises';
3
+ import { stdin, stdout } from 'node:process';
4
+ export function classifyAction(tool) {
5
+ return tool.tier;
6
+ }
7
+ export async function checkPermission(tool, params, config, options) {
8
+ const tier = classifyAction(tool);
9
+ // Check deny list
10
+ if (config.deny?.includes(tool.name)) {
11
+ return { allowed: false, reason: `Tool '${tool.name}' is in the deny list` };
12
+ }
13
+ if (tier === PermissionTier.ReadOnly) {
14
+ return { allowed: true };
15
+ }
16
+ if (tier === PermissionTier.SafeMutation) {
17
+ return { allowed: true };
18
+ }
19
+ if (tier === PermissionTier.StateMutating) {
20
+ if (options?.autoApprove)
21
+ return { allowed: true };
22
+ if (config.autoApprove?.includes(PermissionTier.StateMutating))
23
+ return { allowed: true };
24
+ const prompt = formatPermissionPrompt(tool, params);
25
+ let answer = '';
26
+ if (options?.rl) {
27
+ answer = await options.rl.question(`\n${prompt}\nAllow? [Y/n] `);
28
+ }
29
+ else {
30
+ const rl = readline.createInterface({ input: stdin, output: stdout });
31
+ answer = await rl.question(`\n${prompt}\nAllow? [Y/n] `);
32
+ rl.close();
33
+ }
34
+ if (answer.trim().toLowerCase() === 'n') {
35
+ return { allowed: false, reason: 'User denied permission' };
36
+ }
37
+ return { allowed: true };
38
+ }
39
+ if (tier === PermissionTier.Destructive) {
40
+ if (options?.autoApprove) {
41
+ // Even with autoApprove, destructive actions need explicit confirmation
42
+ // unless the config specifically auto-approves destructive
43
+ if (!config.autoApprove?.includes(PermissionTier.Destructive)) {
44
+ const prompt = formatPermissionPrompt(tool, params);
45
+ let answer = '';
46
+ if (options?.rl) {
47
+ answer = await options.rl.question(`\n⚠ DESTRUCTIVE ACTION\n${prompt}\nType 'yes' to confirm: `);
48
+ }
49
+ else {
50
+ const rl = readline.createInterface({ input: stdin, output: stdout });
51
+ answer = await rl.question(`\n⚠ DESTRUCTIVE ACTION\n${prompt}\nType 'yes' to confirm: `);
52
+ rl.close();
53
+ }
54
+ if (answer.trim().toLowerCase() !== 'yes') {
55
+ return { allowed: false, reason: 'User denied destructive action' };
56
+ }
57
+ }
58
+ }
59
+ return { allowed: true };
60
+ }
61
+ return { allowed: false, reason: 'Unknown permission tier' };
62
+ }
63
+ export function formatPermissionPrompt(tool, params) {
64
+ const paramStr = Object.entries(params)
65
+ .map(([k, v]) => ` ${k}: ${typeof v === 'string' ? v : JSON.stringify(v)}`)
66
+ .join('\n');
67
+ return `Tool: ${tool.name}\n${paramStr}`;
68
+ }