Skip to content

feat(server): ship watcher prompt in device poll payload (+ owletto pointer)#1088

Merged
buremba merged 1 commit into
mainfrom
feat/watcher-ship-prompt
May 26, 2026
Merged

feat(server): ship watcher prompt in device poll payload (+ owletto pointer)#1088
buremba merged 1 commit into
mainfrom
feat/watcher-ship-prompt

Conversation

@buremba
Copy link
Copy Markdown
Member

@buremba buremba commented May 26, 2026

What

Device-local watchers got no instructions: the device poll envelope (worker-api.ts, the run_type==='watcher' branch) shipped only watcher id/name/slug, so the Mac app's local CLI received a bare "process this" and improvised toward the wrong data source. This joins the run's pinned watcher version and ships its prompt as payload.watcher.prompt, so the device runs the watcher's real instructions.

How

  • Poll SQL joins watcher_versions on COALESCE((approved_input->>'version_id')::bigint, current_version_id) — the same run-pinned resolution complete_window uses (manage_watchers.ts:1614), with the same AND wv.watcher_id = w.watcher_group_id guard so a forged version_id can't resolve another watcher's prompt.
  • Adds prompt to the payload + row type.
  • Bumps the owletto submodule pointer to the matching Mac-app change (lobu-ai/owletto#231), which decodes and runs the prompt.

Tests

  • New integration test (manual-trigger.test.ts): queue a run (pins prompt v1) → create_version v2 → assert the poll payload returns v1 (the snapshot), not the edit.
  • make review: typecheck/unit/integration all green; pi endorsed the approach (right layer, multi-replica safe) and flagged the scope-guard, now applied. 8/8 in the touched test file pass with the guard.

Not done (follow-ups noted by pi, out of scope)

Prompt rides in claude -p argv (future: stdin/temp-file if prompts carry secrets); no max prompt-length cap yet; version-pin-in-JSONB → dedicated column tracked in #799.

Deploy note

Live e2e needs this server deployed (prod) + the Mac app rebuilt with owletto#231; validated in isolation (server ships it [tested], app runs it [compiles], the prompt produces a real plan [local claude -p test]) but the assembled prod path is unobserved until shipped.

Summary by CodeRabbit

  • Bug Fixes

    • Poll endpoint now returns the prompt snapshot that was pinned when a run was queued, preserving consistency even if the watcher is edited later.
  • Tests

    • Added an integration test covering poll endpoint behavior and a test helper to support watcher administration scenarios.
  • Chores

    • Updated a linked subproject reference.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fc86f2a2-2755-480f-961a-b2b04e1ab83c

📥 Commits

Reviewing files that changed from the base of the PR and between 7d4134b and 23ce31f.

📒 Files selected for processing (3)
  • packages/owletto
  • packages/server/src/__tests__/integration/watchers/manual-trigger.test.ts
  • packages/server/src/worker-api.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/owletto

📝 Walkthrough

Walkthrough

The PR updates the watcher-run poll endpoint to return the watcher prompt snapshot from the version pinned when the run was queued, rather than the current version. The database query is extended with a LEFT JOIN to fetch the correct version's prompt, the response payload is augmented to include it, and a new integration test validates the behavior across version edits.

Changes

Watcher Prompt Snapshot Delivery

Layer / File(s) Summary
Database Query & Response Type for Pinned Prompt
packages/server/src/worker-api.ts
The poll query LEFT JOINs watcher_versions to source watcher_prompt from the approved version (or current version if no approval). The row type is extended to include watcher_prompt: string | null.
Watcher Payload with Pinned Prompt
packages/server/src/worker-api.ts
The pollWorkerJob response includes a prompt field populated from the queried watcher_prompt, preserving version-pinned instructions.
Integration Test: Pinned Prompt Across Version Edits
packages/server/src/__tests__/integration/watchers/manual-trigger.test.ts
New ownerCtx helper enables admin tooling calls. Test verifies /api/workers/poll returns the queued version's prompt (v1) even after post-queue watcher edits (v2).
Submodule pointer
packages/owletto
Updated submodule commit reference to a new revision.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • lobu-ai/lobu#814: Also modifies packages/server/src/worker-api.ts's watcher polling behavior and response shape.
  • lobu-ai/lobu#1058: Enriches the pollWorkerJob watcher payload; overlaps on the same worker poll codepath.
  • lobu-ai/lobu#1007: Another PR that updates the packages/owletto submodule pointer.

"I'm a rabbit in a dev burrow bright,
I pinned a prompt, held it tight,
Queued the run, then edits flew,
But the snapshot stayed — steadfast and true,
Hops of joy for stable byte!" 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: shipping the watcher prompt in the device poll payload and updating the owletto submodule pointer.
Description check ✅ Passed The description is comprehensive and complete, with clear What/How/Tests sections that align with the required template structure, though formatted as custom sections rather than using template checkboxes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/watcher-ship-prompt

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter
Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/server/src/worker-api.ts (1)

544-546: ⚡ Quick win

Harden watcher_versions join against non-numeric approved_input.version_id
createWatcherRun persists approved_input.version_id as number | null (snapshot of watchers.current_version_id), so the current ::bigint cast shouldn’t fail for runs created by this code. Still, a corrupted/legacy row with non-numeric approved_input.version_id would make the poll SQL hard-fail; guarding the cast prevents repeated /api/workers/poll 500s.

Proposed fix
       LEFT JOIN watcher_versions wv
-        ON wv.id = COALESCE((r.approved_input->>'version_id')::bigint, w.current_version_id)
+        ON wv.id = COALESCE(
+          CASE
+            WHEN (r.approved_input->>'version_id') ~ '^[0-9]+$'
+              THEN (r.approved_input->>'version_id')::bigint
+            ELSE NULL
+          END,
+          w.current_version_id
+        )
         AND wv.watcher_id = w.watcher_group_id
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/server/src/worker-api.ts` around lines 544 - 546, Guard the unsafe
cast of r.approved_input->>'version_id' to bigint in the LEFT JOIN to avoid hard
failures for non-numeric legacy/corrupted rows: replace
COALESCE((r.approved_input->>'version_id')::bigint, w.current_version_id) with a
safe expression such as COALESCE(CASE WHEN (r.approved_input->>'version_id') ~
'^\d+$' THEN (r.approved_input->>'version_id')::bigint ELSE NULL END,
w.current_version_id) so the join to watcher_versions (alias wv) falls back to
w.current_version_id instead of erroring; this change touches the LEFT JOIN
using r.approved_input and w.current_version_id (created by createWatcherRun).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/server/src/worker-api.ts`:
- Around line 544-546: Guard the unsafe cast of r.approved_input->>'version_id'
to bigint in the LEFT JOIN to avoid hard failures for non-numeric
legacy/corrupted rows: replace
COALESCE((r.approved_input->>'version_id')::bigint, w.current_version_id) with a
safe expression such as COALESCE(CASE WHEN (r.approved_input->>'version_id') ~
'^\d+$' THEN (r.approved_input->>'version_id')::bigint ELSE NULL END,
w.current_version_id) so the join to watcher_versions (alias wv) falls back to
w.current_version_id instead of erroring; this change touches the LEFT JOIN
using r.approved_input and w.current_version_id (created by createWatcherRun).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 329d3c51-1afb-4228-8bdc-e31cf41112e9

📥 Commits

Reviewing files that changed from the base of the PR and between 7f2ae5e and 7d4134b.

📒 Files selected for processing (3)
  • packages/owletto
  • packages/server/src/__tests__/integration/watchers/manual-trigger.test.ts
  • packages/server/src/worker-api.ts

@buremba
Copy link
Copy Markdown
Member Author

buremba commented May 26, 2026

bug_free 84, simplicity 90, slop 0, bugs 0, 0 blockers

Read diff and logs. Typecheck/unit passed; changed manual-trigger integration passed (7 tests) including run-pinned prompt. [env] integration exit 1 came from untouched src/lobu/tests/agent-routes-apply.test.ts embedded Postgres shared-memory exhaustion. Skipped extra runtime probe.

Full verdict JSON
{
  "bug_free_confidence": 84,
  "bugs": 0,
  "slop": 0,
  "simplicity": 90,
  "blockers": [],
  "change_type": "feat",
  "behavior_change_risk": "low",
  "tests_adequate": true,
  "suggested_fixes": [],
  "notes": "Read diff and logs. Typecheck/unit passed; changed manual-trigger integration passed (7 tests) including run-pinned prompt. [env] integration exit 1 came from untouched src/lobu/__tests__/agent-routes-apply.test.ts embedded Postgres shared-memory exhaustion. Skipped extra runtime probe.",
  "categories": {
    "src": 17,
    "tests": 60,
    "docs": 0,
    "config": 0,
    "deps": 2,
    "migrations": 0,
    "ci": 0,
    "generated": 0
  }
}

Local review gate — branch protection can require the pi-review commit status. See docs/REVIEW_SCHEMA.md.

@buremba buremba force-pushed the feat/watcher-ship-prompt branch from 7d4134b to 78a9f8d Compare May 26, 2026 18:30
@buremba buremba enabled auto-merge (squash) May 26, 2026 18:31
The device-watcher poll envelope omitted the watcher's prompt/schema, so a
device-local (local-CLI) executor had no channel for the watcher's actual
instructions. Join the current watcher_version and include wv.prompt as
payload.watcher.prompt so the device can run the real prompt. Null-safe for
watchers without a version row.
@buremba buremba force-pushed the feat/watcher-ship-prompt branch from 78a9f8d to 23ce31f Compare May 26, 2026 18:33
@buremba buremba merged commit 073cf8d into main May 26, 2026
21 checks passed
@buremba buremba deleted the feat/watcher-ship-prompt branch May 26, 2026 18:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants