@sarkar-ai/deskmate 0.2.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/.env.example +49 -0
- package/LICENSE +21 -0
- package/README.md +360 -0
- package/dist/cli/init.js +387 -0
- package/dist/cli.js +184 -0
- package/dist/clients/telegram.js +148 -0
- package/dist/core/agent/factory.js +67 -0
- package/dist/core/agent/index.js +32 -0
- package/dist/core/agent/providers/claude-code.js +158 -0
- package/dist/core/agent/types.js +9 -0
- package/dist/core/approval.js +192 -0
- package/dist/core/executor.js +237 -0
- package/dist/core/logger.js +76 -0
- package/dist/core/platform.js +130 -0
- package/dist/gateway/gateway.js +435 -0
- package/dist/gateway/index.js +9 -0
- package/dist/gateway/security.js +35 -0
- package/dist/gateway/session.js +195 -0
- package/dist/gateway/types.js +8 -0
- package/dist/index.js +130 -0
- package/dist/mcp/server.js +156 -0
- package/dist/telegram/bot.js +333 -0
- package/install.sh +817 -0
- package/package.json +73 -0
- package/uninstall.sh +121 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TelegramClient = void 0;
|
|
4
|
+
const grammy_1 = require("grammy");
|
|
5
|
+
const logger_1 = require("../core/logger");
|
|
6
|
+
const log = (0, logger_1.createLogger)("TelegramClient");
|
|
7
|
+
class TelegramClient {
|
|
8
|
+
clientType = "telegram";
|
|
9
|
+
bot;
|
|
10
|
+
handler = null;
|
|
11
|
+
constructor(token) {
|
|
12
|
+
this.bot = new grammy_1.Bot(token);
|
|
13
|
+
}
|
|
14
|
+
async start(handler) {
|
|
15
|
+
this.handler = handler;
|
|
16
|
+
// Map commands to IncomingMessage
|
|
17
|
+
this.bot.command("start", (ctx) => this.dispatch(ctx, "start"));
|
|
18
|
+
this.bot.command("screenshot", (ctx) => this.dispatch(ctx, "screenshot"));
|
|
19
|
+
this.bot.command("status", (ctx) => this.dispatch(ctx, "status"));
|
|
20
|
+
this.bot.command("reset", (ctx) => this.dispatch(ctx, "reset"));
|
|
21
|
+
// Text messages
|
|
22
|
+
this.bot.on("message:text", async (ctx) => {
|
|
23
|
+
if (!this.handler)
|
|
24
|
+
return;
|
|
25
|
+
await this.handler.handleMessage({
|
|
26
|
+
platformMessageId: String(ctx.message.message_id),
|
|
27
|
+
channelId: String(ctx.chat.id),
|
|
28
|
+
userId: String(ctx.from?.id ?? ""),
|
|
29
|
+
clientType: this.clientType,
|
|
30
|
+
text: ctx.message.text,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
// Approval callbacks
|
|
34
|
+
this.bot.callbackQuery(/^approve:(.+)$/, async (ctx) => {
|
|
35
|
+
if (!this.handler)
|
|
36
|
+
return;
|
|
37
|
+
const actionId = ctx.match[1];
|
|
38
|
+
await this.handler.handleApproval({ actionId, approved: true }, String(ctx.chat?.id ?? ""));
|
|
39
|
+
await ctx.answerCallbackQuery({ text: "Approved!" });
|
|
40
|
+
});
|
|
41
|
+
this.bot.callbackQuery(/^reject:(.+)$/, async (ctx) => {
|
|
42
|
+
if (!this.handler)
|
|
43
|
+
return;
|
|
44
|
+
const actionId = ctx.match[1];
|
|
45
|
+
await this.handler.handleApproval({ actionId, approved: false }, String(ctx.chat?.id ?? ""));
|
|
46
|
+
await ctx.answerCallbackQuery({ text: "Rejected" });
|
|
47
|
+
});
|
|
48
|
+
// Start polling
|
|
49
|
+
this.bot.start({
|
|
50
|
+
drop_pending_updates: true,
|
|
51
|
+
onStart: (info) => log.info("Telegram bot started", { username: info.username }),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async dispatch(ctx, command) {
|
|
55
|
+
if (!this.handler)
|
|
56
|
+
return;
|
|
57
|
+
await this.handler.handleMessage({
|
|
58
|
+
platformMessageId: String(ctx.message?.message_id ?? ""),
|
|
59
|
+
channelId: String(ctx.chat.id),
|
|
60
|
+
userId: String(ctx.from?.id ?? ""),
|
|
61
|
+
clientType: this.clientType,
|
|
62
|
+
text: ctx.message?.text ?? "",
|
|
63
|
+
command,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async sendMessage(message) {
|
|
67
|
+
const chatId = Number(message.channelId);
|
|
68
|
+
// Image message
|
|
69
|
+
if (message.image) {
|
|
70
|
+
const source = typeof message.image === "string"
|
|
71
|
+
? new grammy_1.InputFile(message.image)
|
|
72
|
+
: new grammy_1.InputFile(message.image);
|
|
73
|
+
const sent = await this.bot.api.sendPhoto(chatId, source, {
|
|
74
|
+
caption: message.imageCaption,
|
|
75
|
+
});
|
|
76
|
+
return String(sent.message_id);
|
|
77
|
+
}
|
|
78
|
+
const text = message.text ?? "";
|
|
79
|
+
const truncated = text.slice(0, 4000);
|
|
80
|
+
// Edit existing message
|
|
81
|
+
if (message.editMessageId) {
|
|
82
|
+
const msgId = Number(message.editMessageId);
|
|
83
|
+
try {
|
|
84
|
+
if (message.parseMode === "markdown") {
|
|
85
|
+
await this.bot.api.editMessageText(chatId, msgId, truncated, {
|
|
86
|
+
parse_mode: "Markdown",
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
await this.bot.api.editMessageText(chatId, msgId, truncated);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// If markdown parse fails, retry as plain
|
|
95
|
+
await this.bot.api.editMessageText(chatId, msgId, truncated);
|
|
96
|
+
}
|
|
97
|
+
return message.editMessageId;
|
|
98
|
+
}
|
|
99
|
+
// New message
|
|
100
|
+
const opts = {};
|
|
101
|
+
if (message.parseMode === "markdown") {
|
|
102
|
+
opts.parse_mode = "Markdown";
|
|
103
|
+
}
|
|
104
|
+
const sent = await this.bot.api.sendMessage(chatId, truncated, opts);
|
|
105
|
+
return String(sent.message_id);
|
|
106
|
+
}
|
|
107
|
+
async sendApprovalPrompt(prompt) {
|
|
108
|
+
const chatId = Number(prompt.channelId);
|
|
109
|
+
const keyboard = new grammy_1.InlineKeyboard()
|
|
110
|
+
.text("Approve", `approve:${prompt.actionId}`)
|
|
111
|
+
.text("Reject", `reject:${prompt.actionId}`);
|
|
112
|
+
let emoji = "";
|
|
113
|
+
switch (prompt.type) {
|
|
114
|
+
case "command":
|
|
115
|
+
emoji = "Command";
|
|
116
|
+
break;
|
|
117
|
+
case "write_file":
|
|
118
|
+
emoji = "Write File";
|
|
119
|
+
break;
|
|
120
|
+
case "folder_access":
|
|
121
|
+
emoji = "Folder Access";
|
|
122
|
+
break;
|
|
123
|
+
case "read_file":
|
|
124
|
+
emoji = "Read File";
|
|
125
|
+
break;
|
|
126
|
+
default:
|
|
127
|
+
emoji = "Action";
|
|
128
|
+
}
|
|
129
|
+
await this.bot.api.sendMessage(chatId, `*Approval Required — ${emoji}*\n\n` +
|
|
130
|
+
`Type: \`${prompt.type}\`\n` +
|
|
131
|
+
`${prompt.description}\n\n` +
|
|
132
|
+
`${prompt.details}\n\n` +
|
|
133
|
+
`Expires in ${prompt.expiresInSeconds}s`, { parse_mode: "Markdown", reply_markup: keyboard });
|
|
134
|
+
}
|
|
135
|
+
async updateApprovalStatus(channelId, _actionId, approved) {
|
|
136
|
+
// The callback query handler already answered; we don't need to
|
|
137
|
+
// edit anything extra here since Grammy's callbackQuery handler
|
|
138
|
+
// will have already been invoked. If we wanted to edit the original
|
|
139
|
+
// approval message we'd need to track message IDs per actionId.
|
|
140
|
+
// For now, the inline callback answer suffices.
|
|
141
|
+
log.debug("Approval status updated", { channelId, approved });
|
|
142
|
+
}
|
|
143
|
+
async stop() {
|
|
144
|
+
this.bot.stop();
|
|
145
|
+
log.info("Telegram bot stopped");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
exports.TelegramClient = TelegramClient;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Agent Provider Factory
|
|
4
|
+
*
|
|
5
|
+
* Creates agent provider instances based on configuration.
|
|
6
|
+
* Ships with claude-code as the default provider.
|
|
7
|
+
* Users can register custom providers via registerProvider().
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.createAgentProvider = createAgentProvider;
|
|
11
|
+
exports.getDefaultProviderType = getDefaultProviderType;
|
|
12
|
+
exports.getAvailableProviders = getAvailableProviders;
|
|
13
|
+
exports.registerProvider = registerProvider;
|
|
14
|
+
exports.isProviderAvailable = isProviderAvailable;
|
|
15
|
+
const claude_code_1 = require("./providers/claude-code");
|
|
16
|
+
const logger_1 = require("../logger");
|
|
17
|
+
const log = (0, logger_1.createLogger)("AgentFactory");
|
|
18
|
+
// Registry of available providers — extensible via registerProvider()
|
|
19
|
+
const providerRegistry = new Map([
|
|
20
|
+
["claude-code", claude_code_1.ClaudeCodeProvider],
|
|
21
|
+
]);
|
|
22
|
+
/**
|
|
23
|
+
* Create an agent provider based on configuration
|
|
24
|
+
*/
|
|
25
|
+
function createAgentProvider(config) {
|
|
26
|
+
const providerType = config?.type || getDefaultProviderType();
|
|
27
|
+
log.info("Creating agent provider", { type: providerType });
|
|
28
|
+
const ProviderClass = providerRegistry.get(providerType);
|
|
29
|
+
if (!ProviderClass) {
|
|
30
|
+
const available = Array.from(providerRegistry.keys()).join(", ");
|
|
31
|
+
throw new Error(`Unknown agent provider: ${providerType}. Available: ${available}`);
|
|
32
|
+
}
|
|
33
|
+
return new ProviderClass();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get the default provider type from environment or fallback
|
|
37
|
+
*/
|
|
38
|
+
function getDefaultProviderType() {
|
|
39
|
+
const envProvider = process.env.AGENT_PROVIDER;
|
|
40
|
+
if (envProvider && providerRegistry.has(envProvider)) {
|
|
41
|
+
return envProvider;
|
|
42
|
+
}
|
|
43
|
+
return "claude-code";
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get list of available provider types
|
|
47
|
+
*/
|
|
48
|
+
function getAvailableProviders() {
|
|
49
|
+
return Array.from(providerRegistry.keys());
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Register a custom agent provider.
|
|
53
|
+
*
|
|
54
|
+
* Example:
|
|
55
|
+
* registerProvider("my-agent", MyAgentProvider);
|
|
56
|
+
* // then set AGENT_PROVIDER=my-agent in .env
|
|
57
|
+
*/
|
|
58
|
+
function registerProvider(type, providerClass) {
|
|
59
|
+
log.info("Registering custom provider", { type });
|
|
60
|
+
providerRegistry.set(type, providerClass);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Check if a provider type is available
|
|
64
|
+
*/
|
|
65
|
+
function isProviderAvailable(type) {
|
|
66
|
+
return providerRegistry.has(type);
|
|
67
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Agent Module
|
|
4
|
+
*
|
|
5
|
+
* Provides an abstraction layer for AI agent providers.
|
|
6
|
+
* Ships with claude-code as the default (and recommended) provider.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* import { createAgentProvider } from "./core/agent";
|
|
10
|
+
* const agent = createAgentProvider();
|
|
11
|
+
* const response = await agent.query("Hello");
|
|
12
|
+
*
|
|
13
|
+
* To add a custom provider:
|
|
14
|
+
* 1. Implement the AgentProvider interface
|
|
15
|
+
* 2. Call registerProvider("my-agent", MyProvider)
|
|
16
|
+
* 3. Set AGENT_PROVIDER=my-agent in .env
|
|
17
|
+
*
|
|
18
|
+
* Environment:
|
|
19
|
+
* AGENT_PROVIDER - Set to override default provider (default: "claude-code")
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.ClaudeCodeProvider = exports.isProviderAvailable = exports.registerProvider = exports.getAvailableProviders = exports.getDefaultProviderType = exports.createAgentProvider = void 0;
|
|
23
|
+
// Factory
|
|
24
|
+
var factory_1 = require("./factory");
|
|
25
|
+
Object.defineProperty(exports, "createAgentProvider", { enumerable: true, get: function () { return factory_1.createAgentProvider; } });
|
|
26
|
+
Object.defineProperty(exports, "getDefaultProviderType", { enumerable: true, get: function () { return factory_1.getDefaultProviderType; } });
|
|
27
|
+
Object.defineProperty(exports, "getAvailableProviders", { enumerable: true, get: function () { return factory_1.getAvailableProviders; } });
|
|
28
|
+
Object.defineProperty(exports, "registerProvider", { enumerable: true, get: function () { return factory_1.registerProvider; } });
|
|
29
|
+
Object.defineProperty(exports, "isProviderAvailable", { enumerable: true, get: function () { return factory_1.isProviderAvailable; } });
|
|
30
|
+
// Built-in provider
|
|
31
|
+
var claude_code_1 = require("./providers/claude-code");
|
|
32
|
+
Object.defineProperty(exports, "ClaudeCodeProvider", { enumerable: true, get: function () { return claude_code_1.ClaudeCodeProvider; } });
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Claude Code Agent Provider
|
|
4
|
+
*
|
|
5
|
+
* Uses the Claude Agent SDK (@anthropic-ai/claude-agent-sdk) to process requests.
|
|
6
|
+
* This provider leverages Claude Code's built-in tools (Bash, Read, Write, etc.)
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.ClaudeCodeProvider = void 0;
|
|
43
|
+
const claude_agent_sdk_1 = require("@anthropic-ai/claude-agent-sdk");
|
|
44
|
+
const logger_1 = require("../../logger");
|
|
45
|
+
const log = (0, logger_1.createLogger)("ClaudeCodeProvider");
|
|
46
|
+
class ClaudeCodeProvider {
|
|
47
|
+
name = "claude-code";
|
|
48
|
+
version = "1.0.0";
|
|
49
|
+
defaultTools = ["Bash", "Read", "Write", "Edit", "Glob", "Grep"];
|
|
50
|
+
async query(prompt, options) {
|
|
51
|
+
let result = "";
|
|
52
|
+
let sessionId;
|
|
53
|
+
for await (const event of this.queryStream(prompt, options)) {
|
|
54
|
+
if (event.type === "done" && event.response) {
|
|
55
|
+
return event.response;
|
|
56
|
+
}
|
|
57
|
+
if (event.type === "text" && event.text) {
|
|
58
|
+
result = event.text;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { text: result, sessionId };
|
|
62
|
+
}
|
|
63
|
+
async *queryStream(prompt, options) {
|
|
64
|
+
const queryOptions = {
|
|
65
|
+
systemPrompt: options?.systemPrompt,
|
|
66
|
+
cwd: options?.workingDir || process.env.WORKING_DIR || process.env.HOME || "/",
|
|
67
|
+
allowedTools: options?.allowedTools || this.defaultTools,
|
|
68
|
+
permissionMode: "bypassPermissions",
|
|
69
|
+
maxTurns: options?.maxTurns || 10,
|
|
70
|
+
};
|
|
71
|
+
// Resume session if provided
|
|
72
|
+
if (options?.sessionId) {
|
|
73
|
+
queryOptions.resume = options.sessionId;
|
|
74
|
+
log.debug("Resuming session", { sessionId: options.sessionId });
|
|
75
|
+
}
|
|
76
|
+
let result = "";
|
|
77
|
+
let sessionId;
|
|
78
|
+
let usedTools = false;
|
|
79
|
+
try {
|
|
80
|
+
yield { type: "thinking" };
|
|
81
|
+
for await (const message of (0, claude_agent_sdk_1.query)({ prompt, options: queryOptions })) {
|
|
82
|
+
log.debug("Agent message", { type: message.type, subtype: message.subtype });
|
|
83
|
+
// Capture session ID from init message
|
|
84
|
+
if (message.type === "system" && message.subtype === "init") {
|
|
85
|
+
sessionId = message.session_id;
|
|
86
|
+
log.debug("Got session ID", { sessionId });
|
|
87
|
+
}
|
|
88
|
+
// Handle result messages
|
|
89
|
+
if ("result" in message && message.result) {
|
|
90
|
+
result = message.result;
|
|
91
|
+
}
|
|
92
|
+
// Handle assistant text messages
|
|
93
|
+
if (message.type === "assistant" && "content" in message) {
|
|
94
|
+
const content = message.content;
|
|
95
|
+
if (Array.isArray(content)) {
|
|
96
|
+
for (const block of content) {
|
|
97
|
+
if (block.type === "text" && block.text) {
|
|
98
|
+
result = block.text;
|
|
99
|
+
yield { type: "text", text: block.text };
|
|
100
|
+
}
|
|
101
|
+
if (block.type === "tool_use") {
|
|
102
|
+
usedTools = true;
|
|
103
|
+
yield {
|
|
104
|
+
type: "tool_use",
|
|
105
|
+
toolName: block.name,
|
|
106
|
+
toolInput: block.input,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Handle tool results
|
|
113
|
+
if (message.type === "user" && "content" in message) {
|
|
114
|
+
const content = message.content;
|
|
115
|
+
if (Array.isArray(content)) {
|
|
116
|
+
for (const block of content) {
|
|
117
|
+
if (block.type === "tool_result") {
|
|
118
|
+
yield {
|
|
119
|
+
type: "tool_result",
|
|
120
|
+
toolResult: typeof block.content === "string"
|
|
121
|
+
? block.content
|
|
122
|
+
: JSON.stringify(block.content),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
yield {
|
|
130
|
+
type: "done",
|
|
131
|
+
response: {
|
|
132
|
+
text: result || "Task completed (no output)",
|
|
133
|
+
sessionId,
|
|
134
|
+
usedTools,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
log.error("Query failed", { error: error.message });
|
|
140
|
+
yield { type: "error", error: error.message };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async isAvailable() {
|
|
144
|
+
try {
|
|
145
|
+
// Check if claude CLI is available
|
|
146
|
+
const { exec } = await Promise.resolve().then(() => __importStar(require("child_process")));
|
|
147
|
+
const { promisify } = await Promise.resolve().then(() => __importStar(require("util")));
|
|
148
|
+
const execAsync = promisify(exec);
|
|
149
|
+
await execAsync("which claude");
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
log.warn("Claude Code CLI not found");
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
exports.ClaudeCodeProvider = ClaudeCodeProvider;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Agent Provider Abstraction
|
|
4
|
+
*
|
|
5
|
+
* This module defines the interface for AI agent providers.
|
|
6
|
+
* Implement this interface to add support for different AI backends
|
|
7
|
+
* (Claude Code, OpenAI, local LLMs, etc.)
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.approvalManager = exports.ApprovalManager = void 0;
|
|
4
|
+
const events_1 = require("events");
|
|
5
|
+
const logger_1 = require("./logger");
|
|
6
|
+
const platform_1 = require("./platform");
|
|
7
|
+
const log = (0, logger_1.createLogger)("Approval");
|
|
8
|
+
// Protected folders that require approval (platform-aware)
|
|
9
|
+
const PROTECTED_FOLDERS = (0, platform_1.getProtectedFolderPatterns)();
|
|
10
|
+
class ApprovalManager extends events_1.EventEmitter {
|
|
11
|
+
pendingActions = new Map();
|
|
12
|
+
notifiers = [];
|
|
13
|
+
autoApprovePatterns = [];
|
|
14
|
+
defaultTimeoutMs = 5 * 60 * 1000; // 5 minutes
|
|
15
|
+
approvedFolders = new Set(); // Folders approved in this session
|
|
16
|
+
requireApprovalForAll;
|
|
17
|
+
constructor() {
|
|
18
|
+
super();
|
|
19
|
+
this.requireApprovalForAll = process.env.REQUIRE_APPROVAL_FOR_ALL === "true";
|
|
20
|
+
// Load pre-approved folders from env
|
|
21
|
+
const allowedFolders = process.env.ALLOWED_FOLDERS?.split(":").filter(Boolean) || [];
|
|
22
|
+
allowedFolders.forEach(folder => this.approvedFolders.add(folder));
|
|
23
|
+
}
|
|
24
|
+
// Check if a path requires folder access approval
|
|
25
|
+
isProtectedPath(filePath) {
|
|
26
|
+
// If folder is already approved in this session, no approval needed
|
|
27
|
+
for (const approved of this.approvedFolders) {
|
|
28
|
+
if (filePath.startsWith(approved)) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Check against protected folder patterns
|
|
33
|
+
return PROTECTED_FOLDERS.some(pattern => pattern.test(filePath));
|
|
34
|
+
}
|
|
35
|
+
// Mark a folder as approved for this session
|
|
36
|
+
approveFolder(folderPath) {
|
|
37
|
+
this.approvedFolders.add(folderPath);
|
|
38
|
+
log.info("Folder approved for session", { folderPath });
|
|
39
|
+
}
|
|
40
|
+
// Request approval for folder access
|
|
41
|
+
async requestFolderAccess(filePath) {
|
|
42
|
+
if (!this.isProtectedPath(filePath)) {
|
|
43
|
+
return true; // Not protected, no approval needed
|
|
44
|
+
}
|
|
45
|
+
// Extract the base protected folder from the path
|
|
46
|
+
const baseFolder = (0, platform_1.extractBaseFolder)(filePath) || filePath;
|
|
47
|
+
const approved = await this.requestApproval("folder_access", `Access to ${baseFolder}`, { path: filePath, baseFolder }, { autoApprove: false, timeoutMs: 2 * 60 * 1000 } // 2 minute timeout
|
|
48
|
+
);
|
|
49
|
+
if (approved) {
|
|
50
|
+
this.approveFolder(baseFolder);
|
|
51
|
+
}
|
|
52
|
+
return approved;
|
|
53
|
+
}
|
|
54
|
+
addNotifier(notifier) {
|
|
55
|
+
this.notifiers.push(notifier);
|
|
56
|
+
}
|
|
57
|
+
addAutoApprovePattern(pattern) {
|
|
58
|
+
this.autoApprovePatterns.push(pattern);
|
|
59
|
+
}
|
|
60
|
+
shouldAutoApprove(action) {
|
|
61
|
+
// If require approval for all is set, never auto-approve
|
|
62
|
+
if (this.requireApprovalForAll) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
// Folder access always requires explicit approval
|
|
66
|
+
if (action.type === "folder_access") {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
if (action.type === "command") {
|
|
70
|
+
const cmd = action.details.command;
|
|
71
|
+
// Safe read-only commands
|
|
72
|
+
const safeCommands = [
|
|
73
|
+
/^ls\b/,
|
|
74
|
+
/^pwd$/,
|
|
75
|
+
/^whoami$/,
|
|
76
|
+
/^date$/,
|
|
77
|
+
/^cat\s/,
|
|
78
|
+
/^head\s/,
|
|
79
|
+
/^tail\s/,
|
|
80
|
+
/^wc\s/,
|
|
81
|
+
/^du\s/,
|
|
82
|
+
/^df\s/,
|
|
83
|
+
/^echo\s/,
|
|
84
|
+
/^which\s/,
|
|
85
|
+
/^type\s/,
|
|
86
|
+
/^file\s/,
|
|
87
|
+
/^stat\s/,
|
|
88
|
+
/^uname/,
|
|
89
|
+
/^hostname$/,
|
|
90
|
+
/^uptime$/,
|
|
91
|
+
/^ps\b/,
|
|
92
|
+
/^top\s+-l\s+1/,
|
|
93
|
+
/^docker\s+ps/,
|
|
94
|
+
/^docker\s+images/,
|
|
95
|
+
/^git\s+status/,
|
|
96
|
+
/^git\s+log/,
|
|
97
|
+
/^git\s+branch/,
|
|
98
|
+
/^git\s+diff/,
|
|
99
|
+
/^npm\s+list/,
|
|
100
|
+
/^node\s+-v/,
|
|
101
|
+
/^python\s+--version/,
|
|
102
|
+
];
|
|
103
|
+
for (const pattern of [...safeCommands, ...this.autoApprovePatterns]) {
|
|
104
|
+
if (pattern.test(cmd)) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
async requestApproval(type, description, details, options) {
|
|
112
|
+
const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
113
|
+
const timeoutMs = options?.timeoutMs || this.defaultTimeoutMs;
|
|
114
|
+
const action = {
|
|
115
|
+
id,
|
|
116
|
+
type,
|
|
117
|
+
description,
|
|
118
|
+
details,
|
|
119
|
+
createdAt: new Date(),
|
|
120
|
+
expiresAt: new Date(Date.now() + timeoutMs),
|
|
121
|
+
resolve: () => { }, // Will be set below
|
|
122
|
+
};
|
|
123
|
+
// Check for auto-approve
|
|
124
|
+
if (options?.autoApprove !== false && this.shouldAutoApprove(action)) {
|
|
125
|
+
log.info("Action auto-approved", { id, type, description });
|
|
126
|
+
log.debug("Auto-approved action details", { details });
|
|
127
|
+
this.emit("auto-approved", action);
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
log.info("Approval requested", { id, type, description, expiresAt: action.expiresAt });
|
|
131
|
+
// Create a promise that will be resolved when approved/rejected
|
|
132
|
+
const approvalPromise = new Promise((resolve) => {
|
|
133
|
+
action.resolve = resolve;
|
|
134
|
+
});
|
|
135
|
+
this.pendingActions.set(id, action);
|
|
136
|
+
// Set up timeout
|
|
137
|
+
const timeout = setTimeout(() => {
|
|
138
|
+
if (this.pendingActions.has(id)) {
|
|
139
|
+
log.warn("Approval request expired", { id, type, description });
|
|
140
|
+
this.pendingActions.delete(id);
|
|
141
|
+
action.resolve(false);
|
|
142
|
+
this.emit("expired", action);
|
|
143
|
+
}
|
|
144
|
+
}, timeoutMs);
|
|
145
|
+
// Notify all registered notifiers
|
|
146
|
+
for (const notifier of this.notifiers) {
|
|
147
|
+
try {
|
|
148
|
+
await notifier(action);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
console.error("Notifier error:", error);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
this.emit("pending", action);
|
|
155
|
+
// Wait for approval
|
|
156
|
+
const result = await approvalPromise;
|
|
157
|
+
clearTimeout(timeout);
|
|
158
|
+
this.pendingActions.delete(id);
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
approve(actionId) {
|
|
162
|
+
const action = this.pendingActions.get(actionId);
|
|
163
|
+
if (action) {
|
|
164
|
+
log.info("Action approved", { id: actionId, type: action.type, description: action.description });
|
|
165
|
+
action.resolve(true);
|
|
166
|
+
this.emit("approved", action);
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
log.warn("Attempted to approve unknown action", { actionId });
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
reject(actionId) {
|
|
173
|
+
const action = this.pendingActions.get(actionId);
|
|
174
|
+
if (action) {
|
|
175
|
+
log.info("Action rejected", { id: actionId, type: action.type, description: action.description });
|
|
176
|
+
action.resolve(false);
|
|
177
|
+
this.emit("rejected", action);
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
log.warn("Attempted to reject unknown action", { actionId });
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
getPendingActions() {
|
|
184
|
+
return Array.from(this.pendingActions.values());
|
|
185
|
+
}
|
|
186
|
+
getPendingAction(id) {
|
|
187
|
+
return this.pendingActions.get(id);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
exports.ApprovalManager = ApprovalManager;
|
|
191
|
+
// Singleton instance
|
|
192
|
+
exports.approvalManager = new ApprovalManager();
|