Skip to content

fix(cron): capture originating session/agent on the cron wake tool call#83738

Merged
shakkernerd merged 7 commits into
openclaw:mainfrom
anagnorisis2peripeteia:feat/cron-wake-origin-capture
Jun 10, 2026
Merged

fix(cron): capture originating session/agent on the cron wake tool call#83738
shakkernerd merged 7 commits into
openclaw:mainfrom
anagnorisis2peripeteia:feat/cron-wake-origin-capture

Conversation

@anagnorisis2peripeteia

@anagnorisis2peripeteia anagnorisis2peripeteia commented May 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Problem. The cron wake MCP tool forwards only {mode, text} to the gateway, so every wake enqueues against the heartbeat/main default. Wakes scheduled from a non-main session (Telegram topic, Discord thread, multi-agent setup) silently route to the wrong conversation lane — the event text never returns to the conversation that scheduled it. Closes the tool-path half of cron wake action does not support agentId — always routes to default agent #46886 / [Bug]: hooks.mappings[].agentId and sessionKey silently ignored for action="wake" #64556.
  • What changed.
    • cron-tool.ts wake action resolves the calling agent's sessionKey/agentId via the existing resolveInternalSessionKey / resolveSessionAgentId pattern (mirrors the add action). Explicit tool params take precedence; an explicit cross-agent sessionKey derives its agentId from the key itself (parseAgentSessionKey) so the caller's agentId is never paired with a foreign session.
    • WakeParamsSchema (+ regenerated Swift protocol client), gateway wake handler, cron service contract, and wake() thread the optional sessionKey/agentId through to enqueueSystemEvent and the heartbeat request.
    • wake() carries the originating session's stored channel delivery context (e.g. the bound Telegram message_thread_id) onto the system event via an optional resolveOriginDeliveryContext dep, implemented in server-cron.ts from the session store (not by string-splitting the session key) — a wake-now heartbeat replies in the originating topic, not the chat root. No-origin wakes keep the exact pre-fix enqueueSystemEvent(text, undefined) shape.
  • AI-assisted: yes — drafted with Claude.

Rebase onto current main (this push)

  • Rebased onto current main. Main has since absorbed the scheduled-job half of this design natively: sessionTarget:"main" cron jobs now run through the dedicated agent:<id>:cron:<job>:run:<ts> lane and borrow the target session's delivery context (resolveMainSessionCronDeliveryContext). This PR therefore no longer touches executeMainSessionCronJob/the run lane at all — it layers the same origin-capture idea onto the manual wake path only, which main still lacks. (This resolves the earlier ClawSweeper [P1] "Preserve main cron run lanes" by construction.)
  • wake() moved to src/cron/service/wake.ts upstream; the change follows it there.
  • Swift gateway protocol client regenerated (pnpm protocol:gen && pnpm protocol:gen:swift) for the new WakeParams.agentId field — resolves the earlier [P2].

Real behavior proof

  • Behavior or issue addressed: A cron wake scheduled from a non-main Telegram forum-topic session must enqueue against the originating session, and the wake-triggered heartbeat turn must deliver its reply back into that same topic instead of the heartbeat/main default (tool-path half of cron wake action does not support agentId — always routes to default agent #46886 / [Bug]: hooks.mappings[].agentId and sessionKey silently ignored for action="wake" #64556).
  • Real environment tested: Real OpenClaw gateway built from this head (pnpm build:docker), real Telegram Bot API (dedicated test bot) and a real Telegram Desktop client driven by the tdlib user driver, running in a Linux container; claude-cli (claude-sonnet-4-6) agent backend; BEFORE (57633c42b, current main) and AFTER (this branch) builds captured with identical config including agents.defaults.heartbeat: {"every":"1h","target":"last"}. This recreates the Mantis telegram-desktop-proof flow (telegram-desktop + user-driver + ffmpeg screen recording).
  • Exact steps or command run after this patch: In a fresh forum topic, the driver account sends: "I am testing my cron wake routing in this topic. Please schedule a wake for right now using your cron tool (action wake, mode now) with text: You were woken by a cron wake. Run date -u with your exec tool and reply with exactly PROOF-83738 wake turn ran at followed by the command output. After your wake tool call returns, reply only: scheduled." — recorded by ffmpeg with the gateway at debug log level.
  • Evidence after fix: screen recording after-83738v2-small.gif and tool-bearing wake-turn recording after-83738v3-small.gif; screenshots after v2 still, after v3 still; raw mp4s v2, v3; redacted runtime logs gw-after v2, gw-after v3.
  • Observed result after fix: the turn reply "scheduled." lands in the topic (outbound send ok … threadId=74 messageId=76); the wake fires an immediate heartbeat turn ON the topic session (trigger=heartbeat … historyPrompt=present); that second agent turn runs date -u via the exec tool and its reply — "PROOF-83738 wake turn ran at Wed Jun 10 04:43:21 UTC 2026" — is delivered into the SAME topic (threadId=74 messageId=77). Live command output in the reply demonstrates a real freshly-executed agent turn, not a delayed canned message.
  • Before evidence (optional): before-83738v2-small.gif, before still, before mp4, gw-before log — same scenario on current main: "scheduled." arrives, then the wake-triggered heartbeat runs detached (historyPrompt=none) and its reply is never delivered to the topic or the chat root.
  • What was not tested: Discord/Matrix live wake routing (the deliveryContext path is channel-generic; pinned by wake-origin-delivery.test.ts); live cross-agent wakes (the agentId-derivation contract is pinned by the cron-tool wake-routing unit tests).

Current-head proof — no heartbeat target configured (the a53fddb9f59 fallback)

Driven on the exact PR head with no agents.defaults.heartbeat block at all, via the same wake RPC Mantis uses (system event --mode now --session-key <topic>). The wake-triggered turn runs date -u and its reply lands back in the originating forum topic — proving the new no-heartbeat-target fallback:

no-target wake reply in topic

Reply captured live: "PROOF-83738 no-target wake ran at Wed Jun 10 13:14:33 UTC 2026" (full still · mp4). Gateway debug log (gw-notarget.log) shows the chain with no heartbeat target set:

[telegram] Inbound …topic:130 …                                     <- seed turn
[agent/cli-backend] cli exec … trigger=user …
[telegram] outbound send ok … threadId=130                          <- seed reply "ready"
[agent/cli-backend] cli exec … trigger=heartbeat … historyPrompt=present   <- wake turn ran ON the topic session
[telegram] outbound send ok … messageId=133 … threadId=130          <- wake reply, SAME topic

target: "none" still suppresses (gw-tnone.log): with heartbeat.target: "none", the same wake fires the heartbeat turn (trigger=heartbeat, outBytes=12) but produces zero outbound send — operator opt-out preserved.

Proof detail — Mantis-flow recreation

This is a recreation of the Mantis telegram-desktop-proof flow using the patched local crabbox (same mechanism: real Telegram Desktop client + tdlib user driver + ffmpeg screen capture in a Linux container; both builds, same config incl. agents.defaults.heartbeat: {"every":"1h","target":"last"}, bot + group ids are test-only). Scenario: a user in a forum topic asks the agent to schedule cron wake (mode "now"); the wake text instructs the heartbeat to reply in-thread.

BEFORE (current main, 57633c42b) AFTER (this branch, 880d10a34)
turn reply ("scheduled.") ✅ in topic ✅ in topic
wake-triggered heartbeat reply never delivered anywhere (not topic, not chat root) "PROOF-83738 wake delivered." lands back in the same topic
BEFORE AFTER
before after

The wake starts a real second agent TURN, not a delayed canned reply — in this capture the wake text instructs the woken turn to run date -u via the exec tool, and the in-topic reply embeds the live command output ("PROOF-83738 wake turn ran at Wed Jun 10 04:43:21 UTC 2026"), which only a freshly-executed agent turn can produce:

wake turn runs a tool

(full still · mp4 · gateway debug log)

Full-window stills: before / after · raw mp4s: before / after · redacted gateway logs: before / after

Key gateway-log lines (AFTER build, debug level):

[telegram] Inbound message telegram:group:-100…:topic:74 -> @…2bot (group, 227 chars)
[agent/cli-backend] cli exec: … trigger=user …
[telegram] outbound send ok … messageId=76 … threadId=74            <- turn reply "scheduled."
[agent/cli-backend] cli exec: … trigger=heartbeat … historyPrompt=present   <- the wake fired, ON the topic session
[telegram] outbound send ok … messageId=77 … threadId=74            <- wake reply, SAME topic

Same scenario on BEFORE: the heartbeat fires with historyPrompt=none on a detached lane and no outbound send ever follows — the wake is lost.

A deterministic harness driving the real gateway handler + cron service with logging deps is included (pnpm exec tsx scripts/proof-cron-wake-origin.mts): origin routing, backwards-compatible default routing, the next-heartbeat targeted-immediate collapse, the subagent guard, and whitespace fall-through.

Mutation testing (focused Stryker on the PR-touched code)

  • src/cron/service/wake.ts: 51/51 mutants killed (100%). Two initial survivors exposed real test gaps (agentId-only wakes; sessionKey-only delivery-context resolution) — both now pinned by dedicated tests.
  • src/agents/tools/cron-tool.ts wake arm (L840–893): 26/31 killed (83.9%). All 5 survivors are test-harness equivalents, not behavior gaps: the suite vi.mocks resolveSessionAgentId to a constant (arg mutations invisible), the mode fallback equals the compared literal, and {expectFinal:false} is invisible to the gateway mock. The killable gaps found on the way (wake-without-text guard, context-less callers, explicit next-heartbeat mode) got tests.

Tests / CI

  • New/updated: wake-origin.test.ts, wake-origin-delivery.test.ts, cron-tool wake-routing suite (10 cases), validateWakeParams schema cases. All wake/cron/protocol suites green on Windows and Linux.
  • Full Linux test suite run in the proof container on this head: unit config fully green (1502/1502); extensions + full vitest green except extensions/bonjour/advertiser.test.ts flaking under parallel load (passes in isolation) and src/agents/compaction-planning-worker.test.ts (packaged-worker test, fails identically on unmodified upstream main in the same container — pre-existing environmental). src/gateway/server-cron.test.ts 46/46 green.
  • Windows prepush mirror: pnpm check (lint, typecheck prod), build:strict-smoke, protocol-gen mirror, plugins assets all green; the 11 extension test files that fail locally on Windows are pre-existing path-separator environment failures (all 191 tests in those files pass on Linux on this head, unchanged by this PR).

@openclaw-barnacle openclaw-barnacle Bot added app: web-ui App: web-ui gateway Gateway runtime agents Agent runtime and tooling size: M proof: supplied External PR includes structured after-fix real behavior proof. labels May 18, 2026
@clawsweeper

clawsweeper Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 10, 2026, 3:49 PM ET / 19:49 UTC.

Summary
The branch routes manual cron wakes through the originating agent and session, carries stored channel/thread delivery context into the wake event, rejects contradictory target identities, and delivers origin-carrying heartbeat replies when no heartbeat target is configured.

PR surface: Source +187, Tests +529, Other +146. Total +862 across 24 files.

Reproducibility: yes. The inspected real Telegram before recording reproduces the missing wake reply on current main, and the after recordings show a freshly executed wake turn returning to the same topic.

Review metrics: 3 noteworthy metrics.

  • Gateway wake parameters: 1 added. The protocol adds agentId alongside the existing optional sessionKey, affecting generated clients and direct RPC callers.
  • Shared delivery default: 1 changed. Absent heartbeat target configuration can now result in delivery when the drained event carries a valid origin.
  • RPC validation rule: 1 added. Contradictory explicit agent and agent-prefixed session identities now fail instead of being silently canonicalized.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster ✨ media proof bonus
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Obtain explicit maintainer approval for absent-target origin delivery and contradictory RPC identity rejection.

Risk before merge

  • [P1] Existing deployments with no heartbeat target configured can begin delivering replies for any heartbeat run carrying a valid turnSource; this is intentional in the patch but broader than cron wake protocol plumbing.
  • [P1] Direct wake RPC callers that previously supplied contradictory agentId and agent-prefixed sessionKey values will now receive INVALID_REQUEST rather than silent target canonicalization.
  • [P1] The branch changes session selection and outbound destination together, so a regression could resume the wrong transcript or send a wake reply to the wrong channel or thread despite passing isolated unit tests.

Maintainer options:

  1. Approve the documented behavior (recommended)
    Accept the shared origin-first absent-target rule and stricter identity validation as deliberate compatibility changes, then land the exact reviewed head.
  2. Narrow absent-target delivery
    Carry a closed wake-origin signal and permit absent-target delivery only for that event class if the shared turnSource rule is too broad.
  3. Preserve permissive RPC compatibility
    Canonicalize or derive contradictory target identities instead of rejecting them if existing external wake clients must remain permissive.

Next step before merge

  • No automated repair is needed; a maintainer should approve or reject the compatibility-sensitive absent-target origin-delivery and contradictory-identity rules, then land the exact head if accepted.

Security
Cleared: The exact diff introduces no concrete dependency, workflow, permission, secret-handling, package-resolution, artifact-download, or other supply-chain concern.

Review details

Best possible solution:

Keep the origin-aware wake routing and stored delivery-context design, but merge only after maintainers explicitly approve the shared origin-first behavior for absent heartbeat targets and the stricter contradictory-identity RPC contract.

Do we have a high-confidence way to reproduce the issue?

Yes. The inspected real Telegram before recording reproduces the missing wake reply on current main, and the after recordings show a freshly executed wake turn returning to the same topic.

Is this the best way to solve the issue?

Yes for the core bug: capturing the origin at the cron tool boundary and resolving transport context from the session store is the narrowest maintainable design. Maintainer judgment is still required for the shared absent-target fallback and stricter direct-RPC validation policy.

AGENTS.md: found and applied where relevant.

Codex review notes: reasoning high; reviewed against e6b0a22f36cd.

Label changes

Label changes:

  • add proof: sufficient: Contributor real behavior proof is sufficient. The inspected real Telegram Desktop recordings show the current-main failure, a live tool-backed wake turn delivered in the same topic after the fix, and the no-heartbeat-target final behavior.

Label justifications:

  • P2: The PR fixes a real, reproduced wrong-session and missing-reply bug with limited cron wake scope, while the compatibility choices warrant normal maintainer review.
  • merge-risk: 🚨 compatibility: The patch changes absent-heartbeat-target defaults and rejects an input combination that direct wake clients previously passed through.
  • merge-risk: 🚨 message-delivery: The patch changes whether wake-triggered replies are suppressed and which channel or thread receives them.
  • merge-risk: 🚨 session-state: The newly captured origin controls which agent session and transcript the heartbeat turn resumes.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (recording): The inspected real Telegram Desktop recordings show the current-main failure, a live tool-backed wake turn delivered in the same topic after the fix, and the no-heartbeat-target final behavior.
  • proof: sufficient: Contributor real behavior proof is sufficient. The inspected real Telegram Desktop recordings show the current-main failure, a live tool-backed wake turn delivered in the same topic after the fix, and the no-heartbeat-target final behavior.
  • proof: 🎥 video: Contributor real behavior proof includes video or recording evidence. The inspected real Telegram Desktop recordings show the current-main failure, a live tool-backed wake turn delivered in the same topic after the fix, and the no-heartbeat-target final behavior.
  • mantis: telegram-visible-proof: Mantis should capture Telegram visible proof. The central user-visible behavior is a wake-triggered reply returning to the originating Telegram forum topic, which the supplied native recording demonstrates clearly.
Evidence reviewed

PR surface:

Source +187, Tests +529, Other +146. Total +862 across 24 files.

View PR surface stats
Area Files Added Removed Net
Source 10 195 8 +187
Tests 12 547 18 +529
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 2 147 1 +146
Total 24 889 27 +862

What I checked:

  • Current-main bug path: Current main's cron wake tool sends only mode and text, so the wake RPC does not receive the calling session or agent identity. (src/agents/tools/cron-tool.ts:843, e6b0a22f36cd)
  • Origin-aware tool routing: The final branch resolves or accepts the wake sessionKey and agentId, derives ownership for explicit cross-agent session keys, and forwards both through the gateway call. (src/agents/tools/cron-tool.ts:843, 0e3687c4ef11)
  • Wake service ownership: The wake service now attaches the resolved session, agent, and stored origin delivery context to the system event and targeted heartbeat request while preserving the untargeted enqueue shape. (src/cron/service/wake.ts:6, 0e3687c4ef11)
  • Stored channel context resolution: Gateway cron construction resolves the target through the canonical cron target helper and reads the channel-correct delivery context from the session store rather than parsing transport details from the session key. (src/gateway/server-cron.ts:329, 0e3687c4ef11)
  • Shared delivery policy change: When heartbeat target is absent, the shared outbound resolver now delivers to a valid turnSource origin; explicit target none and runs without an origin remain suppressed. (src/infra/outbound/targets.ts:114, 0e3687c4ef11)
  • Real Telegram before and after proof: The inspected recordings show current main returning only the initial scheduled response, while the branch produces a second freshly executed, tool-backed wake reply in the same Telegram forum topic. (0e3687c4ef11)

Likely related people:

  • Ayaan Zaidi: Current-main blame ties the recently refactored cron wake helper and adjacent heartbeat delivery invariants to this contributor's June 10 work, making them a strong routing candidate for session and delivery semantics. (role: recent area contributor; confidence: high; commits: 3078a8335d58; files: src/cron/service/wake.ts, src/infra/outbound/targets.ts)
  • martingarramon: The PR discussion identifies this reviewer as having already reviewed the core wake-origin bridge before the stored delivery-context follow-up, so they have relevant prior context for the final behavior. (role: reviewer; confidence: medium; commits: bd6705fa79e3; files: src/cron/service/wake.ts, src/gateway/server-cron.ts, src/infra/outbound/targets.ts)
  • shakkernerd: The live timeline shows assignment and two final force-pushes on June 10, indicating direct involvement in rebasing or preparing the exact branch for maintainer review. (role: recent branch integrator; confidence: medium; commits: 0e3687c4ef11; files: src/agents/tools/cron-tool.ts, src/cron/service/wake.ts, src/infra/outbound/targets.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. mantis: telegram-visible-proof Mantis should capture Telegram visible proof. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. labels May 18, 2026
@clawsweeper clawsweeper Bot temporarily deployed to qa-live-shared May 18, 2026 19:56 Inactive
@openclaw-barnacle openclaw-barnacle Bot added triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. and removed proof: supplied External PR includes structured after-fix real behavior proof. labels May 18, 2026
@anagnorisis2peripeteia anagnorisis2peripeteia force-pushed the feat/cron-wake-origin-capture branch 2 times, most recently from fbca52b to 7d13307 Compare May 19, 2026 06:02
@anagnorisis2peripeteia

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@anagnorisis2peripeteia

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

anagnorisis2peripeteia added a commit to anagnorisis2peripeteia/openclaw that referenced this pull request May 19, 2026
…outing

Drives the patched gateway wake handler (validateWakeParams +
isSubagentSessionKey guard) through cron.wake() / timer.ts with logging
deps. Captures stdout demonstrating that a wake fired from a non-main
session targets the originating sessionKey + agentId on both
enqueueSystemEvent and the immediate-heartbeat nudge, instead of falling
through to the heartbeat/main default.

Five scenarios:
  1. non-main session + non-default agent (the bug-fix case)
  2. no origin -> backwards-compatible default routing
  3. next-heartbeat + sessionKey -> targeted-immediate collapse
  4. subagent sessionKey rejected by gateway handler guard
  5. whitespace-only origin -> defence-in-depth default fallthrough

All ids in the script are synthetic — no real chat identifiers are
referenced. Run with: pnpm exec tsx scripts/proof-cron-wake-origin.mts
@openclaw-barnacle openclaw-barnacle Bot added scripts Repository scripts proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels May 19, 2026

@martingarramon martingarramon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The threading is complete and consistent. The wake case at cron-tool.ts:837 uses resolveMainSessionAlias + resolveInternalSessionKey with the same resolution pattern already present at :464 (add) and :674 (list). Explicit params on the tool call (sessionKey, agentId) take precedence over inferred values from opts.agentSessionKey — that's the right priority.

The backward-compat guard in timer.ts is important and correct: when neither sessionKey nor agentId is set, the function passes no opts at all, preserving the pre-fix default-session binding in enqueueSystemEvent. This means existing wakes that don't supply session context continue to work without change.

Pipeline trace confirmed on origin/main:

  • cron-tool.ts resolves and passes { sessionKey, agentId } to the gateway
  • gateway/server-methods/cron.ts threads both through to cron.wake
  • service-contract.ts WakeParamsSchema accepts both as optional
  • service/ops.ts wakeNow accepts agentId (was missing before this PR)
  • service/timer.ts forwards to enqueueSystemEvent with the backward-compat guard

@anagnorisis2peripeteia anagnorisis2peripeteia force-pushed the feat/cron-wake-origin-capture branch from bd6705f to fdf835d Compare May 19, 2026 16:31
anagnorisis2peripeteia added a commit to anagnorisis2peripeteia/openclaw that referenced this pull request May 19, 2026
…outing

Drives the patched gateway wake handler (validateWakeParams +
isSubagentSessionKey guard) through cron.wake() / timer.ts with logging
deps. Captures stdout demonstrating that a wake fired from a non-main
session targets the originating sessionKey + agentId on both
enqueueSystemEvent and the immediate-heartbeat nudge, instead of falling
through to the heartbeat/main default.

Five scenarios:
  1. non-main session + non-default agent (the bug-fix case)
  2. no origin -> backwards-compatible default routing
  3. next-heartbeat + sessionKey -> targeted-immediate collapse
  4. subagent sessionKey rejected by gateway handler guard
  5. whitespace-only origin -> defence-in-depth default fallthrough

All ids in the script are synthetic — no real chat identifiers are
referenced. Run with: pnpm exec tsx scripts/proof-cron-wake-origin.mts
@anagnorisis2peripeteia

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@anagnorisis2peripeteia

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Command router queued. I will update this comment with the next step.

Re-review progress:

@anagnorisis2peripeteia

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Pushed ce24d841bbc addressing the [P1]: the contradictory agentId/agent-prefixed-sessionKey guard now also lives at the gateway wake handler, so direct RPC callers and generated clients get an INVALID_REQUEST instead of a silently re-targeted wake. Pinned by two new handler tests; cron validation suite 78/78 and cron-tool suite 110/110 green, tsgo clean.

@clawsweeper

clawsweeper Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@anagnorisis2peripeteia

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Command router queued. I will update this comment with the next step.

Re-review progress:

@anagnorisis2peripeteia

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Pushed a53fddb9f59 — root-cause fix for the failed Mantis native-Telegram proof. Mantis's run showed the wake did trigger the heartbeat turn on the topic session (the PR's routing worked) but the reply never reached Telegram: resolveHeartbeatDeliveryTarget defaults to target none whenever agents.defaults.heartbeat.target is unset, silently dropping the reply even though the wake explicitly carried its origin delivery context.

Fix: when no heartbeat target is configured and the drained event carries a deliverable origin turn source, deliver to that origin. Explicit target: "none" still suppresses (operator opt-out), and runs without an origin turn source keep the previous default. Pinned by three new tests in targets.test.ts.

Re-verified in the Mantis-equivalent local harness (real gateway + Telegram bot + tdlib driver, wake driven via the same wake RPC Mantis uses, including mid-turn races) across all three config shapes — no heartbeat config at all, {every} without target, and target:"last": the wake now starts the heartbeat turn AND the reply is delivered back to the originating chat in every case. The Mantis telegram-desktop proof should pass on re-run.

@clawsweeper

clawsweeper Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@anagnorisis2peripeteia

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Command router queued. I will update this comment with the next step.

Re-review progress:

@anagnorisis2peripeteia

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Command router queued. I will update this comment with the next step.

Re-review progress:

@anagnorisis2peripeteia

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Command router queued. I will update this comment with the next step.

Re-review progress:

@anagnorisis2peripeteia

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Command router queued. I will update this comment with the next step.

Re-review progress:

@anagnorisis2peripeteia

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Added the current-head proof you asked for (the [P1] proof-guidance item): the no-heartbeat-target fallback, captured on the exact PR head with no agents.defaults.heartbeat configured, driven via the same wake RPC Mantis uses.

  • No target configured: wake turn runs on the topic session and its reply (live date -u output, "PROOF-83738 no-target wake ran at Wed Jun 10 13:14:33 UTC 2026") is delivered back into the same forum topic — new GIF + full still + mp4 + redacted gateway debug log in the body.
  • target: "none" still suppresses: same wake fires the heartbeat turn but produces zero outbound sends (operator opt-out preserved) — gateway log attached.

This is in addition to the earlier target: last recordings. Branch is MERGEABLE, all CI green.

@clawsweeper

clawsweeper Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Command router queued. I will update this comment with the next step.

Re-review progress:

@anagnorisis2peripeteia

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Command router queued. I will update this comment with the next step.

Re-review progress:

The cron wake tool forwarded only {mode, text} to the gateway, so every
wake enqueued against the heartbeat/main default. Wakes scheduled from a
non-main session (Telegram topic, Discord thread, multi-agent setup)
silently routed to the wrong conversation lane.

- cron-tool wake action resolves the calling agent's sessionKey/agentId
  (mirrors the add action); explicit tool params take precedence, and an
  explicit cross-agent sessionKey derives its agentId from the key itself
  so the caller's agentId is never paired with a foreign session.
- WakeParamsSchema, gateway wake handler, cron service contract and
  wake() thread the optional sessionKey/agentId through to
  enqueueSystemEvent and the heartbeat request.
- wake() also carries the originating session's stored channel delivery
  context (e.g. the bound Telegram message_thread_id) onto the system
  event via an optional resolveOriginDeliveryContext dep implemented in
  server-cron from the session store - a wake-now heartbeat replies in
  the originating topic, not the chat root. No-origin wakes keep the
  exact pre-fix enqueueSystemEvent(text, undefined) shape.
- Swift gateway protocol client regenerated for the new WakeParams
  field.

Scheduled main-session cron jobs are untouched: current main already
routes them through the dedicated cron run lane with borrowed delivery
context, and this change layers on top of that design.

Closes the tool-path half of openclaw#46886 / openclaw#64556.
…outing

Drives the real gateway wake handler into the real cron service wake()
with logging deps, demonstrating origin routing, backwards-compatible
default routing, the next-heartbeat targeted-immediate collapse, the
subagent guard, and whitespace fall-through. All identifiers synthetic.

Run: pnpm exec tsx scripts/proof-cron-wake-origin.mts
Focused Stryker mutation on the PR-touched code surfaced gaps the
example-based tests missed: agentId-only wakes (enqueue opts + heartbeat
threading), sessionKey-only wakes (origin delivery-context resolution),
wake without text (required-param guard), context-less wake callers
(optional chaining), and an explicit next-heartbeat mode. All pinned;
wake.ts now scores 51/51 killed mutants, the cron-tool wake arm 26/31
(remaining survivors are test-harness equivalents).
The wake action's tool description gained the sessionKey/agentId
override guidance, which flows into the codex dynamic-tools prompt
fixtures.
An explicit agentId paired with an explicit sessionKey owned by a
different agent is ambiguous: the gateway target resolver treats agentId
as authoritative and would silently canonicalize the wake onto a session
under that agent which the caller never named. Reject the contradictory
pair instead of guessing a canonical owner; matching explicit pairs and
single-field overrides are unchanged.
…ndary

Direct gateway callers and generated clients bypass the cron tool, so
mirror its guard in the wake handler: an explicit agentId that disagrees
with the agent owning an agent-prefixed sessionKey is rejected as
INVALID_REQUEST before reaching the cron service, instead of letting the
target resolver silently canonicalize the wake onto an unnamed lane.
…et is configured

The wake path carries the originating session's delivery context onto
the system event, but resolveHeartbeatDeliveryTarget defaulted to
target "none" whenever agents.defaults.heartbeat was absent — the
woken turn ran and its reply was silently dropped. When no heartbeat
target is configured and the drained event explicitly carried a
deliverable origin turn source, resolve delivery to that origin; an
explicit target "none" still suppresses, and runs without an origin
turn source keep the previous default.
@shakkernerd

Copy link
Copy Markdown
Member

Land-ready proof for head 0e3687c4ef119da82682ea9897eda0055ab7c77d.

Verification run:

  • Rebased onto current origin/main (e6b0a22f36cdb90ea382f648318954edfa292432) and confirmed the branch contains that base.
  • git diff --check origin/main...HEAD
  • Testbox tbx_01ktsf7bdejxqtc9qqk8p8r82p: pnpm check:changed passed; pnpm test:changed passed through touched runtime shards and only stopped on missing Playwright ffmpeg in the UI E2E environment.
  • Testbox tbx_01ktsgqpyva0fp688whjzb8c4q: reran the affected UI E2E files after installing ffmpeg; passed.
  • Testbox tbx_01ktsgvgrs9h1q28x86fp8htcv: focused cron/gateway/protocol/outbound tests passed:
    packages/gateway-protocol/src/index.test.ts src/agents/tools/cron-tool.test.ts src/cron/service/wake-origin-delivery.test.ts src/cron/service/wake-origin.test.ts src/gateway/server-methods/cron.validation.test.ts src/infra/outbound/targets.test.ts
  • Autoreview: .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main clean, no accepted/actionable findings.
  • GitHub checks are complete; PR reports MERGEABLE / CLEAN.

Squash-merging now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling app: web-ui App: web-ui gateway Gateway runtime mantis: telegram-visible-proof Mantis should capture Telegram visible proof. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. proof: 🎥 video Contributor real behavior proof includes video or recording evidence. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. scripts Repository scripts size: L status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants