224 Commits

Author SHA1 Message Date
xiaoju e350bb371f docs: add V2-VERIFIED.md confirming council v2 daemon integration
Made-with: Cursor
2026-04-17 02:31:12 +00:00
xiaoju 888ad1a829 fix(council-v2): update baseline before execution + add concurrency test
Moore machine baseline must update BEFORE role execution starts,
not after. This prevents duplicate dispatch when tick() is called
again while a slow role (e.g. Cursor ~60s) is still running.

Added test: 'baseline updates before execution' verifies that a
concurrent tick sees snapshot === baseline and skips.
2026-04-17 02:29:01 +00:00
xiaoju 236849e08e fix(council-v2): Moore diff compares projected snapshot, not action keys
The Moore machine should fire outputs only when STATE changes,
not when ACTIONS change. Compare JSON-serialized snapshot baseline
to determine if a new tick needs to run moderator + roles.
2026-04-17 02:22:22 +00:00
xiaoju 325ef616f8 chore: clean up test artifacts 2026-04-17 02:16:37 +00:00
xiaoju ba1be4d3d2 fix(council-v2): add reentry guard to createTopicTicker 2026-04-17 02:16:06 +00:00
xiaoju 6342509bad feat: add hello-v2.md test file for Council v2 daemon
Made-with: Cursor
2026-04-17 02:10:54 +00:00
xiaoju 15d0d67198 feat(council-v2): createTopicTicker + export roles from index 2026-04-17 00:59:34 +00:00
xiaoju 8848333d1e feat(council-v2): extract reusable role implementations (architect/coder/reviewer) 2026-04-17 00:59:25 +00:00
xiaoju 40a5d55228 feat(council-v2): full live pipeline — architect(LLM) + coder(Cursor) + reviewer(Cursor) 2026-04-17 00:44:20 +00:00
xiaoju a1e869fc19 docs: add COUNCIL-V2.md overview of the Council v2 model
Covers TopicType interface (events, projection, roles, moderator),
Topic-as-Rule pattern, one-rule-per-type design, and Moore machine
diff-driven ticks with references to source files in topics/.

Made-with: Cursor
2026-04-17 00:43:13 +00:00
xiaoju 13a3203b86 feat(council-v2): demo --live --v2 wires architect to real LLM 2026-04-17 00:36:51 +00:00
xiaoju e0a6f7147c test(council-v2): E2E + demo --v2 support 2026-04-17 00:33:42 +00:00
xiaoju 5ad95357c6 feat(council-v2): TopicType interface + topic-rule-adapter + coding-task 2026-04-17 00:33:31 +00:00
xiaoju 79336ba6a5 feat(council): demo --keep and --db= flags for event DB persistence 2026-04-16 14:07:16 +00:00
xiaoju 8b30d6ebee fix(rules): task-rule diff-driven — only emit on state transitions (Moore machine) 2026-04-16 13:51:34 +00:00
xiaoju f07e3e61b3 feat(council): demo supports --live LLM mode (env: PULSE_LLM_BASE_URL/API_KEY/MODEL) 2026-04-16 13:36:05 +00:00
xiaoju e2b4e6113d feat(council): add full pipeline demo script 2026-04-16 13:33:25 +00:00
xiaoju d526f9dabd test(council): E2E full pipeline — task → code → review → close 2026-04-16 13:03:21 +00:00
xiaoju 0e5cd57843 feat(council): Phase 4 — Container registry + Persona×Container binding 2026-04-16 12:44:30 +00:00
xiaoju 179104245b feat(council): Phase 3 — Topic spawn + nested Council 2026-04-16 11:35:13 +00:00
xiaoju 131783144d feat(council): Phase 2 — TaskModerator + Council execution loop 2026-04-16 11:24:47 +00:00
xiaoju 60ecf0c08b fix(test): t5-migrate use integer id for AUTOINCREMENT schema 2026-04-16 11:05:39 +00:00
xiaoju 1b8f5e1296 feat(council): Phase 1 — Persona registry + dynamic broker 2026-04-16 10:56:37 +00:00
xingyue e3b9ccdeb6 chore: remove deprecated code and stale files
closes #24

- Delete stale `hello` test file
- Remove deprecated type aliases: TaskExecutingMeta, TaskAckedMeta,
  TaskGivenUpMeta, ProjectPausedMeta, ProjectArchivedMeta (all `never`)
- Remove deprecated VitalRecord type alias (was just EventRecord)
- Remove deprecated AgentCapabilityStats interface and
  buildAgentCapabilityStats function (returned empty object)
- Remove agent-capability-stats from rebuildSnapshot
- Clean up re-exports in index.ts and upulse/store.ts
- All 355 tests pass, 0 lint errors
2026-04-16 18:45:41 +08:00
xiaoju f012ba7f6c revert(store): restore INTEGER AUTOINCREMENT for events.id 2026-04-16 10:08:52 +00:00
xiaoju 9882d86825 chore: lint fix 2026-04-16 09:48:12 +00:00
xiaoju e3c995728d fix(llm-client): scan all choices for tool_calls (copilot-api returns 2 choices) 2026-04-16 09:42:47 +00:00
xiaoju 184e18f0c1 fix(broker): remove tool_choice required (copilot-api 미지원) 2026-04-16 09:24:36 +00:00
xiaoju 8cd86efafe fix(store): INSERT ULID into events.id column
Previously events.id was left null because the INSERT statement omitted
the id column. Now generates a ULID before each INSERT and passes it
as the first bind parameter. Also migrates the schema from
INTEGER PRIMARY KEY AUTOINCREMENT to TEXT PRIMARY KEY and updates
projections.last_event_id from INTEGER to TEXT accordingly.

Made-with: Cursor
2026-04-16 08:49:03 +00:00
xiaoju 8df9931e9e fix(store): change EventRecord.id type from number to string
After #118 homogeneous scope db refactoring, the events table id column
uses ULID TEXT in production DBs, but rowToRecord() still called
Number(row.id) which produced NaN → 0 for all events.

This caused executorLoop to skip all effects after the first one
(inflight.has(0) always true) and getLatest('effect-acked', '0')
matched wrong events.

Changes:
- EventRecord.id: number → string
- rowToRecord(): Number(row.id) → String(row.id)
- ProjectionState.lastEventId: number → string
- executorLoop inflight: Set<number> → Set<string>
- Remove redundant String() wrapping on effectEvent.id
- Update tests for string id comparisons

Made-with: Cursor
2026-04-16 08:12:47 +00:00
小橘 🍊 1aa28d4aed feat(pulse): Task state machine redesign — pending/routing/assigned/closed + broker executor (closes #119)
* feat(pulse): Task state machine redesign — pending/assigned/closed + broker executor

Redesign task lifecycle from 6-state (pending/assigned/executing/acked/
closed/given-up) to a cleaner 3-state machine (pending/assigned/closed)
with a task-responded event that cycles back to pending for re-brokering.

- Rewrite task-events.ts: new TaskType, TaskCreatedMeta with description/
  type/priority/creatorId, TaskRespondedMeta, ProjectCreatedMeta with
  required repoDir, ProjectState type
- Rewrite pending-tasks-projection.ts: adapt fold logic to new state
  machine, add buildProjectsFromEvents and buildInflightBrokerFromEvents
- New rules/task-rule.ts: lightweight rule (no LLM) that emits broker
  effects for pending tasks and cursor effects for assigned tasks
- New executors/broker.ts: LLM-based broker that assigns pending tasks
  to agents via tool calling
- Rewrite cursor-agent.ts (pulse-cursor): CursorEffect now uses
  kind/taskId/projectId instead of type/prompt/scenario/repoDir,
  executor resolves task and project details from routineStore,
  writes task-responded events instead of task-acked
- Update all tests for new state machine

Closes #119

* fix(task-state-machine): add routing status for broker inflight idempotency

Made-with: Cursor

---------

Co-authored-by: 小橘 <xiaoju@shazhou.work>
2026-04-16 06:16:40 +08:00
小橘 🍊 e44f0a3e73 refactor(pulse): homogeneous scope db — def tables in every scope (#118)
* refactor(pulse): homogeneous scope db — def tables in every scope + fix last_event_id type (closes #115, closes #117)

* refactor(pulse): homogeneous scope db — def tables in every scope + fix last_event_id type (closes #115, closes #117)

---------

Co-authored-by: 小墨 <xiaomooo@shazhou.work>
2026-04-15 23:39:54 +08:00
xingyue 6d7a70cfc2 refactor: replace ULID with INTEGER AUTOINCREMENT for events PK (closes #114)
ULID's random part was non-monotonic within the same millisecond,
causing WHERE id > ? cursor queries to silently miss events.
This was the root cause of the flaky projection integration test.

Changes:
- events.id: TEXT PRIMARY KEY → INTEGER PRIMARY KEY AUTOINCREMENT
- Remove makeUlid() entirely
- appendEvent uses SQLite last_insert_rowid()
- projection-engine: lastEventId string → number (default 0)
- executorLoop: effectEvent.id → String() for key storage
- All 8 affected files updated, 319 tests pass (0 fail)

Breaking: EventRecord.id type changed from string to number.
Existing .db files need schema migration (recreate recommended).
2026-04-15 23:02:28 +08:00
xiaoju 52d9c8813f fix(agent-loop): include repoDir in buildTaskSummary for LLM context 2026-04-15 14:59:44 +00:00
xiaoju 3e7bbf3d81 fix(pending-tasks): store repoDir in TaskState, relax close/give-up auth for unassigned tasks 2026-04-15 14:54:51 +00:00
xingyue 815326153f feat(pulse): automatic vitals GC — downsample + archive + CAS sweep (closes #113)
Tiered retention policy:
  < 1h:    full resolution (keep all)
  1h–24h:  1 per 5 min (downsample, keep LWW)
  24h–7d:  1 per 1 hour (downsample)
  > 7d:    hard delete (archive)

Implementation:
- gc.ts: gcVitals(), gcOrphanObjects(), runGc(), createGcTrigger()
- Tick-count based trigger (every 240 ticks ≈ 1h at 15s interval)
- GC stats written to _system scope as kind=gc events
- CAS mark-and-sweep deletes orphaned objects/ files
- Hardcoded defaults, configurable via GcConfig
- Integrated into runPulse() via gc option

12 new tests covering all GC paths.

Reviewed-by: 小墨 🖊️ (oc-xiaoju/pulse#113)
2026-04-15 22:31:51 +08:00
小橘 🍊 a8d610595a feat(pulse): Agent Loop Rule — LLM-driven per-project task dispatch (#111)
* feat(pulse): Agent Loop Rule — LLM-driven per-project task dispatch

- Add LlmClient interface + createOpenAiLlmClient (OpenAI-compatible)
- Add createAgentLoopRule: LLM function-calling driven task dispatch
  - assign_task, close_task, give_up_task, set_next_check tools
  - Per-project cooldown, concurrent project loops via Promise.all
  - LLM timeout handling with fallback event logging
- Export new APIs from package index
- 8 test cases covering all dispatch paths

Closes #103, Ref #83

Made-with: Cursor

* fix(agent-loop): correct message order, reduce no-task tickMs to 60s

* fix(agent-loop): correct message order, reduce no-task tickMs to 60s

- Add regression test verifying LLM messages order: history before user taskSummary
- Source fixes (message order, tickMs 300s→60s) were in previous commit

Made-with: Cursor

---------

Co-authored-by: 小橘 <xiaoju@shazhou.work>
2026-04-15 22:13:59 +08:00
xingyue cad814249e fix: enforce single daemon instance for foreground mode + PID lockfile (closes #112) 2026-04-15 20:51:20 +08:00
xingyue 58c755c1d5 Merge remote-tracking branch 'origin/fix/ui-pagination' 2026-04-15 20:51:08 +08:00
xingyue e79e65c4a8 fix: accurate memoryPct on macOS and Linux (closes #109)
macOS: use vm_stat to include inactive + purgeable pages as available.
Linux: use /proc/meminfo MemAvailable instead of MemFree.
Fallback to os.freemem() on other platforms.

Before: macOS reports ~95% on a 16GB machine with 7.5GB available.
After: correctly reports ~55%.

Also updated upulse init template so new installations get the fix.
2026-04-15 20:43:48 +08:00
luming a11fae87f9 fix(ui): cap events pagination at 500 + show end-of-data state
- EventsPage: track currentLimit state, cap at MAX_EVENTS_LIMIT=500,
  show 'All events loaded' or 'Limit reached (500)' states
- DeploysPage: add comment noting server-side 50/kind cap

— 小糯 🐱
2026-04-15 20:43:13 +08:00
luming 7e0f417367 fix(ui): correct distDir path — serve React app instead of fallback HTML
server.ts is at src/ui/server.ts, so the dist path needs to go up
two levels (src/ui → src → package root) to reach ui-app/dist/.
Was going up only one level, causing hasDistDir=false and always
falling back to the old embedded DASHBOARD_HTML (no routing).

— 小糯 🐱
2026-04-15 20:37:49 +08:00
小橘 🍊 25d7700c6b feat(pulse): agent loop trace + project events + active-projects projection (#108)
- Add project-created/paused/archived event schemas
- Add llm-call-started/completed + tool-response event schemas
- Add TraceMessage, AgentLoopTraceData, ActiveProjectsData types
- Implement buildActiveProjectsFromEvents fold (project lifecycle)
- Implement buildAgentLoopTraceFromEvents fold (ring buffer + nextCheckAt)
- Wire active-projects + agent-loop-trace:{projectId} into rebuildSnapshot
- Export all new types and functions from index.ts

Ref #103

Co-authored-by: 小橘 <xiaoju@shazhou.work>
2026-04-15 20:26:24 +08:00
小橘 🍊 f88044381d feat(upulse): React WebUI dashboard with full data model coverage (#107)
* feat(upulse): React WebUI dashboard with full data model coverage

Replace the embedded single-file HTML dashboard with a proper React app:

## Frontend (ui-app/)
- React 19 + Vite + Tailwind 4 + Radix UI (same stack as OGraph board)
- 6 pages: Dashboard, Events, Scopes, Projections, Definitions, Deploys
- Dark zinc theme, responsive sidebar navigation
- Auto-refresh on Dashboard (5s) and Events (10s)
- Click-to-expand JSON details throughout

## Backend (server.ts)
New API endpoints for the v2 data model:
- GET /api/scopes — list all scope names
- GET /api/scopes/:scope/events — events for a specific scope
- GET /api/scopes/:scope/projections — projection states per scope
- GET /api/defs/objects — object definitions
- GET /api/defs/events — event definitions
- GET /api/defs/projections — projection definitions with sources

Static file serving from ui-app/dist/ with SPA fallback.
Falls back to embedded HTML when dist/ doesn't exist.

All existing API endpoints preserved unchanged.

— 小糯 🐱

* fix: address review feedback on PR #107

1. Scope event count: /api/scopes now returns { name, eventCount } per scope
   via SELECT COUNT(*) on each scope DB (no more imprecise client-side probing)
2. Build artifacts: added .gitignore for ui-app/ (dist/, node_modules/, *.tsbuildinfo)
3. API error handling: scope endpoints now return 503 when store unavailable,
   404 when scope not found (was silently returning empty arrays)

— 小糯 🐱

---------

Co-authored-by: 鹿鸣 <luming@shazhou.work>
2026-04-15 20:17:42 +08:00
小橘 🍊 4adf192b03 feat(pulse): migrate coding-task-* to task-* events + routine scope (closes #105) (#106)
Co-authored-by: 小墨 <xiaomooo@shazhou.work>
2026-04-15 20:07:54 +08:00
小橘 🍊 234ca41c46 feat(pulse): objects instance table + event object_id ref (closes #102) (#104)
Co-authored-by: 小墨 <xiaomooo@shazhou.work>
2026-04-15 19:43:53 +08:00
小橘 🍊 413c496f99 feat(pulse): async effect execution — fire-and-forget executor loop
Co-authored-by: 小墨 <xiaomooo@shazhou.work>
2026-04-15 19:01:42 +08:00
小橘 🍊 4cfb45dc8b fix(pulse-openclaw): update gateway-health test mock for systemctl MainPID
PR #80 changed pgrep to systemctl show --property=MainPID + ps,
but tests still mocked only one execSync call (the ps call).
Now mock systemctl call first, then ps call.

Ref #80

Co-authored-by: 小橘 <xiaoju@shazhou.work>
2026-04-15 18:48:21 +08:00
小橘 🍊 38a94a3daf feat(pulse): remove pendingTasksWatcher polling workaround (Phase 3)
Phase 1 added buildPendingTasksFromEvents (correct event-fold approach).
Phase 2 wired it into rebuildSnapshot/runPulse.
Phase 3 removes the old polling workaround (pending-tasks.ts).

The polling workaround (pending-tasks.ts) was only on feat/task-dispatch-loop
and never merged to main. This commit cleans up the last reference to
pendingTasksWatcher in pending-tasks-projection.ts comments.

Ref #83

Made-with: Cursor

Co-authored-by: 小橘 <xiaoju@shazhou.work>
2026-04-15 18:35:45 +08:00
小橘 🍊 7a8975a8bd feat(pulse): inject task projections into rebuildSnapshot (Phase 2)
rebuildSnapshot now accepts an optional systemStore parameter; when
provided and senseKeys includes 'pending-tasks', buildPendingTasksFromEvents
and buildAgentCapabilityStats are automatically included in the snapshot
as 'pending-tasks' and 'agent-capability-stats' senseKeys.

runPulse automatically passes systemStore to rebuildSnapshot so that
task projections are available to rules without extra configuration.

Closes #89, ref #83

Made-with: Cursor

Co-authored-by: 小橘 <xiaoju@shazhou.work>
2026-04-15 18:22:31 +08:00