clearclaw 0.1.0 → 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/dist/channel/telegram.d.ts +3 -2
- package/dist/channel/telegram.js +38 -11
- package/dist/config.d.ts +2 -1
- package/dist/config.js +6 -5
- package/dist/engine/claude-code.js +32 -6
- package/dist/index.js +17 -1
- package/dist/orchestrator.d.ts +6 -1
- package/dist/orchestrator.js +63 -12
- package/dist/types.d.ts +10 -0
- package/package.json +3 -2
|
@@ -3,10 +3,11 @@ import type { Channel, Button, ButtonResponse, SendMessageOpts } from "../types.
|
|
|
3
3
|
export declare class TelegramChannel extends EventEmitter implements Channel {
|
|
4
4
|
name: string;
|
|
5
5
|
private bot;
|
|
6
|
-
private
|
|
6
|
+
private allowedUserIds;
|
|
7
7
|
private typingIntervals;
|
|
8
8
|
private pendingCallbacks;
|
|
9
|
-
|
|
9
|
+
private pendingTextResolvers;
|
|
10
|
+
constructor(botToken: string, allowedUserIds: Set<string>);
|
|
10
11
|
connect(): Promise<void>;
|
|
11
12
|
disconnect(): Promise<void>;
|
|
12
13
|
ownsId(chatId: string): boolean;
|
package/dist/channel/telegram.js
CHANGED
|
@@ -4,22 +4,36 @@ import log from "../logger.js";
|
|
|
4
4
|
export class TelegramChannel extends EventEmitter {
|
|
5
5
|
name = "telegram";
|
|
6
6
|
bot;
|
|
7
|
-
|
|
7
|
+
allowedUserIds;
|
|
8
8
|
typingIntervals = new Map();
|
|
9
9
|
// Pending interactive responses: callbackId → resolve function
|
|
10
10
|
pendingCallbacks = new Map();
|
|
11
|
-
|
|
11
|
+
// Pending text input: chatId → resolve function (for requestText follow-ups)
|
|
12
|
+
pendingTextResolvers = new Map();
|
|
13
|
+
constructor(botToken, allowedUserIds) {
|
|
12
14
|
super();
|
|
13
|
-
this.
|
|
15
|
+
this.allowedUserIds = allowedUserIds;
|
|
14
16
|
this.bot = new Bot(botToken);
|
|
15
17
|
}
|
|
16
18
|
async connect() {
|
|
17
19
|
// Auth check: only respond to allowed user
|
|
18
20
|
this.bot.on("message:text", (ctx) => {
|
|
19
|
-
if (ctx.from?.id
|
|
21
|
+
if (!ctx.from?.id || !this.allowedUserIds.has(`tg:${ctx.from.id}`))
|
|
20
22
|
return;
|
|
21
23
|
const chatId = `tg:${ctx.chat.id}`;
|
|
22
|
-
|
|
24
|
+
// Intercept for requestText follow-ups before emitting as a new message
|
|
25
|
+
const textResolver = this.pendingTextResolvers.get(chatId);
|
|
26
|
+
if (textResolver) {
|
|
27
|
+
this.pendingTextResolvers.delete(chatId);
|
|
28
|
+
textResolver(ctx.message.text);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const user = {
|
|
32
|
+
id: `tg:${ctx.from.id}`,
|
|
33
|
+
name: [ctx.from.first_name, ctx.from.last_name].filter(Boolean).join(" "),
|
|
34
|
+
...(ctx.from.username && { handle: ctx.from.username }),
|
|
35
|
+
};
|
|
36
|
+
this.emit("message", { chatId, user, text: ctx.message.text });
|
|
23
37
|
});
|
|
24
38
|
// Handle inline keyboard button presses
|
|
25
39
|
this.bot.on("callback_query:data", async (ctx) => {
|
|
@@ -76,10 +90,10 @@ export class TelegramChannel extends EventEmitter {
|
|
|
76
90
|
async sendInteractive(chatId, text, buttons) {
|
|
77
91
|
const numId = this.numericId(chatId);
|
|
78
92
|
const callbackId = crypto.randomUUID().slice(0, 8);
|
|
79
|
-
// Pre-compute callback data keys
|
|
80
|
-
const cbEntries = buttons.map((btn) => ({
|
|
93
|
+
// Pre-compute callback data keys (use index to avoid collisions when buttons share values)
|
|
94
|
+
const cbEntries = buttons.map((btn, i) => ({
|
|
81
95
|
btn,
|
|
82
|
-
cbData: `${callbackId}:${
|
|
96
|
+
cbData: `${callbackId}:${i}`,
|
|
83
97
|
}));
|
|
84
98
|
// Build inline keyboard
|
|
85
99
|
const keyboard = new InlineKeyboard();
|
|
@@ -95,19 +109,32 @@ export class TelegramChannel extends EventEmitter {
|
|
|
95
109
|
}
|
|
96
110
|
};
|
|
97
111
|
// Wait for button press or timeout (30 minutes)
|
|
98
|
-
|
|
112
|
+
const pressed = await new Promise((resolve) => {
|
|
99
113
|
const timeout = setTimeout(() => {
|
|
100
114
|
cleanupAll();
|
|
101
|
-
resolve(
|
|
115
|
+
resolve(null);
|
|
102
116
|
}, 30 * 60 * 1000);
|
|
103
117
|
for (const { btn, cbData } of cbEntries) {
|
|
104
118
|
this.pendingCallbacks.set(cbData, () => {
|
|
105
119
|
clearTimeout(timeout);
|
|
106
120
|
cleanupAll();
|
|
107
|
-
resolve(
|
|
121
|
+
resolve(btn);
|
|
108
122
|
});
|
|
109
123
|
}
|
|
110
124
|
});
|
|
125
|
+
if (!pressed)
|
|
126
|
+
return { value: "" };
|
|
127
|
+
// If the button requests text, prompt for follow-up input
|
|
128
|
+
if (pressed.requestText) {
|
|
129
|
+
await this.bot.api.sendMessage(numId, "Type your note:", {
|
|
130
|
+
reply_markup: { force_reply: true },
|
|
131
|
+
});
|
|
132
|
+
const followUpText = await new Promise((resolve) => {
|
|
133
|
+
this.pendingTextResolvers.set(chatId, resolve);
|
|
134
|
+
});
|
|
135
|
+
return { value: pressed.value, text: followUpText };
|
|
136
|
+
}
|
|
137
|
+
return { value: pressed.value };
|
|
111
138
|
}
|
|
112
139
|
async setTyping(chatId, isTyping) {
|
|
113
140
|
if (isTyping) {
|
package/dist/config.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { PermissionMode } from "./types.js";
|
|
2
2
|
export interface Config {
|
|
3
3
|
botToken: string;
|
|
4
|
-
|
|
4
|
+
allowedUserIds: Set<string>;
|
|
5
5
|
permissionMode: PermissionMode;
|
|
6
6
|
dataDir: string;
|
|
7
|
+
defaultPromptPath: string;
|
|
7
8
|
workspacesPath: string;
|
|
8
9
|
logPath: string;
|
|
9
10
|
}
|
package/dist/config.js
CHANGED
|
@@ -5,10 +5,10 @@ export function loadConfig() {
|
|
|
5
5
|
const dataDir = process.env.CLEARCLAW_HOME ?? path.join(os.homedir(), ".clearclaw");
|
|
6
6
|
fs.mkdirSync(dataDir, { recursive: true });
|
|
7
7
|
const botToken = requireEnv("TELEGRAM_BOT_TOKEN");
|
|
8
|
-
const
|
|
9
|
-
if (
|
|
10
|
-
throw new Error("
|
|
11
|
-
|
|
8
|
+
const rawIds = process.env.ALLOWED_USER_IDS ?? process.env.ALLOWED_USER_ID;
|
|
9
|
+
if (!rawIds)
|
|
10
|
+
throw new Error("Missing required env var: ALLOWED_USER_IDS");
|
|
11
|
+
const allowedUserIds = new Set(rawIds.split(",").map((s) => s.trim()).filter(Boolean));
|
|
12
12
|
const permissionMode = (process.env.PERMISSION_MODE ?? "default");
|
|
13
13
|
const valid = [
|
|
14
14
|
"default",
|
|
@@ -22,9 +22,10 @@ export function loadConfig() {
|
|
|
22
22
|
}
|
|
23
23
|
return {
|
|
24
24
|
botToken,
|
|
25
|
-
|
|
25
|
+
allowedUserIds,
|
|
26
26
|
permissionMode,
|
|
27
27
|
dataDir,
|
|
28
|
+
defaultPromptPath: path.join(dataDir, "workspace", "CLAUDE.md"),
|
|
28
29
|
workspacesPath: path.join(dataDir, "workspaces.json"),
|
|
29
30
|
logPath: path.join(dataDir, "clearclaw.log"),
|
|
30
31
|
};
|
|
@@ -4,7 +4,7 @@ import { formatToolDescription } from "../format.js";
|
|
|
4
4
|
export class ClaudeCodeEngine {
|
|
5
5
|
name = "claude-code";
|
|
6
6
|
async *runTurn(opts) {
|
|
7
|
-
const { sessionId, cwd, prompt, permissionMode, onPermissionRequest, signal, } = opts;
|
|
7
|
+
const { sessionId, cwd, prompt, permissionMode, onPermissionRequest, appendSystemPrompt, signal, } = opts;
|
|
8
8
|
const abortController = new AbortController();
|
|
9
9
|
// Wire external signal to our abort controller
|
|
10
10
|
if (signal) {
|
|
@@ -26,7 +26,19 @@ export class ClaudeCodeEngine {
|
|
|
26
26
|
if (resp.decision === "allow") {
|
|
27
27
|
return { behavior: "allow", updatedInput: input };
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
const denyMessage = resp.message
|
|
30
|
+
? `User denied this action with feedback: ${resp.message}`
|
|
31
|
+
: "User denied";
|
|
32
|
+
// Deny with note: let the model read the feedback and adjust.
|
|
33
|
+
// Plain deny: interrupt to stop the turn immediately.
|
|
34
|
+
// (The SDK has a bug where interrupt causes an unhandled rejection —
|
|
35
|
+
// handleControlRequest writes to stdin after the subprocess exits.
|
|
36
|
+
// Suppressed by the unhandledRejection handler in index.ts.)
|
|
37
|
+
return {
|
|
38
|
+
behavior: "deny",
|
|
39
|
+
message: denyMessage,
|
|
40
|
+
interrupt: !resp.message,
|
|
41
|
+
};
|
|
30
42
|
};
|
|
31
43
|
const q = query({
|
|
32
44
|
prompt,
|
|
@@ -38,6 +50,15 @@ export class ClaudeCodeEngine {
|
|
|
38
50
|
canUseTool,
|
|
39
51
|
abortController,
|
|
40
52
|
settingSources: ["user", "project", "local"],
|
|
53
|
+
...(appendSystemPrompt
|
|
54
|
+
? {
|
|
55
|
+
systemPrompt: {
|
|
56
|
+
type: "preset",
|
|
57
|
+
preset: "claude_code",
|
|
58
|
+
append: appendSystemPrompt,
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
: {}),
|
|
41
62
|
},
|
|
42
63
|
});
|
|
43
64
|
let resultSessionId;
|
|
@@ -112,10 +133,15 @@ export class ClaudeCodeEngine {
|
|
|
112
133
|
}
|
|
113
134
|
}
|
|
114
135
|
catch (err) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
136
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
137
|
+
log.info("[sdk] turn aborted");
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
yield {
|
|
141
|
+
type: "error",
|
|
142
|
+
message: err instanceof Error ? err.message : String(err),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
119
145
|
}
|
|
120
146
|
if (resultSessionId) {
|
|
121
147
|
yield { type: "done", sessionId: resultSessionId };
|
package/dist/index.js
CHANGED
|
@@ -5,17 +5,33 @@ import { WorkspaceStore } from "./workspace-store.js";
|
|
|
5
5
|
import { ClaudeCodeEngine } from "./engine/claude-code.js";
|
|
6
6
|
import { TelegramChannel } from "./channel/telegram.js";
|
|
7
7
|
import { Orchestrator } from "./orchestrator.js";
|
|
8
|
+
// The Claude Agent SDK has a bug: handleControlRequest() writes to the
|
|
9
|
+
// subprocess stdin without a .catch(), so aborting during a permission
|
|
10
|
+
// callback causes an unhandled rejection (write to dead process).
|
|
11
|
+
// Suppress that specific error so we can use abort for deny + /cancel.
|
|
12
|
+
process.on("unhandledRejection", (reason) => {
|
|
13
|
+
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
14
|
+
if (msg.includes("Operation aborted") ||
|
|
15
|
+
msg.includes("Failed to write to process stdin") ||
|
|
16
|
+
msg.includes("Cannot write to terminated process")) {
|
|
17
|
+
log.warn("[sdk] suppressed write error (known SDK bug): %s", msg);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
log.fatal({ err: reason }, "Unhandled rejection");
|
|
21
|
+
process.exit(1);
|
|
22
|
+
});
|
|
8
23
|
async function main() {
|
|
9
24
|
const config = loadConfig();
|
|
10
25
|
initLogger(config.logPath);
|
|
11
26
|
const workspaceStore = new WorkspaceStore(config.workspacesPath);
|
|
12
|
-
const channel = new TelegramChannel(config.botToken, config.
|
|
27
|
+
const channel = new TelegramChannel(config.botToken, config.allowedUserIds);
|
|
13
28
|
const engine = new ClaudeCodeEngine();
|
|
14
29
|
const orchestrator = new Orchestrator({
|
|
15
30
|
channel,
|
|
16
31
|
engine,
|
|
17
32
|
workspaceStore,
|
|
18
33
|
permissionMode: config.permissionMode,
|
|
34
|
+
defaultPromptPath: config.defaultPromptPath,
|
|
19
35
|
});
|
|
20
36
|
await orchestrator.start();
|
|
21
37
|
}
|
package/dist/orchestrator.d.ts
CHANGED
|
@@ -5,16 +5,21 @@ export interface OrchestratorOpts {
|
|
|
5
5
|
engine: Engine;
|
|
6
6
|
workspaceStore: WorkspaceStore;
|
|
7
7
|
permissionMode: PermissionMode;
|
|
8
|
+
defaultPromptPath: string;
|
|
8
9
|
}
|
|
9
10
|
export declare class Orchestrator {
|
|
10
11
|
private channel;
|
|
11
12
|
private engine;
|
|
12
13
|
private workspaceStore;
|
|
13
14
|
private permissionMode;
|
|
14
|
-
private
|
|
15
|
+
private defaultPromptPath;
|
|
16
|
+
private chats;
|
|
15
17
|
constructor(opts: OrchestratorOpts);
|
|
18
|
+
private chat;
|
|
16
19
|
start(): Promise<void>;
|
|
17
20
|
stop(): Promise<void>;
|
|
18
21
|
private handleMessage;
|
|
19
22
|
private routeEngineEvent;
|
|
23
|
+
/** Read default workspace CLAUDE.md to append to non-default workspace sessions. */
|
|
24
|
+
private readDefaultPrompt;
|
|
20
25
|
}
|
package/dist/orchestrator.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
1
3
|
import log from "./logger.js";
|
|
2
4
|
import { formatToolUse, formatToolResult } from "./format.js";
|
|
3
5
|
export class Orchestrator {
|
|
@@ -5,12 +7,22 @@ export class Orchestrator {
|
|
|
5
7
|
engine;
|
|
6
8
|
workspaceStore;
|
|
7
9
|
permissionMode;
|
|
8
|
-
|
|
10
|
+
defaultPromptPath;
|
|
11
|
+
chats = new Map();
|
|
9
12
|
constructor(opts) {
|
|
10
13
|
this.channel = opts.channel;
|
|
11
14
|
this.engine = opts.engine;
|
|
12
15
|
this.workspaceStore = opts.workspaceStore;
|
|
13
16
|
this.permissionMode = opts.permissionMode;
|
|
17
|
+
this.defaultPromptPath = opts.defaultPromptPath;
|
|
18
|
+
}
|
|
19
|
+
chat(chatId) {
|
|
20
|
+
let s = this.chats.get(chatId);
|
|
21
|
+
if (!s) {
|
|
22
|
+
s = { busy: false, abort: null };
|
|
23
|
+
this.chats.set(chatId, s);
|
|
24
|
+
}
|
|
25
|
+
return s;
|
|
14
26
|
}
|
|
15
27
|
async start() {
|
|
16
28
|
this.channel.on("message", (msg) => {
|
|
@@ -32,7 +44,8 @@ export class Orchestrator {
|
|
|
32
44
|
}
|
|
33
45
|
async handleMessage(msg) {
|
|
34
46
|
try {
|
|
35
|
-
log.info(`[msg] ${msg.text.slice(0, 80)}`);
|
|
47
|
+
log.info(`[msg] ${msg.user.name} (${msg.user.id}) ${msg.text.slice(0, 80)}`);
|
|
48
|
+
const state = this.chat(msg.chatId);
|
|
36
49
|
// /new — reset session
|
|
37
50
|
if (msg.text === "/new") {
|
|
38
51
|
const ws = this.workspaceStore.byChat(msg.chatId);
|
|
@@ -45,37 +58,60 @@ export class Orchestrator {
|
|
|
45
58
|
await this.channel.sendMessage(msg.chatId, "Session cleared.");
|
|
46
59
|
return;
|
|
47
60
|
}
|
|
48
|
-
//
|
|
49
|
-
if (
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
// /cancel — abort the running turn
|
|
62
|
+
if (msg.text === "/cancel") {
|
|
63
|
+
if (state.abort) {
|
|
64
|
+
state.abort.abort();
|
|
65
|
+
log.info("[cmd] turn cancelled");
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
await this.channel.sendMessage(msg.chatId, "Nothing to cancel.");
|
|
69
|
+
}
|
|
52
70
|
return;
|
|
53
71
|
}
|
|
54
|
-
|
|
72
|
+
// Workspace lookup — guard all turn logic below
|
|
55
73
|
const ws = this.workspaceStore.byChat(msg.chatId);
|
|
56
74
|
if (!ws) {
|
|
57
75
|
log.info("[msg] no workspace for chat %s", msg.chatId);
|
|
58
|
-
this.busyChats.delete(msg.chatId);
|
|
59
76
|
await this.channel.sendMessage(msg.chatId, "No workspace linked to this group.");
|
|
60
77
|
return;
|
|
61
78
|
}
|
|
79
|
+
// Reject during active turn (per-chat)
|
|
80
|
+
if (state.busy) {
|
|
81
|
+
log.info("[msg] rejected (busy)");
|
|
82
|
+
await this.channel.sendMessage(msg.chatId, "Still working on the previous message...");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
state.busy = true;
|
|
86
|
+
const abort = new AbortController();
|
|
87
|
+
state.abort = abort;
|
|
62
88
|
log.info(`[turn] start session=${ws.current_session_id ?? "new"} cwd=${ws.cwd}`);
|
|
63
89
|
await this.channel.setTyping(msg.chatId, true);
|
|
90
|
+
// Append default workspace CLAUDE.md to non-default workspace sessions
|
|
91
|
+
const isDefaultWorkspace = ws.cwd === path.dirname(this.defaultPromptPath);
|
|
92
|
+
const appendSystemPrompt = isDefaultWorkspace ? undefined : this.readDefaultPrompt();
|
|
93
|
+
// In group workspaces, prepend sender identity so the engine knows who's talking
|
|
94
|
+
const sender = msg.user.handle ? `${msg.user.name} (@${msg.user.handle})` : msg.user.name;
|
|
95
|
+
const prompt = isDefaultWorkspace ? msg.text : `[${sender}]: ${msg.text}`;
|
|
64
96
|
try {
|
|
65
97
|
for await (const event of this.engine.runTurn({
|
|
66
98
|
sessionId: ws.current_session_id,
|
|
67
99
|
cwd: ws.cwd,
|
|
68
|
-
prompt
|
|
100
|
+
prompt,
|
|
69
101
|
permissionMode: this.permissionMode,
|
|
102
|
+
appendSystemPrompt,
|
|
103
|
+
signal: abort.signal,
|
|
70
104
|
onPermissionRequest: async (req) => {
|
|
71
105
|
log.info(`[perm] ${req.toolName}`);
|
|
72
|
-
const resp = await this.channel.sendInteractive(msg.chatId, `Allow ${req.toolName}
|
|
106
|
+
const resp = await this.channel.sendInteractive(msg.chatId, `Allow ${req.toolName}?\n${req.description}`, [
|
|
73
107
|
{ label: "Allow", value: "allow" },
|
|
74
108
|
{ label: "Deny", value: "deny" },
|
|
109
|
+
{ label: "Deny+Note", value: "deny", requestText: true },
|
|
75
110
|
]);
|
|
76
|
-
log.info(`[perm] ${req.toolName} → ${resp.value || "timeout"}`);
|
|
111
|
+
log.info(`[perm] ${req.toolName} → ${resp.value || "timeout"}${resp.text ? ` "${resp.text}"` : ""}`);
|
|
77
112
|
return {
|
|
78
113
|
decision: resp.value === "allow" ? "allow" : "deny",
|
|
114
|
+
message: resp.text,
|
|
79
115
|
};
|
|
80
116
|
},
|
|
81
117
|
})) {
|
|
@@ -83,8 +119,13 @@ export class Orchestrator {
|
|
|
83
119
|
}
|
|
84
120
|
}
|
|
85
121
|
finally {
|
|
86
|
-
|
|
122
|
+
const cancelled = abort.signal.aborted;
|
|
123
|
+
state.busy = false;
|
|
124
|
+
state.abort = null;
|
|
87
125
|
await this.channel.setTyping(msg.chatId, false);
|
|
126
|
+
if (cancelled) {
|
|
127
|
+
await this.channel.sendMessage(msg.chatId, "Turn cancelled.");
|
|
128
|
+
}
|
|
88
129
|
}
|
|
89
130
|
}
|
|
90
131
|
catch (err) {
|
|
@@ -131,5 +172,15 @@ export class Orchestrator {
|
|
|
131
172
|
break;
|
|
132
173
|
}
|
|
133
174
|
}
|
|
175
|
+
/** Read default workspace CLAUDE.md to append to non-default workspace sessions. */
|
|
176
|
+
readDefaultPrompt() {
|
|
177
|
+
try {
|
|
178
|
+
const content = fs.readFileSync(this.defaultPromptPath, "utf-8").trim();
|
|
179
|
+
return content || undefined;
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
134
185
|
}
|
|
135
186
|
//# sourceMappingURL=orchestrator.js.map
|
package/dist/types.d.ts
CHANGED
|
@@ -17,9 +17,11 @@ export interface Channel {
|
|
|
17
17
|
export interface Button {
|
|
18
18
|
label: string;
|
|
19
19
|
value: string;
|
|
20
|
+
requestText?: boolean;
|
|
20
21
|
}
|
|
21
22
|
export interface ButtonResponse {
|
|
22
23
|
value: string;
|
|
24
|
+
text?: string;
|
|
23
25
|
}
|
|
24
26
|
export interface SendMessageOpts {
|
|
25
27
|
parseMode?: "MarkdownV2" | "HTML";
|
|
@@ -34,6 +36,7 @@ export interface RunTurnOpts {
|
|
|
34
36
|
prompt: string;
|
|
35
37
|
permissionMode: PermissionMode;
|
|
36
38
|
onPermissionRequest: (req: PermissionRequest) => Promise<PermissionResponse>;
|
|
39
|
+
appendSystemPrompt?: string;
|
|
37
40
|
signal?: AbortSignal;
|
|
38
41
|
}
|
|
39
42
|
export interface PermissionRequest {
|
|
@@ -44,6 +47,7 @@ export interface PermissionRequest {
|
|
|
44
47
|
}
|
|
45
48
|
export interface PermissionResponse {
|
|
46
49
|
decision: "allow" | "deny";
|
|
50
|
+
message?: string;
|
|
47
51
|
}
|
|
48
52
|
export type EngineEvent = {
|
|
49
53
|
type: "text";
|
|
@@ -73,8 +77,14 @@ export interface Workspace {
|
|
|
73
77
|
chat_id: string;
|
|
74
78
|
current_session_id: string | null;
|
|
75
79
|
}
|
|
80
|
+
export interface UserInfo {
|
|
81
|
+
id: string;
|
|
82
|
+
name: string;
|
|
83
|
+
handle?: string;
|
|
84
|
+
}
|
|
76
85
|
export interface InboundMessage {
|
|
77
86
|
chatId: string;
|
|
87
|
+
user: UserInfo;
|
|
78
88
|
text: string;
|
|
79
89
|
}
|
|
80
90
|
export type { PermissionMode };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clearclaw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"clearclaw": "dist/index.js"
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
13
|
"start": "tsx src/index.ts",
|
|
14
|
-
"dev": "
|
|
14
|
+
"dev": "tsx --watch src/index.ts",
|
|
15
|
+
"dev:relay": "nodemon --watch dist --exec 'node dist/index.js'",
|
|
15
16
|
"build": "tsc",
|
|
16
17
|
"check": "tsc --noEmit"
|
|
17
18
|
},
|