{"_id":"@elizaos/app-lifeops","_rev":"2-af66f076aef7ba3783c5e668600cbf40","name":"@elizaos/app-lifeops","dist-tags":{"beta":"2.0.0-beta.1","latest":"2.0.0-beta.1"},"versions":{"2.0.0-beta.1":{"name":"@elizaos/app-lifeops","version":"2.0.0-beta.1","_id":"@elizaos/app-lifeops@2.0.0-beta.1","maintainers":[{"name":"haruhunab1320","email":"jakob@magickml.com"},{"name":"shawticus","email":"shawmakesmagic@gmail.com"},{"name":"odilitime","email":"janesmith@airmail.cc"}],"dist":{"shasum":"92cdc3a05edcd3fc69e353003d49ae43e56c0c4a","tarball":"https://registry.npmjs.org/@elizaos/app-lifeops/-/app-lifeops-2.0.0-beta.1.tgz","fileCount":1942,"integrity":"sha512-rP0g+Y8iaRGf7H0dnxDBcNsByvOqlqjN+GiYCJw8Xl3aU6ysVrP3L15gCY7AAGJnFZiZRy3urttf8uoLwj23vw==","signatures":[{"sig":"MEUCIFIscAn8gXtjdvYi97AM4ovm3Vm33RHWYPqhBOBCm4IDAiEA7s2vwr1Fip5qKSe9tiaTDqt/ZYX5YppXsYq5dYuWLBE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":16542100},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","elizaos":{"app":{"heroImage":"assets/hero.png","permissions":["reminders","calendar","health","screentime","contacts","notes"]}},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"},"./plugin":{"types":"./dist/plugin.d.ts","import":"./dist/plugin.js","default":"./dist/plugin.js"},"./package.json":"./package.json"},"gitHead":"fb5c65dd89117b393a3c8d0962fe19302efc7cc7","scripts":{"test":"vitest run --config vitest.config.ts","build":"bun run build:js && bun run build:types","clean":"rm -rf dist","verify":"bun run lint:default-packs && bun run build:types && bun run test","pretest":"node scripts/lint-default-packs.mjs","build:js":"tsup --config ../tsup.plugin-packages.shared.ts","build:types":"tsc --noCheck -p tsconfig.build.json","test:app-state":"vitest run --config vitest.config.ts eliza/plugins/app-lifeops/test/lifeops-app-state.test.ts","test:scenarios":"cd ../../.. && bun run scenarios:lifeops","test:integration":"cd ../../.. && vitest run --config eliza/test/vitest/integration.config.ts eliza/plugins/app-lifeops/test/life-smoke.integration.test.ts","bench:work-threads":"cd ../.. && bun plugins/app-lifeops/scripts/work-thread-benchmark.ts","lint:default-packs":"node scripts/lint-default-packs.mjs","verify:live-schedule":"bun run ./scripts/verify-live-schedule-data.ts"},"_npmUser":{"name":"shawticus","email":"shawmakesmagic@gmail.com"},"_npmVersion":"11.5.1","agentConfig":{"pluginType":"elizaos:plugin:1.0.0","pluginParameters":{}},"description":"LifeOps app plugin: routines, goals, Google Workspace, Apple Reminders, Twilio, browser companion control, hosts-file blocking (self-control), and related agent surfaces.","directories":{},"sideEffects":["./src/widgets/index.ts","./src/api/client-lifeops.ts","./src/ui.ts","./dist/widgets/index.js","./dist/api/client-lifeops.js","./dist/ui.js"],"_nodeVersion":"24.5.0","dependencies":{"zod":"^4.4.3","sharp":"^0.34.5","@elizaos/ui":"2.0.0-beta.1","drizzle-orm":"0.45.2","lucide-react":"^1.0.0","@elizaos/core":"2.0.0-beta.1","@elizaos/agent":"2.0.0-beta.1","@capacitor/core":"8.3.1","@elizaos/shared":"2.0.0-beta.1","react-plaid-link":"^4.0.1","@elizaos/app-core":"2.0.0-beta.1","@elizaos/plugin-x":"2.0.0-beta.1","@elizaos/plugin-google":"2.0.0-beta.1","@elizaos/plugin-health":"2.0.0-beta.1","@elizaos/plugin-signal":"2.0.0-beta.1","@elizaos/plugin-browser":"2.0.0-beta.1","@elizaos/plugin-discord":"2.0.0-beta.1","@elizaos/plugin-calendly":"2.0.0-beta.1","@elizaos/plugin-imessage":"2.0.0-beta.1","@elizaos/plugin-telegram":"2.0.0-beta.1","@elizaos/plugin-whatsapp":"2.0.0-beta.1","@elizaos/scenario-schema":"2.0.0-beta.1","@elizaos/plugin-elizacloud":"2.0.0-beta.1","@elizaos/capacitor-appblocker":"2.0.0-beta.1","@elizaos/native-activity-tracker":"2.0.0-beta.1","@elizaos/capacitor-mobile-signals":"2.0.0-beta.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","jsdom":"^29.0.0","@types/jsdom":"^28.0.0","@electric-sql/pglite":"^0.4.0","@elizaos/scenario-schema":"2.0.0-beta.1"},"peerDependencies":{"react":"*"},"_npmOperationalInternal":{"tmp":"tmp/app-lifeops_2.0.0-beta.1_1778451186650_0.28100537519373026","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2026-05-10T22:13:06.572Z","modified":"2026-06-16T05:13:30.233Z","2.0.0-beta.1":"2026-05-10T22:13:07.011Z"},"description":"LifeOps app plugin: routines, goals, Google Workspace, Apple Reminders, Twilio, browser companion control, hosts-file blocking (self-control), and related agent surfaces.","maintainers":[{"email":"shawmakesmagic@gmail.com","name":"shawticus"},{"email":"janesmith@airmail.cc","name":"odilitime"}],"readme":"# `@elizaos/app-lifeops`\n\nLifeOps is the elizaOS app that runs the user's day: routines, goals,\ncalendar, email, messaging, follow-ups with people, blockers, watchers, and\nthe operational glue around them. This README is the architecture summary\nfor contributors. User-facing docs live in `docs/user/lifeops-setup.mdx`;\nthe QA reference lives in `docs/launchdocs/14-lifeops-qa.md`; the REST\ncontract lives in `docs/rest/lifeops.md`.\n\n## The single primitive\n\nEvery reminder, check-in, follow-up, watcher, recap, approval surface, and\nnag-the-user-when-they-go-quiet flow is a **`ScheduledTask`** owned by the\nrunner at `src/lifeops/scheduled-task/runner.ts`. There is no second\nmechanism. If LifeOps does it, it is a `ScheduledTask`.\n\nThe shape:\n\n```ts\ninterface ScheduledTask {\n  taskId: string;\n  kind: \"reminder\" | \"checkin\" | \"followup\" | \"approval\" | \"recap\" | \"watcher\" | \"output\" | \"custom\";\n  promptInstructions: string;\n  contextRequest?: { /* owner facts, entities, relationships, recent task states, event payload */ };\n  trigger: /* once | cron | interval | relative_to_anchor | during_window | event | manual | after_task */;\n  priority: \"low\" | \"medium\" | \"high\";\n  shouldFire?: { compose: \"all\" | \"any\" | \"first_deny\"; gates: Array<{ kind: string; params? }> };\n  completionCheck?: { kind: string; params?: ...; followupAfterMinutes? };\n  escalation?: { ladderKey?: string; steps?: EscalationStep[] };\n  output?: { destination: ...; target?: string; persistAs?: ... };\n  pipeline?: { onComplete?, onSkip?, onFail? };\n  subject?: { kind: \"entity\" | \"relationship\" | \"thread\" | \"document\" | \"calendar_event\" | \"self\"; id: string };\n  idempotencyKey?: string;\n  respectsGlobalPause: boolean;\n  state: ScheduledTaskState;\n  source: \"default_pack\" | \"user_chat\" | \"first_run\" | \"plugin\";\n  createdBy: string;\n  ownerVisible: boolean;\n  metadata?: Record<string, unknown>;\n}\n```\n\nThe runner pattern-matches **only** on the structural fields above\n(`kind`, `trigger`, `shouldFire`, `completionCheck`, `pipeline`, `output`,\n`subject`, `priority`, `respectsGlobalPause`). It never inspects\n`promptInstructions` content. This is non-negotiable.\n\nThe frozen contract lives in `docs/audit/wave1-interfaces.md` §1.\n\n## Runtime layout\n\n```\nsrc/lifeops/\n  scheduled-task/        Spine: runner, state log, gate registry,\n                         completion-check registry, escalation, runtime\n                         wiring.\n  entities/              Entity primitive: store, merge engine, types.\n  relationships/         Relationship edges: store, observation\n                         extraction, types.\n  registries/            AnchorRegistry, EventKindRegistry, FamilyRegistry,\n                         BlockerRegistry, app/website blocker contributions.\n  signals/               ActivitySignalBus.\n  channels/              ChannelRegistry, priority-posture map, default\n                         channel pack.\n  connectors/            ConnectorRegistry + per-connector contributions\n                         (calendly, discord, duffel, google, imessage,\n                         signal, telegram, twilio, whatsapp, x).\n  send-policy/           Per-connector send-policy contract + registry.\n  owner/                 OwnerFactStore.\n  first-run/             FirstRunService, state store, customize\n                         questions, replay.\n  pending-prompts/       PendingPromptsStore (the planner-visible\n                         \"questions waiting for the user\" surface).\n  global-pause/          GlobalPauseStore.\n  handoff/               HandoffStore (per-room handoff state).\n  i18n/                  MultilingualPromptRegistry.\n  graph-migration/       Migration into the entity/relationship graph.\n  seed-routine-migration/  Migration off legacy seed routines.\n  ...other domain helpers (calendar, email, messaging, payments,\n                          subscriptions, sleep, screen-time, etc.)\n```\n\n## Default packs\n\nDefault packs are bundles of `ScheduledTask` records (and sometimes\nanchor-consolidation policies, escalation ladders, autofill whitelists).\nLifeOps-owned packs live in `src/default-packs/`:\n\n- `daily-rhythm` — gm, gn, daily check-in.\n- `morning-brief` — fired on `wake.confirmed`.\n- `quiet-user-watcher` — daily watcher.\n- `habit-starters` — eight habits, **offered** (not auto-seeded).\n- `inbox-triage-starter` — opt-in, gated on Gmail.\n- `followup-starter` — watcher firing per overdue relationship.\n- `autofill-whitelist-pack`, `consolidation-policies`, `escalation-ladders`\n  — policy-only packs.\n\n`@elizaos/plugin-health` ships `bedtime`, `wake-up`, `sleep-recap` and\nregisters them when a health connector pairs.\n\n### Adding a new default pack\n\n1. Add a file under `src/default-packs/<name>.ts` that exports a\n   `DefaultPack` matching `registry-types.ts`.\n2. Import and append it to `DEFAULT_PACKS` in `src/default-packs/index.ts`.\n3. If the pack should be **auto-enabled**, list it in\n   `getDefaultEnabledPacks`. If it should be **offered** during first-run\n   customize, list it in `getOfferedDefaultPacks`. If neither, the pack\n   only seeds when invoked explicitly.\n4. Run `bun run lint:default-packs` (also runs as `pretest`). The lint\n   rules live in `docs/audit/prompt-content-lint.md`. CI rejects packs\n   that violate them.\n5. Add a record-id constant export so consumers can target the records by\n   stable ID.\n\nThe runtime never seeds packs by name string-match; everything goes through\n`getAllDefaultPacks()`.\n\n## Knowledge graph\n\n`EntityStore` (nodes) and `RelationshipStore` (edges) at\n`src/lifeops/entities/` and `src/lifeops/relationships/`. The graph is\nper-agent. The `entityId === \"self\"` row is bootstrapped on first use.\n\n- **Cadence lives on the edge.** \"Pat — every 14 days\" is a\n  `Relationship`, not an `Entity` attribute. Cadence-bearing\n  `ScheduledTask`s use `subject.kind = \"relationship\"`.\n- **Identities are observed.** `(platform, handle)` pairs route through\n  `observeIdentity`; the merge engine in `entities/merge.ts` collapses\n  entities with high-confidence identity matches. Manual merges go through\n  `POST /api/lifeops/entities/merge` and are audited.\n- **REST surface** — see `docs/rest/lifeops.md`.\n\n## Pause and handoff\n\n- **Global pause** (`global-pause/store.ts`) — stops every\n  `ScheduledTask` with `respectsGlobalPause: true`. Toggleable via UI or\n  `/api/lifeops/app-state`.\n- **Per-room handoff** (`handoff/store.ts`) — flips a multi-party room\n  into handoff after the agent says \"I'll let you take it from here.\"\n  Typed resume conditions (`mention | explicit_resume | silence_minutes |\n  user_request_help`). The `RoomPolicyProvider` reads\n  `HandoffStore.status(roomId).active` and gates further agent\n  contributions.\n\n## Plugin dependencies\n\nLifeOps consumes `@elizaos/plugin-health` for sleep/circadian/health metrics\nand screen-time. The plugin contributes through the registries listed above\n(`AnchorRegistry`, `ConnectorRegistry`, `FamilyRegistry`, default packs).\nLifeOps does not import directly into the health internals; it consumes the\nplugin's public exports only. See `plugins/plugin-health/README.md`.\n\n## Cross-agent invariants\n\n1. The runner never pattern-matches `promptInstructions`.\n2. `subject.kind = \"relationship\"` for cadence-bearing tasks.\n3. Identities are observed, not assigned.\n4. Connectors and channels return typed `DispatchResult`. No `boolean`.\n5. `shouldFire.gates` is always an array.\n6. `acknowledged` ≠ `completed`. Pipeline `onComplete` only fires on\n   `completed`.\n7. Snooze resets the escalation ladder.\n8. Global pause skips tasks with `respectsGlobalPause: true`.\n\n## Where to look next\n\n- Frozen interface contracts: `docs/audit/wave1-interfaces.md`.\n- Implementation plan: `docs/audit/IMPLEMENTATION_PLAN.md`.\n- Post-cleanup architecture summary: `docs/audit/post-cleanup-architecture.md`.\n- Coverage matrix: `coverage-matrix.md`.\n- Prompt-content lint rules: `docs/audit/prompt-content-lint.md`.\n- Health domain: `plugins/plugin-health/README.md`.\n- REST: `docs/rest/lifeops.md`.\n- QA reference: `docs/launchdocs/14-lifeops-qa.md`.\n- User-facing setup: `docs/user/lifeops-setup.mdx`.\n","readmeFilename":"README.md"}