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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
cueclawHome
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-25KI643G.js";
|
|
4
4
|
|
|
5
5
|
// src/db.ts
|
|
6
6
|
import Database from "better-sqlite3";
|
|
@@ -113,6 +113,24 @@ function insertWorkflow(db, workflow) {
|
|
|
113
113
|
workflow.updated_at
|
|
114
114
|
);
|
|
115
115
|
}
|
|
116
|
+
function upsertWorkflow(db, workflow) {
|
|
117
|
+
db.prepare(`
|
|
118
|
+
INSERT OR REPLACE INTO workflows (id, name, description, trigger_json, steps_json, failure_policy_json, phase, schema_version, metadata_json, created_at, updated_at)
|
|
119
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
120
|
+
`).run(
|
|
121
|
+
workflow.id,
|
|
122
|
+
workflow.name,
|
|
123
|
+
workflow.description,
|
|
124
|
+
JSON.stringify(workflow.trigger),
|
|
125
|
+
JSON.stringify(workflow.steps),
|
|
126
|
+
JSON.stringify(workflow.failure_policy),
|
|
127
|
+
workflow.phase,
|
|
128
|
+
workflow.schema_version,
|
|
129
|
+
workflow.metadata ? JSON.stringify(workflow.metadata) : null,
|
|
130
|
+
workflow.created_at,
|
|
131
|
+
workflow.updated_at
|
|
132
|
+
);
|
|
133
|
+
}
|
|
116
134
|
function getWorkflow(db, id) {
|
|
117
135
|
const row = db.prepare("SELECT * FROM workflows WHERE id = ?").get(id);
|
|
118
136
|
return row ? rowToWorkflow(row) : void 0;
|
|
@@ -171,6 +189,7 @@ function getWorkflowRunsByWorkflowId(db, workflowId) {
|
|
|
171
189
|
export {
|
|
172
190
|
initDb,
|
|
173
191
|
insertWorkflow,
|
|
192
|
+
upsertWorkflow,
|
|
174
193
|
getWorkflow,
|
|
175
194
|
listWorkflows,
|
|
176
195
|
updateWorkflowPhase,
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// src/logger.ts
|
|
2
|
+
import pino from "pino";
|
|
3
|
+
import { PassThrough } from "stream";
|
|
4
|
+
import { createWriteStream, mkdirSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { homedir } from "os";
|
|
7
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
8
|
+
var tuiStream = new PassThrough();
|
|
9
|
+
tuiStream.on("data", (chunk) => {
|
|
10
|
+
for (const raw of chunk.toString().split("\n")) {
|
|
11
|
+
if (!raw.trim()) continue;
|
|
12
|
+
try {
|
|
13
|
+
const obj = JSON.parse(raw);
|
|
14
|
+
const lvl = (pino.levels.labels[obj.level] ?? "INFO").toUpperCase();
|
|
15
|
+
const mod = obj.module ? ` [${obj.module}]` : "";
|
|
16
|
+
const msg = obj.msg ?? "";
|
|
17
|
+
for (const fn of listeners) fn(`${lvl}${mod} ${msg}`);
|
|
18
|
+
} catch {
|
|
19
|
+
for (const fn of listeners) fn(raw);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
var fileStream = null;
|
|
24
|
+
var logDir = null;
|
|
25
|
+
var configuredLevel = null;
|
|
26
|
+
function resolveDir(dir) {
|
|
27
|
+
return dir.startsWith("~") ? join(homedir(), dir.slice(1)) : dir;
|
|
28
|
+
}
|
|
29
|
+
function getLevel() {
|
|
30
|
+
return configuredLevel ?? process.env["LOG_LEVEL"] ?? "info";
|
|
31
|
+
}
|
|
32
|
+
var logger = pino({
|
|
33
|
+
level: process.env["LOG_LEVEL"] ?? "info",
|
|
34
|
+
transport: process.env["NODE_ENV"] === "production" ? void 0 : { target: "pino-pretty", options: { colorize: true } }
|
|
35
|
+
});
|
|
36
|
+
function initLogger(opts) {
|
|
37
|
+
const level = opts.level ?? process.env["LOG_LEVEL"] ?? "info";
|
|
38
|
+
configuredLevel = level;
|
|
39
|
+
if (opts.dir) {
|
|
40
|
+
const resolved = resolveDir(opts.dir);
|
|
41
|
+
logDir = resolved;
|
|
42
|
+
mkdirSync(join(resolved, "executions"), { recursive: true });
|
|
43
|
+
fileStream = createWriteStream(join(resolved, "daemon.log"), { flags: "a" });
|
|
44
|
+
logger = pino(
|
|
45
|
+
{ level },
|
|
46
|
+
pino.multistream([
|
|
47
|
+
{ stream: process.stdout },
|
|
48
|
+
{ stream: fileStream }
|
|
49
|
+
])
|
|
50
|
+
);
|
|
51
|
+
} else {
|
|
52
|
+
logger = pino({
|
|
53
|
+
level,
|
|
54
|
+
transport: process.env["NODE_ENV"] === "production" ? void 0 : { target: "pino-pretty", options: { colorize: true } }
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function enableTuiLogging() {
|
|
59
|
+
if (fileStream) {
|
|
60
|
+
logger = pino(
|
|
61
|
+
{ level: getLevel() },
|
|
62
|
+
pino.multistream([
|
|
63
|
+
{ stream: tuiStream },
|
|
64
|
+
{ stream: fileStream }
|
|
65
|
+
])
|
|
66
|
+
);
|
|
67
|
+
} else {
|
|
68
|
+
logger = pino({ level: getLevel() }, tuiStream);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function onLogLine(fn) {
|
|
72
|
+
listeners.add(fn);
|
|
73
|
+
return () => {
|
|
74
|
+
listeners.delete(fn);
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function createChildLogger(bindings) {
|
|
78
|
+
return logger.child(bindings);
|
|
79
|
+
}
|
|
80
|
+
function createExecutionLogger(workflowId, runId) {
|
|
81
|
+
if (!logDir) {
|
|
82
|
+
return logger.child({ workflowId, runId });
|
|
83
|
+
}
|
|
84
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
85
|
+
const filename = `${workflowId}_${date}.log`;
|
|
86
|
+
const execStream = createWriteStream(join(logDir, "executions", filename), { flags: "a" });
|
|
87
|
+
return pino(
|
|
88
|
+
{ level: getLevel() },
|
|
89
|
+
pino.multistream([
|
|
90
|
+
{ stream: execStream },
|
|
91
|
+
...fileStream ? [{ stream: fileStream }] : []
|
|
92
|
+
])
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
function resetLogger() {
|
|
96
|
+
if (fileStream) {
|
|
97
|
+
fileStream.end();
|
|
98
|
+
fileStream = null;
|
|
99
|
+
}
|
|
100
|
+
logDir = null;
|
|
101
|
+
configuredLevel = null;
|
|
102
|
+
logger = pino({
|
|
103
|
+
level: process.env["LOG_LEVEL"] ?? "info",
|
|
104
|
+
transport: process.env["NODE_ENV"] === "production" ? void 0 : { target: "pino-pretty", options: { colorize: true } }
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export {
|
|
109
|
+
logger,
|
|
110
|
+
initLogger,
|
|
111
|
+
enableTuiLogging,
|
|
112
|
+
onLogLine,
|
|
113
|
+
createChildLogger,
|
|
114
|
+
createExecutionLogger,
|
|
115
|
+
resetLogger
|
|
116
|
+
};
|
|
@@ -4,18 +4,23 @@ import {
|
|
|
4
4
|
updateStepRunStatus,
|
|
5
5
|
updateWorkflowPhase,
|
|
6
6
|
updateWorkflowRunStatus
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-JJUF2AJ5.js";
|
|
8
8
|
import {
|
|
9
9
|
cueclawHome,
|
|
10
|
+
getDefaultImage,
|
|
10
11
|
loadConfig
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-25KI643G.js";
|
|
12
13
|
import {
|
|
13
14
|
ConfigError,
|
|
14
15
|
ExecutorError
|
|
15
16
|
} from "./chunk-BVQG3WYO.js";
|
|
16
17
|
import {
|
|
18
|
+
isDev
|
|
19
|
+
} from "./chunk-ZCK3IFLC.js";
|
|
20
|
+
import {
|
|
21
|
+
createExecutionLogger,
|
|
17
22
|
logger
|
|
18
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-KBLMQZ3P.js";
|
|
19
24
|
|
|
20
25
|
// src/executor.ts
|
|
21
26
|
import { nanoid as nanoid3 } from "nanoid";
|
|
@@ -239,7 +244,7 @@ async function runContainerAgent(opts) {
|
|
|
239
244
|
mkdirSync2(opts.workDir, { recursive: true });
|
|
240
245
|
mkdirSync2(join3(opts.ipcDir, "input"), { recursive: true });
|
|
241
246
|
mkdirSync2(join3(opts.ipcDir, "output"), { recursive: true });
|
|
242
|
-
const image = config.container?.image ??
|
|
247
|
+
const image = config.container?.image ?? getDefaultImage();
|
|
243
248
|
const network = config.container?.network ?? "none";
|
|
244
249
|
const volumeMounts = buildVolumeMounts(opts);
|
|
245
250
|
const dockerArgs = [
|
|
@@ -292,7 +297,8 @@ function buildVolumeMounts(opts) {
|
|
|
292
297
|
return mounts;
|
|
293
298
|
}
|
|
294
299
|
async function spawnContainer(dockerArgs, opts, config) {
|
|
295
|
-
return new Promise((
|
|
300
|
+
return new Promise((resolve2) => {
|
|
301
|
+
logger.info({ containerName: opts.containerName, image: dockerArgs[dockerArgs.length - 1] }, "Starting container");
|
|
296
302
|
const proc = spawn("docker", dockerArgs, {
|
|
297
303
|
stdio: ["pipe", "pipe", "pipe"]
|
|
298
304
|
});
|
|
@@ -348,8 +354,9 @@ async function spawnContainer(dockerArgs, opts, config) {
|
|
|
348
354
|
proc.on("close", (code) => {
|
|
349
355
|
clearTimeout(hardTimer);
|
|
350
356
|
clearInterval(idleCheck);
|
|
357
|
+
logger.info({ containerName: opts.containerName, exitCode: code }, "Container exited");
|
|
351
358
|
if (truncated) {
|
|
352
|
-
|
|
359
|
+
resolve2({ status: "failed", error: "Container output size cap exceeded" });
|
|
353
360
|
return;
|
|
354
361
|
}
|
|
355
362
|
const startIdx = stdout.indexOf(OUTPUT_START_MARKER);
|
|
@@ -358,7 +365,7 @@ async function spawnContainer(dockerArgs, opts, config) {
|
|
|
358
365
|
resultBuffer = stdout.slice(startIdx + OUTPUT_START_MARKER.length + 1, endIdx).trim();
|
|
359
366
|
}
|
|
360
367
|
if (code !== 0 && !resultBuffer) {
|
|
361
|
-
|
|
368
|
+
resolve2({
|
|
362
369
|
status: "failed",
|
|
363
370
|
error: stderr || `Container exited with code ${code}`
|
|
364
371
|
});
|
|
@@ -366,16 +373,16 @@ async function spawnContainer(dockerArgs, opts, config) {
|
|
|
366
373
|
}
|
|
367
374
|
try {
|
|
368
375
|
const parsed = JSON.parse(resultBuffer);
|
|
369
|
-
|
|
376
|
+
resolve2({
|
|
370
377
|
status: "succeeded",
|
|
371
378
|
output: parsed.result ?? null,
|
|
372
379
|
sessionId: parsed.sessionId
|
|
373
380
|
});
|
|
374
381
|
} catch {
|
|
375
382
|
if (resultBuffer) {
|
|
376
|
-
|
|
383
|
+
resolve2({ status: "succeeded", output: resultBuffer });
|
|
377
384
|
} else {
|
|
378
|
-
|
|
385
|
+
resolve2({
|
|
379
386
|
status: "failed",
|
|
380
387
|
error: stderr || "No output captured from container"
|
|
381
388
|
});
|
|
@@ -385,7 +392,8 @@ async function spawnContainer(dockerArgs, opts, config) {
|
|
|
385
392
|
proc.on("error", (err) => {
|
|
386
393
|
clearTimeout(hardTimer);
|
|
387
394
|
clearInterval(idleCheck);
|
|
388
|
-
|
|
395
|
+
logger.error({ containerName: opts.containerName, err }, "Docker spawn error");
|
|
396
|
+
resolve2({
|
|
389
397
|
status: "failed",
|
|
390
398
|
error: `Docker spawn error: ${err.message}`
|
|
391
399
|
});
|
|
@@ -393,6 +401,7 @@ async function spawnContainer(dockerArgs, opts, config) {
|
|
|
393
401
|
});
|
|
394
402
|
}
|
|
395
403
|
function gracefulStop(containerName) {
|
|
404
|
+
logger.debug({ containerName }, "Gracefully stopping container");
|
|
396
405
|
try {
|
|
397
406
|
spawn("docker", ["stop", containerName]);
|
|
398
407
|
} catch {
|
|
@@ -418,35 +427,113 @@ function prepareContainerOpts(workflowId, stepId, runId, prompt, cwd, allowedToo
|
|
|
418
427
|
};
|
|
419
428
|
}
|
|
420
429
|
|
|
430
|
+
// src/container-runtime.ts
|
|
431
|
+
import { execFileSync } from "child_process";
|
|
432
|
+
import { resolve, dirname } from "path";
|
|
433
|
+
import { fileURLToPath } from "url";
|
|
434
|
+
import { existsSync as existsSync3 } from "fs";
|
|
435
|
+
var cached;
|
|
436
|
+
var imageCache = /* @__PURE__ */ new Map();
|
|
437
|
+
function isDockerAvailable() {
|
|
438
|
+
if (cached !== void 0) return cached;
|
|
439
|
+
try {
|
|
440
|
+
execFileSync("docker", ["info"], { encoding: "utf-8", stdio: "pipe" });
|
|
441
|
+
cached = true;
|
|
442
|
+
} catch {
|
|
443
|
+
cached = false;
|
|
444
|
+
}
|
|
445
|
+
return cached;
|
|
446
|
+
}
|
|
447
|
+
function isDockerImageAvailable(image) {
|
|
448
|
+
const hit = imageCache.get(image);
|
|
449
|
+
if (hit !== void 0) return hit;
|
|
450
|
+
try {
|
|
451
|
+
execFileSync("docker", ["image", "inspect", image], { encoding: "utf-8", stdio: "pipe" });
|
|
452
|
+
imageCache.set(image, true);
|
|
453
|
+
return true;
|
|
454
|
+
} catch {
|
|
455
|
+
imageCache.set(image, false);
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function ensureDockerImage(image) {
|
|
460
|
+
if (isDockerImageAvailable(image)) return true;
|
|
461
|
+
if (isDev) {
|
|
462
|
+
return buildDevImage(image);
|
|
463
|
+
}
|
|
464
|
+
logger.info({ image }, "Docker image not found locally, attempting pull");
|
|
465
|
+
try {
|
|
466
|
+
execFileSync("docker", ["pull", image], { encoding: "utf-8", stdio: "pipe", timeout: 3e5 });
|
|
467
|
+
imageCache.set(image, true);
|
|
468
|
+
logger.info({ image }, "Docker image pulled successfully");
|
|
469
|
+
return true;
|
|
470
|
+
} catch (err) {
|
|
471
|
+
logger.warn({ image, err }, "Failed to pull Docker image");
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
function buildDevImage(image) {
|
|
476
|
+
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
477
|
+
const projectRoot = resolve(thisDir, "..");
|
|
478
|
+
const buildScript = resolve(projectRoot, "container", "build.sh");
|
|
479
|
+
if (!existsSync3(buildScript)) {
|
|
480
|
+
logger.warn({ buildScript }, "container/build.sh not found, cannot auto-build");
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
logger.info({ image }, "Dev mode: auto-building container image via container/build.sh");
|
|
484
|
+
try {
|
|
485
|
+
execFileSync("bash", [buildScript], {
|
|
486
|
+
encoding: "utf-8",
|
|
487
|
+
stdio: "inherit",
|
|
488
|
+
cwd: resolve(projectRoot, "container")
|
|
489
|
+
});
|
|
490
|
+
imageCache.set(image, true);
|
|
491
|
+
logger.info({ image }, "Dev mode: container image built successfully");
|
|
492
|
+
return true;
|
|
493
|
+
} catch (err) {
|
|
494
|
+
logger.warn({ image, err }, "Dev mode: container image build failed");
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
421
499
|
// src/agent-runner.ts
|
|
422
500
|
function runAgent(opts) {
|
|
423
501
|
const config = loadConfig();
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
opts.stepId,
|
|
428
|
-
|
|
429
|
-
opts.
|
|
430
|
-
|
|
431
|
-
opts.
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
502
|
+
const containerEnabled = config.container?.enabled ?? false;
|
|
503
|
+
if (containerEnabled && opts.workflowId && opts.stepId && opts.runId) {
|
|
504
|
+
if (!isDockerAvailable()) {
|
|
505
|
+
logger.warn({ stepId: opts.stepId }, "Docker not available, falling back to local execution");
|
|
506
|
+
} else if (!ensureDockerImage(config.container?.image ?? getDefaultImage())) {
|
|
507
|
+
logger.warn({ stepId: opts.stepId, image: config.container?.image ?? getDefaultImage() }, "Docker image not available (pull failed), falling back to local execution");
|
|
508
|
+
} else {
|
|
509
|
+
logger.info({ stepId: opts.stepId, mode: "container" }, "Running agent in container mode");
|
|
510
|
+
const containerOpts = prepareContainerOpts(
|
|
511
|
+
opts.workflowId,
|
|
512
|
+
opts.stepId,
|
|
513
|
+
opts.runId,
|
|
514
|
+
opts.prompt,
|
|
515
|
+
opts.cwd,
|
|
516
|
+
opts.allowedTools
|
|
517
|
+
);
|
|
518
|
+
const resultPromise2 = runContainerAgent({
|
|
519
|
+
...containerOpts,
|
|
520
|
+
signal: opts.signal,
|
|
521
|
+
onProgress: opts.onProgress
|
|
522
|
+
});
|
|
523
|
+
return {
|
|
524
|
+
resultPromise: resultPromise2,
|
|
525
|
+
abort: () => {
|
|
526
|
+
try {
|
|
527
|
+
import("child_process").then(({ spawn: spawn2 }) => {
|
|
528
|
+
spawn2("docker", ["stop", containerOpts.containerName]);
|
|
529
|
+
});
|
|
530
|
+
} catch {
|
|
531
|
+
}
|
|
446
532
|
}
|
|
447
|
-
}
|
|
448
|
-
}
|
|
533
|
+
};
|
|
534
|
+
}
|
|
449
535
|
}
|
|
536
|
+
logger.info({ stepId: opts.stepId, mode: "local" }, "Running agent in local mode");
|
|
450
537
|
let aborted = false;
|
|
451
538
|
const resultPromise = (async () => {
|
|
452
539
|
const authToken = config.claude.executor.api_key ?? config.claude.api_key;
|
|
@@ -511,6 +598,7 @@ function runAgent(opts) {
|
|
|
511
598
|
}
|
|
512
599
|
opts.onProgress?.(message);
|
|
513
600
|
}
|
|
601
|
+
logger.debug({ stepId: opts.stepId, status: "succeeded" }, "Agent completed");
|
|
514
602
|
return { status: "succeeded", output: result, sessionId };
|
|
515
603
|
} catch (err) {
|
|
516
604
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -547,13 +635,16 @@ function createSession(db, stepRunId, sdkSessionId) {
|
|
|
547
635
|
INSERT INTO sessions (id, step_run_id, sdk_session_id, created_at, last_used_at, is_active)
|
|
548
636
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
549
637
|
`).run(session.id, session.step_run_id, session.sdk_session_id ?? null, session.created_at, session.last_used_at, 1);
|
|
638
|
+
logger.debug({ sessionId: session.id, stepRunId }, "Session created");
|
|
550
639
|
return session;
|
|
551
640
|
}
|
|
552
641
|
function deactivateSession(db, sessionId) {
|
|
553
642
|
db.prepare("UPDATE sessions SET is_active = 0, last_used_at = ? WHERE id = ?").run((/* @__PURE__ */ new Date()).toISOString(), sessionId);
|
|
643
|
+
logger.debug({ sessionId }, "Session deactivated");
|
|
554
644
|
}
|
|
555
645
|
function updateSessionSdkId(db, sessionId, sdkSessionId) {
|
|
556
646
|
db.prepare("UPDATE sessions SET sdk_session_id = ?, last_used_at = ? WHERE id = ?").run(sdkSessionId, (/* @__PURE__ */ new Date()).toISOString(), sessionId);
|
|
647
|
+
logger.debug({ sessionId, sdkSessionId }, "Session SDK ID updated");
|
|
557
648
|
}
|
|
558
649
|
|
|
559
650
|
// src/executor.ts
|
|
@@ -599,7 +690,7 @@ function hasSkipMarker(inputs) {
|
|
|
599
690
|
}
|
|
600
691
|
return false;
|
|
601
692
|
}
|
|
602
|
-
async function executeStepOnce(step, resolvedInputs, runId, db, cwd, onProgress) {
|
|
693
|
+
async function executeStepOnce(step, resolvedInputs, runId, db, cwd, onProgress, execLogger) {
|
|
603
694
|
const stepRunId = `sr_${nanoid3()}`;
|
|
604
695
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
605
696
|
const stepRun = {
|
|
@@ -610,6 +701,7 @@ async function executeStepOnce(step, resolvedInputs, runId, db, cwd, onProgress)
|
|
|
610
701
|
started_at: now
|
|
611
702
|
};
|
|
612
703
|
insertStepRun(db, stepRun);
|
|
704
|
+
execLogger?.info({ stepId: step.id, stepRunId, attempt: "start" }, `Step started: ${step.id}`);
|
|
613
705
|
const inputContext = Object.keys(resolvedInputs).length > 0 ? `
|
|
614
706
|
|
|
615
707
|
Inputs:
|
|
@@ -628,15 +720,21 @@ ${JSON.stringify(resolvedInputs, null, 2)}` : "";
|
|
|
628
720
|
const session = createSession(db, stepRunId, result.sessionId);
|
|
629
721
|
updateSessionSdkId(db, session.id, result.sessionId);
|
|
630
722
|
deactivateSession(db, session.id);
|
|
723
|
+
execLogger?.debug({ stepId: step.id, sessionId: session.id }, "Session stored");
|
|
631
724
|
}
|
|
632
725
|
updateStepRunStatus(db, stepRunId, result.status, result.output ?? void 0, result.error);
|
|
726
|
+
if (result.status === "failed") {
|
|
727
|
+
execLogger?.error({ stepId: step.id, stepRunId, error: result.error }, `Step failed: ${step.id}`);
|
|
728
|
+
} else {
|
|
729
|
+
execLogger?.info({ stepId: step.id, stepRunId, status: result.status }, `Step completed: ${step.id}`);
|
|
730
|
+
}
|
|
633
731
|
return result;
|
|
634
732
|
}
|
|
635
|
-
async function executeStepWithRetry(step, resolvedInputs, runId, db, cwd, policy, onProgress) {
|
|
733
|
+
async function executeStepWithRetry(step, resolvedInputs, runId, db, cwd, policy, onProgress, execLogger) {
|
|
636
734
|
const maxRetries = policy.max_retries ?? 0;
|
|
637
735
|
let delay = policy.retry_delay_ms ?? 5e3;
|
|
638
736
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
639
|
-
const result = await executeStepOnce(step, resolvedInputs, runId, db, cwd, onProgress);
|
|
737
|
+
const result = await executeStepOnce(step, resolvedInputs, runId, db, cwd, onProgress, execLogger);
|
|
640
738
|
if (result.status !== "failed" || attempt === maxRetries) return result;
|
|
641
739
|
logger.info({ stepId: step.id, attempt, delay }, "Retrying step");
|
|
642
740
|
await new Promise((r) => setTimeout(r, delay));
|
|
@@ -656,6 +754,8 @@ async function executeWorkflow(opts) {
|
|
|
656
754
|
};
|
|
657
755
|
insertWorkflowRun(db, run);
|
|
658
756
|
updateWorkflowPhase(db, workflow.id, "executing");
|
|
757
|
+
const execLogger = createExecutionLogger(workflow.id, runId);
|
|
758
|
+
execLogger.info({ workflowId: workflow.id, runId, steps: workflow.steps.length }, "Workflow execution started");
|
|
659
759
|
const completed = /* @__PURE__ */ new Map();
|
|
660
760
|
const remaining = new Set(workflow.steps.map((s) => s.id));
|
|
661
761
|
const stepMap = new Map(workflow.steps.map((s) => [s.id, s]));
|
|
@@ -663,6 +763,7 @@ async function executeWorkflow(opts) {
|
|
|
663
763
|
try {
|
|
664
764
|
while (remaining.size > 0) {
|
|
665
765
|
if (signal?.aborted) {
|
|
766
|
+
execLogger.warn({ workflowId: workflow.id, runId, remainingSteps: remaining.size }, "Execution aborted via signal");
|
|
666
767
|
for (const id of remaining) {
|
|
667
768
|
completed.set(id, { status: "skipped", error: "Aborted" });
|
|
668
769
|
onProgress?.(id, { status: "skipped" });
|
|
@@ -676,6 +777,7 @@ async function executeWorkflow(opts) {
|
|
|
676
777
|
return step.depends_on.every((dep) => completed.has(dep));
|
|
677
778
|
});
|
|
678
779
|
if (ready.length === 0) {
|
|
780
|
+
execLogger.error({ workflowId: workflow.id, runId, remaining: [...remaining] }, "Deadlock detected");
|
|
679
781
|
throw new ExecutorError("Deadlock: no ready steps but remaining steps exist");
|
|
680
782
|
}
|
|
681
783
|
const executable = [];
|
|
@@ -685,6 +787,7 @@ async function executeWorkflow(opts) {
|
|
|
685
787
|
(dep) => completed.get(dep)?.status === "failed" || completed.get(dep)?.status === "skipped"
|
|
686
788
|
);
|
|
687
789
|
if (depsFailed && workflow.failure_policy.on_step_failure !== "ask_user") {
|
|
790
|
+
execLogger.debug({ stepId: id, reason: "dependency_failed" }, "Step skipped");
|
|
688
791
|
remaining.delete(id);
|
|
689
792
|
completed.set(id, { status: "skipped" });
|
|
690
793
|
const skipRunId = `sr_${nanoid3()}`;
|
|
@@ -693,6 +796,7 @@ async function executeWorkflow(opts) {
|
|
|
693
796
|
}
|
|
694
797
|
const resolvedInputs = resolveInputs(step.inputs, completed, triggerData);
|
|
695
798
|
if (hasSkipMarker(resolvedInputs)) {
|
|
799
|
+
execLogger.debug({ stepId: id, reason: "skip_marker" }, "Step skipped");
|
|
696
800
|
remaining.delete(id);
|
|
697
801
|
completed.set(id, { status: "skipped" });
|
|
698
802
|
const skipRunId = `sr_${nanoid3()}`;
|
|
@@ -713,7 +817,8 @@ async function executeWorkflow(opts) {
|
|
|
713
817
|
db,
|
|
714
818
|
cwd,
|
|
715
819
|
workflow.failure_policy,
|
|
716
|
-
onProgress
|
|
820
|
+
onProgress,
|
|
821
|
+
execLogger
|
|
717
822
|
);
|
|
718
823
|
return { stepId: step.id, result };
|
|
719
824
|
})
|
|
@@ -723,6 +828,7 @@ async function executeWorkflow(opts) {
|
|
|
723
828
|
if (result.status === "failed") {
|
|
724
829
|
const policy = workflow.failure_policy.on_step_failure;
|
|
725
830
|
if (policy === "stop") {
|
|
831
|
+
execLogger.warn({ stepId, policy: "stop" }, "Stop policy triggered, skipping remaining steps");
|
|
726
832
|
for (const remainingId of remaining) {
|
|
727
833
|
completed.set(remainingId, { status: "skipped" });
|
|
728
834
|
const skipRunId = `sr_${nanoid3()}`;
|
|
@@ -734,6 +840,7 @@ async function executeWorkflow(opts) {
|
|
|
734
840
|
}
|
|
735
841
|
if (policy === "ask_user" && onStepFailure) {
|
|
736
842
|
const decision = await onStepFailure(stepMap.get(stepId), result.error ?? "Unknown error");
|
|
843
|
+
execLogger.info({ stepId, decision }, "ask_user decision received");
|
|
737
844
|
if (decision === "stop") {
|
|
738
845
|
for (const remainingId of remaining) {
|
|
739
846
|
completed.set(remainingId, { status: "skipped" });
|
|
@@ -759,6 +866,7 @@ async function executeWorkflow(opts) {
|
|
|
759
866
|
} else {
|
|
760
867
|
updateWorkflowPhase(db, workflow.id, "active");
|
|
761
868
|
}
|
|
869
|
+
execLogger.info({ workflowId: workflow.id, runId, status: finalStatus }, "Workflow execution finished");
|
|
762
870
|
return { runId, status: finalStatus, results: completed };
|
|
763
871
|
}
|
|
764
872
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createAnthropicClient
|
|
3
3
|
} from "./chunk-DVQFSFIZ.js";
|
|
4
|
-
import {
|
|
5
|
-
getConfiguredSecretKeys
|
|
6
|
-
} from "./chunk-ZCK3IFLC.js";
|
|
7
4
|
import {
|
|
8
5
|
PlannerError
|
|
9
6
|
} from "./chunk-BVQG3WYO.js";
|
|
7
|
+
import {
|
|
8
|
+
getConfiguredSecretKeys
|
|
9
|
+
} from "./chunk-ZCK3IFLC.js";
|
|
10
10
|
import {
|
|
11
11
|
logger
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-KBLMQZ3P.js";
|
|
13
13
|
|
|
14
14
|
// src/planner.ts
|
|
15
15
|
import { z } from "zod/v4";
|
|
@@ -236,9 +236,26 @@ function parsePlannerToolResponse(response) {
|
|
|
236
236
|
}
|
|
237
237
|
return { type: "error", error: "Unexpected planner response format" };
|
|
238
238
|
}
|
|
239
|
-
function buildPlannerSystemPrompt(config) {
|
|
239
|
+
function buildPlannerSystemPrompt(config, channelContext) {
|
|
240
240
|
const identity = config.identity?.name ? `
|
|
241
241
|
User identity: ${config.identity.name}` : "";
|
|
242
|
+
let channelSection;
|
|
243
|
+
if (channelContext && channelContext.channel !== "tui") {
|
|
244
|
+
channelSection = `
|
|
245
|
+
## Channel Context
|
|
246
|
+
|
|
247
|
+
The user is requesting this workflow via **${channelContext.channel}**.
|
|
248
|
+
- Chat ID: ${channelContext.chatJid}
|
|
249
|
+
- Sender: ${channelContext.sender}
|
|
250
|
+
|
|
251
|
+
When the workflow needs to send notifications or messages back to the user, use the chat ID and channel above as the recipient. Do not ask the user for recipient information \u2014 you already have it.`;
|
|
252
|
+
} else {
|
|
253
|
+
channelSection = `
|
|
254
|
+
## Channel Context
|
|
255
|
+
|
|
256
|
+
The user is using the TUI (terminal interface). No chat recipient is available.
|
|
257
|
+
If the workflow needs to send notifications or messages to someone, the step description must require explicit recipient input (e.g., email address, phone number, chat ID) \u2014 do not assume any default recipient.`;
|
|
258
|
+
}
|
|
242
259
|
return `You are CueClaw Planner. Convert user's natural language into a structured Workflow.
|
|
243
260
|
|
|
244
261
|
## Execution Environment
|
|
@@ -276,9 +293,9 @@ You can reference these in workflow steps \u2014 they are available as environme
|
|
|
276
293
|
If a workflow needs credentials not listed above, use the set_secret tool to store them after the user provides the value. Never invent or guess secret values.
|
|
277
294
|
|
|
278
295
|
## User Identity
|
|
279
|
-
${identity}`;
|
|
296
|
+
${identity}${channelSection}`;
|
|
280
297
|
}
|
|
281
|
-
async function generatePlan(userDescription, config) {
|
|
298
|
+
async function generatePlan(userDescription, config, channelContext) {
|
|
282
299
|
const anthropic = createAnthropicClient(config);
|
|
283
300
|
const MAX_RETRIES = 2;
|
|
284
301
|
let retryContext = "";
|
|
@@ -292,13 +309,14 @@ ${retryContext}` : userDescription;
|
|
|
292
309
|
response = await anthropic.messages.create({
|
|
293
310
|
model: config.claude.planner.model,
|
|
294
311
|
max_tokens: 4096,
|
|
295
|
-
system: buildPlannerSystemPrompt(config),
|
|
312
|
+
system: buildPlannerSystemPrompt(config, channelContext),
|
|
296
313
|
messages: [{ role: "user", content: prompt }],
|
|
297
314
|
tools: [plannerTool],
|
|
298
315
|
tool_choice: { type: "tool", name: "create_workflow" }
|
|
299
316
|
});
|
|
300
317
|
} catch (err) {
|
|
301
318
|
const detail = err instanceof Error ? err.message : String(err);
|
|
319
|
+
logger.error({ err, attempt }, "Planner API request failed");
|
|
302
320
|
throw new PlannerError(
|
|
303
321
|
`API request failed: ${detail}. Check your API key and base_url in ~/.cueclaw/config.yaml`
|
|
304
322
|
);
|
|
@@ -322,6 +340,7 @@ ${retryContext}` : userDescription;
|
|
|
322
340
|
if (!parseResult.success) {
|
|
323
341
|
const errMsg = parseResult.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
324
342
|
if (attempt < MAX_RETRIES) {
|
|
343
|
+
logger.warn({ attempt, errors: errMsg }, "Planner output validation failed, retrying");
|
|
325
344
|
retryContext = `[System] Previous plan had validation issues:
|
|
326
345
|
${errMsg}
|
|
327
346
|
Please fix and try again.`;
|
|
@@ -332,6 +351,7 @@ Please fix and try again.`;
|
|
|
332
351
|
const dagErrors = validateDAG(parseResult.data.steps);
|
|
333
352
|
if (dagErrors.length > 0) {
|
|
334
353
|
if (attempt < MAX_RETRIES) {
|
|
354
|
+
logger.warn({ attempt, dagErrors }, "DAG validation failed, retrying");
|
|
335
355
|
retryContext = `[System] DAG dependency issues:
|
|
336
356
|
${dagErrors.join("\n")}
|
|
337
357
|
Please fix the step dependencies.`;
|
|
@@ -340,7 +360,7 @@ Please fix the step dependencies.`;
|
|
|
340
360
|
throw new PlannerError(`DAG validation failed after ${MAX_RETRIES + 1} attempts: ${dagErrors.join(", ")}`);
|
|
341
361
|
}
|
|
342
362
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
343
|
-
|
|
363
|
+
const workflow = {
|
|
344
364
|
...parseResult.data,
|
|
345
365
|
schema_version: "1.0",
|
|
346
366
|
id: `wf_${nanoid()}`,
|
|
@@ -348,10 +368,12 @@ Please fix the step dependencies.`;
|
|
|
348
368
|
created_at: now,
|
|
349
369
|
updated_at: now
|
|
350
370
|
};
|
|
371
|
+
logger.info({ name: workflow.name, stepCount: workflow.steps.length, workflowId: workflow.id }, "Plan generated successfully");
|
|
372
|
+
return workflow;
|
|
351
373
|
}
|
|
352
374
|
throw new PlannerError("Failed to generate valid plan after retries");
|
|
353
375
|
}
|
|
354
|
-
async function modifyPlan(originalWorkflow, modificationDescription, config) {
|
|
376
|
+
async function modifyPlan(originalWorkflow, modificationDescription, config, channelContext) {
|
|
355
377
|
const plannerOutput = {
|
|
356
378
|
name: originalWorkflow.name,
|
|
357
379
|
description: originalWorkflow.description,
|
|
@@ -369,7 +391,8 @@ ${modificationDescription}
|
|
|
369
391
|
|
|
370
392
|
Preserve unmodified steps' IDs, descriptions, and dependencies \u2014 only change what the user specified.
|
|
371
393
|
Return the complete modified workflow using the create_workflow tool.`;
|
|
372
|
-
const result = await generatePlan(combinedPrompt, config);
|
|
394
|
+
const result = await generatePlan(combinedPrompt, config, channelContext);
|
|
395
|
+
logger.info({ workflowId: originalWorkflow.id }, "Plan modified");
|
|
373
396
|
return {
|
|
374
397
|
...result,
|
|
375
398
|
id: originalWorkflow.id,
|
|
@@ -381,6 +404,7 @@ function confirmPlan(workflow) {
|
|
|
381
404
|
throw new PlannerError(`Cannot confirm workflow in phase "${workflow.phase}"`);
|
|
382
405
|
}
|
|
383
406
|
const nextPhase = workflow.trigger.type === "manual" ? "executing" : "active";
|
|
407
|
+
logger.info({ workflowId: workflow.id, nextPhase }, "Plan confirmed");
|
|
384
408
|
return {
|
|
385
409
|
...workflow,
|
|
386
410
|
phase: nextPhase,
|
|
@@ -388,6 +412,7 @@ function confirmPlan(workflow) {
|
|
|
388
412
|
};
|
|
389
413
|
}
|
|
390
414
|
function rejectPlan(workflow) {
|
|
415
|
+
logger.info({ workflowId: workflow.id }, "Plan rejected");
|
|
391
416
|
return {
|
|
392
417
|
...workflow,
|
|
393
418
|
phase: "planning",
|