openclaw-scheduler 0.2.10 → 0.2.12

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/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.2.12] -- 2026-06-23
6
+
7
+ ### Changed
8
+ - docs(dispatch): document that `dispatch done` must run from the originating local dispatch shell and that terminal-output watcher fallback requires strict clean completion evidence
9
+
10
+ ## [0.2.11] -- 2026-06-23
11
+
12
+ ### Fixed
13
+ - fix(dispatch): clarify that completion markers must run in the originating local dispatch shell, and allow watcher delivery from clean terminal `stop_reason=end_turn` replies without broadening plain `lastReply` success detection
14
+ - fix(watcher): use the fatal idle threshold for stalled-session errors while keeping quiet high-thinking sessions pending at the probe threshold
15
+
5
16
  ## [0.2.5] -- 2026-04-27
6
17
 
7
18
  ### Fixed
package/README.md CHANGED
@@ -1728,7 +1728,7 @@ npm test
1728
1728
 
1729
1729
  ## Sub-agent Dispatch
1730
1730
 
1731
- The dispatch module (`dispatch/index.mjs`) spawns and steers isolated agent sessions via the OpenClaw Gateway API and tracks them by a human-readable label. Unlike the scheduler's job/run model, dispatch calls the gateway directly -- no scheduler tick delay, no DB write required to start a session. Each session is assigned a unique session key, recorded in a local `labels.json` ledger, and delivered back through a scheduler watcher when the agent calls `done` as its final action. That watcher is the only final-delivery path for dispatched jobs, and it normalizes completions into short human-readable summaries before posting them to chat. The module also supports symlink-based branding: a wrapper directory (such as `my-brand`) contains a `config.json` with a custom name and a symlink to `dispatch/index.mjs`, giving the same CLI a different identity in notifications and logs.
1731
+ The dispatch module (`dispatch/index.mjs`) spawns and steers isolated agent sessions via the OpenClaw Gateway API and tracks them by a human-readable label. Unlike the scheduler's job/run model, dispatch calls the gateway directly -- no scheduler tick delay, no DB write required to start a session. Each session is assigned a unique session key, recorded in the originating host's local `labels.json` ledger, and delivered back through a scheduler watcher when the agent calls `done` from that same local dispatch shell as its final action. Do not run the completion marker from inside `ssh`, Docker, tmux, or another nested shell, because that can update a different label store. The watcher normalizes completions into short human-readable summaries before posting them to chat, and it only falls back to terminal assistant output when the transcript contains strict clean completion evidence. The module also supports symlink-based branding: a wrapper directory (such as `my-brand`) contains a `config.json` with a custom name and a symlink to `dispatch/index.mjs`, giving the same CLI a different identity in notifications and logs.
1732
1732
 
1733
1733
  ### Quick Example
1734
1734
 
@@ -1785,7 +1785,7 @@ For normal chat-triggered dispatches, always pass `--deliver-to` from the inboun
1785
1785
  | `stuck` | Check all running sessions against the stuck threshold. Exits 1 if genuinely stuck sessions remain after auto-resolving completed ones. |
1786
1786
  | `result` | Retrieve the last assistant reply from a session transcript via `chat.history`. |
1787
1787
  | `sync` | Reconcile `labels.json` with sessions store state. Auto-marks sessions as done or error based on idle time. Supports `--dry-run`. |
1788
- | `done` | Agent-side completion signal. The agent calls this as its final action to mark itself done immediately (push-based; no idle timeout wait). The stored completion payload is normalized for channel-safe delivery, with checklist/sha metadata used as a fallback when the raw summary is generic or noisy. |
1788
+ | `done` | Agent-side completion signal. The agent calls this as its final action from the originating local dispatch shell to mark itself done immediately (push-based; no idle timeout wait). Do not call it from inside a remote or nested shell, because the label lookup is local to the dispatch host. The stored completion payload is normalized for channel-safe delivery, with checklist/sha metadata used as a fallback when the raw summary is generic or noisy. |
1789
1789
  | `send` | Inject a message into a running session for mid-run steering. The agent sees it as a new user turn. |
1790
1790
  | `steer` | Alias for `send`. The name makes steering intent explicit. |
1791
1791
  | `heartbeat` | Check whether a session has been active within the last 10 minutes. Accepts `--label` or `--session-key`. |
@@ -139,7 +139,10 @@ node dispatch/index.mjs done \
139
139
  ```
140
140
 
141
141
  Marks the label as `done` immediately so the watcher can resolve the run without
142
- waiting for timeout polling.
142
+ waiting for timeout polling. Run this command from the same local dispatch shell
143
+ that created the label. Do not run it from inside `ssh`, Docker, tmux, or another
144
+ nested shell unless that shell intentionally points at the originating dispatch
145
+ host and `labels.json`; otherwise it can update a different label store.
143
146
 
144
147
  | Flag | Default | Description |
145
148
  |---|---|---|
@@ -291,8 +294,11 @@ No manual token configuration needed on a standard OpenClaw install.
291
294
 
292
295
  When `--deliver-to` is set, dispatch registers a **scheduler watcher job**
293
296
  after dispatching the session. The watcher polls the session result every
294
- minute until the agent sends the structured `done` completion signal, then
295
- delivers via the scheduler's `handleDelivery` pipeline.
297
+ minute until the agent sends the structured local `done` completion signal, then
298
+ delivers via the scheduler's `handleDelivery` pipeline. If the structured signal
299
+ is missed but the transcript has strict clean terminal completion evidence, the
300
+ watcher may deliver that terminal assistant report; arbitrary mid-task replies
301
+ remain diagnostics.
296
302
 
297
303
  ```
298
304
  dispatch enqueue --deliver-to <telegram-user-id>
@@ -325,10 +331,12 @@ gateway/session liveness fails open to "still monitoring" until the hard timeout
325
331
  window or a clear terminal error.
326
332
 
327
333
  While a label is still `running`, a plain assistant reply is diagnostic only.
328
- Successful final delivery requires the agent-side `done` signal and its
329
- structured completion payload. If an older watcher records an error and the
330
- worker later sends a valid `done`, the later completion is authoritative and the
331
- stale error is cleared from the label.
334
+ Successful final delivery prefers the agent-side local `done` signal and its
335
+ structured completion payload. The terminal-assistant fallback is intentionally
336
+ narrow: it requires clean completion evidence from the transcript, not just the
337
+ latest assistant text. If an older watcher records an error and the worker later
338
+ sends a valid local `done`, the later completion is authoritative and the stale
339
+ error is cleared from the label.
332
340
 
333
341
  ### Progress check-ins from subagent sessions
334
342
 
@@ -1134,6 +1134,7 @@ export function buildCompletionSignalInstructions({ label, taskPrompt, doneScrip
1134
1134
  readinessChecks.forEach((line, idx) => lines.push(` ${idx + 1}. ${line}`));
1135
1135
  lines.push('');
1136
1136
  lines.push('Call this as your ABSOLUTE FINAL action -- nothing else runs after this:');
1137
+ lines.push(' IMPORTANT: run this command in the dispatch session shell on this host. Do not run it inside ssh, docker, tmux, or any remote shell; remote label stores cannot mark this dispatch complete.');
1137
1138
  lines.push(` node '${doneScriptPath}' done --label '${escapedLabel}' \\`);
1138
1139
  lines.push(' --summary "<human-readable summary of what you actually did>" \\');
1139
1140
  lines.push(` --checklist '${checklistExample}' \\`);
@@ -1052,6 +1052,22 @@ function hasStructuredCompletion(result) {
1052
1052
  return hasCompletionSignal(result?.completion);
1053
1053
  }
1054
1054
 
1055
+ function getCleanTerminalReply(status) {
1056
+ if (!status?.sessionKey) return null;
1057
+ const entry = getSessionStoreEntry(status.sessionKey);
1058
+ const sessionId = entry?.sessionId || null;
1059
+ const sessionAgent = status.sessionKey.split(':')[1] || 'main';
1060
+ const terminalJsonlReply = sessionId ? getSessionTerminalReply(sessionId, sessionAgent) : null;
1061
+ if (!sessionId || !terminalJsonlReply) return null;
1062
+ return isSessionCleanlyFinished(sessionId, sessionAgent) ? terminalJsonlReply : null;
1063
+ }
1064
+
1065
+ function getStrictTerminalReply(result, status) {
1066
+ const terminalJsonlReply = getCleanTerminalReply(status);
1067
+ if (!terminalJsonlReply) return null;
1068
+ return result?.lastReply || terminalJsonlReply;
1069
+ }
1070
+
1055
1071
  if (!label) {
1056
1072
  process.stderr.write('[watcher] --label is required\n');
1057
1073
  process.exit(2);
@@ -1159,16 +1175,13 @@ function runOnceAndExit() {
1159
1175
  }
1160
1176
 
1161
1177
  if (status.sessionKey) {
1162
- const entry = getSessionStoreEntry(status.sessionKey);
1163
- const sessionId = entry?.sessionId || null;
1164
- const sessionAgent = status.sessionKey.split(':')[1] || 'main';
1165
- const terminalJsonlReply = sessionId ? getSessionTerminalReply(sessionId, sessionAgent) : null;
1166
- if (sessionId && terminalJsonlReply && isSessionCleanlyFinished(sessionId, sessionAgent)) {
1178
+ const terminalJsonlReply = getCleanTerminalReply(status);
1179
+ if (terminalJsonlReply) {
1167
1180
  const result = dispatch('result', ['--label', label]);
1168
1181
  if (hasStructuredCompletion(result)) {
1169
1182
  deliverResult(label, result?.lastReply || terminalJsonlReply, 'completed (stop_reason=end_turn)', result?.completion || null);
1170
1183
  }
1171
- process.stderr.write(`[watcher] stop_reason=end_turn observed without completion signal -- continuing to monitor\n`);
1184
+ deliverResult(label, terminalJsonlReply, 'completed (stop_reason=end_turn)', null);
1172
1185
  }
1173
1186
  }
1174
1187
 
@@ -1181,6 +1194,10 @@ function runOnceAndExit() {
1181
1194
  if (hasStructuredCompletion(result)) {
1182
1195
  deliverResult(label, result?.lastReply || null, null, result?.completion || null);
1183
1196
  }
1197
+ const terminalReply = getStrictTerminalReply(result, status);
1198
+ if (terminalReply) {
1199
+ deliverResult(label, terminalReply, 'completed (stop_reason=end_turn)', null);
1200
+ }
1184
1201
 
1185
1202
  const stallReason = ageMs >= idleFailureMs
1186
1203
  ? getRunningSessionStallReason(status, idleFailureMs)
@@ -1550,7 +1567,7 @@ while (Date.now() < deadline) {
1550
1567
  deliverResult(label, result?.lastReply || terminalJsonlReply, 'completed (stop_reason=end_turn)', result?.completion || null);
1551
1568
  // deliverResult exits
1552
1569
  }
1553
- process.stderr.write(`[watcher] stop_reason=end_turn observed without completion signal -- continuing to monitor\n`);
1570
+ deliverResult(label, terminalJsonlReply, 'completed (stop_reason=end_turn)', null);
1554
1571
  }
1555
1572
  }
1556
1573
 
@@ -1569,6 +1586,10 @@ while (Date.now() < deadline) {
1569
1586
  if (hasStructuredCompletion(result)) {
1570
1587
  deliverResult(label, result?.lastReply || null, null, result?.completion || null);
1571
1588
  }
1589
+ const terminalReply = getStrictTerminalReply(result, status);
1590
+ if (terminalReply) {
1591
+ deliverResult(label, terminalReply, 'completed (stop_reason=end_turn)', null);
1592
+ }
1572
1593
 
1573
1594
  const stallReason = ageMs >= idleFailureMs
1574
1595
  ? getRunningSessionStallReason(status, idleFailureMs)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-scheduler",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
4
4
  "description": "SQLite-backed job scheduler and workflow engine for OpenClaw agents",
5
5
  "type": "module",
6
6
  "main": "./index.js",