224 Commits

Author SHA1 Message Date
小橘 🍊 72b9fbe4e9 feat(pulse): task event schema + pending-tasks projection fold
* feat(pulse): task event schema + pending-tasks projection fold

- task-events.ts: TaskState, PendingTasksData, AgentCapabilityStats types
- pending-tasks-projection.ts: buildPendingTasksFromEvents + buildAgentCapabilityStats
- Authorization guard: only assigned agent can close/give-up
- Tests: 15 cases covering all 6 lifecycle steps from #85

Closes #85, ref #83

Made-with: Cursor

* style: fix biome format in gateway-health.ts

Made-with: Cursor

---------

Co-authored-by: 小橘 <xiaoju@shazhou.work>
2026-04-15 18:05:03 +08:00
小橘 🍊 c4e43d46f1 fix(e2e): increase T5 promote test timeout to 30s
deploy promote runs tsc --noEmit which can exceed the default 5s
bun test timeout on low-core VMs.

Ref #79

Co-authored-by: 小墨 <xiaomooo@shazhou.work>
2026-04-15 16:34:42 +08:00
小橘 🍊 90bd0a732f fix: replace pgrep with systemctl for process detection
Closes #79, Ref #77
2026-04-15 15:47:42 +08:00
小橘 🍊 b9bcc1c926 fix: resolve parallel test noise — mkdir guard, projection_defs check, watcher teardown (#76)
- openScopeDb: ensure parent directory exists before creating Database
- foldAllProjections: check projection_defs table exists before querying
- watcher: break tick loop on "Database has closed" instead of logging

Made-with: Cursor

Co-authored-by: 小橘 <xiaoju@shazhou.work>
2026-04-15 14:45:03 +08:00
xiaomo dc7e800415 feat(upulse): auto-install systemd user service on init
- Creates pulse.service in ~/.config/systemd/user/
- Restart=always with 5s delay, rate limited 10/60s
- Enables service automatically (user starts manually)
- Linux only, graceful fallback on other platforms
2026-04-15 06:32:02 +00:00
xiaomo 18c1e14b05 fix: stabilize CI — test teardown settle + ignore unhandled errors with 0 fail 2026-04-15 04:58:06 +00:00
xiaomo d286660f46 perf: pre-compile JSONata expressions with cache (fixes #68) 2026-04-15 04:51:09 +00:00
xiaomo a21504b6b9 fix: defs.ts DI refactor + makeUlid dedup (fixes #67, fixes #70) 2026-04-15 04:49:09 +00:00
xiaomo 4229a5fde6 fix: ensure objects/ dir exists in putObject (fixes CI race condition) 2026-04-15 04:29:08 +00:00
xiaomo 32984be9e4 fix: biome lint fixes for CI 2026-04-15 04:24:27 +00:00
xiaomo c0432db2da feat: implement Rule Projections (RFC #58 Phase 3)
- Add RuleDef<S,E> interface with projection dependencies
- Add buildSnapshotFromProjections function for cross-scope reading
- Add runPulseV2 with projection-based snapshot building
- Extend ScopedStore with scopeDatabase method for projection access
- Add comprehensive test suite covering all 7 requirements:
  1. RuleDef declaration with scope/name projections
  2. Cross-scope projection reading
  3. Snapshot key format consistency (scope/name)
  4. Existing survival rules migration
  5. Onion middleware order preservation
  6. Graceful handling of unregistered projections
  7. E2E tick loop with fold → snapshot → rule → effects

Closes #62
2026-04-15 04:09:55 +00:00
xiaomo 27ae4bb17c Fix Review Issues (#65, #66, #69)
Bug #65: Unify JSONata bindings between validateExpression and foldProjection
- Modified defs.ts validateExpression to use bindings (, , )
- Updated all test expressions to use bindings syntax for consistency
- Both validation and execution now use the same bindings approach

Bug #66: Include sources in projection hash calculation
- Extended calculateProjectionHash to include sources array
- Prevents hash collisions when only expression changes
- Ensures projection_def_sources foreign key constraints work correctly

Bug #69: Export new modules from index.ts
- Added exports for defs.js and projection-engine.js
- External packages can now import definitions and projection engine

All tests pass (225/225) with added test coverage for the fixes.
2026-04-15 04:04:42 +00:00
xiaomo 263803e2e0 feat: implement Pulse RFC #58 Phase 2 - Projection Engine
- Add projections table to scope databases with schema:
  name, value (JSON), last_event_id (ULID), code_rev, updated_at

- Implement projection-engine.ts with core APIs:
  * foldProjection() - incremental fold for single projection
  * foldAllProjections() - batch fold all projections in scope
  * getProjectionState() - retrieve current projection state
  * resetProjections() - reset+replay for promote/rollback

- Incremental fold algorithm:
  * Query events after last_event_id, filter by sources
  * Evaluate JSONata expressions with proper bindings
  * Handle runtime errors gracefully, write error events
  * Update state atomically with new last_event_id

- Support for LWW (Last Writer Wins) and non-LWW projections
- JSONata expression evaluation with state/event/params context
- Comprehensive test suite covering all RFC requirements:
  * LWW projection with 3 events → latest value
  * Incremental fold idempotency
  * Non-LWW counter accumulation (5 events → count=5)
  * Promote: reset + replay with new code_rev
  * Rollback: reset + replay with old code_rev
  * Runtime error degradation (write error events, don't crash)
  * Batch processing multiple projections
  * Event filtering by eventKey
  * Full integration workflow

All tests pass (222/222). Ready for integration.

closes #61
2026-04-15 03:36:53 +00:00
xiaomo 4c9439facf feat: implement Definition Layer (RFC #58 Phase 1)
- Add three append-only definition tables: object_defs, event_defs, projection_defs
- Support content-addressed versioning with SHA-256 hashes
- Implement JSONata expression validation for projections
- Add projection_def_sources table with foreign key constraints
- Support (name, code_rev) unique constraints for versioning
- Add comprehensive test suite covering all 9 scenarios from Issue #60

closes #60
2026-04-15 03:14:54 +00:00
小橘 🍊 b94c575d91 feat(pulse): runPulse accepts ScopedStore — Phase 4 (#59)
- Add scopedStore option to runPulse (store param kept for backward compat)
- Watchers write to _vitals scope, tick/effect events go to _system scope
- rebuildSnapshot uses dual-store (system + vitals) inside runPulse
- Add test suite: ScopedStore routing, watcher isolation, backward compat
- Add future Rule scopes declaration comment (RFC #53 extensibility)

Made-with: Cursor

Co-authored-by: 小橘 <xiaoju@shazhou.work>
2026-04-15 10:12:27 +08:00
小橘 🍊 3e4b492d7b feat(pulse): unify vitals as events — Phase 3 (closes #53) (#57)
- Replace VitalRecord with type alias to EventRecord (kind='vital')
- Remove vitals-specific methods from PulseStore interface
- Add generic archiveEvents/downsampleEvents to PulseStore
- Update rebuildSnapshot with overloaded signature for system/vitals stores
- Migrate legacy vitals.db data to _vitals scope as kind='vital' events
- Remove openVitalsStore, vitalsDbFile config, PULSE_VITALS_DB env var
- Update gc, deploy, server, init, watcher to use events API
- Update all tests (200 pulse + 76 upulse pass)

Made-with: Cursor

Co-authored-by: 小橘 <xiaoju@shazhou.work>
2026-04-15 09:53:14 +08:00
小橘 🍊 a421788f65 feat(upulse): migrate to scoped events store — Phase 2 (#56)
Co-authored-by: 小橘 <xiaoju@shazhou.work>
2026-04-15 09:24:13 +08:00
小橘 🍊 9a1da8f433 feat(pulse): scoped events store — Phase 1 (closes #53-p1) (#54)
Add ScopedStore interface and createScopedStore() factory that partitions
events into per-scope SQLite files while sharing a single CAS objects/
directory. Scope names are validated ([a-z0-9_-], max 64 chars) and
databases are lazily opened and cached. Existing createStore API remains
fully backward compatible. Includes 20 new tests.

Made-with: Cursor

Co-authored-by: 小橘 <xiaoju@shazhou.work>
2026-04-15 08:55:58 +08:00
luming 0cfbce865b fix: resolve type errors and harden pre-push hook
- Server<undefined> type param for Bun.serve (TS2314)
- Remove stale @ts-expect-error in deploy.ts (TS2578)
- Pre-push hook now runs tsc --noEmit for all packages
- Pre-push hook now tests all packages (was missing upulse, pulse-cursor, pulse-openclaw)
- Use subshells + absolute ROOT path instead of fragile cd chains

Fixes CI failure on PR #52.

— 小糯 🐱
2026-04-15 07:41:20 +08:00
小橘 🍊 e84839d1f8 Merge pull request #52 from oc-xiaoju/feat/ui
feat: upulse ui — local WebUI monitoring dashboard
2026-04-15 04:58:28 +08:00
xiaomo 6e54c2ab2d fix: complete engine/staging project isolation (#44)
- Remove symlink: engine/ and staging/ have independent node_modules
- Init: bun install in engine before staging worktree (bun.lock committed)
- Init: symlink migration detection (auto-replace legacy symlinks)
- Promote: graceful 'bun test' when staging has no test files
- Promote: frozen-lockfile with fallback to regular bun install
- Promote: migrate discovers sense keys from vitals (post-#47 compat)
- Rollback: frozen-lockfile with fallback to regular bun install
- Fix createRule template to use correct 3-param Rule<S,E> signature
- Update E2E T5 to seed vitals data (tick no longer writes collect events)
- Update E2E T2/T4 test patterns for new Rule<S,E> signature
2026-04-14 17:10:45 +00:00
luming 8cf19f58ab feat: upulse ui — local WebUI monitoring dashboard
Add a local WebUI dashboard for monitoring Pulse runtime state.
Run `upulse ui` to start at http://localhost:3140.

Features:
- Dashboard: daemon status, last tick, code rev, recent events timeline
- Vitals: sense key selection, canvas line charts, raw data view
- Rules: engine rule files in onion order
- Deploy: promote/rollback history with active version badge
- Event detail: click any event to view CAS object data

Technical:
- Zero dependencies: Bun HTTP server + single-file embedded HTML
- Linear-inspired dark theme, monospace accents
- 7 API endpoints reading from PulseStore
- Auto-polling (5s) on dashboard
- localhost-only, read-only, no auth needed

Files:
- commands/ui.ts: CLI command registration
- ui/server.ts: Bun HTTP server + API routes
- ui/dashboard.ts: embedded HTML/CSS/JS (874 lines)
- ui/server.test.ts: 8 API tests

Closes #50
2026-04-15 01:02:12 +08:00
小橘 🍊 20aa456b5f refactor: tick reads from vitals/events, no more manual collection (closes #47)
tick.ts was manually calling collectSystem() and writing events,
bypassing the watcher framework. This was legacy from the old design
where tick handled both collection and rule execution.

New design: tick is a pure read → decide → execute operation:
- Rebuild snapshot from existing vitals/events (rebuildSnapshot)
- Discover senseKeys from existing collect events in store
- Use findEffectiveEpoch for version-aware snapshots
- Run rules to produce effects
- Log effects (collect effects are skipped — daemon handles collection)

Changes:
- Remove collectSystem import and all manual collect() calls
- Remove bootstrap logic (empty store → empty snapshot, rules decide)
- Use prev = curr for single-tick (no meaningful time delta)
- Add queryByKind to store type for senseKey discovery
- Update E2E test: check for tick events instead of collect events

Closes #47

Co-authored-by: 鹿鸣 <luming@shazhou.work>
2026-04-15 00:46:55 +08:00
小橘 🍊 b3bddf8c1a Merge pull request #46 from oc-xiaoju/feat/cursor-health-watcher
feat(pulse-cursor): add health watcher + rule for Cursor CLI monitoring
2026-04-15 00:42:34 +08:00
xiaomo 5f29fbc9cb feat: engine/staging project isolation + chainExecutors (closes #44) 2026-04-14 16:37:45 +00:00
xiaoju de2e22a5d2 feat(pulse-cursor): add health watcher + rule for Cursor CLI monitoring
- cursorWatcher: collects CLI availability, auth status, running
  processes, session history, and subscription tier
- cursorHealthGuard: Rule that emits alerts on CLI disappearance,
  auth expiration, process over-concurrency, and process count changes
- 22 new tests covering watcher and rule behavior

Made-with: Cursor
2026-04-14 16:26:02 +00:00
小橘 🍊 a9be34f563 feat: @uncaged/pulse-openclaw — OpenClaw adapter package (closes #40)
* feat: @uncaged/pulse-oc — OpenClaw adapter package (closes #40)

- Extract gateway-health & llm-health watchers from core to pulse-oc
- Add gateway-health-guard rule and archive-sessions executor
- Update CI, README, CONTRIBUTING for the new package
- Fix E2E tests to use core ESSENTIAL_PROCESSES after openclaw removal

Made-with: Cursor

* feat: @uncaged/pulse-openclaw — OpenClaw adapter package (closes #40)

* chore: rename pulse-oc → pulse-openclaw

Made-with: Cursor

---------

Co-authored-by: 小橘 <xiaoju@shazhou.work>
2026-04-15 00:15:09 +08:00
小橘 🍊 3ced39c6fa chore: purge legacy terminology — collectors/effectors/Autonomic → watchers/executors/Percept (closes #42)
Made-with: Cursor

Co-authored-by: 小橘 <xiaoju@shazhou.work>
2026-04-14 23:57:59 +08:00
小橘 🍊 e457f631c7 refactor: extract cursor-agent executor to @uncaged/pulse-cursor package
Made-with: Cursor

Co-authored-by: 小橘 <xiaoju@shazhou.work>
2026-04-14 23:52:24 +08:00
xiaomo 0e1e77177f refactor: unify directory structure — watchers/rules/executors (closes #39) 2026-04-14 15:40:15 +00:00
小橘 🍊 21a8c38040 chore: add pre-push hook for lint + tests (closes #29) (#38)
* chore: add pre-push hook for lint + tests (closes #29)

- scripts/install-hooks.mjs: zero-dep hook installer, runs on postinstall
- Pre-push runs: biome lint + pulse tests + pulse-hermes tests
- CI: add pulse-hermes type check + tests, use bun run lint
- CONTRIBUTING.md: document hook setup

Uses pure Node.js APIs (no Bun dependency) so postinstall works with any runtime.
Users can delete .git/hooks/pre-push to opt out.

* fix: TS2322 in findEffectiveEpoch — cast meta.to to string

meta is Record<string, unknown>, so meta.to is unknown.
Explicit cast to (string | undefined) satisfies codeRev: string param.

---------

Co-authored-by: 鹿鸣 <luming@shazhou.work>
2026-04-14 23:38:34 +08:00
小橘 🍊 36fd007573 Merge pull request #32 from oc-xiaoju/fix/review-blocker-and-majors
fix: address review BLOCKER + 4 MAJORs from 凌瞰
2026-04-14 23:21:56 +08:00
小橘 🍊 701198cb2c feat: @uncaged/pulse-hermes — watcher + executor adapter (#37)
* feat: @uncaged/pulse-hermes — watcher + executor adapter (closes #35)

New package: @uncaged/pulse-hermes

Watcher (hermes-watcher.ts):
- Reads gateway_state.json for gateway health, active sessions, platform states
- Checks process liveness via PID + pgrep fallback
- Checks cron scheduler liveness
- shouldWake triggers on: gateway death, error state, agent idle transition

Executor (hermes-executor.ts):
- CLI mode: spawns `hermes run` with prompt file
- Supports model override, toolset selection, timeout
- API and Telegram modes stubbed for future implementation

22 tests, all passing.

* fix: biome lint — template literals, unused imports, import order

---------

Co-authored-by: 鹿鸣 <luming@shazhou.work>
2026-04-14 23:16:42 +08:00
xingyue 0addbcbb81 fix: address review BLOCKER + 4 MAJORs from 凌瞰
BLOCKER:
- survival/executors.ts: replace execSync string interpolation with
  execFileSync for restart-service, clear-cache, rollback-code,
  rollback-config. Add SAFE_ARG whitelist validation. Preserve DI
  interface by adding execFileSyncFn to SurvivalExecDeps.

MAJOR fixes:
1. store.ts: hashObject 64→128 bits (birthday collision safe)
2. index.ts/findEffectiveEpoch: try-catch on JSON.parse
3. index.ts/rebuildSnapshot: track CAS misses as _error:cas_miss
4. watcher.ts: write _error:{key} vital on collection failure

Tests: 235 pass, 0 fail (including new unsafe input rejection tests)

Ref: oc-xiaoju/pulse#11
2026-04-14 23:14:51 +08:00
xiaomo ef7a1dde60 Merge branch 'main' of https://github.com/oc-xiaoju/pulse 2026-04-14 15:08:28 +00:00
xiaomo 9beb90fcfe feat: gateway-health watcher + archive-sessions executor + fix mock isolation (closes #36) 2026-04-14 15:07:52 +00:00
xiaoju 5a8195eec8 chore: clean up pnpm remnants, restore bun as package manager
Made-with: Cursor
2026-04-14 14:58:39 +00:00
小橘 🍊 d3bdf09a7e refactor: rename coding-agent executor to cursor-agent (closes #33)
- Rename coding-agent.ts → cursor-agent.ts
- Rename coding-agent.test.ts → cursor-agent.test.ts
- Rename CodingEffect → CursorEffect
- Rename createCodingExecutor → createCursorExecutor
- Update all imports, re-exports, and CONTRIBUTING.md references

Made-with: Cursor

Co-authored-by: 小橘 <xiaoju@shazhou.work>
2026-04-14 22:42:15 +08:00
xiaomo 7d73efd298 chore: remove leftover bun.lock and npm lock files 2026-04-14 14:32:17 +00:00
xiaomo 1fb6178efa chore: migrate package manager from bun to pnpm
- Add pnpm-workspace.yaml for monorepo workspace
- Add root package.json with packageManager field
- Add .npmrc with shamefully-hoist=true
- Replace bun.lock with pnpm-lock.yaml
- Use workspace:* protocol for @uncaged/pulse dependency
- Update init.ts scaffold to use pnpm install
- Update CLI commands (tick/dev/deploy) to use pnpm/npx
- Update E2E test comments to reference pnpm
- Keep bun as TS runtime (bun:sqlite, bun:test still needed)

closes #19
2026-04-14 14:31:45 +00:00
xiaomo cef69a0ef0 test: Phase 5 — end-to-end survival chain validation (closes #23)
Implements 9 comprehensive E2E test scenarios for survival layer:
- Process crash → processWatchdog → restart
- High memory → resourceGuard → archive + restart
- Disk full → resourceGuard → gc + clear cache
- LLM down → llmWatchdog → restart (no bypass)
- Post-promote errors → autoRollback
- Repeated restart failures → notify escalation
- wakeTick interrupt timing validation
- Full watcher → wakeTick → tick chain
- Normal state → bypass survival → business rules

Technical implementation:
- Uses real SQLite store (no mocking of storage layer)
- Mocked executors (no real systemctl/network calls)
- performance.now() timing validation
- Proper beforeEach/afterEach lifecycle management
- All 9 tests pass with full coverage
2026-04-14 14:09:44 +00:00
xiaomo 5e6951404b fix: correct survival rules data structure mismatches to prevent silent failures
- Fix processWatchdog to read Record<string, boolean> from process-alive watcher
- Fix resourceGuard to read flat {diskPct, memoryPct} from system-resource watcher
- Fix networkWatchdog to check {dnsOk, httpOk} from network watcher
- Add proper SurvivalSnapshot interface replacing Rule<any, SurvivalEffect>
- Fix rebuildHealth panicCount to count rollback-config events from DB
- Update rollback-code executor to use upulse rollback instead of git checkout
- Update tests with correct mock data structures

All survival rules now access correct data fields, preventing silent failures.
2026-04-14 13:46:16 +00:00
小橘 🍊 6b9d913ec3 refactor: migrate executor to Bun.spawn + test improvements (closes #30) (#31)
- Replace execSync with Bun.spawn() for async process spawning
- Implement timeout via setTimeout + proc.kill()
- Convert resolveApiKey to async using Bun.spawn(['sh', '-c', ...])
- Add lazy API key caching in createCodingExecutor closure
- Clean up temp prompt files in finally block
- Add runtime effect.type validation
- Consolidate 5 prompt scenario tests into test.each
- Use expect().rejects.toThrow for error assertion
- Restore strong expect(result.success).toBe(true) for exit code 0
- Remove node:child_process dependency entirely

Made-with: Cursor

Co-authored-by: 小橘 <xiaoju@shazhou.work>
2026-04-14 21:34:25 +08:00
小橘 🍊 7223b4e115 feat: Coding Agent Executor — thin spawner for automated coding tasks (closes #25)
## Summary
Implements the Coding Agent Executor for Pulse's P0 layer.

- New `packages/pulse/src/executors/` directory
- `createCodingExecutor()` spawns Cursor Agent CLI with scenario-specific prompts
- 5 scenarios: bug-fix, rfc-impl, debug, ci-fix, refactor
- Direct `apiKey` option + shell-based `apiKeyCommand` fallback
- 13 tests, CI-resilient (Bun.file() reads, no stdout dependency)

小橘 🍊(NEKO Team)
2026-04-14 21:06:29 +08:00
xiaomo dfb2d662c5 feat: implement P0 survival layer rules and executors (closes #27)
- Add packages/pulse/src/survival/ with 5 core files
- Implement 7 survival rules in onion order (panicRollback, autoRollback, processWatchdog, resourceGuard, llmWatchdog, networkWatchdog, errorEscalate)
- Add SurvivalEffect executors for restart-service, archive-sessions, gc-vitals, clear-cache, rollback-code, rollback-config, notify-owner
- Add rebuildHealth function to rebuild health snapshot from events
- Add comprehensive test coverage (33 tests) for rules and executors
- Export survival layer from main index.ts
- All tests pass, biome checks clean
2026-04-14 12:53:57 +00:00
xiaomo 454cfb3999 feat: add llm-health watcher with dual probes (Phase 4a, closes #27) 2026-04-14 12:20:57 +00:00
xiaomo 455b928c74 Merge chore/biome-lint + apply biome formatting to Phase 2/3 code 2026-04-14 11:49:26 +00:00
xiaomo 7720202a68 style: apply biome formatting after merge 2026-04-14 11:35:51 +00:00
小橘 🍊 b7d7ae9774 chore: add Biome linter + format all code + CI lint step (#26)
- biome.json: 2-space indent, single quotes, recommended rules
- All 30 source files formatted (auto-fix, no logic changes)
- CI: Biome lint runs before type check and tests
- Root package.json with lint/lint:fix scripts

89 unit tests green (82 core + 7 watcher).

小橘 🍊(NEKO Team)

Co-authored-by: 小橘 <xiaoju@shazhou.work>
2026-04-14 19:22:54 +08:00
xiaoju 392249c4a2 chore: add Biome linter + format all code + CI lint step
- biome.json: 2-space indent, single quotes, recommended rules
- All 28 source files formatted
- CI: lint runs before type check and tests
- Root package.json with lint/lint:fix scripts
- Fix noNonNullAssertedOptionalChain in store.test.ts

82 unit tests still green.

小橘 🍊(NEKO Team)
2026-04-14 11:16:44 +00:00