cueclaw 0.1.3 → 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 CHANGED
@@ -19,6 +19,32 @@
19
19
  2. **Review** the generated execution plan (DAG)
20
20
  3. **Confirm**, and CueClaw runs it in the background as a daemon
21
21
 
22
+ ```
23
+ You: "Every day at 10am, scrape GitHub Trending for the top 10 projects,
24
+ summarize each with stars/language/description, and send the digest
25
+ to me on Telegram."
26
+
27
+ CueClaw:
28
+ ┌─ Plan: GitHub Trending Digest ─────────────┐
29
+ │ Trigger: cron (0 10 * * *) │
30
+ │ │
31
+ │ 1. Fetch GitHub Trending page │
32
+ │ 2. Extract top 10 projects with metadata │
33
+ │ └─ depends on: step 1 │
34
+ │ 3. Generate formatted digest summary │
35
+ │ └─ depends on: step 2 │
36
+ │ 4. Send digest via Telegram │
37
+ │ └─ depends on: step 3 │
38
+ │ │
39
+ │ [Y] Confirm [M] Modify [N] Cancel │
40
+ └────────────────────────────────────────────┘
41
+ ```
42
+
43
+ ### More Examples
44
+
45
+ <details>
46
+ <summary><b>X (Twitter) Auto Engage</b> — poll trigger, daily cron summary</summary>
47
+
22
48
  ```
23
49
  You: "Every 30 minutes, check my X timeline for trending AI/LLM tweets,
24
50
  reply with a professional but friendly tone, and post a daily
@@ -35,12 +61,10 @@ CueClaw:
35
61
  │ └─ depends on: step 2 │
36
62
  │ 4. Daily: compose & post trend summary │
37
63
  │ └─ cron: 0 21 * * * │
38
- │ │
39
- │ [Y] Confirm [M] Modify [N] Cancel │
40
64
  └────────────────────────────────────────────┘
41
65
  ```
42
66
 
43
- ### More Examples
67
+ </details>
44
68
 
45
69
  <details>
46
70
  <summary><b>GitHub Issue to Draft PR</b> — poll trigger, multi-step DAG</summary>
@@ -66,30 +90,6 @@ CueClaw:
66
90
 
67
91
  </details>
68
92
 
69
- <details>
70
- <summary><b>GitHub Trending Daily Digest</b> — cron trigger, Telegram notification</summary>
71
-
72
- ```
73
- You: "Every day at 10am, scrape GitHub Trending for the top 10 projects,
74
- summarize each with stars/language/description, and send the digest
75
- to me on Telegram."
76
-
77
- CueClaw:
78
- ┌─ Plan: GitHub Trending Digest ─────────────┐
79
- │ Trigger: cron (0 10 * * *) │
80
- │ │
81
- │ 1. Fetch GitHub Trending page │
82
- │ 2. Extract top 10 projects with metadata │
83
- │ └─ depends on: step 1 │
84
- │ 3. Generate formatted digest summary │
85
- │ └─ depends on: step 2 │
86
- │ 4. Send digest via Telegram │
87
- │ └─ depends on: step 3 │
88
- └────────────────────────────────────────────┘
89
- ```
90
-
91
- </details>
92
-
93
93
  <details>
94
94
  <summary><b>PR Review Loop</b> — interactive commands via poll trigger</summary>
95
95
 
@@ -6,14 +6,14 @@ import {
6
6
  readPidFile,
7
7
  removePidFile,
8
8
  spawnDaemonProcess
9
- } from "./chunk-HDUFGPCI.js";
9
+ } from "./chunk-54BGF7G5.js";
10
10
  import {
11
11
  getServiceStatus
12
- } from "./chunk-CXBDJQJJ.js";
12
+ } from "./chunk-MEAAX2SW.js";
13
13
  import {
14
14
  checkEnvironment,
15
15
  validateAuth
16
- } from "./chunk-KRNAXOQ4.js";
16
+ } from "./chunk-FKKDQVRE.js";
17
17
  import {
18
18
  askQuestionTool,
19
19
  buildPlannerSystemPrompt,
@@ -28,7 +28,7 @@ import {
28
28
  } from "./chunk-DVQFSFIZ.js";
29
29
  import {
30
30
  executeWorkflow
31
- } from "./chunk-X3WNTN5V.js";
31
+ } from "./chunk-3HV3MHME.js";
32
32
  import {
33
33
  deleteWorkflow,
34
34
  getStepRunsByRunId,
@@ -38,14 +38,14 @@ import {
38
38
  listWorkflows,
39
39
  updateWorkflowPhase,
40
40
  upsertWorkflow
41
- } from "./chunk-JJUF2AJ5.js";
41
+ } from "./chunk-HKZ6IN7X.js";
42
42
  import {
43
43
  cueclawHome,
44
44
  loadConfig,
45
45
  loadExistingConfig,
46
46
  validateConfig,
47
47
  writeConfig
48
- } from "./chunk-25KI643G.js";
48
+ } from "./chunk-5TV4LNC3.js";
49
49
  import "./chunk-BVQG3WYO.js";
50
50
  import {
51
51
  isDev,
@@ -872,14 +872,15 @@ ${failedSteps.join("\n")}` : "";
872
872
  }, []);
873
873
  const handleCancel = useCallback4(() => {
874
874
  if (workflow) {
875
- rejectPlan(workflow);
875
+ const rejected = rejectPlan(workflow);
876
+ updateWorkflowPhase(db, workflow.id, rejected.phase);
876
877
  }
877
878
  if (plannerSessionRef.current) {
878
879
  plannerSessionRef.current = null;
879
880
  }
880
881
  dispatch({ type: "SHOW_CHAT" });
881
882
  dispatch({ type: "ADD_MESSAGE", message: { type: "assistant", text: "Plan cancelled." } });
882
- }, [workflow]);
883
+ }, [workflow, db]);
883
884
  const handleExecutionBack = useCallback4(() => {
884
885
  dispatch({ type: "SHOW_CHAT" });
885
886
  }, []);
@@ -1715,8 +1716,17 @@ function AppProvider({ cwd, skipOnboarding, children }) {
1715
1716
  const stepRuns = latestRun ? getStepRunsByRunId(db, latestRun.id) : [];
1716
1717
  dispatch({ type: "SHOW_DETAIL", workflow, runs, stepRuns });
1717
1718
  }, [db]);
1718
- const handleStatusStop = useCallback7((_workflow) => {
1719
- }, []);
1719
+ const handleStatusStop = useCallback7((workflow) => {
1720
+ const controller = execution.abortMapRef.current.get(workflow.id);
1721
+ if (controller) {
1722
+ controller.abort();
1723
+ dispatch({ type: "ADD_MESSAGE", message: { type: "system", text: `Stopping workflow: ${workflow.name}` } });
1724
+ } else {
1725
+ dispatch({ type: "ADD_MESSAGE", message: { type: "warning", text: `Workflow "${workflow.name}" is not currently executing.` } });
1726
+ }
1727
+ const workflows = listWorkflows(db);
1728
+ dispatch({ type: "SHOW_STATUS", workflows });
1729
+ }, [db, execution.abortMapRef]);
1720
1730
  const handleStatusDelete = useCallback7((workflow) => {
1721
1731
  deleteWorkflow(db, workflow.id);
1722
1732
  const updated = listWorkflows(db);
@@ -4,12 +4,12 @@ import {
4
4
  updateStepRunStatus,
5
5
  updateWorkflowPhase,
6
6
  updateWorkflowRunStatus
7
- } from "./chunk-JJUF2AJ5.js";
7
+ } from "./chunk-HKZ6IN7X.js";
8
8
  import {
9
9
  cueclawHome,
10
10
  getDefaultImage,
11
11
  loadConfig
12
- } from "./chunk-25KI643G.js";
12
+ } from "./chunk-5TV4LNC3.js";
13
13
  import {
14
14
  ConfigError,
15
15
  ExecutorError
@@ -538,11 +538,12 @@ function runAgent(opts) {
538
538
  const resultPromise = (async () => {
539
539
  const authToken = config.claude.executor.api_key ?? config.claude.api_key;
540
540
  const baseUrl = config.claude.executor.base_url ?? config.claude.base_url;
541
- const prevAuthToken = process.env["ANTHROPIC_AUTH_TOKEN"];
542
- const prevBaseUrl = process.env["ANTHROPIC_BASE_URL"];
543
- process.env["ANTHROPIC_AUTH_TOKEN"] = authToken;
541
+ const stepEnv = { ...process.env };
542
+ if (authToken) stepEnv["ANTHROPIC_AUTH_TOKEN"] = authToken;
544
543
  if (baseUrl !== "https://api.anthropic.com") {
545
- process.env["ANTHROPIC_BASE_URL"] = baseUrl;
544
+ stepEnv["ANTHROPIC_BASE_URL"] = baseUrl;
545
+ } else {
546
+ delete stepEnv["ANTHROPIC_BASE_URL"];
546
547
  }
547
548
  try {
548
549
  const { query } = await import("@anthropic-ai/claude-agent-sdk");
@@ -564,7 +565,8 @@ function runAgent(opts) {
564
565
  "WebFetch"
565
566
  ],
566
567
  settingSources: ["project"],
567
- permissionMode: permMode
568
+ permissionMode: permMode,
569
+ env: stepEnv
568
570
  }
569
571
  });
570
572
  let sessionId;
@@ -605,10 +607,6 @@ function runAgent(opts) {
605
607
  logger.error({ err, stepId: opts.stepId }, "Agent execution failed");
606
608
  return { status: "failed", error: errorMsg };
607
609
  } finally {
608
- if (prevAuthToken !== void 0) process.env["ANTHROPIC_AUTH_TOKEN"] = prevAuthToken;
609
- else delete process.env["ANTHROPIC_AUTH_TOKEN"];
610
- if (prevBaseUrl !== void 0) process.env["ANTHROPIC_BASE_URL"] = prevBaseUrl;
611
- else delete process.env["ANTHROPIC_BASE_URL"];
612
610
  }
613
611
  })();
614
612
  return {
@@ -646,6 +644,12 @@ function updateSessionSdkId(db, sessionId, sdkSessionId) {
646
644
  db.prepare("UPDATE sessions SET sdk_session_id = ?, last_used_at = ? WHERE id = ?").run(sdkSessionId, (/* @__PURE__ */ new Date()).toISOString(), sessionId);
647
645
  logger.debug({ sessionId, sdkSessionId }, "Session SDK ID updated");
648
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;
652
+ }
649
653
 
650
654
  // src/executor.ts
651
655
  var STEP_REF_PATTERN = /\$steps\.([a-z0-9-]+)\.output/g;
@@ -690,7 +694,7 @@ function hasSkipMarker(inputs) {
690
694
  }
691
695
  return false;
692
696
  }
693
- async function executeStepOnce(step, resolvedInputs, runId, db, cwd, onProgress, execLogger) {
697
+ async function executeStepOnce(step, resolvedInputs, workflowId, runId, db, cwd, onProgress, execLogger) {
694
698
  const stepRunId = `sr_${nanoid3()}`;
695
699
  const now = (/* @__PURE__ */ new Date()).toISOString();
696
700
  const stepRun = {
@@ -710,7 +714,7 @@ ${JSON.stringify(resolvedInputs, null, 2)}` : "";
710
714
  const handle = runAgent({
711
715
  prompt,
712
716
  cwd,
713
- workflowId: step.id,
717
+ workflowId,
714
718
  stepId: step.id,
715
719
  runId,
716
720
  onProgress: onProgress ? (msg) => onProgress(step.id, msg) : void 0
@@ -730,11 +734,11 @@ ${JSON.stringify(resolvedInputs, null, 2)}` : "";
730
734
  }
731
735
  return result;
732
736
  }
733
- async function executeStepWithRetry(step, resolvedInputs, runId, db, cwd, policy, onProgress, execLogger) {
737
+ async function executeStepWithRetry(step, resolvedInputs, workflowId, runId, db, cwd, policy, onProgress, execLogger) {
734
738
  const maxRetries = policy.max_retries ?? 0;
735
739
  let delay = policy.retry_delay_ms ?? 5e3;
736
740
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
737
- const result = await executeStepOnce(step, resolvedInputs, runId, db, cwd, onProgress, execLogger);
741
+ const result = await executeStepOnce(step, resolvedInputs, workflowId, runId, db, cwd, onProgress, execLogger);
738
742
  if (result.status !== "failed" || attempt === maxRetries) return result;
739
743
  logger.info({ stepId: step.id, attempt, delay }, "Retrying step");
740
744
  await new Promise((r) => setTimeout(r, delay));
@@ -813,6 +817,7 @@ async function executeWorkflow(opts) {
813
817
  const result = await executeStepWithRetry(
814
818
  step,
815
819
  resolvedInputs,
820
+ workflow.id,
816
821
  runId,
817
822
  db,
818
823
  cwd,
@@ -825,10 +830,25 @@ async function executeWorkflow(opts) {
825
830
  );
826
831
  for (const { stepId, result } of results) {
827
832
  completed.set(stepId, result);
828
- if (result.status === "failed") {
829
- const policy = workflow.failure_policy.on_step_failure;
830
- if (policy === "stop") {
831
- execLogger.warn({ stepId, policy: "stop" }, "Stop policy triggered, skipping remaining steps");
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") {
832
852
  for (const remainingId of remaining) {
833
853
  completed.set(remainingId, { status: "skipped" });
834
854
  const skipRunId = `sr_${nanoid3()}`;
@@ -838,19 +858,11 @@ async function executeWorkflow(opts) {
838
858
  runFailed = true;
839
859
  break;
840
860
  }
841
- if (policy === "ask_user" && onStepFailure) {
842
- const decision = await onStepFailure(stepMap.get(stepId), result.error ?? "Unknown error");
843
- execLogger.info({ stepId, decision }, "ask_user decision received");
844
- if (decision === "stop") {
845
- for (const remainingId of remaining) {
846
- completed.set(remainingId, { status: "skipped" });
847
- const skipRunId = `sr_${nanoid3()}`;
848
- insertStepRun(db, { id: skipRunId, run_id: runId, step_id: remainingId, status: "skipped" });
849
- }
850
- remaining.clear();
851
- runFailed = true;
852
- break;
853
- }
861
+ if (decision === "retry") {
862
+ remaining.add(stepId);
863
+ completed.delete(stepId);
864
+ execLogger.info({ stepId }, "Re-queuing step for retry");
865
+ continue;
854
866
  }
855
867
  }
856
868
  }
@@ -871,6 +883,7 @@ async function executeWorkflow(opts) {
871
883
  }
872
884
 
873
885
  export {
886
+ cleanupStaleSessions,
874
887
  resolveValue,
875
888
  resolveInputs,
876
889
  executeWorkflow
@@ -8,8 +8,9 @@ import {
8
8
  createAnthropicClient
9
9
  } from "./chunk-DVQFSFIZ.js";
10
10
  import {
11
+ cleanupStaleSessions,
11
12
  executeWorkflow
12
- } from "./chunk-X3WNTN5V.js";
13
+ } from "./chunk-3HV3MHME.js";
13
14
  import {
14
15
  getWorkflow,
15
16
  initDb,
@@ -17,11 +18,11 @@ import {
17
18
  listWorkflows,
18
19
  updateWorkflowPhase,
19
20
  upsertWorkflow
20
- } from "./chunk-JJUF2AJ5.js";
21
+ } from "./chunk-HKZ6IN7X.js";
21
22
  import {
22
23
  cueclawHome,
23
24
  loadConfig
24
- } from "./chunk-25KI643G.js";
25
+ } from "./chunk-5TV4LNC3.js";
25
26
  import {
26
27
  logger
27
28
  } from "./chunk-KBLMQZ3P.js";
@@ -36,6 +37,7 @@ var CONFIRMATION_TIMEOUT = 10 * 6e4;
36
37
  var RATE_LIMIT_WINDOW = 6e4;
37
38
  var RATE_LIMIT_MAX = 10;
38
39
  var CLEANUP_INTERVAL = 5 * 6e4;
40
+ var ACTIVE_JID_TTL = 24 * 60 * 6e4;
39
41
  var MessageRouter = class {
40
42
  constructor(db, config, cwd) {
41
43
  this.db = db;
@@ -45,6 +47,7 @@ var MessageRouter = class {
45
47
  channels = /* @__PURE__ */ new Map();
46
48
  pendingConfirmations = /* @__PURE__ */ new Map();
47
49
  messageTimestamps = /* @__PURE__ */ new Map();
50
+ activeJids = /* @__PURE__ */ new Map();
48
51
  cleanupTimer = null;
49
52
  registerChannel(channel) {
50
53
  this.channels.set(channel.name, channel);
@@ -58,6 +61,7 @@ var MessageRouter = class {
58
61
  clearInterval(this.cleanupTimer);
59
62
  this.cleanupTimer = null;
60
63
  }
64
+ this.activeJids.clear();
61
65
  }
62
66
  /** Disconnect all registered channels */
63
67
  async disconnectAll() {
@@ -65,16 +69,39 @@ var MessageRouter = class {
65
69
  [...this.channels.values()].map((c) => c.disconnect())
66
70
  );
67
71
  }
68
- /** Broadcast a notification to all connected channels (used by MCP handler) */
72
+ /** Broadcast a notification to all active users on connected channels */
69
73
  broadcastNotification(message) {
70
- for (const channel of this.channels.values()) {
71
- if (channel.isConnected()) {
72
- channel.sendMessage("broadcast", message).catch((err) => {
73
- logger.error({ err, channel: channel.name }, "Failed to broadcast notification");
74
+ const now = Date.now();
75
+ for (const [channelName, channel] of this.channels) {
76
+ if (!channel.isConnected()) continue;
77
+ const jids = this.activeJids.get(channelName);
78
+ const recipients = /* @__PURE__ */ new Set();
79
+ if (jids && jids.size > 0) {
80
+ for (const [jid, lastSeenAt] of jids) {
81
+ if (now - lastSeenAt <= ACTIVE_JID_TTL) recipients.add(jid);
82
+ }
83
+ }
84
+ if (recipients.size === 0) {
85
+ for (const jid of this.getConfiguredRecipients(channelName)) {
86
+ recipients.add(jid);
87
+ }
88
+ }
89
+ for (const jid of recipients) {
90
+ channel.sendMessage(jid, message).catch((err) => {
91
+ logger.error({ err, channel: channelName, jid }, "Failed to broadcast notification");
74
92
  });
75
93
  }
76
94
  }
77
95
  }
96
+ getConfiguredRecipients(channelName) {
97
+ if (channelName === "telegram") {
98
+ return this.config.telegram?.allowed_users ?? [];
99
+ }
100
+ if (channelName === "whatsapp") {
101
+ return this.config.whatsapp?.allowed_jids ?? [];
102
+ }
103
+ return [];
104
+ }
78
105
  async handleInbound(channelName, chatJid, message) {
79
106
  const channel = this.channels.get(channelName);
80
107
  if (!channel) {
@@ -84,6 +111,8 @@ var MessageRouter = class {
84
111
  const text = typeof message === "string" ? message : message.text;
85
112
  const sender = typeof message === "string" ? void 0 : message.sender;
86
113
  logger.debug({ channelName, chatJid }, "Inbound message received");
114
+ if (!this.activeJids.has(channelName)) this.activeJids.set(channelName, /* @__PURE__ */ new Map());
115
+ this.activeJids.get(channelName).set(chatJid, Date.now());
87
116
  if (this.isRateLimited(chatJid)) {
88
117
  logger.warn({ chatJid, channelName }, "Rate limit exceeded");
89
118
  await channel.sendMessage(chatJid, "Rate limited, please wait before sending more messages.");
@@ -307,7 +336,8 @@ ${msg}`;
307
336
  }
308
337
  } else if (["no", "n", "cancel", "3"].includes(lower)) {
309
338
  this.pendingConfirmations.delete(chatJid);
310
- rejectPlan(pending.workflow);
339
+ const rejected = rejectPlan(pending.workflow);
340
+ updateWorkflowPhase(this.db, pending.workflowId, rejected.phase);
311
341
  logger.info({ workflowId: pending.workflowId, chatJid }, "Workflow rejected via channel");
312
342
  await channel.sendMessage(chatJid, "Plan cancelled.");
313
343
  } else if (["modify", "m", "2"].includes(lower)) {
@@ -353,6 +383,16 @@ ${msg}`;
353
383
  this.messageTimestamps.set(jid, recent);
354
384
  }
355
385
  }
386
+ for (const [channelName, jids] of this.activeJids) {
387
+ for (const [jid, lastSeenAt] of jids) {
388
+ if (now - lastSeenAt > ACTIVE_JID_TTL) {
389
+ jids.delete(jid);
390
+ }
391
+ }
392
+ if (jids.size === 0) {
393
+ this.activeJids.delete(channelName);
394
+ }
395
+ }
356
396
  }
357
397
  };
358
398
 
@@ -675,6 +715,8 @@ async function startDaemon() {
675
715
  logger.error({ err }, "Failed to start Telegram channel");
676
716
  }
677
717
  }
718
+ const cleaned = cleanupStaleSessions(db);
719
+ if (cleaned > 0) logger.info({ cleaned }, "Cleaned up stale sessions");
678
720
  await recoverRunningWorkflows(db, router);
679
721
  logger.debug("Starting trigger loop and router");
680
722
  const maxConcurrent = 5;
@@ -115,6 +115,12 @@ function loadConfig() {
115
115
  merged.claude = merged.claude ?? {};
116
116
  merged.claude.base_url = process.env["ANTHROPIC_BASE_URL"];
117
117
  }
118
+ if (process.env["CUECLAW_MODEL"]) {
119
+ merged.claude = merged.claude ?? {};
120
+ const model = process.env["CUECLAW_MODEL"];
121
+ merged.claude.planner = { ...merged.claude.planner, model };
122
+ merged.claude.executor = { ...merged.claude.executor, model };
123
+ }
118
124
  if (process.env["TELEGRAM_BOT_TOKEN"]) {
119
125
  merged.telegram = merged.telegram ?? {};
120
126
  merged.telegram.token = process.env["TELEGRAM_BOT_TOKEN"];
@@ -29,8 +29,9 @@ function checkEnvironment() {
29
29
  async function validateAuth(config) {
30
30
  try {
31
31
  const client = createAnthropicClient(config);
32
+ const model = config.claude.planner.model ?? "claude-haiku-4-5-20251001";
32
33
  await client.messages.create({
33
- model: "claude-haiku-4-5-20251001",
34
+ model,
34
35
  max_tokens: 10,
35
36
  messages: [{ role: "user", content: "ping" }]
36
37
  });
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  cueclawHome
3
- } from "./chunk-25KI643G.js";
3
+ } from "./chunk-5TV4LNC3.js";
4
4
 
5
5
  // src/db.ts
6
6
  import Database from "better-sqlite3";
@@ -166,8 +166,16 @@ function insertWorkflowRun(db, run) {
166
166
  `).run(run.id, run.workflow_id, run.trigger_data, run.status, run.started_at, run.completed_at ?? null, run.error ?? null, run.duration_ms ?? null);
167
167
  }
168
168
  function updateWorkflowRunStatus(db, id, status, error) {
169
- const completedAt = status !== "running" ? (/* @__PURE__ */ new Date()).toISOString() : null;
170
- db.prepare("UPDATE workflow_runs SET status = ?, completed_at = ?, error = ? WHERE id = ?").run(status, completedAt, error ?? null, id);
169
+ const now = /* @__PURE__ */ new Date();
170
+ const completedAt = status !== "running" ? now.toISOString() : null;
171
+ let durationMs = null;
172
+ if (completedAt) {
173
+ const row = db.prepare("SELECT started_at FROM workflow_runs WHERE id = ?").get(id);
174
+ if (row?.started_at) {
175
+ durationMs = now.getTime() - new Date(row.started_at).getTime();
176
+ }
177
+ }
178
+ db.prepare("UPDATE workflow_runs SET status = ?, completed_at = ?, error = ?, duration_ms = ? WHERE id = ?").run(status, completedAt, error ?? null, durationMs, id);
171
179
  }
172
180
  function insertStepRun(db, stepRun) {
173
181
  db.prepare(`
@@ -176,8 +184,16 @@ function insertStepRun(db, stepRun) {
176
184
  `).run(stepRun.id, stepRun.run_id, stepRun.step_id, stepRun.status, stepRun.output_json ?? null, stepRun.error ?? null, stepRun.started_at ?? null, stepRun.completed_at ?? null, stepRun.duration_ms ?? null);
177
185
  }
178
186
  function updateStepRunStatus(db, id, status, output, error) {
179
- const completedAt = status === "succeeded" || status === "failed" || status === "skipped" ? (/* @__PURE__ */ new Date()).toISOString() : null;
180
- db.prepare("UPDATE step_runs SET status = ?, output_json = ?, error = ?, completed_at = ? WHERE id = ?").run(status, output ?? null, error ?? null, completedAt, id);
187
+ const now = /* @__PURE__ */ new Date();
188
+ const completedAt = status === "succeeded" || status === "failed" || status === "skipped" ? now.toISOString() : null;
189
+ let durationMs = null;
190
+ if (completedAt) {
191
+ const row = db.prepare("SELECT started_at FROM step_runs WHERE id = ?").get(id);
192
+ if (row?.started_at) {
193
+ durationMs = now.getTime() - new Date(row.started_at).getTime();
194
+ }
195
+ }
196
+ db.prepare("UPDATE step_runs SET status = ?, output_json = ?, error = ?, completed_at = ?, duration_ms = ? WHERE id = ?").run(status, output ?? null, error ?? null, completedAt, durationMs, id);
181
197
  }
182
198
  function getStepRunsByRunId(db, runId) {
183
199
  return db.prepare("SELECT * FROM step_runs WHERE run_id = ?").all(runId);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  cueclawHome
3
- } from "./chunk-25KI643G.js";
3
+ } from "./chunk-5TV4LNC3.js";
4
4
  import {
5
5
  logger
6
6
  } from "./chunk-KBLMQZ3P.js";
package/dist/cli.js CHANGED
@@ -8,13 +8,13 @@ import {
8
8
  insertWorkflow,
9
9
  listWorkflows,
10
10
  updateWorkflowPhase
11
- } from "./chunk-JJUF2AJ5.js";
11
+ } from "./chunk-HKZ6IN7X.js";
12
12
  import {
13
13
  createDefaultConfig,
14
14
  cueclawHome,
15
15
  ensureCueclawHome,
16
16
  loadConfig
17
- } from "./chunk-25KI643G.js";
17
+ } from "./chunk-5TV4LNC3.js";
18
18
  import "./chunk-BVQG3WYO.js";
19
19
  import {
20
20
  loadSecrets
@@ -115,7 +115,7 @@ program.command("new").description("Create a new workflow from a natural languag
115
115
  try {
116
116
  const config = loadConfig();
117
117
  const { generatePlan, confirmPlan } = await import("./planner-MJ3XBCWH.js");
118
- const { executeWorkflow } = await import("./executor-TMY6MGVY.js");
118
+ const { executeWorkflow } = await import("./executor-44MSZ76T.js");
119
119
  logger.info({ description: description.slice(0, 100) }, "Starting plan generation");
120
120
  console.log("Planning workflow...");
121
121
  const workflow = await generatePlan(description, config);
@@ -295,7 +295,7 @@ program.command("resume").argument("<workflow-id>", "Workflow ID").description("
295
295
  updateWorkflowPhase(db, workflowId, "executing");
296
296
  logger.info({ workflowId }, "Resuming workflow execution");
297
297
  console.log(`Executing workflow "${wf.name}"...`);
298
- const { executeWorkflow } = await import("./executor-TMY6MGVY.js");
298
+ const { executeWorkflow } = await import("./executor-44MSZ76T.js");
299
299
  const result = await executeWorkflow({
300
300
  workflow: { ...wf, phase: "executing" },
301
301
  triggerData: null,
@@ -359,7 +359,7 @@ var daemonCmd = program.command("daemon").description("Manage background daemon"
359
359
  daemonCmd.command("start").option("--foreground", "Run in foreground (for system services)").description("Start the daemon (runs in background by default)").action(async (opts) => {
360
360
  if (opts.foreground) {
361
361
  try {
362
- const { startDaemon } = await import("./daemon-4DVXPT4O.js");
362
+ const { startDaemon } = await import("./daemon-Y5HIGH44.js");
363
363
  await startDaemon();
364
364
  } catch (err) {
365
365
  logger.error({ err }, "Daemon failed");
@@ -367,7 +367,7 @@ daemonCmd.command("start").option("--foreground", "Run in foreground (for system
367
367
  }
368
368
  return;
369
369
  }
370
- const { isDaemonRunning, spawnDaemonProcess } = await import("./daemon-4DVXPT4O.js");
370
+ const { isDaemonRunning, spawnDaemonProcess } = await import("./daemon-Y5HIGH44.js");
371
371
  if (isDaemonRunning()) {
372
372
  console.log("Daemon is already running.");
373
373
  return;
@@ -382,7 +382,7 @@ daemonCmd.command("start").option("--foreground", "Run in foreground (for system
382
382
  }
383
383
  });
384
384
  daemonCmd.command("stop").description("Stop the daemon").action(async () => {
385
- const { readPidFile, isProcessAlive, removePidFile } = await import("./daemon-4DVXPT4O.js");
385
+ const { readPidFile, isProcessAlive, removePidFile } = await import("./daemon-Y5HIGH44.js");
386
386
  const pid = readPidFile();
387
387
  if (pid && isProcessAlive(pid)) {
388
388
  process.kill(pid, "SIGTERM");
@@ -391,7 +391,7 @@ daemonCmd.command("stop").description("Stop the daemon").action(async () => {
391
391
  console.log(`Daemon stopped (PID ${pid}).`);
392
392
  return;
393
393
  }
394
- const { getServiceStatus, stopService } = await import("./service-AP5GEITC.js");
394
+ const { getServiceStatus, stopService } = await import("./service-Q7USNFFB.js");
395
395
  const status = getServiceStatus();
396
396
  if (status === "running") {
397
397
  const result = stopService();
@@ -409,14 +409,14 @@ daemonCmd.command("stop").description("Stop the daemon").action(async () => {
409
409
  console.log("Daemon is not running.");
410
410
  });
411
411
  daemonCmd.command("restart").description("Restart the daemon").action(async () => {
412
- const { readPidFile, isProcessAlive, removePidFile } = await import("./daemon-4DVXPT4O.js");
412
+ const { readPidFile, isProcessAlive, removePidFile } = await import("./daemon-Y5HIGH44.js");
413
413
  const pid = readPidFile();
414
414
  if (pid && isProcessAlive(pid)) {
415
415
  process.kill(pid, "SIGTERM");
416
416
  removePidFile();
417
417
  console.log(`Stopped daemon (PID ${pid}).`);
418
418
  } else {
419
- const { getServiceStatus, stopService } = await import("./service-AP5GEITC.js");
419
+ const { getServiceStatus, stopService } = await import("./service-Q7USNFFB.js");
420
420
  if (getServiceStatus() === "running") {
421
421
  const result = stopService();
422
422
  if (!result.success) {
@@ -426,7 +426,7 @@ daemonCmd.command("restart").description("Restart the daemon").action(async () =
426
426
  console.log("Stopped system service daemon.");
427
427
  }
428
428
  }
429
- const { spawnDaemonProcess } = await import("./daemon-4DVXPT4O.js");
429
+ const { spawnDaemonProcess } = await import("./daemon-Y5HIGH44.js");
430
430
  const newPid = spawnDaemonProcess();
431
431
  if (newPid) {
432
432
  console.log(`Daemon restarted in background (PID ${newPid})`);
@@ -436,7 +436,7 @@ daemonCmd.command("restart").description("Restart the daemon").action(async () =
436
436
  }
437
437
  });
438
438
  daemonCmd.command("install").description("Install system service").action(async () => {
439
- const { installService } = await import("./service-AP5GEITC.js");
439
+ const { installService } = await import("./service-Q7USNFFB.js");
440
440
  const result = installService();
441
441
  if (result.success) {
442
442
  logger.info("System service installed");
@@ -448,7 +448,7 @@ daemonCmd.command("install").description("Install system service").action(async
448
448
  }
449
449
  });
450
450
  daemonCmd.command("uninstall").description("Remove system service").action(async () => {
451
- const { uninstallService } = await import("./service-AP5GEITC.js");
451
+ const { uninstallService } = await import("./service-Q7USNFFB.js");
452
452
  const result = uninstallService();
453
453
  if (result.success) {
454
454
  logger.info("System service uninstalled");
@@ -460,13 +460,13 @@ daemonCmd.command("uninstall").description("Remove system service").action(async
460
460
  }
461
461
  });
462
462
  daemonCmd.command("status").description("View daemon status").action(async () => {
463
- const { readPidFile, isProcessAlive } = await import("./daemon-4DVXPT4O.js");
463
+ const { readPidFile, isProcessAlive } = await import("./daemon-Y5HIGH44.js");
464
464
  const pid = readPidFile();
465
465
  if (pid && isProcessAlive(pid)) {
466
466
  console.log(`Daemon status: running (PID ${pid})`);
467
467
  return;
468
468
  }
469
- const { getServiceStatus } = await import("./service-AP5GEITC.js");
469
+ const { getServiceStatus } = await import("./service-Q7USNFFB.js");
470
470
  const status = getServiceStatus();
471
471
  if (status === "running") {
472
472
  console.log(`Daemon status: running (system service)`);
@@ -476,7 +476,7 @@ daemonCmd.command("status").description("View daemon status").action(async () =>
476
476
  });
477
477
  daemonCmd.command("logs").description("View daemon logs").action(async () => {
478
478
  const { join } = await import("path");
479
- const { cueclawHome: cueclawHome2 } = await import("./config-D5A5TNLZ.js");
479
+ const { cueclawHome: cueclawHome2 } = await import("./config-FYL6T5JP.js");
480
480
  const logPath = join(cueclawHome2(), "logs", "daemon.log");
481
481
  const { existsSync } = await import("fs");
482
482
  if (!existsSync(logPath)) {
@@ -495,7 +495,7 @@ botCmd.command("status").description("View channel connection status").action(()
495
495
  program.command("setup").description("First-run setup: validate Docker, build container, smoke test").action(async () => {
496
496
  try {
497
497
  const config = loadConfig();
498
- const { runSetup } = await import("./setup-U2YKLOK6.js");
498
+ const { runSetup } = await import("./setup-JK664Y2M.js");
499
499
  await runSetup(config, process.cwd());
500
500
  } catch (err) {
501
501
  logger.error({ err }, "Setup failed");
@@ -508,7 +508,7 @@ program.command("tui").description("Start interactive TUI").option("--skip-onboa
508
508
  enableTuiLogging();
509
509
  const React = await import("react");
510
510
  const { render } = await import("ink");
511
- const { App } = await import("./app-JK3HBFKZ.js");
511
+ const { App } = await import("./app-6SXWEUZZ.js");
512
512
  render(React.createElement(App, { cwd: process.cwd(), skipOnboarding: opts.skipOnboarding }), { exitOnCtrlC: false });
513
513
  } catch (err) {
514
514
  logger.error({ err }, "Failed to start TUI");
@@ -522,7 +522,7 @@ program.option("--skip-onboarding", "Skip first-run onboarding wizard").action(a
522
522
  enableTuiLogging();
523
523
  const React = await import("react");
524
524
  const { render } = await import("ink");
525
- const { App } = await import("./app-JK3HBFKZ.js");
525
+ const { App } = await import("./app-6SXWEUZZ.js");
526
526
  render(React.createElement(App, { cwd: process.cwd(), skipOnboarding }), { exitOnCtrlC: false });
527
527
  } catch (err) {
528
528
  logger.error({ err }, "Failed to start TUI");
@@ -8,7 +8,7 @@ import {
8
8
  needsOnboarding,
9
9
  validateConfig,
10
10
  writeConfig
11
- } from "./chunk-25KI643G.js";
11
+ } from "./chunk-5TV4LNC3.js";
12
12
  import "./chunk-BVQG3WYO.js";
13
13
  import "./chunk-ZCK3IFLC.js";
14
14
  export {
@@ -7,12 +7,12 @@ import {
7
7
  spawnDaemonProcess,
8
8
  startDaemon,
9
9
  writePidFile
10
- } from "./chunk-HDUFGPCI.js";
10
+ } from "./chunk-54BGF7G5.js";
11
11
  import "./chunk-ZOFGQYXX.js";
12
12
  import "./chunk-DVQFSFIZ.js";
13
- import "./chunk-X3WNTN5V.js";
14
- import "./chunk-JJUF2AJ5.js";
15
- import "./chunk-25KI643G.js";
13
+ import "./chunk-3HV3MHME.js";
14
+ import "./chunk-HKZ6IN7X.js";
15
+ import "./chunk-5TV4LNC3.js";
16
16
  import "./chunk-BVQG3WYO.js";
17
17
  import "./chunk-ZCK3IFLC.js";
18
18
  import "./chunk-KBLMQZ3P.js";
@@ -2,9 +2,9 @@ import {
2
2
  executeWorkflow,
3
3
  resolveInputs,
4
4
  resolveValue
5
- } from "./chunk-X3WNTN5V.js";
6
- import "./chunk-JJUF2AJ5.js";
7
- import "./chunk-25KI643G.js";
5
+ } from "./chunk-3HV3MHME.js";
6
+ import "./chunk-HKZ6IN7X.js";
7
+ import "./chunk-5TV4LNC3.js";
8
8
  import "./chunk-BVQG3WYO.js";
9
9
  import "./chunk-ZCK3IFLC.js";
10
10
  import "./chunk-KBLMQZ3P.js";
@@ -3,8 +3,8 @@ import {
3
3
  installService,
4
4
  stopService,
5
5
  uninstallService
6
- } from "./chunk-CXBDJQJJ.js";
7
- import "./chunk-25KI643G.js";
6
+ } from "./chunk-MEAAX2SW.js";
7
+ import "./chunk-5TV4LNC3.js";
8
8
  import "./chunk-BVQG3WYO.js";
9
9
  import "./chunk-ZCK3IFLC.js";
10
10
  import "./chunk-KBLMQZ3P.js";
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  checkEnvironment,
3
3
  validateAuth
4
- } from "./chunk-KRNAXOQ4.js";
4
+ } from "./chunk-FKKDQVRE.js";
5
5
  import "./chunk-DVQFSFIZ.js";
6
6
  import {
7
7
  getDefaultImage
8
- } from "./chunk-25KI643G.js";
8
+ } from "./chunk-5TV4LNC3.js";
9
9
  import "./chunk-BVQG3WYO.js";
10
10
  import "./chunk-ZCK3IFLC.js";
11
11
  import {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cueclaw",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Orchestrate agent workflows with natural language. Natural language in, executable DAG out.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -44,7 +44,7 @@
44
44
  "commit-msg": "pnpm commitlint --edit $1"
45
45
  },
46
46
  "dependencies": {
47
- "@anthropic-ai/claude-agent-sdk": "^0.2.56",
47
+ "@anthropic-ai/claude-agent-sdk": "^0.2.63",
48
48
  "@anthropic-ai/sdk": "^0.78.0",
49
49
  "@inkjs/ui": "^2.0.0",
50
50
  "@whiskeysockets/baileys": "7.0.0-rc.9",
@@ -52,7 +52,7 @@
52
52
  "commander": "^14.0.3",
53
53
  "cron-parser": "^5.5.0",
54
54
  "dotenv": "^17.3.1",
55
- "grammy": "^1.40.0",
55
+ "grammy": "^1.40.1",
56
56
  "ink": "5",
57
57
  "ink-spinner": "^5.0.0",
58
58
  "ink-text-input": "^6.0.0",
@@ -70,7 +70,7 @@
70
70
  "@commitlint/config-conventional": "^20.4.2",
71
71
  "@eslint/js": "^10.0.1",
72
72
  "@types/better-sqlite3": "^7.6.13",
73
- "@types/node": "^25.3.0",
73
+ "@types/node": "^25.3.2",
74
74
  "@types/react": "^19.2.14",
75
75
  "@vitest/coverage-v8": "^4.0.18",
76
76
  "eslint": "^10.0.2",