Skip to content

feat(schedule): add HTTP reactive polling for scheduled tasks - #307

Open
max-cyq wants to merge 1 commit into
OpenCoworkAI:mainfrom
max-cyq:feature/http-reactive-polling
Open

feat(schedule): add HTTP reactive polling for scheduled tasks#307
max-cyq wants to merge 1 commit into
OpenCoworkAI:mainfrom
max-cyq:feature/http-reactive-polling

Conversation

@max-cyq

@max-cyq max-cyq commented Jul 19, 2026

Copy link
Copy Markdown

Summary

Add an HTTP-only WatchTask capability on top of the existing ScheduledTaskManager. A scheduled task becomes a WatchTask when it has a valid watchConfig. The scheduler pipeline changes from unconditional execution to:

timer → checkCondition() → persist state → changed?
  ├─ yes → start Agent session
  └─ no  → skip, reschedule

Scope

  • HTTP status and bodyHash comparison modes
  • Null baseline: first successful check establishes baseline without starting Agent
  • Persist-before-Agent: changed state written before Agent execution
  • Agent failure does not retry the same observed change
  • Malformed persisted config never falls through to ordinary execution
  • WatchTask busy-trigger recovery: future repeat slot persisted during in-flight execution
  • Production wiring in both headless and GUI constructors
  • Minimum 60-second automatic interval, runNow() immediate

Safety boundaries

  • HTTP/HTTPS only, protocol revalidated after every redirect
  • 10s default timeout (1-30s configurable), 1 MiB response body limit
  • Max 3 followed redirects (manual, no auto-follow)
  • No custom request headers, auth secrets, or response body persistence
  • Localhost/private-network URLs allowed
  • Node 22 built-ins only: fetch, node:crypto, node:http

Out of scope

Command checker, file checker, Agent checker, UI forms, notifications, custom headers, retry/backoff. These are deferred to separate work.

Test coverage

  • 1166 tests passing (Vitest: npm test)
  • npm run lint: 0 errors
  • npm run typecheck: passes
  • npm run build: passes
  • Integration test with loopback HTTP server

Closes #304
Refs #274

Add an HTTP-only WatchTask capability on top of ScheduledTaskManager that
checks external state before starting an Agent session.

Core changes:
- Shared HTTP watch config contract with status/bodyHash modes, canonical
  URL normalization, timeout validation (1-30s), and HEAD+bodyHash rejection
- Backward-compatible SQLite migration via ensureColumn for watch_config,
  last_state, last_checked_at, consecutive_unchanged columns
- Store mapping with malformed persisted config detection (watchConfigError)
- Bounded Node 22 HTTP checker: 10s default timeout, 1MiB body limit, max 3
  manual redirects with protocol revalidation per hop, streaming SHA-256 hash
- Manager create/update validation: requires repeating schedule, minimum 60s
  interval, equivalent config preserves state, semantic change resets baseline
- Scheduler check→persist→act pipeline: null baseline first success no Agent,
  unchanged skip, changed persist-before-Agent, checker/Store failures no
  Agent, Agent failure no same-change retry, malformed config never executes
- WatchTask busy-trigger recovery: future repeat slot persisted when timer
  fires during in-flight execution
- Production wiring in both headless and GUI ScheduledTaskManager constructors
- IPC type-only extension: runtime state output-only, compile-time boundary
  proof, no UI/component/i18n changes
- 15 new/updated test files, 1166 total tests passing

Closes OpenCoworkAI#304
Refs OpenCoworkAI#274

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review mode: initial

Findings

  • [Minor] Generic error message in assertNever could leak internal state details in pathological scenarios.
    In src/main/schedule/scheduled-task-store.ts:131, the assertNever function converts the unexpected value via String(value). If value is an object (which should never happen given the discriminated union), this produces [object Object], which is unhelpful and could theoretically expose internal structure if the object is logged elsewhere. While this path is unreachable by design, consider using JSON.stringify(value) or a static message for safety.
    Suggested fix:
    function assertNever(value: never): never {
      throw new Error(`Unexpected persisted watch configuration result.`);
    }

Summary
Review mode: initial
Review policy: advisory — the check reflects automation health/completion only; it does not approve the PR or resolve findings.

The PR introduces a well-structured HTTP reactive polling capability for scheduled tasks (WatchTask) with thorough validation, error handling, and state management. The implementation is clean, follows existing patterns, and includes comprehensive test coverage (1166 tests passing). No blocking issues found. The only minor suggestion is to harden the assertNever error message. The code respects security boundaries (HTTP-only, size limits, timeouts, no body persistence) and does not introduce regressions.

Testing
Not run (automation). All submitted tests pass as per PR metadata.

Open Cowork Bot

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.

feat(schedule): add HTTP reactive polling for scheduled tasks

2 participants