@suzuke/agend 0.0.1 → 1.0.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/README.md +557 -1
- package/README.zh-TW.md +504 -0
- package/dist/access-path.d.ts +7 -0
- package/dist/access-path.js +12 -0
- package/dist/access-path.js.map +1 -0
- package/dist/approval/approval-server.d.ts +30 -0
- package/dist/approval/approval-server.js +156 -0
- package/dist/approval/approval-server.js.map +1 -0
- package/dist/approval/tmux-prompt-detector.d.ts +34 -0
- package/dist/approval/tmux-prompt-detector.js +264 -0
- package/dist/approval/tmux-prompt-detector.js.map +1 -0
- package/dist/backend/approval-strategy.d.ts +14 -0
- package/dist/backend/approval-strategy.js +2 -0
- package/dist/backend/approval-strategy.js.map +1 -0
- package/dist/backend/claude-code.d.ts +13 -0
- package/dist/backend/claude-code.js +114 -0
- package/dist/backend/claude-code.js.map +1 -0
- package/dist/backend/codex.d.ts +10 -0
- package/dist/backend/codex.js +58 -0
- package/dist/backend/codex.js.map +1 -0
- package/dist/backend/factory.d.ts +2 -0
- package/dist/backend/factory.js +19 -0
- package/dist/backend/factory.js.map +1 -0
- package/dist/backend/gemini-cli.d.ts +10 -0
- package/dist/backend/gemini-cli.js +68 -0
- package/dist/backend/gemini-cli.js.map +1 -0
- package/dist/backend/hook-based-approval.d.ts +20 -0
- package/dist/backend/hook-based-approval.js +41 -0
- package/dist/backend/hook-based-approval.js.map +1 -0
- package/dist/backend/index.d.ts +6 -0
- package/dist/backend/index.js +6 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/opencode.d.ts +10 -0
- package/dist/backend/opencode.js +63 -0
- package/dist/backend/opencode.js.map +1 -0
- package/dist/backend/types.d.ts +26 -0
- package/dist/backend/types.js +2 -0
- package/dist/backend/types.js.map +1 -0
- package/dist/channel/access-manager.d.ts +18 -0
- package/dist/channel/access-manager.js +149 -0
- package/dist/channel/access-manager.js.map +1 -0
- package/dist/channel/adapters/discord.d.ts +45 -0
- package/dist/channel/adapters/discord.js +366 -0
- package/dist/channel/adapters/discord.js.map +1 -0
- package/dist/channel/adapters/telegram.d.ts +58 -0
- package/dist/channel/adapters/telegram.js +569 -0
- package/dist/channel/adapters/telegram.js.map +1 -0
- package/dist/channel/attachment-handler.d.ts +15 -0
- package/dist/channel/attachment-handler.js +55 -0
- package/dist/channel/attachment-handler.js.map +1 -0
- package/dist/channel/factory.d.ts +12 -0
- package/dist/channel/factory.js +38 -0
- package/dist/channel/factory.js.map +1 -0
- package/dist/channel/ipc-bridge.d.ts +26 -0
- package/dist/channel/ipc-bridge.js +170 -0
- package/dist/channel/ipc-bridge.js.map +1 -0
- package/dist/channel/mcp-server.d.ts +10 -0
- package/dist/channel/mcp-server.js +196 -0
- package/dist/channel/mcp-server.js.map +1 -0
- package/dist/channel/mcp-tools.d.ts +909 -0
- package/dist/channel/mcp-tools.js +346 -0
- package/dist/channel/mcp-tools.js.map +1 -0
- package/dist/channel/message-bus.d.ts +17 -0
- package/dist/channel/message-bus.js +86 -0
- package/dist/channel/message-bus.js.map +1 -0
- package/dist/channel/message-queue.d.ts +39 -0
- package/dist/channel/message-queue.js +248 -0
- package/dist/channel/message-queue.js.map +1 -0
- package/dist/channel/tool-router.d.ts +6 -0
- package/dist/channel/tool-router.js +69 -0
- package/dist/channel/tool-router.js.map +1 -0
- package/dist/channel/tool-tracker.d.ts +13 -0
- package/dist/channel/tool-tracker.js +58 -0
- package/dist/channel/tool-tracker.js.map +1 -0
- package/dist/channel/types.d.ts +116 -0
- package/dist/channel/types.js +2 -0
- package/dist/channel/types.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +782 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +85 -0
- package/dist/config.js.map +1 -0
- package/dist/container-manager.d.ts +24 -0
- package/dist/container-manager.js +148 -0
- package/dist/container-manager.js.map +1 -0
- package/dist/context-guardian.d.ts +29 -0
- package/dist/context-guardian.js +123 -0
- package/dist/context-guardian.js.map +1 -0
- package/dist/cost-guard.d.ts +21 -0
- package/dist/cost-guard.js +113 -0
- package/dist/cost-guard.js.map +1 -0
- package/dist/daemon-entry.d.ts +1 -0
- package/dist/daemon-entry.js +29 -0
- package/dist/daemon-entry.js.map +1 -0
- package/dist/daemon.d.ts +88 -0
- package/dist/daemon.js +820 -0
- package/dist/daemon.js.map +1 -0
- package/dist/daily-summary.d.ts +13 -0
- package/dist/daily-summary.js +55 -0
- package/dist/daily-summary.js.map +1 -0
- package/dist/db.d.ts +10 -0
- package/dist/db.js +43 -0
- package/dist/db.js.map +1 -0
- package/dist/event-log.d.ts +22 -0
- package/dist/event-log.js +66 -0
- package/dist/event-log.js.map +1 -0
- package/dist/export-import.d.ts +2 -0
- package/dist/export-import.js +110 -0
- package/dist/export-import.js.map +1 -0
- package/dist/fleet-context.d.ts +36 -0
- package/dist/fleet-context.js +4 -0
- package/dist/fleet-context.js.map +1 -0
- package/dist/fleet-manager.d.ts +115 -0
- package/dist/fleet-manager.js +1742 -0
- package/dist/fleet-manager.js.map +1 -0
- package/dist/fleet-system-prompt.d.ts +11 -0
- package/dist/fleet-system-prompt.js +60 -0
- package/dist/fleet-system-prompt.js.map +1 -0
- package/dist/hang-detector.d.ts +16 -0
- package/dist/hang-detector.js +53 -0
- package/dist/hang-detector.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/install-recorder.d.ts +30 -0
- package/dist/install-recorder.js +159 -0
- package/dist/install-recorder.js.map +1 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.js +63 -0
- package/dist/logger.js.map +1 -0
- package/dist/meeting/orchestrator.d.ts +30 -0
- package/dist/meeting/orchestrator.js +355 -0
- package/dist/meeting/orchestrator.js.map +1 -0
- package/dist/meeting/prompt-builder.d.ts +12 -0
- package/dist/meeting/prompt-builder.js +96 -0
- package/dist/meeting/prompt-builder.js.map +1 -0
- package/dist/meeting/role-assigner.d.ts +2 -0
- package/dist/meeting/role-assigner.js +25 -0
- package/dist/meeting/role-assigner.js.map +1 -0
- package/dist/meeting/types.d.ts +21 -0
- package/dist/meeting/types.js +2 -0
- package/dist/meeting/types.js.map +1 -0
- package/dist/meeting-manager.d.ts +10 -0
- package/dist/meeting-manager.js +38 -0
- package/dist/meeting-manager.js.map +1 -0
- package/dist/memory-layer.d.ts +13 -0
- package/dist/memory-layer.js +44 -0
- package/dist/memory-layer.js.map +1 -0
- package/dist/plugin/agend/.claude-plugin/plugin.json +5 -0
- package/dist/plugin/agend/.mcp.json +9 -0
- package/dist/plugin/ccd-channel/.claude-plugin/plugin.json +5 -0
- package/dist/plugin/ccd-channel/.mcp.json +9 -0
- package/dist/process-manager.d.ts +31 -0
- package/dist/process-manager.js +264 -0
- package/dist/process-manager.js.map +1 -0
- package/dist/scheduler/db.d.ts +16 -0
- package/dist/scheduler/db.js +132 -0
- package/dist/scheduler/db.js.map +1 -0
- package/dist/scheduler/db.test.d.ts +1 -0
- package/dist/scheduler/db.test.js +92 -0
- package/dist/scheduler/db.test.js.map +1 -0
- package/dist/scheduler/index.d.ts +4 -0
- package/dist/scheduler/index.js +4 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +25 -0
- package/dist/scheduler/scheduler.js +119 -0
- package/dist/scheduler/scheduler.js.map +1 -0
- package/dist/scheduler/scheduler.test.d.ts +1 -0
- package/dist/scheduler/scheduler.test.js +119 -0
- package/dist/scheduler/scheduler.test.js.map +1 -0
- package/dist/scheduler/types.d.ts +47 -0
- package/dist/scheduler/types.js +7 -0
- package/dist/scheduler/types.js.map +1 -0
- package/dist/service-installer.d.ts +14 -0
- package/dist/service-installer.js +91 -0
- package/dist/service-installer.js.map +1 -0
- package/dist/setup-wizard.d.ts +14 -0
- package/dist/setup-wizard.js +517 -0
- package/dist/setup-wizard.js.map +1 -0
- package/dist/stt.d.ts +10 -0
- package/dist/stt.js +33 -0
- package/dist/stt.js.map +1 -0
- package/dist/tmux-manager.d.ts +22 -0
- package/dist/tmux-manager.js +131 -0
- package/dist/tmux-manager.js.map +1 -0
- package/dist/topic-commands.d.ts +22 -0
- package/dist/topic-commands.js +176 -0
- package/dist/topic-commands.js.map +1 -0
- package/dist/transcript-monitor.d.ts +21 -0
- package/dist/transcript-monitor.js +149 -0
- package/dist/transcript-monitor.js.map +1 -0
- package/dist/types.d.ts +153 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/webhook-emitter.d.ts +15 -0
- package/dist/webhook-emitter.js +41 -0
- package/dist/webhook-emitter.js.map +1 -0
- package/package.json +60 -4
- package/templates/launchd.plist.ejs +29 -0
- package/templates/systemd.service.ejs +15 -0
- package/index.js +0 -1
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
const DANGER_PATTERNS = [
|
|
4
|
+
/\brm\b/, // any file deletion
|
|
5
|
+
/\bgit\s+push\b/, // any push (not just --force)
|
|
6
|
+
/\bgit\s+reset\b/, // any reset
|
|
7
|
+
/\bgit\s+clean\b/, // any clean
|
|
8
|
+
/\bgit\s+checkout\s+\./, // discard changes
|
|
9
|
+
/\bgit\s+restore\b/, // discard changes
|
|
10
|
+
/\bmv\b/, // move/rename files
|
|
11
|
+
/\bdd\b/,
|
|
12
|
+
/\bmkfs\b/,
|
|
13
|
+
/\bsudo\b/,
|
|
14
|
+
/\bchmod\b/,
|
|
15
|
+
/\bchown\b/,
|
|
16
|
+
/\bkill\b/,
|
|
17
|
+
/\bpkill\b/,
|
|
18
|
+
/(?<!\d)>\s*\/(?:etc|usr|var|bin|sbin|lib|opt|root|System|Library)\b/, // redirect to system paths (not /tmp, not 2>/dev/null)
|
|
19
|
+
/(?:\/usr)?\/s?bin\/(rm|chmod|chown|mkfs|dd)\b/, // full path variants
|
|
20
|
+
/\b(?:command|env|builtin)\s+(rm|chmod|chown|sudo)\b/, // command wrappers
|
|
21
|
+
/\$\(.*\b(rm|dd|mkfs)\b/, // command substitution with dangerous commands
|
|
22
|
+
];
|
|
23
|
+
function isSafeTool(toolName) {
|
|
24
|
+
if (toolName === "Bash" || toolName.startsWith("Bash("))
|
|
25
|
+
return false;
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
function isDangerousCommand(command) {
|
|
29
|
+
return DANGER_PATTERNS.some(pattern => pattern.test(command));
|
|
30
|
+
}
|
|
31
|
+
const APPROVAL_TIMEOUT_MS = 120_000;
|
|
32
|
+
export class ApprovalServer {
|
|
33
|
+
server = null;
|
|
34
|
+
messageBus;
|
|
35
|
+
port;
|
|
36
|
+
ipcServer;
|
|
37
|
+
topicMode;
|
|
38
|
+
instanceName;
|
|
39
|
+
token;
|
|
40
|
+
constructor(opts) {
|
|
41
|
+
this.messageBus = opts.messageBus;
|
|
42
|
+
this.port = opts.port;
|
|
43
|
+
this.ipcServer = opts.ipcServer ?? null;
|
|
44
|
+
this.topicMode = opts.topicMode ?? false;
|
|
45
|
+
this.instanceName = opts.instanceName ?? "";
|
|
46
|
+
this.token = randomBytes(32).toString("hex");
|
|
47
|
+
}
|
|
48
|
+
getToken() {
|
|
49
|
+
return this.token;
|
|
50
|
+
}
|
|
51
|
+
async start() {
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
this.server = createServer(async (req, res) => {
|
|
54
|
+
if (req.headers.authorization !== `Bearer ${this.token}`) {
|
|
55
|
+
res.writeHead(401);
|
|
56
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (req.method !== "POST" || req.url !== "/approve") {
|
|
60
|
+
res.writeHead(404);
|
|
61
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
let body = "";
|
|
65
|
+
req.on("data", (chunk) => { body += chunk; });
|
|
66
|
+
req.on("end", async () => {
|
|
67
|
+
try {
|
|
68
|
+
const { tool_name, tool_input } = JSON.parse(body);
|
|
69
|
+
let permissionDecision;
|
|
70
|
+
let permissionDecisionReason;
|
|
71
|
+
if (tool_name === "Bash" && typeof tool_input?.command === "string" && isDangerousCommand(tool_input.command)) {
|
|
72
|
+
// Dangerous Bash commands → require human approval
|
|
73
|
+
const prompt = `⚠️ ${tool_name}\n\`\`\`\n${tool_input.command}\n\`\`\``;
|
|
74
|
+
const decision = await this.requestApproval(prompt);
|
|
75
|
+
permissionDecision = decision;
|
|
76
|
+
permissionDecisionReason = decision === "allow"
|
|
77
|
+
? "approved by user"
|
|
78
|
+
: "denied by user";
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
// Everything else (all tools + normal Bash) → auto-allow
|
|
82
|
+
permissionDecision = "allow";
|
|
83
|
+
}
|
|
84
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
85
|
+
res.end(JSON.stringify({
|
|
86
|
+
hookSpecificOutput: {
|
|
87
|
+
hookEventName: "PreToolUse",
|
|
88
|
+
permissionDecision,
|
|
89
|
+
...(permissionDecisionReason ? { permissionDecisionReason } : {}),
|
|
90
|
+
},
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
95
|
+
res.end(JSON.stringify({ error: "Bad request" }));
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
this.server.on("error", reject);
|
|
100
|
+
this.server.listen(this.port, "127.0.0.1", () => {
|
|
101
|
+
const address = this.server.address();
|
|
102
|
+
const actualPort = typeof address === "object" && address !== null ? address.port : this.port;
|
|
103
|
+
resolve(actualPort);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
requestApproval(prompt) {
|
|
108
|
+
if (this.topicMode && this.ipcServer) {
|
|
109
|
+
return this.requestApprovalViaIpc(prompt);
|
|
110
|
+
}
|
|
111
|
+
return this.requestApprovalViaBus(prompt);
|
|
112
|
+
}
|
|
113
|
+
/** DM mode: use messageBus directly (adapter is registered on this daemon) */
|
|
114
|
+
async requestApprovalViaBus(prompt) {
|
|
115
|
+
const result = await this.messageBus.requestApproval(prompt);
|
|
116
|
+
return result.decision === "deny" ? "deny" : "allow";
|
|
117
|
+
}
|
|
118
|
+
/** Topic mode: forward approval request to fleet manager via IPC */
|
|
119
|
+
requestApprovalViaIpc(prompt) {
|
|
120
|
+
return new Promise((resolve) => {
|
|
121
|
+
const approvalId = `approval-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
122
|
+
const timeout = setTimeout(() => {
|
|
123
|
+
cleanup();
|
|
124
|
+
resolve("deny");
|
|
125
|
+
}, APPROVAL_TIMEOUT_MS);
|
|
126
|
+
const onMessage = (msg) => {
|
|
127
|
+
if (msg.type === "fleet_approval_response" && msg.approvalId === approvalId) {
|
|
128
|
+
cleanup();
|
|
129
|
+
resolve(msg.decision === "deny" ? "deny" : "allow");
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
const cleanup = () => {
|
|
133
|
+
clearTimeout(timeout);
|
|
134
|
+
this.ipcServer?.removeListener("message", onMessage);
|
|
135
|
+
};
|
|
136
|
+
this.ipcServer?.on("message", onMessage);
|
|
137
|
+
this.ipcServer?.broadcast({
|
|
138
|
+
type: "fleet_approval_request",
|
|
139
|
+
approvalId,
|
|
140
|
+
instanceName: this.instanceName,
|
|
141
|
+
prompt,
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
async stop() {
|
|
146
|
+
return new Promise((resolve) => {
|
|
147
|
+
if (!this.server) {
|
|
148
|
+
resolve();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
this.server.close(() => resolve());
|
|
152
|
+
this.server = null;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=approval-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-server.js","sourceRoot":"","sources":["../../src/approval/approval-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { TmuxManager } from "../tmux-manager.js";
|
|
2
|
+
import type { ApprovalResponse } from "../channel/types.js";
|
|
3
|
+
export type PromptType = "permission" | "settings_error" | "dev_channels" | "mcp_trust" | "file_creation" | "unknown";
|
|
4
|
+
/** Detect whether text contains a Claude Code interactive prompt */
|
|
5
|
+
export declare function detectInteractivePrompt(text: string): boolean;
|
|
6
|
+
/** Classify a detected prompt to determine handling strategy */
|
|
7
|
+
export declare function classifyPrompt(text: string): PromptType;
|
|
8
|
+
export declare function detectPermissionPrompt(text: string): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Extract tool name from Claude Code permission prompt text.
|
|
11
|
+
* Returns the permission-format tool name (e.g. "mcp__puppeteer__puppeteer_navigate").
|
|
12
|
+
*/
|
|
13
|
+
export declare function extractToolPattern(text: string): string | null;
|
|
14
|
+
/** Build a clean prompt message for Telegram display */
|
|
15
|
+
export declare function formatPromptForDisplay(text: string): string;
|
|
16
|
+
export declare function loadToolAllowlist(instanceDir: string): string[];
|
|
17
|
+
export declare function saveToolToAllowlist(instanceDir: string, pattern: string): void;
|
|
18
|
+
export declare class TmuxPromptDetector {
|
|
19
|
+
private outputLogPath;
|
|
20
|
+
private tmux;
|
|
21
|
+
private approvalFn;
|
|
22
|
+
private logger;
|
|
23
|
+
private instanceDir?;
|
|
24
|
+
private pollTimer;
|
|
25
|
+
private byteOffset;
|
|
26
|
+
private pendingApproval;
|
|
27
|
+
constructor(outputLogPath: string, tmux: TmuxManager, approvalFn: (prompt: string) => Promise<ApprovalResponse>, logger: {
|
|
28
|
+
info(...args: any[]): void;
|
|
29
|
+
warn(...args: any[]): void;
|
|
30
|
+
error(...args: any[]): void;
|
|
31
|
+
}, instanceDir?: string | undefined);
|
|
32
|
+
startPolling(intervalMs?: number): void;
|
|
33
|
+
stop(): void;
|
|
34
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { readFileSync, statSync, writeFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/** Strip ANSI escape codes from terminal output */
|
|
4
|
+
function stripAnsi(text) {
|
|
5
|
+
// eslint-disable-next-line no-control-regex
|
|
6
|
+
return text.replace(/\x1b\[\d*C/g, " ") // cursor forward → space
|
|
7
|
+
.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "") // other CSI sequences
|
|
8
|
+
.replace(/\x1b\][^\x07]*\x07/g, "") // OSC sequences
|
|
9
|
+
.replace(/\x1b[()][0-9A-B]/g, "") // charset switches
|
|
10
|
+
.replace(/[\x00-\x08\x0e-\x1f]/g, ""); // misc control chars
|
|
11
|
+
}
|
|
12
|
+
/** Detect whether text contains a Claude Code interactive prompt */
|
|
13
|
+
export function detectInteractivePrompt(text) {
|
|
14
|
+
const clean = stripAnsi(text);
|
|
15
|
+
// All Claude Code interactive prompts have numbered options like "1." or "❯ 1."
|
|
16
|
+
// combined with "Esc to cancel" or "Enter to confirm"
|
|
17
|
+
const hasNumberedOption = /[❯>]?\s*1\.\s/.test(clean);
|
|
18
|
+
const hasPromptChrome = /Esc to cancel|Enter to confirm/.test(clean);
|
|
19
|
+
return hasNumberedOption && hasPromptChrome;
|
|
20
|
+
}
|
|
21
|
+
/** Classify a detected prompt to determine handling strategy */
|
|
22
|
+
export function classifyPrompt(text) {
|
|
23
|
+
const clean = stripAnsi(text);
|
|
24
|
+
// Permission / tool use: "Do you want to proceed?" with Yes/No
|
|
25
|
+
if (/Do you want to proceed/i.test(clean) && /\bYes\b/.test(clean) && /\bNo\b/.test(clean)) {
|
|
26
|
+
return "permission";
|
|
27
|
+
}
|
|
28
|
+
// Settings error: has "Settings Error" or "Continue without these settings"
|
|
29
|
+
if (/Settings Error/i.test(clean) || /Continue without these settings/i.test(clean)) {
|
|
30
|
+
return "settings_error";
|
|
31
|
+
}
|
|
32
|
+
// Dev channels: "I am using this for local development"
|
|
33
|
+
if (/I am using this for local development/i.test(clean)) {
|
|
34
|
+
return "dev_channels";
|
|
35
|
+
}
|
|
36
|
+
// MCP trust: "New MCP server found" or "Use this and all future"
|
|
37
|
+
if (/New MCP server found/i.test(clean) || /Use this and all future/i.test(clean)) {
|
|
38
|
+
return "mcp_trust";
|
|
39
|
+
}
|
|
40
|
+
// File creation: "Do you want to create"
|
|
41
|
+
if (/Do you want to create/i.test(clean)) {
|
|
42
|
+
return "file_creation";
|
|
43
|
+
}
|
|
44
|
+
return "unknown";
|
|
45
|
+
}
|
|
46
|
+
// ── Kept for backwards compat with tests ─────────────────────────────────────
|
|
47
|
+
export function detectPermissionPrompt(text) {
|
|
48
|
+
const clean = stripAnsi(text);
|
|
49
|
+
return /1\.\s*Yes\b/.test(clean) && /3\.\s*No\b/.test(clean);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Extract tool name from Claude Code permission prompt text.
|
|
53
|
+
* Returns the permission-format tool name (e.g. "mcp__puppeteer__puppeteer_navigate").
|
|
54
|
+
*/
|
|
55
|
+
export function extractToolPattern(text) {
|
|
56
|
+
const clean = stripAnsi(text);
|
|
57
|
+
// "don't" may appear as don't, don.t, or dont (apostrophe stripped by terminal)
|
|
58
|
+
const m = clean.match(/don.?t ask again for\s+(.+?)\s+commands?\s+in\b/i);
|
|
59
|
+
if (!m)
|
|
60
|
+
return null;
|
|
61
|
+
const display = m[1].trim();
|
|
62
|
+
// MCP tool: "server - tool_name" → "mcp__server__tool_name"
|
|
63
|
+
const mcpMatch = display.match(/^(\S+)\s*-\s*(\S+)$/);
|
|
64
|
+
if (mcpMatch) {
|
|
65
|
+
return `mcp__${mcpMatch[1]}__${mcpMatch[2]}`;
|
|
66
|
+
}
|
|
67
|
+
// Built-in tool: "Bash" → "Bash(*)"
|
|
68
|
+
return `${display}(*)`;
|
|
69
|
+
}
|
|
70
|
+
/** Build a clean prompt message for Telegram display */
|
|
71
|
+
export function formatPromptForDisplay(text) {
|
|
72
|
+
const clean = stripAnsi(text)
|
|
73
|
+
.replace(/\r/g, "")
|
|
74
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
75
|
+
.trim();
|
|
76
|
+
// Tool use prompt: extract tool name and args
|
|
77
|
+
const toolMatch = clean.match(/(\S+\s*-\s*\S+)\s*\(([^)]*)\)\s*\(MCP\)/i)
|
|
78
|
+
?? clean.match(/(\S+)\s*\(([^)]*)\)/);
|
|
79
|
+
if (toolMatch) {
|
|
80
|
+
const tool = toolMatch[1].trim();
|
|
81
|
+
const args = toolMatch[2].trim();
|
|
82
|
+
const truncatedArgs = args.length > 200 ? args.slice(0, 200) + "…" : args;
|
|
83
|
+
return `⚠️ ${tool}\n\`\`\`\n${truncatedArgs}\n\`\`\``;
|
|
84
|
+
}
|
|
85
|
+
// Fallback: cleaned text, truncated
|
|
86
|
+
const truncated = clean.length > 500 ? clean.slice(0, 500) + "…" : clean;
|
|
87
|
+
return `⚠️ Prompt\n${truncated}`;
|
|
88
|
+
}
|
|
89
|
+
// ── Persistent tool allowlist ────────────────────────────────────────────────
|
|
90
|
+
const ALLOWLIST_FILE = "tool-allowlist.json";
|
|
91
|
+
export function loadToolAllowlist(instanceDir) {
|
|
92
|
+
const p = join(instanceDir, ALLOWLIST_FILE);
|
|
93
|
+
if (!existsSync(p))
|
|
94
|
+
return [];
|
|
95
|
+
try {
|
|
96
|
+
const data = JSON.parse(readFileSync(p, "utf8"));
|
|
97
|
+
return Array.isArray(data) ? data : [];
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export function saveToolToAllowlist(instanceDir, pattern) {
|
|
104
|
+
const list = loadToolAllowlist(instanceDir);
|
|
105
|
+
if (!list.includes(pattern)) {
|
|
106
|
+
list.push(pattern);
|
|
107
|
+
writeFileSync(join(instanceDir, ALLOWLIST_FILE), JSON.stringify(list, null, 2));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// ── Prompt handler helpers ───────────────────────────────────────────────────
|
|
111
|
+
/**
|
|
112
|
+
* Select option N in a Claude Code interactive menu.
|
|
113
|
+
* Option 1 is pre-selected (❯), so:
|
|
114
|
+
* option 1 → Enter
|
|
115
|
+
* option 2 → Down + Enter
|
|
116
|
+
* option 3 → Down + Down + Enter
|
|
117
|
+
*/
|
|
118
|
+
async function selectOption(tmux, option) {
|
|
119
|
+
for (let i = 1; i < option; i++) {
|
|
120
|
+
await tmux.sendSpecialKey("Down");
|
|
121
|
+
}
|
|
122
|
+
await tmux.sendSpecialKey("Enter");
|
|
123
|
+
}
|
|
124
|
+
async function pressEscape(tmux) {
|
|
125
|
+
await tmux.sendSpecialKey("Escape");
|
|
126
|
+
}
|
|
127
|
+
// ── Main detector ────────────────────────────────────────────────────────────
|
|
128
|
+
export class TmuxPromptDetector {
|
|
129
|
+
outputLogPath;
|
|
130
|
+
tmux;
|
|
131
|
+
approvalFn;
|
|
132
|
+
logger;
|
|
133
|
+
instanceDir;
|
|
134
|
+
pollTimer = null;
|
|
135
|
+
byteOffset = 0;
|
|
136
|
+
pendingApproval = false;
|
|
137
|
+
constructor(outputLogPath, tmux, approvalFn, logger, instanceDir) {
|
|
138
|
+
this.outputLogPath = outputLogPath;
|
|
139
|
+
this.tmux = tmux;
|
|
140
|
+
this.approvalFn = approvalFn;
|
|
141
|
+
this.logger = logger;
|
|
142
|
+
this.instanceDir = instanceDir;
|
|
143
|
+
}
|
|
144
|
+
startPolling(intervalMs = 2000) {
|
|
145
|
+
if (this.pollTimer !== null)
|
|
146
|
+
return;
|
|
147
|
+
// Skip existing content — only detect prompts written after we start
|
|
148
|
+
try {
|
|
149
|
+
this.byteOffset = statSync(this.outputLogPath).size;
|
|
150
|
+
}
|
|
151
|
+
catch { /* file may not exist yet */ }
|
|
152
|
+
this.pollTimer = setInterval(async () => {
|
|
153
|
+
// Read new content from log file
|
|
154
|
+
let newContent;
|
|
155
|
+
try {
|
|
156
|
+
const stat = statSync(this.outputLogPath);
|
|
157
|
+
const fileSize = stat.size;
|
|
158
|
+
if (fileSize <= this.byteOffset)
|
|
159
|
+
return;
|
|
160
|
+
const buf = Buffer.alloc(fileSize - this.byteOffset);
|
|
161
|
+
const fd = await import("node:fs").then(fs => fs.openSync(this.outputLogPath, "r"));
|
|
162
|
+
const { readSync, closeSync } = await import("node:fs");
|
|
163
|
+
const bytesRead = readSync(fd, buf, 0, buf.length, this.byteOffset);
|
|
164
|
+
closeSync(fd);
|
|
165
|
+
if (bytesRead <= 0)
|
|
166
|
+
return;
|
|
167
|
+
newContent = buf.subarray(0, bytesRead).toString("utf8");
|
|
168
|
+
this.byteOffset += bytesRead;
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// File may not exist yet (ENOENT); silently ignore
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
// Detect and handle prompts
|
|
175
|
+
try {
|
|
176
|
+
if (!detectInteractivePrompt(newContent) || this.pendingApproval)
|
|
177
|
+
return;
|
|
178
|
+
const promptType = classifyPrompt(newContent);
|
|
179
|
+
this.logger.info({ promptType }, "TmuxPromptDetector: interactive prompt detected");
|
|
180
|
+
switch (promptType) {
|
|
181
|
+
case "dev_channels":
|
|
182
|
+
case "mcp_trust":
|
|
183
|
+
// Auto-confirm: option 1 is already selected
|
|
184
|
+
await selectOption(this.tmux, 1);
|
|
185
|
+
this.logger.info({ promptType }, "TmuxPromptDetector: auto-confirmed");
|
|
186
|
+
break;
|
|
187
|
+
case "settings_error":
|
|
188
|
+
// "Continue without these settings" is option 2
|
|
189
|
+
await selectOption(this.tmux, 2);
|
|
190
|
+
this.logger.info("TmuxPromptDetector: auto-continued past settings error");
|
|
191
|
+
break;
|
|
192
|
+
case "file_creation":
|
|
193
|
+
// Auto-deny file creation prompts (SKILL.md etc.)
|
|
194
|
+
await pressEscape(this.tmux);
|
|
195
|
+
this.logger.info("TmuxPromptDetector: auto-denied file creation");
|
|
196
|
+
break;
|
|
197
|
+
case "permission":
|
|
198
|
+
// Forward to user via Telegram
|
|
199
|
+
this.pendingApproval = true;
|
|
200
|
+
try {
|
|
201
|
+
const toolPattern = extractToolPattern(newContent);
|
|
202
|
+
const cleanPrompt = formatPromptForDisplay(newContent);
|
|
203
|
+
const result = await this.approvalFn(cleanPrompt);
|
|
204
|
+
this.logger.info({ decision: result.decision }, "TmuxPromptDetector: user responded");
|
|
205
|
+
if (result.decision === "always_allow") {
|
|
206
|
+
await selectOption(this.tmux, 2); // "Yes, and don't ask again"
|
|
207
|
+
if (toolPattern && this.instanceDir) {
|
|
208
|
+
saveToolToAllowlist(this.instanceDir, toolPattern);
|
|
209
|
+
this.logger.info({ toolPattern }, "TmuxPromptDetector: added to allowlist");
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
else if (result.decision === "approve") {
|
|
213
|
+
await selectOption(this.tmux, 1); // "Yes"
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
await selectOption(this.tmux, 3); // "No"
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
this.logger.warn("TmuxPromptDetector: approval error, denying", err);
|
|
221
|
+
await pressEscape(this.tmux);
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
this.pendingApproval = false;
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
case "unknown":
|
|
228
|
+
default:
|
|
229
|
+
// Forward unknown prompts to user too
|
|
230
|
+
this.pendingApproval = true;
|
|
231
|
+
try {
|
|
232
|
+
const cleanPrompt = formatPromptForDisplay(newContent);
|
|
233
|
+
const result = await this.approvalFn(cleanPrompt);
|
|
234
|
+
this.logger.info({ decision: result.decision }, "TmuxPromptDetector: user responded to unknown prompt");
|
|
235
|
+
if (result.decision === "deny") {
|
|
236
|
+
await pressEscape(this.tmux);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
await selectOption(this.tmux, 1);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
this.logger.error({ err }, "Unknown prompt approval error");
|
|
244
|
+
await pressEscape(this.tmux);
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
this.pendingApproval = false;
|
|
248
|
+
}
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
this.logger.error({ err }, "Prompt detection error");
|
|
254
|
+
}
|
|
255
|
+
}, intervalMs);
|
|
256
|
+
}
|
|
257
|
+
stop() {
|
|
258
|
+
if (this.pollTimer !== null) {
|
|
259
|
+
clearInterval(this.pollTimer);
|
|
260
|
+
this.pollTimer = null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
//# sourceMappingURL=tmux-prompt-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tmux-prompt-detector.js","sourceRoot":"","sources":["../../src/approval/tmux-prompt-detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ApprovalStrategy {
|
|
2
|
+
/**
|
|
3
|
+
* Return hook definitions to merge into CLI settings.
|
|
4
|
+
* Hook-based: returns { hooks: { PreToolUse: [...] } }
|
|
5
|
+
* Shell-wrapper: returns {} (no hooks needed)
|
|
6
|
+
*/
|
|
7
|
+
setup(port: number): {
|
|
8
|
+
hooks?: Record<string, unknown>;
|
|
9
|
+
};
|
|
10
|
+
/** Start the approval service. Returns the actual port. */
|
|
11
|
+
start(): Promise<number>;
|
|
12
|
+
/** Stop the approval service */
|
|
13
|
+
stop(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-strategy.js","sourceRoot":"","sources":["../../src/backend/approval-strategy.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CliBackend, CliBackendConfig } from "./types.js";
|
|
2
|
+
export declare class ClaudeCodeBackend implements CliBackend {
|
|
3
|
+
private instanceDir;
|
|
4
|
+
constructor(instanceDir: string);
|
|
5
|
+
buildCommand(config: CliBackendConfig): string;
|
|
6
|
+
writeConfig(config: CliBackendConfig): void;
|
|
7
|
+
getContextUsage(): number | null;
|
|
8
|
+
getSessionId(): string | null;
|
|
9
|
+
cleanup(_config: CliBackendConfig): void;
|
|
10
|
+
/** Pre-approve ANTHROPIC_API_KEY in ~/.claude.json to skip the interactive prompt */
|
|
11
|
+
private preApproveApiKey;
|
|
12
|
+
private writeStatusLineScript;
|
|
13
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
export class ClaudeCodeBackend {
|
|
5
|
+
instanceDir;
|
|
6
|
+
constructor(instanceDir) {
|
|
7
|
+
this.instanceDir = instanceDir;
|
|
8
|
+
}
|
|
9
|
+
buildCommand(config) {
|
|
10
|
+
const settingsPath = join(this.instanceDir, "claude-settings.json");
|
|
11
|
+
const mcpConfigPath = join(this.instanceDir, "mcp-config.json");
|
|
12
|
+
// Forward Anthropic env vars to the CLI process (tmux shell doesn't inherit daemon's env)
|
|
13
|
+
const envPrefix = ["CMUX_CLAUDE_HOOKS_DISABLED=1", "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1"];
|
|
14
|
+
for (const key of ["ANTHROPIC_BASE_URL", "ANTHROPIC_API_KEY"]) {
|
|
15
|
+
if (process.env[key])
|
|
16
|
+
envPrefix.push(`${key}=${process.env[key]}`);
|
|
17
|
+
}
|
|
18
|
+
let cmd = `${envPrefix.join(" ")} claude --settings ${settingsPath} --mcp-config ${mcpConfigPath} --dangerously-skip-permissions`;
|
|
19
|
+
const sessionIdFile = join(this.instanceDir, "session-id");
|
|
20
|
+
if (existsSync(sessionIdFile)) {
|
|
21
|
+
const sid = readFileSync(sessionIdFile, "utf-8").trim();
|
|
22
|
+
if (sid && /^[a-zA-Z0-9_-]+$/.test(sid))
|
|
23
|
+
cmd += ` --resume ${sid}`;
|
|
24
|
+
}
|
|
25
|
+
if (config.model) {
|
|
26
|
+
cmd += ` --model ${config.model}`;
|
|
27
|
+
}
|
|
28
|
+
if (config.systemPrompt) {
|
|
29
|
+
const promptPath = join(this.instanceDir, "system-prompt.md");
|
|
30
|
+
writeFileSync(promptPath, config.systemPrompt);
|
|
31
|
+
cmd += ` --system-prompt "${promptPath}"`;
|
|
32
|
+
}
|
|
33
|
+
return cmd;
|
|
34
|
+
}
|
|
35
|
+
writeConfig(config) {
|
|
36
|
+
// 1. Write mcp-config.json to instance dir (loaded via --mcp-config)
|
|
37
|
+
const mcpConfigPath = join(this.instanceDir, "mcp-config.json");
|
|
38
|
+
const mcpConfig = { mcpServers: config.mcpServers };
|
|
39
|
+
writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2));
|
|
40
|
+
// 2. Write statusline script
|
|
41
|
+
const statusLineCommand = this.writeStatusLineScript();
|
|
42
|
+
// 3. Write claude-settings.json (permissions handled by --dangerously-skip-permissions)
|
|
43
|
+
const settings = {
|
|
44
|
+
statusLine: {
|
|
45
|
+
type: "command",
|
|
46
|
+
command: statusLineCommand,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
writeFileSync(join(this.instanceDir, "claude-settings.json"), JSON.stringify(settings));
|
|
50
|
+
// 4. Pre-approve API key to skip interactive prompt on startup
|
|
51
|
+
this.preApproveApiKey(config);
|
|
52
|
+
}
|
|
53
|
+
getContextUsage() {
|
|
54
|
+
try {
|
|
55
|
+
const sf = join(this.instanceDir, "statusline.json");
|
|
56
|
+
const data = JSON.parse(readFileSync(sf, "utf-8"));
|
|
57
|
+
return data.context_window?.used_percentage ?? null;
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
// File may not exist yet during startup — return null to signal unavailable
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
getSessionId() {
|
|
65
|
+
try {
|
|
66
|
+
const sf = join(this.instanceDir, "statusline.json");
|
|
67
|
+
const data = JSON.parse(readFileSync(sf, "utf-8"));
|
|
68
|
+
return data.session_id ?? null;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
cleanup(_config) {
|
|
75
|
+
// mcp-config.json is in instance dir, cleaned up when instance is deleted
|
|
76
|
+
}
|
|
77
|
+
/** Pre-approve ANTHROPIC_API_KEY in ~/.claude.json to skip the interactive prompt */
|
|
78
|
+
preApproveApiKey(_config) {
|
|
79
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
80
|
+
if (!apiKey)
|
|
81
|
+
return;
|
|
82
|
+
const fingerprint = apiKey.length > 20 ? apiKey.slice(-20) : apiKey;
|
|
83
|
+
const claudeJsonPath = join(homedir(), ".claude.json");
|
|
84
|
+
let claudeCfg = {};
|
|
85
|
+
try {
|
|
86
|
+
claudeCfg = JSON.parse(readFileSync(claudeJsonPath, "utf-8"));
|
|
87
|
+
}
|
|
88
|
+
catch { /* new file or parse error */ }
|
|
89
|
+
const existing = claudeCfg.customApiKeyResponses;
|
|
90
|
+
const approved = existing?.approved ?? [];
|
|
91
|
+
if (!approved.includes(fingerprint)) {
|
|
92
|
+
claudeCfg.customApiKeyResponses = {
|
|
93
|
+
approved: [...approved, fingerprint],
|
|
94
|
+
rejected: existing?.rejected ?? [],
|
|
95
|
+
};
|
|
96
|
+
writeFileSync(claudeJsonPath, JSON.stringify(claudeCfg, null, 2));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
writeStatusLineScript() {
|
|
100
|
+
const statusFile = join(this.instanceDir, "statusline.json");
|
|
101
|
+
// Use a Node.js script instead of bash to avoid shell injection via statusFile path
|
|
102
|
+
const script = [
|
|
103
|
+
"#!/usr/bin/env node",
|
|
104
|
+
"const fs = require('fs');",
|
|
105
|
+
"let input = '';",
|
|
106
|
+
"process.stdin.on('data', d => input += d);",
|
|
107
|
+
`process.stdin.on('end', () => { fs.writeFileSync(${JSON.stringify(statusFile)}, input); console.log('ok'); });`,
|
|
108
|
+
].join("\n");
|
|
109
|
+
const scriptPath = join(this.instanceDir, "statusline.js");
|
|
110
|
+
writeFileSync(scriptPath, script, { mode: 0o755 });
|
|
111
|
+
return scriptPath;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=claude-code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../src/backend/claude-code.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CliBackend, CliBackendConfig } from "./types.js";
|
|
2
|
+
export declare class CodexBackend implements CliBackend {
|
|
3
|
+
private instanceDir;
|
|
4
|
+
constructor(instanceDir: string);
|
|
5
|
+
buildCommand(config: CliBackendConfig): string;
|
|
6
|
+
writeConfig(config: CliBackendConfig): void;
|
|
7
|
+
getContextUsage(): number | null;
|
|
8
|
+
getSessionId(): string | null;
|
|
9
|
+
cleanup(config: CliBackendConfig): void;
|
|
10
|
+
}
|