cueclaw 0.1.2 → 0.1.4
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 +203 -21
- package/dist/app-6SXWEUZZ.js +3289 -0
- package/dist/{chunk-SEYPA5M2.js → chunk-3HV3MHME.js} +185 -64
- package/dist/chunk-54BGF7G5.js +771 -0
- package/dist/{chunk-RSKXBXSJ.js → chunk-5TV4LNC3.js} +22 -1
- package/dist/{chunk-KRNAXOQ4.js → chunk-FKKDQVRE.js} +2 -1
- package/dist/{chunk-G43R5ASK.js → chunk-HKZ6IN7X.js} +40 -5
- package/dist/chunk-KBLMQZ3P.js +116 -0
- package/dist/{chunk-PZZ6FBGB.js → chunk-MEAAX2SW.js} +4 -3
- package/dist/{chunk-WE5J7GMR.js → chunk-ZOFGQYXX.js} +36 -11
- package/dist/cli.js +123 -89
- package/dist/{config-6NWFKNLW.js → config-FYL6T5JP.js} +4 -1
- package/dist/daemon-Y5HIGH44.js +28 -0
- package/dist/{executor-LS3Y4DO5.js → executor-44MSZ76T.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-Q7USNFFB.js} +4 -3
- package/dist/{setup-NWBKTQCO.js → setup-JK664Y2M.js} +8 -3
- package/dist/{telegram-EFPHL4HC.js → telegram-FH5O4F3K.js} +25 -2
- package/dist/{whatsapp-HFMOFSFI.js → whatsapp-RLNSXSFI.js} +10 -2
- package/package.json +4 -4
- 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
|
@@ -4,18 +4,23 @@ import {
|
|
|
4
4
|
updateStepRunStatus,
|
|
5
5
|
updateWorkflowPhase,
|
|
6
6
|
updateWorkflowRunStatus
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-HKZ6IN7X.js";
|
|
8
8
|
import {
|
|
9
9
|
cueclawHome,
|
|
10
|
+
getDefaultImage,
|
|
10
11
|
loadConfig
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-5TV4LNC3.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,44 +427,123 @@ 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;
|
|
453
540
|
const baseUrl = config.claude.executor.base_url ?? config.claude.base_url;
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
process.env["ANTHROPIC_AUTH_TOKEN"] = authToken;
|
|
541
|
+
const stepEnv = { ...process.env };
|
|
542
|
+
if (authToken) stepEnv["ANTHROPIC_AUTH_TOKEN"] = authToken;
|
|
457
543
|
if (baseUrl !== "https://api.anthropic.com") {
|
|
458
|
-
|
|
544
|
+
stepEnv["ANTHROPIC_BASE_URL"] = baseUrl;
|
|
545
|
+
} else {
|
|
546
|
+
delete stepEnv["ANTHROPIC_BASE_URL"];
|
|
459
547
|
}
|
|
460
548
|
try {
|
|
461
549
|
const { query } = await import("@anthropic-ai/claude-agent-sdk");
|
|
@@ -477,7 +565,8 @@ function runAgent(opts) {
|
|
|
477
565
|
"WebFetch"
|
|
478
566
|
],
|
|
479
567
|
settingSources: ["project"],
|
|
480
|
-
permissionMode: permMode
|
|
568
|
+
permissionMode: permMode,
|
|
569
|
+
env: stepEnv
|
|
481
570
|
}
|
|
482
571
|
});
|
|
483
572
|
let sessionId;
|
|
@@ -511,16 +600,13 @@ function runAgent(opts) {
|
|
|
511
600
|
}
|
|
512
601
|
opts.onProgress?.(message);
|
|
513
602
|
}
|
|
603
|
+
logger.debug({ stepId: opts.stepId, status: "succeeded" }, "Agent completed");
|
|
514
604
|
return { status: "succeeded", output: result, sessionId };
|
|
515
605
|
} catch (err) {
|
|
516
606
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
517
607
|
logger.error({ err, stepId: opts.stepId }, "Agent execution failed");
|
|
518
608
|
return { status: "failed", error: errorMsg };
|
|
519
609
|
} finally {
|
|
520
|
-
if (prevAuthToken !== void 0) process.env["ANTHROPIC_AUTH_TOKEN"] = prevAuthToken;
|
|
521
|
-
else delete process.env["ANTHROPIC_AUTH_TOKEN"];
|
|
522
|
-
if (prevBaseUrl !== void 0) process.env["ANTHROPIC_BASE_URL"] = prevBaseUrl;
|
|
523
|
-
else delete process.env["ANTHROPIC_BASE_URL"];
|
|
524
610
|
}
|
|
525
611
|
})();
|
|
526
612
|
return {
|
|
@@ -547,13 +633,22 @@ function createSession(db, stepRunId, sdkSessionId) {
|
|
|
547
633
|
INSERT INTO sessions (id, step_run_id, sdk_session_id, created_at, last_used_at, is_active)
|
|
548
634
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
549
635
|
`).run(session.id, session.step_run_id, session.sdk_session_id ?? null, session.created_at, session.last_used_at, 1);
|
|
636
|
+
logger.debug({ sessionId: session.id, stepRunId }, "Session created");
|
|
550
637
|
return session;
|
|
551
638
|
}
|
|
552
639
|
function deactivateSession(db, sessionId) {
|
|
553
640
|
db.prepare("UPDATE sessions SET is_active = 0, last_used_at = ? WHERE id = ?").run((/* @__PURE__ */ new Date()).toISOString(), sessionId);
|
|
641
|
+
logger.debug({ sessionId }, "Session deactivated");
|
|
554
642
|
}
|
|
555
643
|
function updateSessionSdkId(db, sessionId, sdkSessionId) {
|
|
556
644
|
db.prepare("UPDATE sessions SET sdk_session_id = ?, last_used_at = ? WHERE id = ?").run(sdkSessionId, (/* @__PURE__ */ new Date()).toISOString(), sessionId);
|
|
645
|
+
logger.debug({ sessionId, sdkSessionId }, "Session SDK ID updated");
|
|
646
|
+
}
|
|
647
|
+
function cleanupStaleSessions(db, maxAgeMs = 7 * 24 * 60 * 60 * 1e3) {
|
|
648
|
+
const cutoff = new Date(Date.now() - maxAgeMs).toISOString();
|
|
649
|
+
const result = db.prepare("DELETE FROM sessions WHERE is_active = 0 AND last_used_at < ?").run(cutoff);
|
|
650
|
+
logger.info({ deletedCount: result.changes, cutoff }, "Stale sessions cleaned up");
|
|
651
|
+
return result.changes;
|
|
557
652
|
}
|
|
558
653
|
|
|
559
654
|
// src/executor.ts
|
|
@@ -599,7 +694,7 @@ function hasSkipMarker(inputs) {
|
|
|
599
694
|
}
|
|
600
695
|
return false;
|
|
601
696
|
}
|
|
602
|
-
async function executeStepOnce(step, resolvedInputs, runId, db, cwd, onProgress) {
|
|
697
|
+
async function executeStepOnce(step, resolvedInputs, workflowId, runId, db, cwd, onProgress, execLogger) {
|
|
603
698
|
const stepRunId = `sr_${nanoid3()}`;
|
|
604
699
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
605
700
|
const stepRun = {
|
|
@@ -610,6 +705,7 @@ async function executeStepOnce(step, resolvedInputs, runId, db, cwd, onProgress)
|
|
|
610
705
|
started_at: now
|
|
611
706
|
};
|
|
612
707
|
insertStepRun(db, stepRun);
|
|
708
|
+
execLogger?.info({ stepId: step.id, stepRunId, attempt: "start" }, `Step started: ${step.id}`);
|
|
613
709
|
const inputContext = Object.keys(resolvedInputs).length > 0 ? `
|
|
614
710
|
|
|
615
711
|
Inputs:
|
|
@@ -618,7 +714,7 @@ ${JSON.stringify(resolvedInputs, null, 2)}` : "";
|
|
|
618
714
|
const handle = runAgent({
|
|
619
715
|
prompt,
|
|
620
716
|
cwd,
|
|
621
|
-
workflowId
|
|
717
|
+
workflowId,
|
|
622
718
|
stepId: step.id,
|
|
623
719
|
runId,
|
|
624
720
|
onProgress: onProgress ? (msg) => onProgress(step.id, msg) : void 0
|
|
@@ -628,15 +724,21 @@ ${JSON.stringify(resolvedInputs, null, 2)}` : "";
|
|
|
628
724
|
const session = createSession(db, stepRunId, result.sessionId);
|
|
629
725
|
updateSessionSdkId(db, session.id, result.sessionId);
|
|
630
726
|
deactivateSession(db, session.id);
|
|
727
|
+
execLogger?.debug({ stepId: step.id, sessionId: session.id }, "Session stored");
|
|
631
728
|
}
|
|
632
729
|
updateStepRunStatus(db, stepRunId, result.status, result.output ?? void 0, result.error);
|
|
730
|
+
if (result.status === "failed") {
|
|
731
|
+
execLogger?.error({ stepId: step.id, stepRunId, error: result.error }, `Step failed: ${step.id}`);
|
|
732
|
+
} else {
|
|
733
|
+
execLogger?.info({ stepId: step.id, stepRunId, status: result.status }, `Step completed: ${step.id}`);
|
|
734
|
+
}
|
|
633
735
|
return result;
|
|
634
736
|
}
|
|
635
|
-
async function executeStepWithRetry(step, resolvedInputs, runId, db, cwd, policy, onProgress) {
|
|
737
|
+
async function executeStepWithRetry(step, resolvedInputs, workflowId, runId, db, cwd, policy, onProgress, execLogger) {
|
|
636
738
|
const maxRetries = policy.max_retries ?? 0;
|
|
637
739
|
let delay = policy.retry_delay_ms ?? 5e3;
|
|
638
740
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
639
|
-
const result = await executeStepOnce(step, resolvedInputs, runId, db, cwd, onProgress);
|
|
741
|
+
const result = await executeStepOnce(step, resolvedInputs, workflowId, runId, db, cwd, onProgress, execLogger);
|
|
640
742
|
if (result.status !== "failed" || attempt === maxRetries) return result;
|
|
641
743
|
logger.info({ stepId: step.id, attempt, delay }, "Retrying step");
|
|
642
744
|
await new Promise((r) => setTimeout(r, delay));
|
|
@@ -656,6 +758,8 @@ async function executeWorkflow(opts) {
|
|
|
656
758
|
};
|
|
657
759
|
insertWorkflowRun(db, run);
|
|
658
760
|
updateWorkflowPhase(db, workflow.id, "executing");
|
|
761
|
+
const execLogger = createExecutionLogger(workflow.id, runId);
|
|
762
|
+
execLogger.info({ workflowId: workflow.id, runId, steps: workflow.steps.length }, "Workflow execution started");
|
|
659
763
|
const completed = /* @__PURE__ */ new Map();
|
|
660
764
|
const remaining = new Set(workflow.steps.map((s) => s.id));
|
|
661
765
|
const stepMap = new Map(workflow.steps.map((s) => [s.id, s]));
|
|
@@ -663,6 +767,7 @@ async function executeWorkflow(opts) {
|
|
|
663
767
|
try {
|
|
664
768
|
while (remaining.size > 0) {
|
|
665
769
|
if (signal?.aborted) {
|
|
770
|
+
execLogger.warn({ workflowId: workflow.id, runId, remainingSteps: remaining.size }, "Execution aborted via signal");
|
|
666
771
|
for (const id of remaining) {
|
|
667
772
|
completed.set(id, { status: "skipped", error: "Aborted" });
|
|
668
773
|
onProgress?.(id, { status: "skipped" });
|
|
@@ -676,6 +781,7 @@ async function executeWorkflow(opts) {
|
|
|
676
781
|
return step.depends_on.every((dep) => completed.has(dep));
|
|
677
782
|
});
|
|
678
783
|
if (ready.length === 0) {
|
|
784
|
+
execLogger.error({ workflowId: workflow.id, runId, remaining: [...remaining] }, "Deadlock detected");
|
|
679
785
|
throw new ExecutorError("Deadlock: no ready steps but remaining steps exist");
|
|
680
786
|
}
|
|
681
787
|
const executable = [];
|
|
@@ -685,6 +791,7 @@ async function executeWorkflow(opts) {
|
|
|
685
791
|
(dep) => completed.get(dep)?.status === "failed" || completed.get(dep)?.status === "skipped"
|
|
686
792
|
);
|
|
687
793
|
if (depsFailed && workflow.failure_policy.on_step_failure !== "ask_user") {
|
|
794
|
+
execLogger.debug({ stepId: id, reason: "dependency_failed" }, "Step skipped");
|
|
688
795
|
remaining.delete(id);
|
|
689
796
|
completed.set(id, { status: "skipped" });
|
|
690
797
|
const skipRunId = `sr_${nanoid3()}`;
|
|
@@ -693,6 +800,7 @@ async function executeWorkflow(opts) {
|
|
|
693
800
|
}
|
|
694
801
|
const resolvedInputs = resolveInputs(step.inputs, completed, triggerData);
|
|
695
802
|
if (hasSkipMarker(resolvedInputs)) {
|
|
803
|
+
execLogger.debug({ stepId: id, reason: "skip_marker" }, "Step skipped");
|
|
696
804
|
remaining.delete(id);
|
|
697
805
|
completed.set(id, { status: "skipped" });
|
|
698
806
|
const skipRunId = `sr_${nanoid3()}`;
|
|
@@ -709,20 +817,38 @@ async function executeWorkflow(opts) {
|
|
|
709
817
|
const result = await executeStepWithRetry(
|
|
710
818
|
step,
|
|
711
819
|
resolvedInputs,
|
|
820
|
+
workflow.id,
|
|
712
821
|
runId,
|
|
713
822
|
db,
|
|
714
823
|
cwd,
|
|
715
824
|
workflow.failure_policy,
|
|
716
|
-
onProgress
|
|
825
|
+
onProgress,
|
|
826
|
+
execLogger
|
|
717
827
|
);
|
|
718
828
|
return { stepId: step.id, result };
|
|
719
829
|
})
|
|
720
830
|
);
|
|
721
831
|
for (const { stepId, result } of results) {
|
|
722
832
|
completed.set(stepId, result);
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
833
|
+
}
|
|
834
|
+
for (const { stepId, result } of results) {
|
|
835
|
+
if (result.status !== "failed") continue;
|
|
836
|
+
const policy = workflow.failure_policy.on_step_failure;
|
|
837
|
+
if (policy === "stop") {
|
|
838
|
+
execLogger.warn({ stepId, policy: "stop" }, "Stop policy triggered, skipping remaining steps");
|
|
839
|
+
for (const remainingId of remaining) {
|
|
840
|
+
completed.set(remainingId, { status: "skipped" });
|
|
841
|
+
const skipRunId = `sr_${nanoid3()}`;
|
|
842
|
+
insertStepRun(db, { id: skipRunId, run_id: runId, step_id: remainingId, status: "skipped" });
|
|
843
|
+
}
|
|
844
|
+
remaining.clear();
|
|
845
|
+
runFailed = true;
|
|
846
|
+
break;
|
|
847
|
+
}
|
|
848
|
+
if (policy === "ask_user" && onStepFailure) {
|
|
849
|
+
const decision = await onStepFailure(stepMap.get(stepId), result.error ?? "Unknown error");
|
|
850
|
+
execLogger.info({ stepId, decision }, "ask_user decision received");
|
|
851
|
+
if (decision === "stop") {
|
|
726
852
|
for (const remainingId of remaining) {
|
|
727
853
|
completed.set(remainingId, { status: "skipped" });
|
|
728
854
|
const skipRunId = `sr_${nanoid3()}`;
|
|
@@ -732,18 +858,11 @@ async function executeWorkflow(opts) {
|
|
|
732
858
|
runFailed = true;
|
|
733
859
|
break;
|
|
734
860
|
}
|
|
735
|
-
if (
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
const skipRunId = `sr_${nanoid3()}`;
|
|
741
|
-
insertStepRun(db, { id: skipRunId, run_id: runId, step_id: remainingId, status: "skipped" });
|
|
742
|
-
}
|
|
743
|
-
remaining.clear();
|
|
744
|
-
runFailed = true;
|
|
745
|
-
break;
|
|
746
|
-
}
|
|
861
|
+
if (decision === "retry") {
|
|
862
|
+
remaining.add(stepId);
|
|
863
|
+
completed.delete(stepId);
|
|
864
|
+
execLogger.info({ stepId }, "Re-queuing step for retry");
|
|
865
|
+
continue;
|
|
747
866
|
}
|
|
748
867
|
}
|
|
749
868
|
}
|
|
@@ -759,10 +878,12 @@ async function executeWorkflow(opts) {
|
|
|
759
878
|
} else {
|
|
760
879
|
updateWorkflowPhase(db, workflow.id, "active");
|
|
761
880
|
}
|
|
881
|
+
execLogger.info({ workflowId: workflow.id, runId, status: finalStatus }, "Workflow execution finished");
|
|
762
882
|
return { runId, status: finalStatus, results: completed };
|
|
763
883
|
}
|
|
764
884
|
|
|
765
885
|
export {
|
|
886
|
+
cleanupStaleSessions,
|
|
766
887
|
resolveValue,
|
|
767
888
|
resolveInputs,
|
|
768
889
|
executeWorkflow
|