clearclaw 0.3.0 → 0.5.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/dist/channel/slack.d.ts +30 -0
- package/dist/channel/slack.js +411 -0
- package/dist/channel/telegram.d.ts +12 -2
- package/dist/channel/telegram.js +196 -33
- package/dist/config.d.ts +13 -3
- package/dist/config.js +18 -4
- package/dist/engine/claude-code.d.ts +2 -1
- package/dist/engine/claude-code.js +78 -8
- package/dist/files.d.ts +3 -0
- package/dist/files.js +24 -0
- package/dist/format.d.ts +4 -0
- package/dist/format.js +22 -18
- package/dist/index.js +10 -2
- package/dist/logger.js +1 -1
- package/dist/orchestrator.d.ts +2 -1
- package/dist/orchestrator.js +175 -102
- package/dist/types.d.ts +25 -4
- package/package.json +4 -1
package/dist/orchestrator.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export interface OrchestratorOpts {
|
|
|
6
6
|
workspaceStore: WorkspaceStore;
|
|
7
7
|
permissionMode: PermissionMode;
|
|
8
8
|
defaultPromptPath: string;
|
|
9
|
+
filesPath: string;
|
|
9
10
|
}
|
|
10
11
|
export declare class Orchestrator {
|
|
11
12
|
private channel;
|
|
@@ -13,6 +14,7 @@ export declare class Orchestrator {
|
|
|
13
14
|
private workspaceStore;
|
|
14
15
|
private permissionMode;
|
|
15
16
|
private defaultPromptPath;
|
|
17
|
+
private filesPath;
|
|
16
18
|
private chats;
|
|
17
19
|
constructor(opts: OrchestratorOpts);
|
|
18
20
|
private chat;
|
|
@@ -21,7 +23,6 @@ export declare class Orchestrator {
|
|
|
21
23
|
private handleMessage;
|
|
22
24
|
private routeEngineEvent;
|
|
23
25
|
private updateStatusMessage;
|
|
24
|
-
private clearStatusMessage;
|
|
25
26
|
/** Read default workspace CLAUDE.md to append to non-default workspace sessions. */
|
|
26
27
|
private readDefaultPrompt;
|
|
27
28
|
}
|
package/dist/orchestrator.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
|
|
4
|
+
import { z } from "zod";
|
|
3
5
|
import log from "./logger.js";
|
|
4
|
-
import {
|
|
6
|
+
import { saveFile } from "./files.js";
|
|
7
|
+
import { formatToolStatusLine, formatToolCallSummary, formatPermissionPrompt, timeAgo } from "./format.js";
|
|
5
8
|
const MODE_OPTIONS = [
|
|
6
9
|
{ label: "Default", value: "default" },
|
|
7
10
|
{ label: "Accept Edits", value: "acceptEdits" },
|
|
@@ -14,6 +17,7 @@ export class Orchestrator {
|
|
|
14
17
|
workspaceStore;
|
|
15
18
|
permissionMode;
|
|
16
19
|
defaultPromptPath;
|
|
20
|
+
filesPath;
|
|
17
21
|
chats = new Map();
|
|
18
22
|
constructor(opts) {
|
|
19
23
|
this.channel = opts.channel;
|
|
@@ -21,12 +25,13 @@ export class Orchestrator {
|
|
|
21
25
|
this.workspaceStore = opts.workspaceStore;
|
|
22
26
|
this.permissionMode = opts.permissionMode;
|
|
23
27
|
this.defaultPromptPath = opts.defaultPromptPath;
|
|
28
|
+
this.filesPath = opts.filesPath;
|
|
24
29
|
}
|
|
25
30
|
chat(chatId) {
|
|
26
31
|
let s = this.chats.get(chatId);
|
|
27
32
|
if (!s) {
|
|
28
33
|
s = {
|
|
29
|
-
busy: false, abort: null, permissionMode: null,
|
|
34
|
+
busy: false, abort: null, permissionMode: null, stats: null, lastStatusText: null,
|
|
30
35
|
toolCallHandle: null,
|
|
31
36
|
};
|
|
32
37
|
this.chats.set(chatId, s);
|
|
@@ -91,6 +96,51 @@ export class Orchestrator {
|
|
|
91
96
|
await this.channel.sendMessage(msg.chatId, "Session cleared.");
|
|
92
97
|
return;
|
|
93
98
|
}
|
|
99
|
+
// /resume — switch to a previous session
|
|
100
|
+
if (msg.text === "/resume") {
|
|
101
|
+
if (state.busy) {
|
|
102
|
+
await this.channel.sendMessage(msg.chatId, "A turn is in progress. Wait for it to finish before switching sessions.");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const ws = this.workspaceStore.byChat(msg.chatId);
|
|
106
|
+
if (!ws) {
|
|
107
|
+
await this.channel.sendMessage(msg.chatId, "No workspace linked to this group.");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const sessions = await this.engine.listSessions(ws.cwd);
|
|
111
|
+
if (sessions.length === 0) {
|
|
112
|
+
await this.channel.sendMessage(msg.chatId, "No sessions found for this workspace.");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const MAX_BTN = 45;
|
|
116
|
+
// Strip ClearClaw's "[User (@handle)]: " prefix from SDK summaries
|
|
117
|
+
const stripped = sessions.map((s) => ({
|
|
118
|
+
...s,
|
|
119
|
+
summary: s.summary.replace(/^\[.*?\]:\s*/, ""),
|
|
120
|
+
}));
|
|
121
|
+
// Build detailed list for message body
|
|
122
|
+
const listing = stripped.map((s, i) => {
|
|
123
|
+
const current = s.sessionId === ws.current_session_id ? " ✅" : "";
|
|
124
|
+
const meta = [timeAgo(s.lastModified), s.gitBranch].filter(Boolean).join(" · ");
|
|
125
|
+
return `${i + 1}. ${s.summary}${current}\n ${meta}`;
|
|
126
|
+
}).join("\n");
|
|
127
|
+
// Concise button labels (single line)
|
|
128
|
+
const buttons = stripped.map((s, i) => {
|
|
129
|
+
const label = `${i + 1}. ${s.summary}`;
|
|
130
|
+
const truncated = label.length > MAX_BTN
|
|
131
|
+
? label.slice(0, MAX_BTN - 1) + "…"
|
|
132
|
+
: label;
|
|
133
|
+
return [{ label: truncated, value: s.sessionId }];
|
|
134
|
+
});
|
|
135
|
+
const resp = await this.channel.sendInteractive(msg.chatId, `Pick a session to resume:\n\n${listing}`, buttons);
|
|
136
|
+
if (resp.value) {
|
|
137
|
+
this.workspaceStore.setSession(ws.name, resp.value);
|
|
138
|
+
const picked = stripped.find((s) => s.sessionId === resp.value);
|
|
139
|
+
await this.channel.sendMessage(msg.chatId, `Resumed session: ${picked?.summary ?? resp.value}`);
|
|
140
|
+
log.info("[cmd] resumed session %s for workspace %s", resp.value, ws.name);
|
|
141
|
+
}
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
94
144
|
// /cancel — abort the running turn
|
|
95
145
|
if (msg.text === "/cancel") {
|
|
96
146
|
if (state.abort) {
|
|
@@ -126,31 +176,78 @@ export class Orchestrator {
|
|
|
126
176
|
// In group workspaces, prepend sender identity so the engine knows who's talking
|
|
127
177
|
const sender = msg.user.handle ? `${msg.user.name} (@${msg.user.handle})` : msg.user.name;
|
|
128
178
|
const prompt = isDefaultWorkspace ? msg.text : `[${sender}]: ${msg.text}`;
|
|
179
|
+
// Save attachments to disk for the audit log (failures must not block the turn)
|
|
180
|
+
if (msg.attachments?.length) {
|
|
181
|
+
const results = await Promise.allSettled(msg.attachments.map((att) => saveFile(att, ws.name, this.filesPath)));
|
|
182
|
+
for (const r of results) {
|
|
183
|
+
if (r.status === "rejected")
|
|
184
|
+
log.warn({ err: r.reason }, "[turn] failed to save attachment");
|
|
185
|
+
}
|
|
186
|
+
const saved = results.filter((r) => r.status === "fulfilled").length;
|
|
187
|
+
if (saved > 0)
|
|
188
|
+
log.info("[turn] saved %d/%d attachment(s) for workspace %s", saved, msg.attachments.length, ws.name);
|
|
189
|
+
}
|
|
190
|
+
const mcpServer = createSdkMcpServer({
|
|
191
|
+
name: "clearclaw",
|
|
192
|
+
tools: [
|
|
193
|
+
tool("send_file", "Send a file or image to the current chat conversation", {
|
|
194
|
+
file_path: z.string().optional().describe("Absolute path to the file to send"),
|
|
195
|
+
data: z.string().optional().describe("Base64-encoded file data (alternative to file_path)"),
|
|
196
|
+
filename: z.string().optional().describe("Filename (required when using data, optional with file_path)"),
|
|
197
|
+
caption: z.string().optional().describe("Optional caption to accompany the file"),
|
|
198
|
+
}, async (args) => {
|
|
199
|
+
if (!args.file_path && !args.data)
|
|
200
|
+
throw new Error("Either file_path or data must be provided");
|
|
201
|
+
const buffer = args.file_path
|
|
202
|
+
? await fs.promises.readFile(args.file_path)
|
|
203
|
+
: Buffer.from(args.data, "base64");
|
|
204
|
+
const name = args.filename ?? path.basename(args.file_path ?? "file");
|
|
205
|
+
await this.channel.sendFile(msg.chatId, buffer, name, { caption: args.caption });
|
|
206
|
+
return { content: [{ type: "text", text: `Sent ${name} to chat` }] };
|
|
207
|
+
}),
|
|
208
|
+
],
|
|
209
|
+
});
|
|
129
210
|
try {
|
|
130
211
|
for await (const event of this.engine.runTurn({
|
|
131
212
|
sessionId: ws.current_session_id,
|
|
132
213
|
cwd: ws.cwd,
|
|
133
214
|
prompt,
|
|
215
|
+
attachments: msg.attachments,
|
|
134
216
|
permissionMode: state.permissionMode ?? this.permissionMode,
|
|
135
217
|
appendSystemPrompt,
|
|
218
|
+
mcpServers: { clearclaw: mcpServer },
|
|
136
219
|
signal: abort.signal,
|
|
137
220
|
onPermissionRequest: async (req) => {
|
|
221
|
+
// Auto-allow ClearClaw's own MCP tools — no chat prompt needed
|
|
222
|
+
if (req.toolName.startsWith("mcp__clearclaw__")) {
|
|
223
|
+
log.info(`[perm] ${req.toolName} → auto-allow`);
|
|
224
|
+
return { decision: "allow" };
|
|
225
|
+
}
|
|
138
226
|
log.info(`[perm] ${req.toolName}`);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
[
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
227
|
+
try {
|
|
228
|
+
const promptText = formatPermissionPrompt(req.toolName, req.input, req.description);
|
|
229
|
+
const resp = await this.channel.sendInteractive(msg.chatId, promptText, [
|
|
230
|
+
[
|
|
231
|
+
{ label: "👍 Allow", value: "allow" },
|
|
232
|
+
{ label: "👎 Deny", value: "deny" },
|
|
233
|
+
],
|
|
234
|
+
[
|
|
235
|
+
{ label: "📝 Deny + Note", value: "deny", requestText: true },
|
|
236
|
+
],
|
|
237
|
+
]);
|
|
238
|
+
log.info(`[perm] ${req.toolName} → ${resp.value || "timeout"}${resp.text ? ` "${resp.text}"` : ""}`);
|
|
239
|
+
return {
|
|
240
|
+
decision: resp.value === "allow" ? "allow" : "deny",
|
|
241
|
+
message: resp.text,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
catch (err) {
|
|
245
|
+
log.warn({ err }, "[perm] failed to send prompt for %s, auto-denying", req.toolName);
|
|
246
|
+
return {
|
|
247
|
+
decision: "deny",
|
|
248
|
+
message: "Permission prompt could not be delivered to chat — denied automatically.",
|
|
249
|
+
};
|
|
250
|
+
}
|
|
154
251
|
},
|
|
155
252
|
})) {
|
|
156
253
|
await this.routeEngineEvent(msg.chatId, event, ws.name);
|
|
@@ -173,113 +270,89 @@ export class Orchestrator {
|
|
|
173
270
|
}
|
|
174
271
|
async routeEngineEvent(chatId, event, workspaceName) {
|
|
175
272
|
const state = this.chat(chatId);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
273
|
+
try {
|
|
274
|
+
switch (event.type) {
|
|
275
|
+
case "text":
|
|
276
|
+
await this.channel.sendMessage(chatId, event.text);
|
|
277
|
+
break;
|
|
278
|
+
case "tool_use": {
|
|
279
|
+
// Rolling message: each tool_use replaces the single message's content
|
|
280
|
+
const line = formatToolStatusLine(event.toolName, event.input);
|
|
281
|
+
if (state.toolCallHandle) {
|
|
282
|
+
try {
|
|
283
|
+
await this.channel.editMessage(chatId, state.toolCallHandle, line);
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
const handles = await this.channel.sendMessage(chatId, line, { consumeTyping: false });
|
|
287
|
+
state.toolCallHandle = handles[0];
|
|
288
|
+
}
|
|
186
289
|
}
|
|
187
|
-
|
|
188
|
-
const handles = await this.channel.sendMessage(chatId, line);
|
|
290
|
+
else {
|
|
291
|
+
const handles = await this.channel.sendMessage(chatId, line, { consumeTyping: false });
|
|
189
292
|
state.toolCallHandle = handles[0];
|
|
190
293
|
}
|
|
294
|
+
break;
|
|
191
295
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
296
|
+
case "tool_result":
|
|
297
|
+
// All tool results suppressed — Claude summarizes in its text response
|
|
298
|
+
break;
|
|
299
|
+
case "rate_limit": {
|
|
300
|
+
const resetMsg = event.resetsAt
|
|
301
|
+
? ` Resets at ${new Date(event.resetsAt).toLocaleTimeString()}.`
|
|
302
|
+
: "";
|
|
303
|
+
log.warn(`[turn] rate limit: ${event.status}${resetMsg}`);
|
|
304
|
+
await this.channel.sendMessage(chatId, `⚠️ Rate limited (${event.status}).${resetMsg}`);
|
|
305
|
+
break;
|
|
195
306
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
// Edit rolling tool message to show summary
|
|
211
|
-
if (state.toolCallHandle && event.stats) {
|
|
212
|
-
const summary = formatToolCallSummary(event.stats.toolCalls);
|
|
213
|
-
if (summary) {
|
|
214
|
-
try {
|
|
215
|
-
await this.channel.editMessage(chatId, state.toolCallHandle, summary);
|
|
307
|
+
case "done": {
|
|
308
|
+
// Persist state first — channel calls below are best-effort
|
|
309
|
+
log.info(`[turn] done session=${event.sessionId}`);
|
|
310
|
+
this.workspaceStore.setSession(workspaceName, event.sessionId);
|
|
311
|
+
if (event.stats)
|
|
312
|
+
state.stats = event.stats;
|
|
313
|
+
// Best-effort: update tool summary and status message
|
|
314
|
+
if (state.toolCallHandle && event.stats) {
|
|
315
|
+
const summary = formatToolCallSummary(event.stats.toolCalls);
|
|
316
|
+
if (summary) {
|
|
317
|
+
try {
|
|
318
|
+
await this.channel.editMessage(chatId, state.toolCallHandle, summary);
|
|
319
|
+
}
|
|
320
|
+
catch { /* edit failed, leave as-is */ }
|
|
216
321
|
}
|
|
217
|
-
catch { /* edit failed, leave as-is */ }
|
|
218
322
|
}
|
|
323
|
+
state.toolCallHandle = null;
|
|
324
|
+
await this.updateStatusMessage(chatId, state);
|
|
325
|
+
break;
|
|
219
326
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
state.stats = event.stats;
|
|
225
|
-
await this.updateStatusMessage(chatId, state);
|
|
226
|
-
break;
|
|
327
|
+
case "error":
|
|
328
|
+
log.error(`[turn] error: ${event.message}`);
|
|
329
|
+
await this.channel.sendMessage(chatId, `Error: ${event.message}`);
|
|
330
|
+
break;
|
|
227
331
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
332
|
+
}
|
|
333
|
+
catch (err) {
|
|
334
|
+
// Channel errors must never propagate — they'd crash the engine turn loop,
|
|
335
|
+
// killing the SDK session and losing the session ID.
|
|
336
|
+
log.warn({ err }, "[route] failed to relay %s event to channel", event.type);
|
|
232
337
|
}
|
|
233
338
|
}
|
|
234
339
|
async updateStatusMessage(chatId, state) {
|
|
235
340
|
const mode = state.permissionMode ?? this.permissionMode;
|
|
236
341
|
const modeLabel = MODE_OPTIONS.find((o) => o.value === mode)?.label ?? mode;
|
|
237
|
-
|
|
342
|
+
let text;
|
|
238
343
|
if (state.stats) {
|
|
239
344
|
const pct = state.stats.contextWindow > 0
|
|
240
345
|
? Math.round((state.stats.contextUsed / state.stats.contextWindow) * 100)
|
|
241
346
|
: 0;
|
|
242
|
-
|
|
347
|
+
text = `🤖 ${formatModelName(state.stats.model)} ${pct}% | 🔒 ${modeLabel}`;
|
|
243
348
|
}
|
|
244
349
|
else {
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
const text = parts.join("\n");
|
|
248
|
-
if (state.statusHandle) {
|
|
249
|
-
try {
|
|
250
|
-
await this.channel.editMessage(chatId, state.statusHandle, text);
|
|
251
|
-
}
|
|
252
|
-
catch (err) {
|
|
253
|
-
if (!(err instanceof Error && err.message.includes("message is not modified")))
|
|
254
|
-
throw err;
|
|
255
|
-
}
|
|
350
|
+
text = `🔒 ${modeLabel}`;
|
|
256
351
|
}
|
|
257
|
-
|
|
258
|
-
// Clear stale pins from previous server runs
|
|
259
|
-
try {
|
|
260
|
-
await this.channel.unpinAllMessages(chatId);
|
|
261
|
-
}
|
|
262
|
-
catch { /* not admin */ }
|
|
263
|
-
const handles = await this.channel.sendMessage(chatId, text);
|
|
264
|
-
state.statusHandle = handles[0];
|
|
265
|
-
try {
|
|
266
|
-
await this.channel.pinMessage(chatId, state.statusHandle);
|
|
267
|
-
}
|
|
268
|
-
catch (err) {
|
|
269
|
-
log.warn("[cmd] failed to pin status message (bot may not be admin): %s", err instanceof Error ? err.message : String(err));
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
async clearStatusMessage(chatId, state) {
|
|
274
|
-
if (!state.statusHandle)
|
|
352
|
+
if (text === state.lastStatusText)
|
|
275
353
|
return;
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
catch (err) {
|
|
280
|
-
log.warn("[cmd] failed to delete status message: %s", err instanceof Error ? err.message : String(err));
|
|
281
|
-
}
|
|
282
|
-
state.statusHandle = null;
|
|
354
|
+
await this.channel.updateStatus(chatId, text);
|
|
355
|
+
state.lastStatusText = text;
|
|
283
356
|
}
|
|
284
357
|
/** Read default workspace CLAUDE.md to append to non-default workspace sessions. */
|
|
285
358
|
readDefaultPrompt() {
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PermissionMode } from "@anthropic-ai/claude-agent-sdk";
|
|
1
|
+
import type { McpServerConfig, PermissionMode } from "@anthropic-ai/claude-agent-sdk";
|
|
2
2
|
export interface ChannelEvents {
|
|
3
3
|
message: [msg: InboundMessage];
|
|
4
4
|
}
|
|
@@ -8,12 +8,14 @@ export interface Channel {
|
|
|
8
8
|
disconnect(): Promise<void>;
|
|
9
9
|
ownsId(chatId: string): boolean;
|
|
10
10
|
sendMessage(chatId: string, text: string, opts?: SendMessageOpts): Promise<string[]>;
|
|
11
|
-
sendInteractive(chatId: string, text: string, buttons: Button[][]
|
|
11
|
+
sendInteractive(chatId: string, text: string, buttons: Button[][]): Promise<ButtonResponse>;
|
|
12
12
|
editMessage(chatId: string, handle: string, text: string): Promise<void>;
|
|
13
13
|
deleteMessage(chatId: string, handle: string): Promise<void>;
|
|
14
14
|
pinMessage(chatId: string, handle: string): Promise<void>;
|
|
15
15
|
unpinAllMessages(chatId: string): Promise<void>;
|
|
16
|
+
updateStatus(chatId: string, text: string): Promise<void>;
|
|
16
17
|
setTyping(chatId: string, isTyping: boolean): Promise<void>;
|
|
18
|
+
sendFile(chatId: string, buffer: Buffer, filename: string, opts?: SendFileOpts): Promise<void>;
|
|
17
19
|
on<K extends keyof ChannelEvents>(event: K, listener: (...args: ChannelEvents[K]) => void): this;
|
|
18
20
|
off<K extends keyof ChannelEvents>(event: K, listener: (...args: ChannelEvents[K]) => void): this;
|
|
19
21
|
emit<K extends keyof ChannelEvents>(event: K, ...args: ChannelEvents[K]): boolean;
|
|
@@ -29,21 +31,33 @@ export interface ButtonResponse {
|
|
|
29
31
|
}
|
|
30
32
|
export interface SendMessageOpts {
|
|
31
33
|
parseMode?: "MarkdownV2" | "HTML";
|
|
34
|
+
/** When false, skip consuming the typing placeholder (e.g. for tool status messages). */
|
|
35
|
+
consumeTyping?: boolean;
|
|
32
36
|
}
|
|
33
|
-
export interface
|
|
34
|
-
|
|
37
|
+
export interface SendFileOpts {
|
|
38
|
+
caption?: string;
|
|
39
|
+
mimeType?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface SessionInfo {
|
|
42
|
+
sessionId: string;
|
|
43
|
+
summary: string;
|
|
44
|
+
lastModified: number;
|
|
45
|
+
gitBranch?: string;
|
|
35
46
|
}
|
|
36
47
|
export interface Engine {
|
|
37
48
|
name: string;
|
|
38
49
|
runTurn(opts: RunTurnOpts): AsyncIterable<EngineEvent>;
|
|
50
|
+
listSessions(cwd: string): Promise<SessionInfo[]>;
|
|
39
51
|
}
|
|
40
52
|
export interface RunTurnOpts {
|
|
41
53
|
sessionId: string | null;
|
|
42
54
|
cwd: string;
|
|
43
55
|
prompt: string;
|
|
56
|
+
attachments?: Attachment[];
|
|
44
57
|
permissionMode: PermissionMode;
|
|
45
58
|
onPermissionRequest: (req: PermissionRequest) => Promise<PermissionResponse>;
|
|
46
59
|
appendSystemPrompt?: string;
|
|
60
|
+
mcpServers?: Record<string, McpServerConfig>;
|
|
47
61
|
signal?: AbortSignal;
|
|
48
62
|
}
|
|
49
63
|
export interface PermissionRequest {
|
|
@@ -98,9 +112,16 @@ export interface UserInfo {
|
|
|
98
112
|
name: string;
|
|
99
113
|
handle?: string;
|
|
100
114
|
}
|
|
115
|
+
/** File data from a channel, kept in memory for engine consumption. */
|
|
116
|
+
export interface Attachment {
|
|
117
|
+
buffer: Buffer;
|
|
118
|
+
mimeType: string;
|
|
119
|
+
filename?: string;
|
|
120
|
+
}
|
|
101
121
|
export interface InboundMessage {
|
|
102
122
|
chatId: string;
|
|
103
123
|
user: UserInfo;
|
|
104
124
|
text: string;
|
|
125
|
+
attachments?: Attachment[];
|
|
105
126
|
}
|
|
106
127
|
export type { PermissionMode };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clearclaw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Your coding agent, from your phone — personal assistant + project workspaces on your machine, built on the CLI's permissions, config, and memory instead of replacing them.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,13 +37,16 @@
|
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@anthropic-ai/claude-agent-sdk": "^0.2.50",
|
|
40
|
+
"@slack/bolt": "^4.6.0",
|
|
40
41
|
"diff": "^8.0.3",
|
|
41
42
|
"grammy": "^1.30.0",
|
|
43
|
+
"mime": "^4.1.0",
|
|
42
44
|
"pino": "^10.3.1",
|
|
43
45
|
"pino-pretty": "^13.1.3",
|
|
44
46
|
"pino-roll": "^4.0.0"
|
|
45
47
|
},
|
|
46
48
|
"devDependencies": {
|
|
49
|
+
"@anthropic-ai/sdk": "^0.78.0",
|
|
47
50
|
"@types/diff": "^7.0.2",
|
|
48
51
|
"@types/node": "^22.0.0",
|
|
49
52
|
"nodemon": "^3.1.14",
|