cueclaw 0.1.2 → 0.1.3
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 +202 -20
- package/dist/app-JK3HBFKZ.js +3279 -0
- package/dist/{chunk-RSKXBXSJ.js → chunk-25KI643G.js} +16 -1
- package/dist/{chunk-PZZ6FBGB.js → chunk-CXBDJQJJ.js} +4 -3
- package/dist/chunk-HDUFGPCI.js +729 -0
- package/dist/{chunk-G43R5ASK.js → chunk-JJUF2AJ5.js} +20 -1
- package/dist/chunk-KBLMQZ3P.js +116 -0
- package/dist/{chunk-SEYPA5M2.js → chunk-X3WNTN5V.js} +147 -39
- package/dist/{chunk-WE5J7GMR.js → chunk-ZOFGQYXX.js} +36 -11
- package/dist/cli.js +123 -89
- package/dist/{config-6NWFKNLW.js → config-D5A5TNLZ.js} +4 -1
- package/dist/daemon-4DVXPT4O.js +28 -0
- package/dist/{executor-LS3Y4DO5.js → executor-TMY6MGVY.js} +5 -4
- package/dist/logger-HKMIMPCE.js +18 -0
- package/dist/{planner-UU4T5IEN.js → planner-MJ3XBCWH.js} +3 -3
- package/dist/{service-VTUYSAAZ.js → service-AP5GEITC.js} +4 -3
- package/dist/{setup-NWBKTQCO.js → setup-U2YKLOK6.js} +7 -2
- package/dist/{telegram-EFPHL4HC.js → telegram-FH5O4F3K.js} +25 -2
- package/dist/{whatsapp-HFMOFSFI.js → whatsapp-RLNSXSFI.js} +10 -2
- package/package.json +1 -1
- package/dist/app-LWDIWH7K.js +0 -1953
- package/dist/chunk-FAT2VKMJ.js +0 -232
- package/dist/chunk-IB6TU7TP.js +0 -310
- package/dist/chunk-QBOYMF4A.js +0 -42
- package/dist/daemon-WOR4GE5C.js +0 -96
- package/dist/logger-HD23RPWS.js +0 -12
- package/dist/router-ID6RN5AT.js +0 -14
package/dist/chunk-FAT2VKMJ.js
DELETED
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
executeWorkflow
|
|
3
|
-
} from "./chunk-SEYPA5M2.js";
|
|
4
|
-
import {
|
|
5
|
-
logger
|
|
6
|
-
} from "./chunk-QBOYMF4A.js";
|
|
7
|
-
|
|
8
|
-
// src/trigger-loop.ts
|
|
9
|
-
import { CronExpressionParser } from "cron-parser";
|
|
10
|
-
|
|
11
|
-
// src/trigger.ts
|
|
12
|
-
import { execFile } from "child_process";
|
|
13
|
-
import { promisify } from "util";
|
|
14
|
-
var execFileAsync = promisify(execFile);
|
|
15
|
-
var CHECK_SCRIPT_TIMEOUT = 3e4;
|
|
16
|
-
async function evaluatePollTrigger(workflow, trigger, db) {
|
|
17
|
-
let stdout;
|
|
18
|
-
try {
|
|
19
|
-
const result = await execFileAsync("sh", ["-c", trigger.check_script], {
|
|
20
|
-
timeout: CHECK_SCRIPT_TIMEOUT,
|
|
21
|
-
encoding: "utf-8"
|
|
22
|
-
});
|
|
23
|
-
stdout = result.stdout.trim();
|
|
24
|
-
} catch (err) {
|
|
25
|
-
logger.error({ workflowId: workflow.id, err }, "Poll check_script failed");
|
|
26
|
-
db.prepare(
|
|
27
|
-
"INSERT OR REPLACE INTO trigger_state (workflow_id, last_error, last_check_at) VALUES (?, ?, ?)"
|
|
28
|
-
).run(workflow.id, err instanceof Error ? err.message : String(err), (/* @__PURE__ */ new Date()).toISOString());
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
const state = db.prepare(
|
|
32
|
-
"SELECT last_result FROM trigger_state WHERE workflow_id = ?"
|
|
33
|
-
).get(workflow.id);
|
|
34
|
-
let triggerData = null;
|
|
35
|
-
if (trigger.diff_mode === "new_items") {
|
|
36
|
-
const newItems = diffNewItems(state?.last_result ?? null, stdout);
|
|
37
|
-
if (newItems.length > 0) triggerData = newItems.join("\n");
|
|
38
|
-
} else {
|
|
39
|
-
if (state?.last_result !== stdout) triggerData = stdout;
|
|
40
|
-
}
|
|
41
|
-
db.prepare(
|
|
42
|
-
"INSERT OR REPLACE INTO trigger_state (workflow_id, last_result, last_check_at) VALUES (?, ?, ?)"
|
|
43
|
-
).run(workflow.id, stdout, (/* @__PURE__ */ new Date()).toISOString());
|
|
44
|
-
if (!triggerData) return null;
|
|
45
|
-
logger.info({ workflowId: workflow.id }, "Poll trigger fired");
|
|
46
|
-
return { workflowId: workflow.id, data: triggerData };
|
|
47
|
-
}
|
|
48
|
-
function diffNewItems(oldOutput, newOutput) {
|
|
49
|
-
if (!oldOutput) return newOutput ? newOutput.split("\n").filter(Boolean) : [];
|
|
50
|
-
const oldLines = new Set(oldOutput.split("\n").filter(Boolean));
|
|
51
|
-
return newOutput.split("\n").filter((line) => line && !oldLines.has(line));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// src/group-queue.ts
|
|
55
|
-
var GroupQueue = class {
|
|
56
|
-
constructor(maxConcurrent = 5) {
|
|
57
|
-
this.maxConcurrent = maxConcurrent;
|
|
58
|
-
}
|
|
59
|
-
running = 0;
|
|
60
|
-
runningByWorkflow = /* @__PURE__ */ new Set();
|
|
61
|
-
queue = [];
|
|
62
|
-
async enqueue(workflowId, task) {
|
|
63
|
-
return new Promise((resolve, reject) => {
|
|
64
|
-
const wrappedTask = async () => {
|
|
65
|
-
try {
|
|
66
|
-
await task();
|
|
67
|
-
resolve();
|
|
68
|
-
} catch (e) {
|
|
69
|
-
reject(e);
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
if (this.running >= this.maxConcurrent || this.runningByWorkflow.has(workflowId)) {
|
|
73
|
-
this.queue.push({ workflowId, task: wrappedTask });
|
|
74
|
-
logger.debug({ workflowId, queueLength: this.queue.length }, "Task queued");
|
|
75
|
-
} else {
|
|
76
|
-
this.running++;
|
|
77
|
-
this.runningByWorkflow.add(workflowId);
|
|
78
|
-
wrappedTask().finally(() => {
|
|
79
|
-
this.running--;
|
|
80
|
-
this.runningByWorkflow.delete(workflowId);
|
|
81
|
-
this.processNext();
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
processNext() {
|
|
87
|
-
const idx = this.queue.findIndex((item) => !this.runningByWorkflow.has(item.workflowId));
|
|
88
|
-
if (idx === -1 || this.running >= this.maxConcurrent) return;
|
|
89
|
-
const next = this.queue.splice(idx, 1)[0];
|
|
90
|
-
this.running++;
|
|
91
|
-
this.runningByWorkflow.add(next.workflowId);
|
|
92
|
-
next.task().finally(() => {
|
|
93
|
-
this.running--;
|
|
94
|
-
this.runningByWorkflow.delete(next.workflowId);
|
|
95
|
-
this.processNext();
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
get pendingCount() {
|
|
99
|
-
return this.queue.length;
|
|
100
|
-
}
|
|
101
|
-
get activeCount() {
|
|
102
|
-
return this.running;
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
// src/trigger-loop.ts
|
|
107
|
-
var TriggerLoop = class {
|
|
108
|
-
constructor(db, router, cwd, maxConcurrent = 5) {
|
|
109
|
-
this.db = db;
|
|
110
|
-
this.router = router;
|
|
111
|
-
this.cwd = cwd;
|
|
112
|
-
this.queue = new GroupQueue(maxConcurrent);
|
|
113
|
-
}
|
|
114
|
-
intervals = /* @__PURE__ */ new Map();
|
|
115
|
-
queue;
|
|
116
|
-
log = logger.child({ module: "trigger-loop" });
|
|
117
|
-
start() {
|
|
118
|
-
const rows = this.db.prepare(
|
|
119
|
-
"SELECT * FROM workflows WHERE phase = 'active'"
|
|
120
|
-
).all();
|
|
121
|
-
for (const row of rows) {
|
|
122
|
-
const workflow = this.rowToWorkflow(row);
|
|
123
|
-
this.registerTrigger(workflow);
|
|
124
|
-
}
|
|
125
|
-
this.log.info({ count: rows.length }, "Trigger loop started");
|
|
126
|
-
}
|
|
127
|
-
registerTrigger(workflow) {
|
|
128
|
-
this.unregisterTrigger(workflow.id);
|
|
129
|
-
const trigger = workflow.trigger;
|
|
130
|
-
if (trigger.type === "poll") {
|
|
131
|
-
const interval = setInterval(async () => {
|
|
132
|
-
try {
|
|
133
|
-
const result = await evaluatePollTrigger(workflow, trigger, this.db);
|
|
134
|
-
if (result) {
|
|
135
|
-
this.executeTrigger(workflow, result.data);
|
|
136
|
-
}
|
|
137
|
-
} catch (err) {
|
|
138
|
-
this.log.error({ workflowId: workflow.id, err }, "Poll trigger error");
|
|
139
|
-
}
|
|
140
|
-
}, trigger.interval_seconds * 1e3);
|
|
141
|
-
this.intervals.set(workflow.id, interval);
|
|
142
|
-
this.log.info({ workflowId: workflow.id, intervalSeconds: trigger.interval_seconds }, "Registered poll trigger");
|
|
143
|
-
}
|
|
144
|
-
if (trigger.type === "cron") {
|
|
145
|
-
const interval = setInterval(() => {
|
|
146
|
-
try {
|
|
147
|
-
const expr = CronExpressionParser.parse(trigger.expression, {
|
|
148
|
-
tz: trigger.timezone ?? "UTC"
|
|
149
|
-
});
|
|
150
|
-
const prev = expr.prev().toDate();
|
|
151
|
-
const now = /* @__PURE__ */ new Date();
|
|
152
|
-
if (now.getTime() - prev.getTime() < 6e4) {
|
|
153
|
-
const state = this.db.prepare(
|
|
154
|
-
"SELECT last_fire_at FROM trigger_state WHERE workflow_id = ?"
|
|
155
|
-
).get(workflow.id);
|
|
156
|
-
const lastFire = state?.last_fire_at ? new Date(state.last_fire_at).getTime() : 0;
|
|
157
|
-
if (prev.getTime() <= lastFire) return;
|
|
158
|
-
this.db.prepare(
|
|
159
|
-
"INSERT OR REPLACE INTO trigger_state (workflow_id, last_fire_at) VALUES (?, ?)"
|
|
160
|
-
).run(workflow.id, prev.toISOString());
|
|
161
|
-
this.executeTrigger(workflow, (/* @__PURE__ */ new Date()).toISOString());
|
|
162
|
-
}
|
|
163
|
-
} catch (err) {
|
|
164
|
-
this.log.error({ workflowId: workflow.id, err }, "Cron evaluation failed");
|
|
165
|
-
}
|
|
166
|
-
}, 6e4);
|
|
167
|
-
this.intervals.set(workflow.id, interval);
|
|
168
|
-
this.log.info({ workflowId: workflow.id, expression: trigger.expression }, "Registered cron trigger");
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
unregisterTrigger(workflowId) {
|
|
172
|
-
const interval = this.intervals.get(workflowId);
|
|
173
|
-
if (interval) {
|
|
174
|
-
clearInterval(interval);
|
|
175
|
-
this.intervals.delete(workflowId);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
executeTrigger(workflow, triggerData) {
|
|
179
|
-
this.queue.enqueue(workflow.id, async () => {
|
|
180
|
-
this.log.info({ workflowId: workflow.id }, "Executing triggered workflow");
|
|
181
|
-
try {
|
|
182
|
-
await executeWorkflow({
|
|
183
|
-
workflow,
|
|
184
|
-
triggerData,
|
|
185
|
-
db: this.db,
|
|
186
|
-
cwd: this.cwd,
|
|
187
|
-
onProgress: (_stepId, msg) => {
|
|
188
|
-
if (typeof msg === "object" && msg?.type === "step_complete") {
|
|
189
|
-
this.router.broadcastNotification(`Step completed: ${_stepId} (${workflow.name})`);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
this.router.broadcastNotification(`Workflow complete: ${workflow.name}`);
|
|
194
|
-
} catch (err) {
|
|
195
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
196
|
-
this.log.error({ workflowId: workflow.id, err }, "Triggered execution failed");
|
|
197
|
-
this.router.broadcastNotification(`Workflow failed: ${workflow.name} \u2014 ${errMsg}`);
|
|
198
|
-
}
|
|
199
|
-
}).catch((err) => {
|
|
200
|
-
this.log.error({ workflowId: workflow.id, err }, "Queue execution error");
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
stop() {
|
|
204
|
-
for (const interval of this.intervals.values()) {
|
|
205
|
-
clearInterval(interval);
|
|
206
|
-
}
|
|
207
|
-
this.intervals.clear();
|
|
208
|
-
this.log.info("Trigger loop stopped");
|
|
209
|
-
}
|
|
210
|
-
get registeredCount() {
|
|
211
|
-
return this.intervals.size;
|
|
212
|
-
}
|
|
213
|
-
rowToWorkflow(row) {
|
|
214
|
-
return {
|
|
215
|
-
id: row.id,
|
|
216
|
-
name: row.name,
|
|
217
|
-
description: row.description,
|
|
218
|
-
steps: JSON.parse(row.steps_json),
|
|
219
|
-
trigger: JSON.parse(row.trigger_json),
|
|
220
|
-
failure_policy: JSON.parse(row.failure_policy_json),
|
|
221
|
-
phase: row.phase,
|
|
222
|
-
schema_version: "1.0",
|
|
223
|
-
created_at: row.created_at,
|
|
224
|
-
updated_at: row.updated_at,
|
|
225
|
-
metadata: row.metadata_json ? JSON.parse(row.metadata_json) : void 0
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
export {
|
|
231
|
-
TriggerLoop
|
|
232
|
-
};
|
package/dist/chunk-IB6TU7TP.js
DELETED
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
confirmPlan,
|
|
3
|
-
generatePlan,
|
|
4
|
-
modifyPlan,
|
|
5
|
-
rejectPlan
|
|
6
|
-
} from "./chunk-WE5J7GMR.js";
|
|
7
|
-
import {
|
|
8
|
-
createAnthropicClient
|
|
9
|
-
} from "./chunk-DVQFSFIZ.js";
|
|
10
|
-
import {
|
|
11
|
-
executeWorkflow
|
|
12
|
-
} from "./chunk-SEYPA5M2.js";
|
|
13
|
-
import {
|
|
14
|
-
getWorkflow,
|
|
15
|
-
insertWorkflow,
|
|
16
|
-
listWorkflows,
|
|
17
|
-
updateWorkflowPhase
|
|
18
|
-
} from "./chunk-G43R5ASK.js";
|
|
19
|
-
import {
|
|
20
|
-
logger
|
|
21
|
-
} from "./chunk-QBOYMF4A.js";
|
|
22
|
-
|
|
23
|
-
// src/router.ts
|
|
24
|
-
var CONFIRMATION_TIMEOUT = 10 * 6e4;
|
|
25
|
-
var RATE_LIMIT_WINDOW = 6e4;
|
|
26
|
-
var RATE_LIMIT_MAX = 10;
|
|
27
|
-
var CLEANUP_INTERVAL = 5 * 6e4;
|
|
28
|
-
var MessageRouter = class {
|
|
29
|
-
constructor(db, config, cwd) {
|
|
30
|
-
this.db = db;
|
|
31
|
-
this.config = config;
|
|
32
|
-
this.cwd = cwd;
|
|
33
|
-
}
|
|
34
|
-
channels = /* @__PURE__ */ new Map();
|
|
35
|
-
pendingConfirmations = /* @__PURE__ */ new Map();
|
|
36
|
-
messageTimestamps = /* @__PURE__ */ new Map();
|
|
37
|
-
cleanupTimer = null;
|
|
38
|
-
registerChannel(channel) {
|
|
39
|
-
this.channels.set(channel.name, channel);
|
|
40
|
-
}
|
|
41
|
-
start() {
|
|
42
|
-
this.cleanupTimer = setInterval(() => this.cleanupRateLimits(), CLEANUP_INTERVAL);
|
|
43
|
-
}
|
|
44
|
-
stop() {
|
|
45
|
-
if (this.cleanupTimer) {
|
|
46
|
-
clearInterval(this.cleanupTimer);
|
|
47
|
-
this.cleanupTimer = null;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
/** Disconnect all registered channels */
|
|
51
|
-
async disconnectAll() {
|
|
52
|
-
await Promise.allSettled(
|
|
53
|
-
[...this.channels.values()].map((c) => c.disconnect())
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
/** Broadcast a notification to all connected channels (used by MCP handler) */
|
|
57
|
-
broadcastNotification(message) {
|
|
58
|
-
for (const channel of this.channels.values()) {
|
|
59
|
-
if (channel.isConnected()) {
|
|
60
|
-
channel.sendMessage("broadcast", message).catch((err) => {
|
|
61
|
-
logger.error({ err, channel: channel.name }, "Failed to broadcast notification");
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
async handleInbound(channelName, chatJid, message) {
|
|
67
|
-
const channel = this.channels.get(channelName);
|
|
68
|
-
if (!channel) {
|
|
69
|
-
logger.warn({ channelName }, "Message from unknown channel");
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
const text = typeof message === "string" ? message : message.text;
|
|
73
|
-
if (this.isRateLimited(chatJid)) {
|
|
74
|
-
await channel.sendMessage(chatJid, "Rate limited, please wait before sending more messages.");
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
if (text.startsWith("/") || text.startsWith("!")) {
|
|
78
|
-
await this.handleCommand(channel, chatJid, text);
|
|
79
|
-
} else if (this.pendingConfirmations.has(chatJid)) {
|
|
80
|
-
const pending = this.pendingConfirmations.get(chatJid);
|
|
81
|
-
if (Date.now() > pending.expiresAt) {
|
|
82
|
-
this.pendingConfirmations.delete(chatJid);
|
|
83
|
-
await channel.sendMessage(chatJid, "Your pending plan has expired. Send a new description to start over.");
|
|
84
|
-
} else {
|
|
85
|
-
await this.handleConfirmation(channel, chatJid, text);
|
|
86
|
-
}
|
|
87
|
-
} else {
|
|
88
|
-
await this.classifyAndRoute(channel, chatJid, text);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
async handleCommand(channel, chatJid, text) {
|
|
92
|
-
const parts = text.slice(1).split(/\s+/);
|
|
93
|
-
const command = parts[0]?.toLowerCase();
|
|
94
|
-
const args = parts.slice(1).join(" ");
|
|
95
|
-
switch (command) {
|
|
96
|
-
case "new":
|
|
97
|
-
if (args) {
|
|
98
|
-
await this.handleNewWorkflow(channel, chatJid, args);
|
|
99
|
-
} else {
|
|
100
|
-
await channel.sendMessage(chatJid, "Send a workflow description to create a new workflow.");
|
|
101
|
-
}
|
|
102
|
-
break;
|
|
103
|
-
case "list": {
|
|
104
|
-
const workflows = listWorkflows(this.db);
|
|
105
|
-
if (workflows.length === 0) {
|
|
106
|
-
await channel.sendMessage(chatJid, "No workflows found.");
|
|
107
|
-
} else {
|
|
108
|
-
const lines = workflows.map(
|
|
109
|
-
(wf) => `${wf.id.slice(0, 8)} ${wf.phase.padEnd(12)} ${wf.name}`
|
|
110
|
-
);
|
|
111
|
-
await channel.sendMessage(chatJid, `Workflows:
|
|
112
|
-
${lines.join("\n")}`);
|
|
113
|
-
}
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
116
|
-
case "status": {
|
|
117
|
-
if (!args) {
|
|
118
|
-
await channel.sendMessage(chatJid, "Usage: /status <workflow-id>");
|
|
119
|
-
break;
|
|
120
|
-
}
|
|
121
|
-
const wf = getWorkflow(this.db, args);
|
|
122
|
-
if (!wf) {
|
|
123
|
-
await channel.sendMessage(chatJid, `Workflow not found: ${args}`);
|
|
124
|
-
} else {
|
|
125
|
-
const steps = wf.steps.map((s, i) => `${i + 1}. ${s.description}`).join("\n");
|
|
126
|
-
await channel.sendMessage(chatJid, `Workflow: ${wf.name}
|
|
127
|
-
Phase: ${wf.phase}
|
|
128
|
-
|
|
129
|
-
Steps:
|
|
130
|
-
${steps}`);
|
|
131
|
-
}
|
|
132
|
-
break;
|
|
133
|
-
}
|
|
134
|
-
case "cancel": {
|
|
135
|
-
if (!args) {
|
|
136
|
-
await channel.sendMessage(chatJid, "Usage: /cancel <workflow-id>");
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
const wf = getWorkflow(this.db, args);
|
|
140
|
-
if (!wf) {
|
|
141
|
-
await channel.sendMessage(chatJid, `Workflow not found: ${args}`);
|
|
142
|
-
} else {
|
|
143
|
-
const rejected = rejectPlan(wf);
|
|
144
|
-
updateWorkflowPhase(this.db, wf.id, rejected.phase);
|
|
145
|
-
await channel.sendMessage(chatJid, `Workflow cancelled: ${wf.name}`);
|
|
146
|
-
}
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
case "help":
|
|
150
|
-
await channel.sendMessage(chatJid, [
|
|
151
|
-
"Commands:",
|
|
152
|
-
"/new <description> \u2014 Create a new workflow",
|
|
153
|
-
"/list \u2014 List all workflows",
|
|
154
|
-
"/status <id> \u2014 View workflow status",
|
|
155
|
-
"/cancel <id> \u2014 Cancel a workflow",
|
|
156
|
-
"/help \u2014 Show this help"
|
|
157
|
-
].join("\n"));
|
|
158
|
-
break;
|
|
159
|
-
default:
|
|
160
|
-
await channel.sendMessage(chatJid, `Unknown command: ${command}. Type /help for available commands.`);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
async handleCallbackAction(channelName, chatId, _workflowId, action) {
|
|
164
|
-
const actionMap = {
|
|
165
|
-
confirm: "yes",
|
|
166
|
-
modify: "modify",
|
|
167
|
-
cancel: "no"
|
|
168
|
-
};
|
|
169
|
-
const mappedText = actionMap[action] ?? action;
|
|
170
|
-
await this.handleInbound(channelName, chatId, { text: mappedText, sender: chatId });
|
|
171
|
-
}
|
|
172
|
-
async classifyAndRoute(channel, chatJid, text) {
|
|
173
|
-
try {
|
|
174
|
-
const client = createAnthropicClient(this.config);
|
|
175
|
-
const response = await client.messages.create({
|
|
176
|
-
model: this.config.claude.planner.model,
|
|
177
|
-
max_tokens: 512,
|
|
178
|
-
system: "You are a router for CueClaw, a workflow automation tool. Classify the user message and call the appropriate tool. Use create_workflow_request if the user wants to automate a task, set up a workflow, schedule something, or perform an action that requires multiple steps. Use chat_reply for greetings, questions about capabilities, casual conversation, or anything that is not a workflow request.",
|
|
179
|
-
messages: [{ role: "user", content: text }],
|
|
180
|
-
tools: [
|
|
181
|
-
{
|
|
182
|
-
name: "create_workflow_request",
|
|
183
|
-
description: "The user wants to create an automation workflow",
|
|
184
|
-
input_schema: { type: "object", properties: {} }
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
name: "chat_reply",
|
|
188
|
-
description: "Reply to casual conversation or questions",
|
|
189
|
-
input_schema: {
|
|
190
|
-
type: "object",
|
|
191
|
-
properties: {
|
|
192
|
-
message: { type: "string", description: "The reply to send to the user" }
|
|
193
|
-
},
|
|
194
|
-
required: ["message"]
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
],
|
|
198
|
-
tool_choice: { type: "any" }
|
|
199
|
-
});
|
|
200
|
-
const toolUse = response.content.find((b) => b.type === "tool_use");
|
|
201
|
-
if (toolUse && toolUse.type === "tool_use" && toolUse.name === "chat_reply") {
|
|
202
|
-
const input = toolUse.input;
|
|
203
|
-
await channel.sendMessage(chatJid, input.message ?? "I'm CueClaw, a workflow automation tool. Send me a task description and I'll create a plan for you!");
|
|
204
|
-
} else {
|
|
205
|
-
await this.handleNewWorkflow(channel, chatJid, text);
|
|
206
|
-
}
|
|
207
|
-
} catch (err) {
|
|
208
|
-
logger.error({ err, chatJid }, "Classification failed, falling back to workflow");
|
|
209
|
-
await this.handleNewWorkflow(channel, chatJid, text);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
async handleNewWorkflow(channel, chatJid, text) {
|
|
213
|
-
await channel.sendMessage(chatJid, "Generating execution plan...");
|
|
214
|
-
channel.setTyping?.(chatJid, true);
|
|
215
|
-
try {
|
|
216
|
-
const workflow = await generatePlan(text, this.config);
|
|
217
|
-
insertWorkflow(this.db, workflow);
|
|
218
|
-
channel.setTyping?.(chatJid, false);
|
|
219
|
-
this.pendingConfirmations.set(chatJid, {
|
|
220
|
-
workflowId: workflow.id,
|
|
221
|
-
workflow,
|
|
222
|
-
expiresAt: Date.now() + CONFIRMATION_TIMEOUT
|
|
223
|
-
});
|
|
224
|
-
await channel.sendConfirmation(chatJid, workflow);
|
|
225
|
-
} catch (err) {
|
|
226
|
-
channel.setTyping?.(chatJid, false);
|
|
227
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
228
|
-
await channel.sendMessage(chatJid, `Failed to generate plan: ${msg}`);
|
|
229
|
-
logger.error({ err, chatJid }, "Plan generation failed");
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
async handleConfirmation(channel, chatJid, text) {
|
|
233
|
-
const pending = this.pendingConfirmations.get(chatJid);
|
|
234
|
-
if (!pending) return;
|
|
235
|
-
const lower = text.toLowerCase().trim();
|
|
236
|
-
if (["yes", "y", "confirm", "1"].includes(lower)) {
|
|
237
|
-
this.pendingConfirmations.delete(chatJid);
|
|
238
|
-
const confirmed = confirmPlan(pending.workflow);
|
|
239
|
-
await channel.sendMessage(chatJid, `Workflow activated: ${confirmed.name} (${confirmed.id})`);
|
|
240
|
-
channel.setTyping?.(chatJid, true);
|
|
241
|
-
try {
|
|
242
|
-
const result = await executeWorkflow({
|
|
243
|
-
workflow: confirmed,
|
|
244
|
-
triggerData: null,
|
|
245
|
-
db: this.db,
|
|
246
|
-
cwd: this.cwd,
|
|
247
|
-
onProgress: async (stepId, msg) => {
|
|
248
|
-
if (typeof msg === "object" && msg?.type === "step_complete") {
|
|
249
|
-
await channel.sendMessage(chatJid, `Step completed: ${stepId}`);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
channel.setTyping?.(chatJid, false);
|
|
254
|
-
await channel.sendMessage(chatJid, `Workflow complete! Status: ${result.status}`);
|
|
255
|
-
} catch (err) {
|
|
256
|
-
channel.setTyping?.(chatJid, false);
|
|
257
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
258
|
-
await channel.sendMessage(chatJid, `Workflow failed: ${msg}`);
|
|
259
|
-
}
|
|
260
|
-
} else if (["no", "n", "cancel", "3"].includes(lower)) {
|
|
261
|
-
this.pendingConfirmations.delete(chatJid);
|
|
262
|
-
rejectPlan(pending.workflow);
|
|
263
|
-
await channel.sendMessage(chatJid, "Plan cancelled.");
|
|
264
|
-
} else if (["modify", "m", "2"].includes(lower)) {
|
|
265
|
-
await channel.sendMessage(chatJid, "Describe your modifications:");
|
|
266
|
-
} else {
|
|
267
|
-
this.pendingConfirmations.delete(chatJid);
|
|
268
|
-
await channel.sendMessage(chatJid, "Modifying plan...");
|
|
269
|
-
channel.setTyping?.(chatJid, true);
|
|
270
|
-
try {
|
|
271
|
-
const modified = await modifyPlan(pending.workflow, text, this.config);
|
|
272
|
-
insertWorkflow(this.db, modified);
|
|
273
|
-
channel.setTyping?.(chatJid, false);
|
|
274
|
-
this.pendingConfirmations.set(chatJid, {
|
|
275
|
-
workflowId: modified.id,
|
|
276
|
-
workflow: modified,
|
|
277
|
-
expiresAt: Date.now() + CONFIRMATION_TIMEOUT
|
|
278
|
-
});
|
|
279
|
-
await channel.sendConfirmation(chatJid, modified);
|
|
280
|
-
} catch (err) {
|
|
281
|
-
channel.setTyping?.(chatJid, false);
|
|
282
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
283
|
-
await channel.sendMessage(chatJid, `Modification failed: ${msg}`);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
isRateLimited(chatJid) {
|
|
288
|
-
const now = Date.now();
|
|
289
|
-
const timestamps = this.messageTimestamps.get(chatJid) ?? [];
|
|
290
|
-
const recent = timestamps.filter((t) => now - t < RATE_LIMIT_WINDOW);
|
|
291
|
-
recent.push(now);
|
|
292
|
-
this.messageTimestamps.set(chatJid, recent);
|
|
293
|
-
return recent.length > RATE_LIMIT_MAX;
|
|
294
|
-
}
|
|
295
|
-
cleanupRateLimits() {
|
|
296
|
-
const now = Date.now();
|
|
297
|
-
for (const [jid, timestamps] of this.messageTimestamps) {
|
|
298
|
-
const recent = timestamps.filter((t) => now - t < RATE_LIMIT_WINDOW);
|
|
299
|
-
if (recent.length === 0) {
|
|
300
|
-
this.messageTimestamps.delete(jid);
|
|
301
|
-
} else {
|
|
302
|
-
this.messageTimestamps.set(jid, recent);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
export {
|
|
309
|
-
MessageRouter
|
|
310
|
-
};
|
package/dist/chunk-QBOYMF4A.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
// src/logger.ts
|
|
2
|
-
import pino from "pino";
|
|
3
|
-
import { PassThrough } from "stream";
|
|
4
|
-
var listeners = /* @__PURE__ */ new Set();
|
|
5
|
-
var tuiStream = new PassThrough();
|
|
6
|
-
tuiStream.on("data", (chunk) => {
|
|
7
|
-
for (const raw of chunk.toString().split("\n")) {
|
|
8
|
-
if (!raw.trim()) continue;
|
|
9
|
-
try {
|
|
10
|
-
const obj = JSON.parse(raw);
|
|
11
|
-
const lvl = (pino.levels.labels[obj.level] ?? "INFO").toUpperCase();
|
|
12
|
-
const mod = obj.module ? ` [${obj.module}]` : "";
|
|
13
|
-
const msg = obj.msg ?? "";
|
|
14
|
-
for (const fn of listeners) fn(`${lvl}${mod} ${msg}`);
|
|
15
|
-
} catch {
|
|
16
|
-
for (const fn of listeners) fn(raw);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
var logger = pino({
|
|
21
|
-
level: process.env["LOG_LEVEL"] ?? "info",
|
|
22
|
-
transport: process.env["NODE_ENV"] === "production" ? void 0 : { target: "pino-pretty", options: { colorize: true } }
|
|
23
|
-
});
|
|
24
|
-
function enableTuiLogging() {
|
|
25
|
-
logger = pino({ level: process.env["LOG_LEVEL"] ?? "info" }, tuiStream);
|
|
26
|
-
}
|
|
27
|
-
function onLogLine(fn) {
|
|
28
|
-
listeners.add(fn);
|
|
29
|
-
return () => {
|
|
30
|
-
listeners.delete(fn);
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
function createChildLogger(bindings) {
|
|
34
|
-
return logger.child(bindings);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export {
|
|
38
|
-
logger,
|
|
39
|
-
enableTuiLogging,
|
|
40
|
-
onLogLine,
|
|
41
|
-
createChildLogger
|
|
42
|
-
};
|
package/dist/daemon-WOR4GE5C.js
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
TriggerLoop
|
|
3
|
-
} from "./chunk-FAT2VKMJ.js";
|
|
4
|
-
import {
|
|
5
|
-
MessageRouter
|
|
6
|
-
} from "./chunk-IB6TU7TP.js";
|
|
7
|
-
import "./chunk-WE5J7GMR.js";
|
|
8
|
-
import "./chunk-DVQFSFIZ.js";
|
|
9
|
-
import "./chunk-ZCK3IFLC.js";
|
|
10
|
-
import "./chunk-SEYPA5M2.js";
|
|
11
|
-
import {
|
|
12
|
-
initDb
|
|
13
|
-
} from "./chunk-G43R5ASK.js";
|
|
14
|
-
import {
|
|
15
|
-
loadConfig
|
|
16
|
-
} from "./chunk-RSKXBXSJ.js";
|
|
17
|
-
import "./chunk-BVQG3WYO.js";
|
|
18
|
-
import {
|
|
19
|
-
logger
|
|
20
|
-
} from "./chunk-QBOYMF4A.js";
|
|
21
|
-
|
|
22
|
-
// src/daemon.ts
|
|
23
|
-
async function startDaemon() {
|
|
24
|
-
const config = loadConfig();
|
|
25
|
-
const db = initDb();
|
|
26
|
-
const cwd = process.cwd();
|
|
27
|
-
const router = new MessageRouter(db, config, cwd);
|
|
28
|
-
if (config.whatsapp?.enabled) {
|
|
29
|
-
try {
|
|
30
|
-
const { WhatsAppChannel } = await import("./whatsapp-HFMOFSFI.js");
|
|
31
|
-
const wa = new WhatsAppChannel(
|
|
32
|
-
config.whatsapp.auth_dir ?? `${process.env["HOME"]}/.cueclaw/auth/whatsapp`,
|
|
33
|
-
config.whatsapp.allowed_jids ?? [],
|
|
34
|
-
(jid, msg) => router.handleInbound("whatsapp", jid, msg)
|
|
35
|
-
);
|
|
36
|
-
router.registerChannel(wa);
|
|
37
|
-
await wa.connect();
|
|
38
|
-
logger.info("WhatsApp channel started");
|
|
39
|
-
} catch (err) {
|
|
40
|
-
logger.error({ err }, "Failed to start WhatsApp channel");
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
if (config.telegram?.enabled) {
|
|
44
|
-
try {
|
|
45
|
-
const { TelegramChannel } = await import("./telegram-EFPHL4HC.js");
|
|
46
|
-
const tg = new TelegramChannel(
|
|
47
|
-
config.telegram.token,
|
|
48
|
-
config.telegram.allowed_users ?? [],
|
|
49
|
-
(jid, msg) => router.handleInbound("telegram", jid, msg)
|
|
50
|
-
);
|
|
51
|
-
router.registerChannel(tg);
|
|
52
|
-
await tg.connect();
|
|
53
|
-
tg.onCallback((wfId, action, chatId) => router.handleCallbackAction("telegram", chatId, wfId, action));
|
|
54
|
-
logger.info("Telegram channel started");
|
|
55
|
-
} catch (err) {
|
|
56
|
-
logger.error({ err }, "Failed to start Telegram channel");
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
await recoverRunningWorkflows(db, router);
|
|
60
|
-
const maxConcurrent = 5;
|
|
61
|
-
const triggerLoop = new TriggerLoop(db, router, cwd, maxConcurrent);
|
|
62
|
-
triggerLoop.start();
|
|
63
|
-
router.start();
|
|
64
|
-
logger.info("Daemon started");
|
|
65
|
-
const shutdown = async () => {
|
|
66
|
-
logger.info("Shutting down daemon...");
|
|
67
|
-
triggerLoop.stop();
|
|
68
|
-
router.stop();
|
|
69
|
-
await router.disconnectAll();
|
|
70
|
-
db.close();
|
|
71
|
-
process.exit(0);
|
|
72
|
-
};
|
|
73
|
-
process.on("SIGTERM", () => void shutdown());
|
|
74
|
-
process.on("SIGINT", () => void shutdown());
|
|
75
|
-
}
|
|
76
|
-
async function recoverRunningWorkflows(db, router) {
|
|
77
|
-
const interruptedRuns = db.prepare(
|
|
78
|
-
"SELECT id, workflow_id FROM workflow_runs WHERE status = 'running'"
|
|
79
|
-
).all();
|
|
80
|
-
if (interruptedRuns.length === 0) return;
|
|
81
|
-
logger.warn({ count: interruptedRuns.length }, "Recovering interrupted workflow runs");
|
|
82
|
-
for (const run of interruptedRuns) {
|
|
83
|
-
db.prepare(
|
|
84
|
-
"UPDATE workflow_runs SET status = 'failed', error = ?, completed_at = ? WHERE id = ?"
|
|
85
|
-
).run("Daemon restarted during execution", (/* @__PURE__ */ new Date()).toISOString(), run.id);
|
|
86
|
-
db.prepare(
|
|
87
|
-
"UPDATE step_runs SET status = 'failed', error = ? WHERE run_id = ? AND status = 'running'"
|
|
88
|
-
).run("Daemon restarted during execution", run.id);
|
|
89
|
-
router.broadcastNotification(
|
|
90
|
-
`Workflow ${run.workflow_id} was interrupted by daemon restart (run ${run.id})`
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
export {
|
|
95
|
-
startDaemon
|
|
96
|
-
};
|