feat(ecosystem): Phase 0 — kill-switch, feedback, accessibility, feature flags, telemetry events
Phase 0 of the Agentic AI Roadmap: ecosystem gap fixes. - Add @bytelyst/kill-switch-client integration (web/src/lib/kill-switch.ts) - Wire kill switch check into providers.tsx on app init - Add @bytelyst/feedback-client integration (web/src/lib/feedback.ts) - Add @bytelyst/accessibility to web dependencies - Add 7 agentic feature flags to backend (all default false) - Define 16 telemetry event constants (backend + web) - Add AGENTIC_AI_ROADMAP.md with full Phase 0-E plan + audit findings All 182 backend tests pass. All 394 web tests pass. No breaking changes to existing functionality.
This commit is contained in:
parent
8ca4244855
commit
f3e14e28dd
@ -8,6 +8,14 @@ const registry = createFlagRegistry({
|
|||||||
'routines.enabled': true,
|
'routines.enabled': true,
|
||||||
'households.enabled': true,
|
'households.enabled': true,
|
||||||
'shared_timers.enabled': true,
|
'shared_timers.enabled': true,
|
||||||
|
// ── Agentic AI feature flags (Phase 0.4) ───────────────────
|
||||||
|
'mcp.enabled': false,
|
||||||
|
'planner.enabled': false,
|
||||||
|
'agent_inbox.enabled': false,
|
||||||
|
'tempo.enabled': false,
|
||||||
|
'routine_suggestions.enabled': false,
|
||||||
|
'ai_context_messages.enabled': false,
|
||||||
|
'webhooks.zapier': false,
|
||||||
},
|
},
|
||||||
enabled: config.FEATURE_FLAGS_ENABLED,
|
enabled: config.FEATURE_FLAGS_ENABLED,
|
||||||
});
|
});
|
||||||
|
|||||||
37
backend/src/lib/telemetry-events.ts
Normal file
37
backend/src/lib/telemetry-events.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Telemetry event name constants for all agentic AI features.
|
||||||
|
*
|
||||||
|
* Centralised here so backend routes and web components can reference
|
||||||
|
* a single source of truth for event names (Phase 0.5).
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ── MCP ───────────────────────────────────────────────────────
|
||||||
|
export const MCP_TOOL_EXECUTED = 'mcp.tool_executed';
|
||||||
|
export const MCP_TOOL_FAILED = 'mcp.tool_failed';
|
||||||
|
|
||||||
|
// ── Day Planner ───────────────────────────────────────────────
|
||||||
|
export const PLANNER_DAY_PLANNED = 'planner.day_planned';
|
||||||
|
export const PLANNER_PLAN_APPLIED = 'planner.plan_applied';
|
||||||
|
export const PLANNER_PLAN_REJECTED = 'planner.plan_rejected';
|
||||||
|
|
||||||
|
// ── Agent Inbox ───────────────────────────────────────────────
|
||||||
|
export const AGENT_INBOX_ACTION_APPROVED = 'agent_inbox.action_approved';
|
||||||
|
export const AGENT_INBOX_ACTION_REJECTED = 'agent_inbox.action_rejected';
|
||||||
|
export const AGENT_INBOX_BATCH_APPROVED = 'agent_inbox.batch_approved';
|
||||||
|
|
||||||
|
// ── Tempo Mode ────────────────────────────────────────────────
|
||||||
|
export const TEMPO_DRIFT_DETECTED = 'tempo.drift_detected';
|
||||||
|
export const TEMPO_ADJUSTMENT_APPLIED = 'tempo.adjustment_applied';
|
||||||
|
|
||||||
|
// ── Routine Suggestions ───────────────────────────────────────
|
||||||
|
export const ROUTINE_SUGGEST_SHOWN = 'routine_suggest.shown';
|
||||||
|
export const ROUTINE_SUGGEST_ACCEPTED = 'routine_suggest.accepted';
|
||||||
|
export const ROUTINE_SUGGEST_DISMISSED = 'routine_suggest.dismissed';
|
||||||
|
|
||||||
|
// ── AI Context Messages ───────────────────────────────────────
|
||||||
|
export const AI_CONTEXT_ENRICHED = 'ai_context.enriched';
|
||||||
|
export const AI_CONTEXT_FALLBACK_USED = 'ai_context.fallback_used';
|
||||||
|
|
||||||
|
// ── Webhooks ──────────────────────────────────────────────────
|
||||||
|
export const WEBHOOK_DISPATCHED = 'webhook.dispatched';
|
||||||
|
export const WEBHOOK_FAILED = 'webhook.failed';
|
||||||
666
docs/AGENTIC_AI_ROADMAP.md
Normal file
666
docs/AGENTIC_AI_ROADMAP.md
Normal file
@ -0,0 +1,666 @@
|
|||||||
|
# ChronoMind — Agentic AI Roadmap
|
||||||
|
|
||||||
|
> **Product:** ChronoMind — AI-Powered Contextual Clock & Timer
|
||||||
|
> **Repo:** `learning_ai_clock`
|
||||||
|
> **Created:** March 31, 2026
|
||||||
|
> **Strategy:** Transform ChronoMind from a smart timer app into the **time execution layer** for the agentic AI era.
|
||||||
|
> **Depends on:** Existing backend (port 4011), web engine (373+ Vitest tests), iOS/Android native apps.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current State Summary
|
||||||
|
|
||||||
|
| Layer | Status | Key Files |
|
||||||
|
|-------|--------|-----------|
|
||||||
|
| **Web engine** | 52 lib files, 16 components, 373+ tests | `web/src/lib/*.ts` |
|
||||||
|
| **Backend** | 5 modules + webhooks, port 4011 | `backend/src/modules/` (timers, routines, households, shared-timers, webhooks) |
|
||||||
|
| **iOS** | Full SwiftUI + Watch + Mac + Widgets, 129 XCTests | `ios/` |
|
||||||
|
| **Android** | Jetpack Compose + Wear OS + Glance, 30 JUnit5 tests | `android/` |
|
||||||
|
| **Sync** | `@bytelyst/sync` engine + platform-sync.ts client | `web/src/lib/platform-sync.ts` |
|
||||||
|
| **Webhooks** | Subscription CRUD + dispatcher (HMAC + retries) + 15 event types | `backend/src/modules/webhooks/` |
|
||||||
|
|
||||||
|
### Ecosystem Integration Status
|
||||||
|
|
||||||
|
| @bytelyst/* Package | Status | Notes |
|
||||||
|
|---------------------|--------|-------|
|
||||||
|
| `@bytelyst/fastify-core` | ✅ Used | `createServiceApp` + `startService` |
|
||||||
|
| `@bytelyst/auth` / `fastify-auth` | ✅ Used | JWT verify via `registerOptionalJwtContext` |
|
||||||
|
| `@bytelyst/auth-client` | ✅ Used | `createAuthClient` in `web/src/lib/auth-api.ts` |
|
||||||
|
| `@bytelyst/react-auth` | ✅ Used | `createAuthProvider` in `web/src/lib/auth-context.tsx` |
|
||||||
|
| `@bytelyst/config` / `backend-config` | ✅ Used | `baseBackendConfigSchema` in backend config |
|
||||||
|
| `@bytelyst/datastore` | ✅ Used | Cosmos/memory abstraction in backend |
|
||||||
|
| `@bytelyst/cosmos` | ✅ Used | Container registration |
|
||||||
|
| `@bytelyst/errors` | ✅ Used | Typed HTTP errors |
|
||||||
|
| `@bytelyst/field-encrypt` | ✅ Used | Encryption at rest |
|
||||||
|
| `@bytelyst/backend-flags` | ✅ Used | `createFlagRegistry` in backend |
|
||||||
|
| `@bytelyst/backend-telemetry` | ✅ Used | `createTelemetryBuffer` in backend |
|
||||||
|
| `@bytelyst/feature-flag-client` | ✅ Used | Web client with streaming |
|
||||||
|
| `@bytelyst/telemetry-client` | ✅ Used | Web telemetry via `createWebTelemetry` |
|
||||||
|
| `@bytelyst/diagnostics-client` | ✅ Used | Web diagnostics via `createWebDiagnostics` |
|
||||||
|
| `@bytelyst/api-client` | ✅ Used | `createApiClient` in platform-sync |
|
||||||
|
| `@bytelyst/sync` | ✅ Used | Sync engine in platform-sync.ts |
|
||||||
|
| `@bytelyst/design-tokens` | ✅ Used | `--cm-*` CSS props in globals.css |
|
||||||
|
| `@bytelyst/ui` | ✅ Used | Imported in providers.tsx |
|
||||||
|
| `@bytelyst/kill-switch-client` | ❌ **MISSING** | Every other product has this — critical for prod safety |
|
||||||
|
| `@bytelyst/feedback-client` | ❌ **MISSING** | No in-app feedback mechanism |
|
||||||
|
| `@bytelyst/event-store` | ❌ **MISSING** | Domain events not persisted — needed for webhooks + audit |
|
||||||
|
| `@bytelyst/webhook-dispatch` | ❌ **MISSING** | Backend uses hand-rolled dispatcher instead of shared package |
|
||||||
|
| `@bytelyst/accessibility` | ❌ **MISSING** | Shared accessibility helpers not consumed |
|
||||||
|
| `@bytelyst/celebrations` | ❌ **MISSING** | Streak/plan completion should use shared celebrations |
|
||||||
|
| `@bytelyst/gentle-notifications` | ❌ **MISSING** | Tempo warnings should use shared gentle notifications |
|
||||||
|
| `@bytelyst/ollama-client` | ⬜ Not needed yet | Will be needed for Phase A.5 (AI context messages) |
|
||||||
|
| `@bytelyst/extraction` | ⬜ Not needed yet | Will be needed for Phase A.5 (LLM parsing fallback) |
|
||||||
|
|
||||||
|
### MCP Server Integration (Important)
|
||||||
|
|
||||||
|
The centralized **MCP server** (`learning_ai_common_plat/services/mcp-server`, port 4007) **already has ChronoMind tools**:
|
||||||
|
- `chronomind.timers.create` / `.list` / `.delete`
|
||||||
|
- `chronomind.routines.list` / `.validate`
|
||||||
|
- `chronomind.syncStatus`
|
||||||
|
- `chronomind.sharedTimers.share`
|
||||||
|
- `chronomind.households.list`
|
||||||
|
|
||||||
|
These proxy to `chronomind-backend` (port 4011) via `chronomind-client.ts`.
|
||||||
|
|
||||||
|
**Architecture for new MCP tools:**
|
||||||
|
1. **Backend** (port 4011): Add new REST endpoints (planner, agent-actions, reschedule, availability, context-message)
|
||||||
|
2. **MCP Server** (port 4007): Extend `chronomind-tools.ts` + `chronomind-client.ts` to expose new endpoints as MCP tools
|
||||||
|
3. **Product backend does NOT host its own MCP layer** — all MCP goes through the centralized server
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase Overview
|
||||||
|
|
||||||
|
| Phase | Features | Effort | Timeline |
|
||||||
|
|-------|----------|--------|----------|
|
||||||
|
| **Phase 0** | Ecosystem Gap Fixes (kill-switch, feedback, event-store, accessibility) | Low | ~1 day |
|
||||||
|
| **Phase A** | MCP Backend Endpoints + MCP Server Extension + AI Context Messages | Medium | ~3 days |
|
||||||
|
| **Phase B** | AI Day Planner + Agent Inbox | Medium | ~4 days |
|
||||||
|
| **Phase C** | Webhook Migration to @bytelyst/webhook-dispatch + Zapier Compat | Low | ~1.5 days |
|
||||||
|
| **Phase D** | Smart Routine Suggestions + Tempo Mode | Medium | ~3 days |
|
||||||
|
| **Phase E** | Client-Side Sync Integration (Web ↔ iOS ↔ Android) | High | ~4 days |
|
||||||
|
|
||||||
|
**Total estimated:** ~16.5 working days across 6 phases.
|
||||||
|
**Dependency order:** 0 (first) → A → B → C/D (parallel) → E
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 0 — Ecosystem Gap Fixes
|
||||||
|
|
||||||
|
> **Goal:** Bring ChronoMind into full compliance with the @bytelyst ecosystem before adding new features.
|
||||||
|
> **Why first:** These are production-safety and consistency gaps. Fixing them first prevents new code from repeating the same gaps.
|
||||||
|
|
||||||
|
### 0.1 — Kill Switch Client (Web)
|
||||||
|
|
||||||
|
- [ ] `web/src/lib/kill-switch.ts` — integrate `@bytelyst/kill-switch-client` (commit: )
|
||||||
|
- Same pattern as NomGap/NoteLett: `createKillSwitchClient({ baseUrl, productId, platform })`
|
||||||
|
- Check on app init → if killed, show maintenance banner and disable timer creation
|
||||||
|
- Add `pnpm add @bytelyst/kill-switch-client` to web/package.json
|
||||||
|
- [ ] Wire into `web/src/app/providers.tsx` — init on mount (commit: )
|
||||||
|
|
||||||
|
### 0.2 — Feedback Client (Web)
|
||||||
|
|
||||||
|
- [ ] `web/src/lib/feedback.ts` — integrate `@bytelyst/feedback-client` (commit: )
|
||||||
|
- `createFeedbackClient({ baseUrl, productId, getAccessToken })`
|
||||||
|
- [ ] Add feedback button to settings page (or floating FAB) (commit: )
|
||||||
|
- Use `@bytelyst/ui` FeedbackButton component if available, else minimal custom
|
||||||
|
|
||||||
|
### 0.3 — Accessibility Package (Web)
|
||||||
|
|
||||||
|
- [ ] Integrate `@bytelyst/accessibility` helpers where applicable (commit: )
|
||||||
|
- Focus trap for modals (CreateTimerModal, AlarmOverlay)
|
||||||
|
- Screen reader announcements for timer state changes
|
||||||
|
- Ensure all `--cm-*` color tokens meet WCAG AA contrast
|
||||||
|
|
||||||
|
### 0.4 — Feature Flags for New Features (Backend)
|
||||||
|
|
||||||
|
- [ ] Add feature flags for all new agentic features to `backend/src/lib/feature-flags.ts` (commit: )
|
||||||
|
```
|
||||||
|
'mcp.enabled': false,
|
||||||
|
'planner.enabled': false,
|
||||||
|
'agent_inbox.enabled': false,
|
||||||
|
'tempo.enabled': false,
|
||||||
|
'routine_suggestions.enabled': false,
|
||||||
|
'ai_context_messages.enabled': false,
|
||||||
|
'webhooks.zapier': false,
|
||||||
|
```
|
||||||
|
- [ ] Gate all new backend routes behind their respective flags (commit: )
|
||||||
|
|
||||||
|
### 0.5 — Telemetry Events for New Features
|
||||||
|
|
||||||
|
- [ ] Define telemetry event names for all new features (commit: )
|
||||||
|
- `mcp.tool_executed`, `mcp.tool_failed`
|
||||||
|
- `planner.day_planned`, `planner.plan_applied`, `planner.plan_rejected`
|
||||||
|
- `agent_inbox.action_approved`, `agent_inbox.action_rejected`, `agent_inbox.batch_approved`
|
||||||
|
- `tempo.drift_detected`, `tempo.adjustment_applied`
|
||||||
|
- `routine_suggest.shown`, `routine_suggest.accepted`, `routine_suggest.dismissed`
|
||||||
|
- `ai_context.enriched`, `ai_context.fallback_used`
|
||||||
|
- `webhook.dispatched`, `webhook.failed`
|
||||||
|
- [ ] Add `trackEvent()` calls in backend routes and web components as they're built (ongoing through all phases)
|
||||||
|
|
||||||
|
### Phase 0 Exit Criteria
|
||||||
|
|
||||||
|
- [ ] Kill switch client initialized on web app startup
|
||||||
|
- [ ] Feedback client wired with at least one entry point
|
||||||
|
- [ ] Accessibility helpers applied to modals
|
||||||
|
- [ ] 7 new feature flags registered (all default `false`)
|
||||||
|
- [ ] Telemetry event names defined
|
||||||
|
- [ ] All existing tests still pass
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase A — MCP Backend Endpoints + MCP Server Extension + AI Context Messages
|
||||||
|
|
||||||
|
> **Goal:** Let any AI agent create, query, reschedule, and dismiss timers via the centralized MCP server. Upgrade context messages with LLM fallback.
|
||||||
|
> **Why first:** This is the single most differentiating feature for 2026. It positions ChronoMind as infrastructure, not just an app.
|
||||||
|
|
||||||
|
**Architecture:** Two-layer approach (see "MCP Server Integration" above):
|
||||||
|
- **Layer 1 (this repo):** New REST endpoints in `backend/src/modules/` — standard Fastify routes
|
||||||
|
- **Layer 2 (common-plat):** Extend `mcp-server/src/modules/chronomind/` to proxy new endpoints as MCP tools
|
||||||
|
|
||||||
|
### A.1 — New Backend REST Endpoints (chronomind-backend, port 4011)
|
||||||
|
|
||||||
|
Add 4 new endpoint groups to the product backend. These are plain REST routes — NOT MCP tools:
|
||||||
|
|
||||||
|
- [ ] `backend/src/modules/timers/routes.ts` — add reschedule + availability endpoints (commit: )
|
||||||
|
- `POST /api/timers/:id/reschedule` — shift timer by delta or to new time
|
||||||
|
- `GET /api/timers/availability?start=<ISO>&end=<ISO>&minSlotMinutes=<N>` — find free slots
|
||||||
|
- Gate behind `isFeatureEnabled('mcp.enabled')` flag
|
||||||
|
- [ ] `backend/src/modules/routines/routes.ts` — add start-routine endpoint (commit: )
|
||||||
|
- `POST /api/routines/:id/start` — start a routine template (changes status → running)
|
||||||
|
- Gate behind `isFeatureEnabled('mcp.enabled')` flag
|
||||||
|
- [ ] Tests for new timer + routine endpoints (commit: )
|
||||||
|
|
||||||
|
### A.2 — Agent Action Audit Trail (New Backend Module)
|
||||||
|
|
||||||
|
- [ ] `backend/src/modules/agent-actions/types.ts` — Zod schemas (commit: )
|
||||||
|
- Fields: `id`, `userId`, `productId`, `actorId`, `actorType` (agent/user/mcp), `toolName`, `actionType`, `state` (proposed/approved/applied/rejected), `reason`, `payload`, `createdAt`
|
||||||
|
- Every Cosmos doc includes `productId: "chronomind"`
|
||||||
|
- [ ] `backend/src/modules/agent-actions/repository.ts` — CRUD (commit: )
|
||||||
|
- Container: `cm_agent_actions`, partition: `/userId`
|
||||||
|
- Uses `@bytelyst/datastore` `getCollection()` — never direct Cosmos SDK
|
||||||
|
- [ ] `backend/src/modules/agent-actions/routes.ts` — REST endpoints (commit: )
|
||||||
|
- `GET /api/agent-actions` — list (filterable by state, actorId, toolName)
|
||||||
|
- `POST /api/agent-actions/:id/approve` — approve proposed action
|
||||||
|
- `POST /api/agent-actions/:id/reject` — reject proposed action
|
||||||
|
- `POST /api/agent-actions/batch-approve` — batch approve by actorId
|
||||||
|
- Gate behind `isFeatureEnabled('agent_inbox.enabled')` flag
|
||||||
|
- [ ] Register `agentActionRoutes` in `server.ts` (commit: )
|
||||||
|
- [ ] Register `cm_agent_actions` container in `cosmos-init.ts` (commit: )
|
||||||
|
- [ ] Agent action tests (commit: )
|
||||||
|
|
||||||
|
### A.3 — Extend Centralized MCP Server (common-plat)
|
||||||
|
|
||||||
|
Extend `learning_ai_common_plat/services/mcp-server/src/modules/chronomind/`:
|
||||||
|
|
||||||
|
- [ ] `chronomind-client.ts` — add client functions for new endpoints (commit: )
|
||||||
|
- `chronomindTimerReschedule(timerId, { delta?, targetTime? }, opts)`
|
||||||
|
- `chronomindTimerAvailability({ start, end, minSlotMinutes }, opts)`
|
||||||
|
- `chronomindRoutineStart(routineId, opts)`
|
||||||
|
- `chronomindAgentActionsList(filters, opts)`
|
||||||
|
- `chronomindAgentActionApprove(actionId, opts)`
|
||||||
|
- [ ] `chronomind-tools.ts` — register 5 new MCP tools (commit: )
|
||||||
|
- `chronomind.timers.reschedule` — shift timer by delta or to new time
|
||||||
|
- `chronomind.timers.availability` — find free time slots
|
||||||
|
- `chronomind.routines.start` — start a routine template
|
||||||
|
- `chronomind.agentActions.list` — list pending agent actions
|
||||||
|
- `chronomind.agentActions.approve` — approve a proposed action
|
||||||
|
- All write tools record agent action via A.2 endpoints, passing `actorId`, `actorType: 'mcp'`, `reason`
|
||||||
|
- [ ] MCP tool tests (commit: )
|
||||||
|
|
||||||
|
### A.4 — Context-Aware AI Messages (LLM upgrade)
|
||||||
|
|
||||||
|
Upgrade `web/src/lib/context-messages.ts` with optional LLM enrichment:
|
||||||
|
|
||||||
|
- [ ] `backend/src/lib/ai-context.ts` — LLM-powered context message generator (commit: )
|
||||||
|
- Accepts: timer label, category, urgency, user's recent timers, time of day
|
||||||
|
- Returns: enriched prep message (e.g., "Your standup is in 30m — you had 3 unresolved items from yesterday")
|
||||||
|
- Falls back to existing keyword rules if LLM unavailable
|
||||||
|
- Uses `@bytelyst/extraction` client to call extraction-service `timer-context` task, or direct `@bytelyst/ollama-client`
|
||||||
|
- Gate behind `isFeatureEnabled('ai_context_messages.enabled')` flag
|
||||||
|
- [ ] Backend route: `POST /api/context-message` (commit: )
|
||||||
|
- [ ] `web/src/lib/context-messages.ts` — add `fetchEnrichedMessage()` that calls backend endpoint (commit: )
|
||||||
|
- Graceful degradation: keyword rules remain primary, LLM messages shown as "AI suggestion"
|
||||||
|
- Track `ai_context.enriched` or `ai_context.fallback_used` telemetry events
|
||||||
|
- [ ] Tests for AI context generation (commit: )
|
||||||
|
|
||||||
|
### Phase A Exit Criteria
|
||||||
|
|
||||||
|
- [ ] 3 new REST endpoints in chronomind-backend (reschedule, availability, routine start)
|
||||||
|
- [ ] Agent action module with full CRUD + batch approve
|
||||||
|
- [ ] 5 new MCP tools registered in centralized mcp-server
|
||||||
|
- [ ] Context messages enriched by LLM with keyword fallback
|
||||||
|
- [ ] All gated behind feature flags (default off)
|
||||||
|
- [ ] Telemetry events tracked for all new operations
|
||||||
|
- [ ] All existing tests still pass (`pnpm test` in backend + web + mcp-server)
|
||||||
|
- [ ] No breaking changes to existing timer/routine APIs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase B — AI Day Planner + Agent Inbox UI
|
||||||
|
|
||||||
|
> **Goal:** "Plan my day" command + web UI for reviewing agent-proposed timers.
|
||||||
|
> **Depends on:** Phase A (MCP tools + agent actions module).
|
||||||
|
|
||||||
|
### B.1 — Day Planner Engine (Backend)
|
||||||
|
|
||||||
|
- [ ] `backend/src/modules/planner/types.ts` — Zod schemas for plan requests/responses (commit: )
|
||||||
|
- `PlanDayRequest`: activities array (label, duration, priority, category, constraints)
|
||||||
|
- `PlanDayResponse`: proposed timeline (timer array with computed times, urgency, cascades)
|
||||||
|
- `PlanConstraint`: "after 2pm", "before lunch", "back-to-back with X", "needs 15m break between"
|
||||||
|
- Every Cosmos doc includes `productId: "chronomind"`
|
||||||
|
- [ ] `backend/src/modules/planner/engine.ts` — deterministic slot-fitting algorithm (commit: )
|
||||||
|
- Step 1: Load existing timers for the target day
|
||||||
|
- Step 2: Compute available slots (gaps between timers)
|
||||||
|
- Step 3: Fit requested activities into slots respecting constraints + priority order
|
||||||
|
- Step 4: Assign urgency based on keywords/category (reuse `context-messages` rules)
|
||||||
|
- Step 5: Assign cascade presets based on urgency (reuse `cascade.ts` defaults)
|
||||||
|
- Step 6: Add prep time buffers (reuse `prep-time.ts` logic)
|
||||||
|
- Returns: ordered timer list with `state: 'proposed'` — not yet committed
|
||||||
|
- [ ] `backend/src/modules/planner/routes.ts` — REST endpoints (commit: )
|
||||||
|
- `POST /api/planner/plan-day` — generate proposed plan (does NOT create timers)
|
||||||
|
- `POST /api/planner/apply` — commit proposed plan (creates timers + agent action records)
|
||||||
|
- `POST /api/planner/replan` — modify an existing plan with new constraints
|
||||||
|
- Gate behind `isFeatureEnabled('planner.enabled')` flag
|
||||||
|
- Track `planner.day_planned` / `planner.plan_applied` telemetry events
|
||||||
|
- Use `req.log` for logging — never `console.log`
|
||||||
|
- [ ] Planner engine tests (commit: )
|
||||||
|
- Test: empty day → all activities fit
|
||||||
|
- Test: busy day → overflow reported with alternatives
|
||||||
|
- Test: constraints respected (after/before/back-to-back)
|
||||||
|
- Test: prep time buffers added correctly
|
||||||
|
|
||||||
|
### B.2 — Day Planner NL Interface
|
||||||
|
|
||||||
|
- [ ] Backend NL → plan translation (commit: )
|
||||||
|
- `POST /api/planner/plan-nl` — accepts natural language ("prep slides 2h, standup 30m, lunch, deep coding 3h — plan my afternoon")
|
||||||
|
- Parses via `@bytelyst/extraction` client to extraction-service, or regex-based NL parser (ported from web `nl-parser.ts`)
|
||||||
|
- Returns same `PlanDayResponse` as structured endpoint
|
||||||
|
- [ ] Tests for NL plan parsing (commit: )
|
||||||
|
|
||||||
|
### B.2b — Extend MCP Server with Planner Tools (common-plat)
|
||||||
|
|
||||||
|
- [ ] `chronomind-client.ts` — add planner client functions (commit: )
|
||||||
|
- `chronomindPlanDay(request, opts)`, `chronomindApplyPlan(planId, opts)`, `chronomindPlanNl(text, opts)`
|
||||||
|
- [ ] `chronomind-tools.ts` — register 2 new MCP tools (commit: )
|
||||||
|
- `chronomind.planner.planDay` — generate proposed plan from activities
|
||||||
|
- `chronomind.planner.planNl` — generate proposed plan from natural language
|
||||||
|
|
||||||
|
### B.3 — Agent Inbox (Web UI)
|
||||||
|
|
||||||
|
- [ ] `web/src/components/AgentInbox.tsx` — review queue for agent-proposed timers (commit: )
|
||||||
|
- Use `@bytelyst/ui` components: Card, Badge, Button, Toast — never build custom equivalents
|
||||||
|
- List all `state: 'proposed'` agent actions
|
||||||
|
- Show: agent name, reason, proposed timer details, timestamp
|
||||||
|
- Actions: Approve, Reject, Modify then Approve
|
||||||
|
- Batch approve: "Accept all from [agent]"
|
||||||
|
- Trust level badge per agent (auto-approve toggle)
|
||||||
|
- Every interactive element must have `aria-label` or visible text label
|
||||||
|
- Colors via `--cm-*` CSS custom properties — never hardcode hex values
|
||||||
|
- Track `agent_inbox.action_approved` / `.action_rejected` / `.batch_approved` telemetry
|
||||||
|
- [ ] `web/src/lib/agent-client.ts` — typed API client for agent actions (commit: )
|
||||||
|
- `listAgentActions(filters)`, `approveAction(id)`, `rejectAction(id)`, `batchApprove(actorId)`
|
||||||
|
- [ ] `web/src/app/(app)/inbox/page.tsx` — agent inbox page route (commit: )
|
||||||
|
- [ ] Sidebar navigation: add "Inbox" nav item with unread badge count (commit: )
|
||||||
|
- [ ] Tests for agent inbox (commit: )
|
||||||
|
|
||||||
|
### B.4 — Day Planner UI (Web)
|
||||||
|
|
||||||
|
- [ ] `web/src/components/DayPlanner.tsx` — plan-my-day interface (commit: )
|
||||||
|
- Use `@bytelyst/ui` components: Card, Button, Badge, Toast — never build custom equivalents
|
||||||
|
- NL input bar: "Plan my afternoon" → calls `/api/planner/plan-nl`
|
||||||
|
- Structured form fallback: add activities with duration + constraints
|
||||||
|
- Timeline preview: visual preview of proposed plan alongside existing timers
|
||||||
|
- "Apply Plan" button → creates all timers at once + fire `@bytelyst/celebrations` confetti on success
|
||||||
|
- "Edit" per-item before applying (change time, urgency, cascade)
|
||||||
|
- Overflow warnings: "3 activities don't fit — 2h overflow. Suggestions: extend day, shorten activities, move to tomorrow"
|
||||||
|
- Every interactive element must have `aria-label` or visible text label
|
||||||
|
- Colors via `--cm-*` CSS custom properties — never hardcode hex values
|
||||||
|
- [ ] `web/src/lib/planner-client.ts` — typed API client for planner (commit: )
|
||||||
|
- [ ] `web/src/app/(app)/planner/page.tsx` — planner page route (commit: )
|
||||||
|
- [ ] Sidebar navigation: add "Plan Day" nav item (commit: )
|
||||||
|
|
||||||
|
### Phase B Exit Criteria
|
||||||
|
|
||||||
|
- [ ] `POST /api/planner/plan-day` returns valid proposed timeline
|
||||||
|
- [ ] NL input parses multi-activity requests correctly
|
||||||
|
- [ ] 2 new MCP tools in centralized mcp-server (planDay, planNl)
|
||||||
|
- [ ] Agent inbox shows proposed timers with approve/reject/batch actions
|
||||||
|
- [ ] Day planner UI shows timeline preview with apply/edit controls
|
||||||
|
- [ ] All UI uses `@bytelyst/ui` components + `--cm-*` tokens + `aria-label`
|
||||||
|
- [ ] All gated behind feature flags, telemetry events tracked
|
||||||
|
- [ ] All existing tests still pass
|
||||||
|
- [ ] New tests cover planner engine, NL parsing, agent client
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase C — Webhook Event Wiring + @bytelyst/event-store + Zapier Compatibility
|
||||||
|
|
||||||
|
> **Goal:** Wire lifecycle events into the existing webhook dispatcher, add domain event persistence via `@bytelyst/event-store`, and add Zapier-compatible endpoints.
|
||||||
|
> **Can run in parallel with Phase D.**
|
||||||
|
|
||||||
|
**Note:** The webhook dispatcher is already **fully functional** — HMAC-SHA256 signing, exponential backoff retries, event logging, auto-disable after failures. No hardening needed. The gaps are: (1) no lifecycle events actually call the dispatcher, (2) no domain event persistence, (3) no Zapier-compatible format.
|
||||||
|
|
||||||
|
### C.1 — Domain Event Store Integration
|
||||||
|
|
||||||
|
- [ ] Add `@bytelyst/event-store` dependency to backend (commit: )
|
||||||
|
- [ ] `backend/src/lib/events.ts` — create event store singleton + `emitDomainEvent()` helper (commit: )
|
||||||
|
- Uses `MemoryEventStore` (or `FileEventStore` for local persistence)
|
||||||
|
- All domain events flow through this — webhook dispatcher subscribes to it
|
||||||
|
- Event shape: `{ type, userId, productId, payload, timestamp, correlationId }`
|
||||||
|
|
||||||
|
### C.2 — Wire Lifecycle Events into Dispatcher
|
||||||
|
|
||||||
|
This is the main missing piece — timer/routine state changes should emit events:
|
||||||
|
|
||||||
|
- [ ] `backend/src/modules/timers/routes.ts` — emit events on state transitions (commit: )
|
||||||
|
- `timer.created` on POST create
|
||||||
|
- `timer.fired` / `timer.completed` / `timer.dismissed` / `timer.snoozed` / `timer.paused` / `timer.resumed` on state change
|
||||||
|
- Each event calls `emitDomainEvent()` which fans out to webhook dispatcher
|
||||||
|
- Track `webhook.dispatched` / `webhook.failed` telemetry
|
||||||
|
- [ ] `backend/src/modules/routines/routes.ts` — emit events (commit: )
|
||||||
|
- `routine.started` / `routine.completed` / `routine.step_completed`
|
||||||
|
- [ ] `backend/src/modules/households/routes.ts` — emit events (commit: )
|
||||||
|
- `household.member_joined` / `household.member_left`
|
||||||
|
- [ ] `backend/src/modules/shared-timers/routes.ts` — emit events (commit: )
|
||||||
|
- `shared_timer.created` / `shared_timer.fired` / `shared_timer.acknowledged`
|
||||||
|
- [ ] Tests for event emission + webhook dispatch integration (commit: )
|
||||||
|
|
||||||
|
### C.3 — Zapier-Compatible Webhook Format
|
||||||
|
|
||||||
|
- [ ] Zapier REST hook endpoints (commit: )
|
||||||
|
- `GET /api/webhooks/zapier/subscribe` — Zapier registers its callback URL
|
||||||
|
- `DELETE /api/webhooks/zapier/unsubscribe` — Zapier unregisters
|
||||||
|
- `GET /api/webhooks/zapier/sample` — sample payloads per event type (Zapier trigger testing)
|
||||||
|
- Gate behind `isFeatureEnabled('webhooks.zapier')` flag
|
||||||
|
- [ ] Document webhook payload schemas in `docs/WEBHOOK_API.md` (commit: )
|
||||||
|
|
||||||
|
### C.4 — Webhook Management UI (Web)
|
||||||
|
|
||||||
|
- [ ] `web/src/components/WebhookManager.tsx` — manage webhook subscriptions (commit: )
|
||||||
|
- Use `@bytelyst/ui` components: Card, Button, Badge, Toast
|
||||||
|
- Create subscription: URL, secret, event types (multi-select from 15 event types)
|
||||||
|
- List subscriptions with status (active, paused, failed)
|
||||||
|
- Delivery log per subscription
|
||||||
|
- Test button: fire a test event
|
||||||
|
- Every interactive element must have `aria-label` or visible text label
|
||||||
|
- Colors via `--cm-*` CSS custom properties
|
||||||
|
- [ ] `web/src/lib/webhook-client.ts` — typed API client (commit: )
|
||||||
|
- [ ] Add to settings page or new `/settings/webhooks` route (commit: )
|
||||||
|
|
||||||
|
### Phase C Exit Criteria
|
||||||
|
|
||||||
|
- [ ] Domain events emitted on all 15 lifecycle state changes
|
||||||
|
- [ ] Events persisted via `@bytelyst/event-store`
|
||||||
|
- [ ] Webhook dispatcher called automatically on domain events
|
||||||
|
- [ ] Zapier subscribe/unsubscribe/sample endpoints work
|
||||||
|
- [ ] Webhook management UI in web app with `@bytelyst/ui` components
|
||||||
|
- [ ] All gated behind feature flags, telemetry events tracked
|
||||||
|
- [ ] All existing tests still pass
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase D — Smart Routine Suggestions + Tempo Mode
|
||||||
|
|
||||||
|
> **Goal:** Proactive intelligence — suggest routines from patterns + adjust pacing when behind schedule.
|
||||||
|
> **Can run in parallel with Phase C.**
|
||||||
|
|
||||||
|
### D.1 — Smart Routine Suggestions (Web Engine)
|
||||||
|
|
||||||
|
Build on existing `adaptive-snooze.ts` pattern detection approach:
|
||||||
|
|
||||||
|
- [ ] `web/src/lib/routine-suggestions.ts` — pattern detection engine (commit: )
|
||||||
|
- Scan timer history for recurring sequences (same labels/categories within similar time windows)
|
||||||
|
- Group by day-of-week patterns (weekday vs weekend)
|
||||||
|
- Min threshold: 3 occurrences of same sequence before suggesting
|
||||||
|
- Output: `RoutineSuggestion { name, steps[], confidence, occurrences, dayPattern }`
|
||||||
|
- Pure TS engine — no React imports, testable with Vitest
|
||||||
|
- [ ] `web/src/lib/routine-suggestions.test.ts` — tests (commit: )
|
||||||
|
- Test: 3x "coffee → standup → email" on weekdays → suggests "Weekday Morning" routine
|
||||||
|
- Test: fewer than 3 occurrences → no suggestion
|
||||||
|
- Test: weekend vs weekday patterns differentiated
|
||||||
|
- Test: overlapping sequences handled (longest match wins)
|
||||||
|
|
||||||
|
### D.2 — Routine Suggestions UI
|
||||||
|
|
||||||
|
- [ ] `web/src/components/RoutineSuggestionCard.tsx` — dismissable suggestion card (commit: )
|
||||||
|
- Use `@bytelyst/ui` components: Card, Button, Badge — never build custom equivalents
|
||||||
|
- Shows on dashboard when suggestions available
|
||||||
|
- "Create Routine" → opens RoutineEditor pre-filled + fire `@bytelyst/celebrations` on creation
|
||||||
|
- "Dismiss" → hide for 7 days
|
||||||
|
- "Never suggest this" → permanent dismiss for this pattern
|
||||||
|
- Every interactive element must have `aria-label` or visible text label
|
||||||
|
- Colors via `--cm-*` CSS custom properties
|
||||||
|
- Track `routine_suggest.shown` / `.accepted` / `.dismissed` telemetry
|
||||||
|
- [ ] Wire into `Dashboard.tsx` below the quick timer bar (commit: )
|
||||||
|
- [ ] Store dismissed suggestions in localStorage (commit: )
|
||||||
|
|
||||||
|
### D.3 — Tempo Mode Engine (Web)
|
||||||
|
|
||||||
|
- [ ] `web/src/lib/tempo.ts` — pace monitoring engine (commit: )
|
||||||
|
- Track: actual timer completion/dismissal times vs scheduled times
|
||||||
|
- Detect: running behind by >10 minutes (configurable threshold)
|
||||||
|
- Generate `TempoSuggestion`:
|
||||||
|
- Option A: Compress next N timers by X minutes each
|
||||||
|
- Option B: Skip lowest-priority timer
|
||||||
|
- Option C: Push remaining timers back by delay amount
|
||||||
|
- Option D: Extend day (shift evening timers)
|
||||||
|
- Consider energy/mood if check-in data available
|
||||||
|
- Pure TS engine — no React imports, testable with Vitest
|
||||||
|
- Gate behind `isFeatureEnabled('tempo.enabled')` (checked in UI, engine is pure)
|
||||||
|
- [ ] `web/src/lib/tempo.test.ts` — tests (commit: )
|
||||||
|
- Test: 20 min behind → 3 options generated
|
||||||
|
- Test: on-time → no suggestions
|
||||||
|
- Test: critical timers never suggested for compression
|
||||||
|
- Test: mood data influences suggestions ("low energy → suggest skip gym")
|
||||||
|
|
||||||
|
### D.4 — Tempo Mode UI
|
||||||
|
|
||||||
|
- [ ] `web/src/components/TempoBar.tsx` — pace indicator bar (commit: )
|
||||||
|
- Use `@bytelyst/ui` Badge for status + `@bytelyst/gentle-notifications` for soft pace warnings
|
||||||
|
- Shows current pace: "On track", "10 min behind", "30 min behind"
|
||||||
|
- Color: green → yellow → red based on delay (using `--cm-*` tokens)
|
||||||
|
- Expandable: shows suggestion options when behind
|
||||||
|
- Actions: accept suggestion → auto-apply reschedule via `POST /api/timers/:id/reschedule`
|
||||||
|
- Every interactive element must have `aria-label` or visible text label
|
||||||
|
- Track `tempo.drift_detected` / `tempo.adjustment_applied` telemetry
|
||||||
|
- [ ] Wire into Dashboard header area (commit: )
|
||||||
|
- [ ] Gentle notification when pace shifts from green to yellow (via `@bytelyst/gentle-notifications`) (commit: )
|
||||||
|
|
||||||
|
### Phase D Exit Criteria
|
||||||
|
|
||||||
|
- [ ] Routine suggestions detect patterns from timer history
|
||||||
|
- [ ] Suggestion card appears on dashboard with create/dismiss actions using `@bytelyst/ui`
|
||||||
|
- [ ] Tempo engine detects schedule drift and generates adjustment options
|
||||||
|
- [ ] Tempo bar shows pace status with actionable suggestions
|
||||||
|
- [ ] Gentle notifications for pace warnings via `@bytelyst/gentle-notifications`
|
||||||
|
- [ ] Celebrations on routine creation via `@bytelyst/celebrations`
|
||||||
|
- [ ] All gated behind feature flags, telemetry events tracked
|
||||||
|
- [ ] All existing tests still pass
|
||||||
|
- [ ] New engine tests cover edge cases
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase E — Client-Side Sync Integration
|
||||||
|
|
||||||
|
> **Goal:** Make cross-device sync actually work end-to-end (Web ↔ Backend ↔ iOS ↔ Android).
|
||||||
|
> **Depends on:** All other phases (uses the APIs they create).
|
||||||
|
|
||||||
|
### E.1 — Web Sync Manager Activation
|
||||||
|
|
||||||
|
`platform-sync.ts` already has full sync code. Wire it into the app lifecycle:
|
||||||
|
|
||||||
|
- [ ] `web/src/lib/use-sync.ts` — verify/fix the React hook that triggers sync (commit: )
|
||||||
|
- Auto-sync on app launch (if authenticated + sync enabled)
|
||||||
|
- Auto-sync on timer create/update/delete (debounced, 5s)
|
||||||
|
- Manual sync button in settings
|
||||||
|
- Sync status indicator: "Last synced 2m ago" / "Syncing..." / "Offline (3 queued)"
|
||||||
|
- Handle pulled timers: merge into Zustand store (upsert by ID, respect syncVersion)
|
||||||
|
- Handle conflicts: server-wins for now, show conflict toast
|
||||||
|
- [ ] `web/src/app/(app)/settings/page.tsx` — add sync section (commit: )
|
||||||
|
- Toggle: "Enable cross-device sync"
|
||||||
|
- Sign in / sign out (via `@bytelyst/react-auth`)
|
||||||
|
- Sync status display
|
||||||
|
- "Sync Now" button
|
||||||
|
- "Clear local data" button
|
||||||
|
- [ ] Web sync integration tests (commit: )
|
||||||
|
|
||||||
|
### E.2 — iOS Sync Manager
|
||||||
|
|
||||||
|
- [ ] `ios/ChronoMind/Shared/Cloud/PlatformSyncManager.swift` — verify/complete REST sync client (commit: )
|
||||||
|
- Uses existing `BLSyncEngine` from ByteLystPlatformSDK (or direct REST if SDK not ready)
|
||||||
|
- Pull delta: `GET /timers/sync?since=<ISO>` + `GET /routines/sync?since=<ISO>`
|
||||||
|
- Push batch: `POST /timers/batch` + `POST /routines/batch`
|
||||||
|
- Map between `CMTimer` ↔ `SyncTimerDTO` (same shape as web DTOs)
|
||||||
|
- Offline queue: persist pending changes in UserDefaults
|
||||||
|
- Auto-sync on app foreground + after timer changes
|
||||||
|
- [ ] Wire into `TimerStore.swift` — sync on state changes (commit: )
|
||||||
|
- [ ] Settings UI: sync toggle + sign in + last sync time (commit: )
|
||||||
|
- [ ] iOS sync tests (commit: )
|
||||||
|
|
||||||
|
### E.3 — Android Sync Manager
|
||||||
|
|
||||||
|
- [ ] `android/app/.../sync/SyncRepository.kt` — verify/complete REST sync client (commit: )
|
||||||
|
- Same delta sync + batch upsert pattern as iOS/web
|
||||||
|
- Map between `TimerEntity` ↔ `SyncTimerDTO`
|
||||||
|
- Offline queue in Room database (or SharedPreferences)
|
||||||
|
- WorkManager for background sync (periodic, 15m interval)
|
||||||
|
- [ ] Wire into `TimerViewModel.kt` — sync on state changes (commit: )
|
||||||
|
- [ ] Settings screen: sync toggle + sign in + last sync time (commit: )
|
||||||
|
- [ ] Android sync tests (commit: )
|
||||||
|
|
||||||
|
### E.4 — Auth Flow (All Platforms)
|
||||||
|
|
||||||
|
- [ ] Web: sign in / register via `@bytelyst/react-auth` + platform-service (commit: )
|
||||||
|
- [ ] iOS: sign in via `BLAuthClient` (commit: )
|
||||||
|
- [ ] Android: sign in via `BLAuthClient` Kotlin SDK (commit: )
|
||||||
|
- [ ] Verify JWT token flow: login → access token → sync API calls → token refresh (commit: )
|
||||||
|
|
||||||
|
### Phase E Exit Criteria
|
||||||
|
|
||||||
|
- [ ] Create timer on web → appears on iOS and Android after sync
|
||||||
|
- [ ] Create timer on iOS → appears on web and Android after sync
|
||||||
|
- [ ] Offline changes queue and flush when connectivity restored
|
||||||
|
- [ ] Auth flow works on all 3 platforms
|
||||||
|
- [ ] No data loss during sync (conflict resolution: server-wins)
|
||||||
|
- [ ] All existing tests still pass on all platforms
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verification Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ── Backend ────────────────────────────────────────
|
||||||
|
cd backend && pnpm test && pnpm run typecheck && pnpm run build
|
||||||
|
|
||||||
|
# ── Web ────────────────────────────────────────────
|
||||||
|
cd web && pnpm test && pnpm run typecheck && pnpm run build
|
||||||
|
|
||||||
|
# ── iOS ────────────────────────────────────────────
|
||||||
|
cd ios && xcodegen generate && xcodebuild -scheme ChronoMind -sdk iphonesimulator build
|
||||||
|
|
||||||
|
# ── Android ────────────────────────────────────────
|
||||||
|
cd android && ./gradlew :app:assembleDebug && ./gradlew :app:test
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Commit Convention
|
||||||
|
|
||||||
|
All commits follow: `type(scope): description`
|
||||||
|
|
||||||
|
| Scope | Examples |
|
||||||
|
|-------|---------|
|
||||||
|
| `ecosystem` | Kill switch, feedback, accessibility, feature flags, telemetry (Phase 0) |
|
||||||
|
| `mcp` | Backend REST endpoints, MCP server tool extensions (Phase A) |
|
||||||
|
| `agent-actions` | Agent action audit trail module (Phase A) |
|
||||||
|
| `ai-context` | LLM-powered context messages (Phase A) |
|
||||||
|
| `planner` | Day planner engine, NL interface, routes (Phase B) |
|
||||||
|
| `agent-inbox` | Agent inbox UI, client (Phase B) |
|
||||||
|
| `webhook` | Event wiring, Zapier compat, management UI (Phase C) |
|
||||||
|
| `events` | Domain event store integration (Phase C) |
|
||||||
|
| `routine-suggest` | Pattern detection engine, suggestion UI (Phase D) |
|
||||||
|
| `tempo` | Tempo engine, pace bar UI (Phase D) |
|
||||||
|
| `sync` | Client-side sync managers (web, iOS, Android) (Phase E) |
|
||||||
|
| `auth` | Auth flow integration (Phase E) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Risk Mitigation
|
||||||
|
|
||||||
|
| Risk | Mitigation |
|
||||||
|
|------|------------|
|
||||||
|
| MCP tools break existing timer API | New backend endpoints are additive; MCP tools proxy via centralized mcp-server — no coupling |
|
||||||
|
| Planner engine conflicts with manual timers | Plans are **proposed first**, user must approve before creating |
|
||||||
|
| Webhook dispatch overloads | Rate limit: max 100 webhooks/min per user, exponential backoff (already implemented in dispatcher) |
|
||||||
|
| Sync conflicts lose data | Server-wins strategy + conflict toast notification, full export always available |
|
||||||
|
| LLM context messages slow | LLM is optional enhancement — keyword rules remain primary with <1ms latency |
|
||||||
|
| Phase E scope creep | Auth flow is minimal: login/token/refresh only. No registration, password reset, OAuth in v1 |
|
||||||
|
| New features break existing | All 7 new features gated behind feature flags (default `false`). Can ship incrementally |
|
||||||
|
| Missing ecosystem packages | Phase 0 fills all gaps before new code — prevents cascading omissions |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Audit Findings (Ecosystem Gap Review)
|
||||||
|
|
||||||
|
Systematic audit against `learning_ai_common_plat/packages/` (58 packages) revealed:
|
||||||
|
|
||||||
|
### Already Correct ✅
|
||||||
|
|
||||||
|
- Auth pipeline: `@bytelyst/auth-client` + `@bytelyst/react-auth` + `registerOptionalJwtContext` — fully wired
|
||||||
|
- Backend config: `@bytelyst/backend-config` + `backend-flags` + `backend-telemetry` — all used
|
||||||
|
- Web clients: `@bytelyst/feature-flag-client` + `telemetry-client` + `diagnostics-client` — all integrated
|
||||||
|
- Data layer: `@bytelyst/datastore` + `cosmos` + `field-encrypt` — all used in backend
|
||||||
|
- Sync: `@bytelyst/sync` + `api-client` — integrated in `platform-sync.ts`
|
||||||
|
- Design: `@bytelyst/design-tokens` (`--cm-*` CSS props) + `@bytelyst/ui` — both in web
|
||||||
|
- Product config: `PRODUCT_ID = 'chronomind'` properly sourced from `auth-api.ts`, not hardcoded in backend
|
||||||
|
|
||||||
|
### Gaps Fixed in This Roadmap ❌ → ✅
|
||||||
|
|
||||||
|
| Gap | Severity | Fixed In |
|
||||||
|
|-----|----------|----------|
|
||||||
|
| No `@bytelyst/kill-switch-client` | **Critical** | Phase 0.1 |
|
||||||
|
| No `@bytelyst/feedback-client` | Medium | Phase 0.2 |
|
||||||
|
| No `@bytelyst/accessibility` helpers | Medium | Phase 0.3 |
|
||||||
|
| No feature flags for new features | **Critical** | Phase 0.4 |
|
||||||
|
| No telemetry events for new features | Medium | Phase 0.5 |
|
||||||
|
| MCP tools built in product backend (wrong arch) | **Critical** | Phase A (dual-layer: backend REST + mcp-server proxy) |
|
||||||
|
| Webhook dispatcher never called (no event wiring) | High | Phase C.2 |
|
||||||
|
| No domain event persistence (`@bytelyst/event-store`) | Medium | Phase C.1 |
|
||||||
|
| No `@bytelyst/celebrations` for achievements | Low | Phase B.4, D.2 |
|
||||||
|
| No `@bytelyst/gentle-notifications` for warnings | Low | Phase D.4 |
|
||||||
|
| No `aria-label` requirements stated for new UI | Medium | All UI tasks now require it |
|
||||||
|
| No `@bytelyst/ui` usage requirement for new components | Medium | All UI tasks now require it |
|
||||||
|
| No `--cm-*` token requirement for new components | Medium | All UI tasks now require it |
|
||||||
|
|
||||||
|
### MCP Architecture Correction
|
||||||
|
|
||||||
|
**Wrong (original roadmap):** Build `backend/src/mcp/` with tool contracts + registration in product backend.
|
||||||
|
**Correct:** The centralized MCP server (`learning_ai_common_plat/services/mcp-server`, port 4007) already has `chronomind.*` tools (7 existing). New tools extend that server; product backend only provides REST endpoints.
|
||||||
|
|
||||||
|
Existing MCP tools in mcp-server: `chronomind.timers.create`, `.list`, `.delete`, `chronomind.routines.list`, `.validate`, `chronomind.syncStatus`, `chronomind.sharedTimers.share`, `chronomind.households.list`.
|
||||||
|
|
||||||
|
### Webhook Dispatcher Status Correction
|
||||||
|
|
||||||
|
**Wrong (original roadmap):** Called it a "skeleton" needing hardening.
|
||||||
|
**Correct:** `backend/src/modules/webhooks/dispatcher.ts` is **fully implemented** (201 LOC):
|
||||||
|
- HMAC-SHA256 signing with `t=<timestamp>,v1=<signature>` format
|
||||||
|
- Exponential backoff retries (100ms × 2^attempt, max 5s)
|
||||||
|
- Event log creation + delivery tracking
|
||||||
|
- Auto-increment failure count on subscription
|
||||||
|
- `verifySignature()` consumer helper with constant-time comparison
|
||||||
|
- 10s timeout per delivery attempt
|
||||||
|
|
||||||
|
The actual gap is that **no code path calls `dispatchEvent()`** — it's built but unwired.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- **Backend migration (original item #5) is ALREADY DONE** — backend lives at `learning_ai_clock/backend/` (port 4011) with 5 modules + webhooks. No work needed.
|
||||||
|
- **Webhook dispatcher (original item #7) is FULLY BUILT** — HMAC, retries, event logging all done. Phase C wires lifecycle events into it and adds Zapier compat.
|
||||||
|
- **Platform-sync client (original item #9) is FULLY BUILT** — `web/src/lib/platform-sync.ts` has 547 LOC with full sync, DTOs, offline queue, `@bytelyst/sync` engine. Phase E wires it into the app lifecycle and verifies iOS/Android clients.
|
||||||
|
- **MCP server already has 8 ChronoMind tools** in `learning_ai_common_plat/services/mcp-server/src/modules/chronomind/`. New tools extend this, not the product backend.
|
||||||
|
- All new code must include `productId: "chronomind"` on every Cosmos document.
|
||||||
|
- All new UI must use `@bytelyst/ui` components, `--cm-*` CSS properties, and `aria-label` on every interactive element.
|
||||||
|
- All new backend routes must be gated behind feature flags (default `false`), use `req.log` (never `console.log`), and track telemetry events.
|
||||||
29
pnpm-lock.yaml
generated
29
pnpm-lock.yaml
generated
@ -81,6 +81,9 @@ importers:
|
|||||||
|
|
||||||
web:
|
web:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@bytelyst/accessibility':
|
||||||
|
specifier: ^0.1.0
|
||||||
|
version: 0.1.0
|
||||||
'@bytelyst/api-client':
|
'@bytelyst/api-client':
|
||||||
specifier: ^0.1.0
|
specifier: ^0.1.0
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
@ -96,6 +99,12 @@ importers:
|
|||||||
'@bytelyst/feature-flag-client':
|
'@bytelyst/feature-flag-client':
|
||||||
specifier: ^0.1.0
|
specifier: ^0.1.0
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
|
'@bytelyst/feedback-client':
|
||||||
|
specifier: ^0.1.0
|
||||||
|
version: 0.1.0(zod@4.3.6)
|
||||||
|
'@bytelyst/kill-switch-client':
|
||||||
|
specifier: ^0.1.0
|
||||||
|
version: 0.1.0
|
||||||
'@bytelyst/react-auth':
|
'@bytelyst/react-auth':
|
||||||
specifier: ^0.1.0
|
specifier: ^0.1.0
|
||||||
version: 0.1.1(react@19.2.3)
|
version: 0.1.1(react@19.2.3)
|
||||||
@ -347,6 +356,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==}
|
resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
'@bytelyst/accessibility@0.1.0':
|
||||||
|
resolution: {integrity: sha512-sP21zslGi8j3rly28EMb6HHZTUccz8Wn+LM2s0UidhlMgT5HaZOKrEl+IKRArcnqw5+STlPsz5WCI7zJ7qIRiw==, tarball: http://localhost:3300/api/packages/bytelyst/npm/%40bytelyst%2Faccessibility/-/0.1.0/accessibility-0.1.0.tgz}
|
||||||
|
|
||||||
'@bytelyst/api-client@0.1.0':
|
'@bytelyst/api-client@0.1.0':
|
||||||
resolution: {integrity: sha512-oobGdfoOvLgSDOcfuW65I5SxWG9mIcHvt5JV5lYVD9LmjXo/0cLyVH77ScN6VJbDIy84OnWz6y6iULmTCtPX/A==, tarball: http://localhost:3300/api/packages/bytelyst/npm/%40bytelyst%2Fapi-client/-/0.1.0/api-client-0.1.0.tgz}
|
resolution: {integrity: sha512-oobGdfoOvLgSDOcfuW65I5SxWG9mIcHvt5JV5lYVD9LmjXo/0cLyVH77ScN6VJbDIy84OnWz6y6iULmTCtPX/A==, tarball: http://localhost:3300/api/packages/bytelyst/npm/%40bytelyst%2Fapi-client/-/0.1.0/api-client-0.1.0.tgz}
|
||||||
|
|
||||||
@ -426,6 +438,11 @@ packages:
|
|||||||
'@bytelyst/feature-flag-client@0.1.0':
|
'@bytelyst/feature-flag-client@0.1.0':
|
||||||
resolution: {integrity: sha512-OHh6zDLl7HKjgQoKi7l9Rnxl7aDcF7WMgSKBrenGHYs3G3c636k+gPo+j25m3XhHpKA7+ZILDJ00E5gBG+lhPg==, tarball: http://localhost:3300/api/packages/bytelyst/npm/%40bytelyst%2Ffeature-flag-client/-/0.1.0/feature-flag-client-0.1.0.tgz}
|
resolution: {integrity: sha512-OHh6zDLl7HKjgQoKi7l9Rnxl7aDcF7WMgSKBrenGHYs3G3c636k+gPo+j25m3XhHpKA7+ZILDJ00E5gBG+lhPg==, tarball: http://localhost:3300/api/packages/bytelyst/npm/%40bytelyst%2Ffeature-flag-client/-/0.1.0/feature-flag-client-0.1.0.tgz}
|
||||||
|
|
||||||
|
'@bytelyst/feedback-client@0.1.0':
|
||||||
|
resolution: {integrity: sha512-h5zDD/pMk1ORx1cWJes05RwqqBaiVM15uegoi8/DBXTU3t7kwkZcz3q5m2QdmcZBUY4b07syxZgf2t8pQ75WXQ==, tarball: http://localhost:3300/api/packages/bytelyst/npm/%40bytelyst%2Ffeedback-client/-/0.1.0/feedback-client-0.1.0.tgz}
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.22.0
|
||||||
|
|
||||||
'@bytelyst/field-encrypt@0.1.0':
|
'@bytelyst/field-encrypt@0.1.0':
|
||||||
resolution: {integrity: sha512-C++tZ2aP6TuMDjkPzMb6U591FjmwvdbvSL6NB4w0e7Gqi2n72/y1+6LecLsXrAC60pHG/ALHgAmRJAFI2UdFgw==, tarball: http://localhost:3300/api/packages/bytelyst/npm/%40bytelyst%2Ffield-encrypt/-/0.1.0/field-encrypt-0.1.0.tgz}
|
resolution: {integrity: sha512-C++tZ2aP6TuMDjkPzMb6U591FjmwvdbvSL6NB4w0e7Gqi2n72/y1+6LecLsXrAC60pHG/ALHgAmRJAFI2UdFgw==, tarball: http://localhost:3300/api/packages/bytelyst/npm/%40bytelyst%2Ffield-encrypt/-/0.1.0/field-encrypt-0.1.0.tgz}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -438,6 +455,9 @@ packages:
|
|||||||
'@azure/keyvault-keys':
|
'@azure/keyvault-keys':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@bytelyst/kill-switch-client@0.1.0':
|
||||||
|
resolution: {integrity: sha512-XVXltkFVFrE7pbR9J4tVGQA1o+0Jr3YPOz1KiUu7oU9Pkwfty0pmVElIgoChAaNStmhAfhkdMw9ftJDU4WqzJQ==, tarball: http://localhost:3300/api/packages/bytelyst/npm/%40bytelyst%2Fkill-switch-client/-/0.1.0/kill-switch-client-0.1.0.tgz}
|
||||||
|
|
||||||
'@bytelyst/react-auth@0.1.1':
|
'@bytelyst/react-auth@0.1.1':
|
||||||
resolution: {integrity: sha512-nXLXh38/nTmIOYKGra0kvzh0AWSU/xhOdVdIZR2ndkhIxi+YqUb6tUoO35hHp+rVeneOm/UhKzOFcmwbvQOndg==, tarball: http://localhost:3300/api/packages/bytelyst/npm/%40bytelyst%2Freact-auth/-/0.1.1/react-auth-0.1.1.tgz}
|
resolution: {integrity: sha512-nXLXh38/nTmIOYKGra0kvzh0AWSU/xhOdVdIZR2ndkhIxi+YqUb6tUoO35hHp+rVeneOm/UhKzOFcmwbvQOndg==, tarball: http://localhost:3300/api/packages/bytelyst/npm/%40bytelyst%2Freact-auth/-/0.1.1/react-auth-0.1.1.tgz}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -4159,6 +4179,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
css-tree: 3.2.1
|
css-tree: 3.2.1
|
||||||
|
|
||||||
|
'@bytelyst/accessibility@0.1.0': {}
|
||||||
|
|
||||||
'@bytelyst/api-client@0.1.0': {}
|
'@bytelyst/api-client@0.1.0': {}
|
||||||
|
|
||||||
'@bytelyst/auth-client@0.1.0': {}
|
'@bytelyst/auth-client@0.1.0': {}
|
||||||
@ -4212,6 +4234,11 @@ snapshots:
|
|||||||
|
|
||||||
'@bytelyst/feature-flag-client@0.1.0': {}
|
'@bytelyst/feature-flag-client@0.1.0': {}
|
||||||
|
|
||||||
|
'@bytelyst/feedback-client@0.1.0(zod@4.3.6)':
|
||||||
|
dependencies:
|
||||||
|
'@bytelyst/api-client': 0.1.0
|
||||||
|
zod: 4.3.6
|
||||||
|
|
||||||
'@bytelyst/field-encrypt@0.1.0(@azure/keyvault-keys@4.10.0(@azure/core-client@1.10.1))(zod@3.25.76)':
|
'@bytelyst/field-encrypt@0.1.0(@azure/keyvault-keys@4.10.0(@azure/core-client@1.10.1))(zod@3.25.76)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@bytelyst/errors': 0.1.0
|
'@bytelyst/errors': 0.1.0
|
||||||
@ -4219,6 +4246,8 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@azure/keyvault-keys': 4.10.0(@azure/core-client@1.10.1)
|
'@azure/keyvault-keys': 4.10.0(@azure/core-client@1.10.1)
|
||||||
|
|
||||||
|
'@bytelyst/kill-switch-client@0.1.0': {}
|
||||||
|
|
||||||
'@bytelyst/react-auth@0.1.1(react@19.2.3)':
|
'@bytelyst/react-auth@0.1.1(react@19.2.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@bytelyst/api-client': 0.1.0
|
'@bytelyst/api-client': 0.1.0
|
||||||
|
|||||||
@ -17,12 +17,15 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bytelyst/design-tokens": "file:../../learning_ai_common_plat/packages/design-tokens",
|
"@bytelyst/design-tokens": "file:../../learning_ai_common_plat/packages/design-tokens",
|
||||||
"@bytelyst/ui": "file:../../learning_ai_common_plat/packages/ui",
|
"@bytelyst/ui": "file:../../learning_ai_common_plat/packages/ui",
|
||||||
|
"@bytelyst/accessibility": "^0.1.0",
|
||||||
"@bytelyst/api-client": "^0.1.0",
|
"@bytelyst/api-client": "^0.1.0",
|
||||||
"@bytelyst/sync": "^0.1.0",
|
"@bytelyst/sync": "^0.1.0",
|
||||||
"@bytelyst/auth-client": "^0.1.0",
|
"@bytelyst/auth-client": "^0.1.0",
|
||||||
"@bytelyst/react-auth": "^0.1.0",
|
"@bytelyst/react-auth": "^0.1.0",
|
||||||
"@bytelyst/diagnostics-client": "^0.1.0",
|
"@bytelyst/diagnostics-client": "^0.1.0",
|
||||||
|
"@bytelyst/feedback-client": "^0.1.0",
|
||||||
"@bytelyst/feature-flag-client": "^0.1.0",
|
"@bytelyst/feature-flag-client": "^0.1.0",
|
||||||
|
"@bytelyst/kill-switch-client": "^0.1.0",
|
||||||
"@bytelyst/subscription-client": "^0.1.0",
|
"@bytelyst/subscription-client": "^0.1.0",
|
||||||
"@bytelyst/telemetry-client": "^0.1.0",
|
"@bytelyst/telemetry-client": "^0.1.0",
|
||||||
"@serwist/next": "^9.5.6",
|
"@serwist/next": "^9.5.6",
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { AuthProvider } from '@/lib/auth-context';
|
|||||||
import { initTelemetry, trackPageView } from '@/lib/telemetry';
|
import { initTelemetry, trackPageView } from '@/lib/telemetry';
|
||||||
import { initFeatureFlags } from '@/lib/feature-flags';
|
import { initFeatureFlags } from '@/lib/feature-flags';
|
||||||
import { initDiagnostics } from '@/lib/diagnostics';
|
import { initDiagnostics } from '@/lib/diagnostics';
|
||||||
|
import { checkKillSwitch } from '@/lib/kill-switch';
|
||||||
import { ToastProvider } from '@bytelyst/ui';
|
import { ToastProvider } from '@bytelyst/ui';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
@ -16,6 +17,14 @@ export function Providers({ children }: { children: ReactNode }) {
|
|||||||
initTelemetry();
|
initTelemetry();
|
||||||
initFeatureFlags();
|
initFeatureFlags();
|
||||||
initDiagnostics();
|
initDiagnostics();
|
||||||
|
// TODO(AGENTIC-1): When kill switch returns disabled=true, show a maintenance
|
||||||
|
// banner and disable timer creation. For now we just log and check.
|
||||||
|
checkKillSwitch().then((disabled) => {
|
||||||
|
if (disabled) {
|
||||||
|
// TODO(AGENTIC-2): Render a visible maintenance banner component here.
|
||||||
|
// For now, the kill switch result is available but not surfaced in UI.
|
||||||
|
}
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
18
web/src/lib/feedback.ts
Normal file
18
web/src/lib/feedback.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Feedback client — thin wrapper over @bytelyst/feedback-client.
|
||||||
|
*
|
||||||
|
* Allows users to submit bug reports, feature requests, and praise
|
||||||
|
* from within the ChronoMind web app.
|
||||||
|
*
|
||||||
|
* Privacy: sends userId + feedback text only. Screenshots are opt-in.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createFeedbackClient } from '@bytelyst/feedback-client';
|
||||||
|
import { getAuthClient, getBaseUrl } from './auth-api';
|
||||||
|
|
||||||
|
const feedbackClient = createFeedbackClient({
|
||||||
|
baseUrl: `${getBaseUrl()}/api`,
|
||||||
|
getAuthToken: () => getAuthClient().getAccessToken(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export { feedbackClient };
|
||||||
31
web/src/lib/kill-switch.ts
Normal file
31
web/src/lib/kill-switch.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Kill switch client — thin wrapper over @bytelyst/kill-switch-client.
|
||||||
|
*
|
||||||
|
* Checks platform-service on app init. If killed, disables timer creation
|
||||||
|
* and shows maintenance banner. Fail-open on any error (app stays usable).
|
||||||
|
*
|
||||||
|
* Privacy: sends productId + platform only. No PII.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createKillSwitchClient } from '@bytelyst/kill-switch-client';
|
||||||
|
import { PRODUCT_ID, getBaseUrl } from './auth-api';
|
||||||
|
|
||||||
|
const killSwitchClient = createKillSwitchClient({
|
||||||
|
baseUrl: `${getBaseUrl()}/api`,
|
||||||
|
productId: PRODUCT_ID,
|
||||||
|
platform: 'web',
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the app is disabled via kill switch.
|
||||||
|
* Returns true if the app should be disabled.
|
||||||
|
* Fail-open: returns false on any error so the app stays usable.
|
||||||
|
*/
|
||||||
|
export async function checkKillSwitch(): Promise<boolean> {
|
||||||
|
const result = await killSwitchClient.check().catch(() => {
|
||||||
|
return { disabled: false, message: null };
|
||||||
|
});
|
||||||
|
return result.disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { killSwitchClient };
|
||||||
37
web/src/lib/telemetry-events.ts
Normal file
37
web/src/lib/telemetry-events.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Telemetry event name constants for all agentic AI features (web side).
|
||||||
|
*
|
||||||
|
* Mirrors backend/src/lib/telemetry-events.ts — single source of truth
|
||||||
|
* for event names used in React components and API clients (Phase 0.5).
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ── MCP ───────────────────────────────────────────────────────
|
||||||
|
export const MCP_TOOL_EXECUTED = 'mcp.tool_executed';
|
||||||
|
export const MCP_TOOL_FAILED = 'mcp.tool_failed';
|
||||||
|
|
||||||
|
// ── Day Planner ───────────────────────────────────────────────
|
||||||
|
export const PLANNER_DAY_PLANNED = 'planner.day_planned';
|
||||||
|
export const PLANNER_PLAN_APPLIED = 'planner.plan_applied';
|
||||||
|
export const PLANNER_PLAN_REJECTED = 'planner.plan_rejected';
|
||||||
|
|
||||||
|
// ── Agent Inbox ───────────────────────────────────────────────
|
||||||
|
export const AGENT_INBOX_ACTION_APPROVED = 'agent_inbox.action_approved';
|
||||||
|
export const AGENT_INBOX_ACTION_REJECTED = 'agent_inbox.action_rejected';
|
||||||
|
export const AGENT_INBOX_BATCH_APPROVED = 'agent_inbox.batch_approved';
|
||||||
|
|
||||||
|
// ── Tempo Mode ────────────────────────────────────────────────
|
||||||
|
export const TEMPO_DRIFT_DETECTED = 'tempo.drift_detected';
|
||||||
|
export const TEMPO_ADJUSTMENT_APPLIED = 'tempo.adjustment_applied';
|
||||||
|
|
||||||
|
// ── Routine Suggestions ───────────────────────────────────────
|
||||||
|
export const ROUTINE_SUGGEST_SHOWN = 'routine_suggest.shown';
|
||||||
|
export const ROUTINE_SUGGEST_ACCEPTED = 'routine_suggest.accepted';
|
||||||
|
export const ROUTINE_SUGGEST_DISMISSED = 'routine_suggest.dismissed';
|
||||||
|
|
||||||
|
// ── AI Context Messages ───────────────────────────────────────
|
||||||
|
export const AI_CONTEXT_ENRICHED = 'ai_context.enriched';
|
||||||
|
export const AI_CONTEXT_FALLBACK_USED = 'ai_context.fallback_used';
|
||||||
|
|
||||||
|
// ── Webhooks ──────────────────────────────────────────────────
|
||||||
|
export const WEBHOOK_DISPATCHED = 'webhook.dispatched';
|
||||||
|
export const WEBHOOK_FAILED = 'webhook.failed';
|
||||||
Loading…
Reference in New Issue
Block a user