@withbuddi/buddi 0.1.0-pre.18 → 0.1.0-pre.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -4
- package/extension/background.js +38 -7
- package/node_modules/@buddi/cli/dist/.tsbuildinfo +1 -1
- package/node_modules/@buddi/cli/dist/args.d.ts +7 -0
- package/node_modules/@buddi/cli/dist/args.d.ts.map +1 -1
- package/node_modules/@buddi/cli/dist/args.js +14 -0
- package/node_modules/@buddi/cli/dist/args.js.map +1 -1
- package/node_modules/@buddi/cli/dist/commands.d.ts.map +1 -1
- package/node_modules/@buddi/cli/dist/commands.js +37 -2
- package/node_modules/@buddi/cli/dist/commands.js.map +1 -1
- package/node_modules/@buddi/cli/dist/dashboard-app.d.ts +6 -0
- package/node_modules/@buddi/cli/dist/dashboard-app.d.ts.map +1 -1
- package/node_modules/@buddi/cli/dist/dashboard-app.js +16 -1
- package/node_modules/@buddi/cli/dist/dashboard-app.js.map +1 -1
- package/node_modules/@buddi/cli/dist/doctor-probes.d.ts.map +1 -1
- package/node_modules/@buddi/cli/dist/doctor-probes.js +5 -2
- package/node_modules/@buddi/cli/dist/doctor-probes.js.map +1 -1
- package/node_modules/@buddi/cli/dist/doctor.d.ts +16 -0
- package/node_modules/@buddi/cli/dist/doctor.d.ts.map +1 -1
- package/node_modules/@buddi/cli/dist/doctor.js +18 -0
- package/node_modules/@buddi/cli/dist/doctor.js.map +1 -1
- package/node_modules/@buddi/cli/dist/index.d.ts +1 -0
- package/node_modules/@buddi/cli/dist/index.d.ts.map +1 -1
- package/node_modules/@buddi/cli/dist/index.js +1 -0
- package/node_modules/@buddi/cli/dist/index.js.map +1 -1
- package/node_modules/@buddi/cli/dist/main.d.ts.map +1 -1
- package/node_modules/@buddi/cli/dist/main.js +22 -2
- package/node_modules/@buddi/cli/dist/main.js.map +1 -1
- package/node_modules/@buddi/cli/dist/status.d.ts +10 -0
- package/node_modules/@buddi/cli/dist/status.d.ts.map +1 -1
- package/node_modules/@buddi/cli/dist/status.js +4 -0
- package/node_modules/@buddi/cli/dist/status.js.map +1 -1
- package/node_modules/@buddi/cli/dist/uninstall-cmd.d.ts +42 -0
- package/node_modules/@buddi/cli/dist/uninstall-cmd.d.ts.map +1 -0
- package/node_modules/@buddi/cli/dist/uninstall-cmd.js +208 -0
- package/node_modules/@buddi/cli/dist/uninstall-cmd.js.map +1 -0
- package/node_modules/@buddi/core/dist/.tsbuildinfo +1 -1
- package/node_modules/@buddi/core/dist/agents/catalog.d.ts.map +1 -1
- package/node_modules/@buddi/core/dist/agents/catalog.js +7 -1
- package/node_modules/@buddi/core/dist/agents/catalog.js.map +1 -1
- package/node_modules/@buddi/core/dist/agents/models.d.ts.map +1 -1
- package/node_modules/@buddi/core/dist/agents/models.js +4 -3
- package/node_modules/@buddi/core/dist/agents/models.js.map +1 -1
- package/node_modules/@buddi/core/dist/agents/skills.d.ts +2 -2
- package/node_modules/@buddi/core/dist/goals/frequency.d.ts +51 -0
- package/node_modules/@buddi/core/dist/goals/frequency.d.ts.map +1 -0
- package/node_modules/@buddi/core/dist/goals/frequency.js +113 -0
- package/node_modules/@buddi/core/dist/goals/frequency.js.map +1 -0
- package/node_modules/@buddi/core/dist/goals/index.d.ts +2 -0
- package/node_modules/@buddi/core/dist/goals/index.d.ts.map +1 -1
- package/node_modules/@buddi/core/dist/goals/index.js +2 -0
- package/node_modules/@buddi/core/dist/goals/index.js.map +1 -1
- package/node_modules/@buddi/core/dist/goals/math.d.ts +8 -2
- package/node_modules/@buddi/core/dist/goals/math.d.ts.map +1 -1
- package/node_modules/@buddi/core/dist/goals/math.js +17 -4
- package/node_modules/@buddi/core/dist/goals/math.js.map +1 -1
- package/node_modules/@buddi/core/dist/goals/owner.d.ts +129 -0
- package/node_modules/@buddi/core/dist/goals/owner.d.ts.map +1 -0
- package/node_modules/@buddi/core/dist/goals/owner.js +239 -0
- package/node_modules/@buddi/core/dist/goals/owner.js.map +1 -0
- package/node_modules/@buddi/core/dist/goals/store.d.ts.map +1 -1
- package/node_modules/@buddi/core/dist/goals/store.js +11 -4
- package/node_modules/@buddi/core/dist/goals/store.js.map +1 -1
- package/node_modules/@buddi/core/dist/goals/types.d.ts +17 -3
- package/node_modules/@buddi/core/dist/goals/types.d.ts.map +1 -1
- package/node_modules/@buddi/core/dist/goals/types.js +8 -2
- package/node_modules/@buddi/core/dist/goals/types.js.map +1 -1
- package/node_modules/@buddi/core/dist/learning/announce.d.ts +29 -0
- package/node_modules/@buddi/core/dist/learning/announce.d.ts.map +1 -0
- package/node_modules/@buddi/core/dist/learning/announce.js +49 -0
- package/node_modules/@buddi/core/dist/learning/announce.js.map +1 -0
- package/node_modules/@buddi/core/dist/learning/index.d.ts +1 -0
- package/node_modules/@buddi/core/dist/learning/index.d.ts.map +1 -1
- package/node_modules/@buddi/core/dist/learning/index.js +1 -0
- package/node_modules/@buddi/core/dist/learning/index.js.map +1 -1
- package/node_modules/@buddi/core/dist/learning/store.d.ts.map +1 -1
- package/node_modules/@buddi/core/dist/learning/store.js +15 -4
- package/node_modules/@buddi/core/dist/learning/store.js.map +1 -1
- package/node_modules/@buddi/core/dist/notifications/notify.d.ts.map +1 -1
- package/node_modules/@buddi/core/dist/notifications/notify.js +1 -0
- package/node_modules/@buddi/core/dist/notifications/notify.js.map +1 -1
- package/node_modules/@buddi/core/dist/notifications/store.d.ts +2 -0
- package/node_modules/@buddi/core/dist/notifications/store.d.ts.map +1 -1
- package/node_modules/@buddi/core/dist/notifications/store.js +9 -0
- package/node_modules/@buddi/core/dist/notifications/store.js.map +1 -1
- package/node_modules/@buddi/core/dist/notifications/types.d.ts +6 -0
- package/node_modules/@buddi/core/dist/notifications/types.d.ts.map +1 -1
- package/node_modules/@buddi/core/dist/pages.d.ts +2 -2
- package/node_modules/@buddi/core/dist/provider-accounts.d.ts +7 -1
- package/node_modules/@buddi/core/dist/provider-accounts.d.ts.map +1 -1
- package/node_modules/@buddi/core/dist/provider-accounts.js +14 -1
- package/node_modules/@buddi/core/dist/provider-accounts.js.map +1 -1
- package/node_modules/@buddi/core/dist/provider.d.ts +6 -0
- package/node_modules/@buddi/core/dist/provider.d.ts.map +1 -1
- package/node_modules/@buddi/core/dist/provider.js.map +1 -1
- package/node_modules/@buddi/core/dist/sentinels/run.d.ts.map +1 -1
- package/node_modules/@buddi/core/dist/sentinels/run.js +4 -1
- package/node_modules/@buddi/core/dist/sentinels/run.js.map +1 -1
- package/node_modules/@buddi/core/dist/sentinels/types.d.ts +20 -0
- package/node_modules/@buddi/core/dist/sentinels/types.d.ts.map +1 -1
- package/node_modules/@buddi/core/dist/sentinels/types.js.map +1 -1
- package/node_modules/@buddi/core/dist/surfaces.d.ts +2 -0
- package/node_modules/@buddi/core/dist/surfaces.d.ts.map +1 -1
- package/node_modules/@buddi/core/dist/surfaces.js +7 -0
- package/node_modules/@buddi/core/dist/surfaces.js.map +1 -1
- package/node_modules/@buddi/core/dist/uninstall.d.ts +79 -0
- package/node_modules/@buddi/core/dist/uninstall.d.ts.map +1 -0
- package/node_modules/@buddi/core/dist/uninstall.js +109 -0
- package/node_modules/@buddi/core/dist/uninstall.js.map +1 -0
- package/node_modules/@buddi/core/dist/views.d.ts +66 -66
- package/node_modules/@buddi/core/migrations/044_owner_metrics.sql +45 -0
- package/node_modules/@buddi/core/migrations/045_frequency_goals.sql +19 -0
- package/node_modules/@buddi/core/migrations/046_ollama_device_key.sql +7 -0
- package/node_modules/@buddi/core/migrations/047_reported_context_windows.sql +5 -0
- package/node_modules/@buddi/core/package.json +4 -0
- package/node_modules/@buddi/gateway/dist/.tsbuildinfo +1 -1
- package/node_modules/@buddi/gateway/dist/chat/usage.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/chat/usage.js +1 -0
- package/node_modules/@buddi/gateway/dist/chat/usage.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/codex-accounts.d.ts +24 -11
- package/node_modules/@buddi/gateway/dist/codex-accounts.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/codex-accounts.js +151 -215
- package/node_modules/@buddi/gateway/dist/codex-accounts.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/index.d.ts +2 -0
- package/node_modules/@buddi/gateway/dist/index.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/index.js +2 -0
- package/node_modules/@buddi/gateway/dist/index.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/missions/agent-run.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/missions/agent-run.js +1 -0
- package/node_modules/@buddi/gateway/dist/missions/agent-run.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/missions/execute.d.ts +7 -0
- package/node_modules/@buddi/gateway/dist/missions/execute.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/missions/execute.js +3 -1
- package/node_modules/@buddi/gateway/dist/missions/execute.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/missions/goals-format.d.ts +32 -5
- package/node_modules/@buddi/gateway/dist/missions/goals-format.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/missions/goals-format.js +59 -2
- package/node_modules/@buddi/gateway/dist/missions/goals-format.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/missions/goals-page.d.ts +60 -5
- package/node_modules/@buddi/gateway/dist/missions/goals-page.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/missions/goals-page.js +355 -8
- package/node_modules/@buddi/gateway/dist/missions/goals-page.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/missions/goals.d.ts +64 -5
- package/node_modules/@buddi/gateway/dist/missions/goals.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/missions/goals.js +834 -116
- package/node_modules/@buddi/gateway/dist/missions/goals.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/missions/sentinel-wake.d.ts +5 -0
- package/node_modules/@buddi/gateway/dist/missions/sentinel-wake.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/missions/sentinel-wake.js +12 -0
- package/node_modules/@buddi/gateway/dist/missions/sentinel-wake.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/ollama-accounts.d.ts +87 -0
- package/node_modules/@buddi/gateway/dist/ollama-accounts.d.ts.map +1 -0
- package/node_modules/@buddi/gateway/dist/ollama-accounts.js +179 -0
- package/node_modules/@buddi/gateway/dist/ollama-accounts.js.map +1 -0
- package/node_modules/@buddi/gateway/dist/owner-notify.d.ts +2 -1
- package/node_modules/@buddi/gateway/dist/owner-notify.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/owner-notify.js +6 -2
- package/node_modules/@buddi/gateway/dist/owner-notify.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/provider-accounts.d.ts +47 -1
- package/node_modules/@buddi/gateway/dist/provider-accounts.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/provider-accounts.js +204 -38
- package/node_modules/@buddi/gateway/dist/provider-accounts.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/serve.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/serve.js +1 -0
- package/node_modules/@buddi/gateway/dist/serve.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/subscription-signins.d.ts +28 -0
- package/node_modules/@buddi/gateway/dist/subscription-signins.d.ts.map +1 -0
- package/node_modules/@buddi/gateway/dist/subscription-signins.js +30 -0
- package/node_modules/@buddi/gateway/dist/subscription-signins.js.map +1 -0
- package/node_modules/@buddi/gateway/dist/surfaces/context-budget.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/surfaces/context-budget.js +10 -3
- package/node_modules/@buddi/gateway/dist/surfaces/context-budget.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/telegram/api.d.ts +43 -9
- package/node_modules/@buddi/gateway/dist/telegram/api.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/telegram/api.js +80 -21
- package/node_modules/@buddi/gateway/dist/telegram/api.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/telegram/attachments.d.ts +6 -0
- package/node_modules/@buddi/gateway/dist/telegram/attachments.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/telegram/attachments.js +1 -0
- package/node_modules/@buddi/gateway/dist/telegram/attachments.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/telegram/browser-view.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/telegram/browser-view.js +2 -1
- package/node_modules/@buddi/gateway/dist/telegram/browser-view.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/telegram/channel.d.ts +7 -1
- package/node_modules/@buddi/gateway/dist/telegram/channel.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/telegram/channel.js +24 -2
- package/node_modules/@buddi/gateway/dist/telegram/channel.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/telegram/commands.d.ts +28 -0
- package/node_modules/@buddi/gateway/dist/telegram/commands.d.ts.map +1 -0
- package/node_modules/@buddi/gateway/dist/telegram/commands.js +74 -0
- package/node_modules/@buddi/gateway/dist/telegram/commands.js.map +1 -0
- package/node_modules/@buddi/gateway/dist/telegram/main.d.ts +3 -0
- package/node_modules/@buddi/gateway/dist/telegram/main.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/telegram/main.js +71 -2
- package/node_modules/@buddi/gateway/dist/telegram/main.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/telegram/outbound.d.ts +191 -0
- package/node_modules/@buddi/gateway/dist/telegram/outbound.d.ts.map +1 -0
- package/node_modules/@buddi/gateway/dist/telegram/outbound.js +671 -0
- package/node_modules/@buddi/gateway/dist/telegram/outbound.js.map +1 -0
- package/node_modules/@buddi/gateway/dist/telegram/proposals.d.ts +65 -0
- package/node_modules/@buddi/gateway/dist/telegram/proposals.d.ts.map +1 -0
- package/node_modules/@buddi/gateway/dist/telegram/proposals.js +183 -0
- package/node_modules/@buddi/gateway/dist/telegram/proposals.js.map +1 -0
- package/node_modules/@buddi/gateway/dist/telegram/surface.d.ts +47 -47
- package/node_modules/@buddi/gateway/dist/telegram/surface.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/telegram/surface.js +99 -290
- package/node_modules/@buddi/gateway/dist/telegram/surface.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/onboarding.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/onboarding.js +4 -0
- package/node_modules/@buddi/gateway/dist/web/onboarding.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/secrets.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/secrets.js +3 -2
- package/node_modules/@buddi/gateway/dist/web/secrets.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/server.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/server.js +36 -14
- package/node_modules/@buddi/gateway/dist/web/server.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/telegram.d.ts +39 -0
- package/node_modules/@buddi/gateway/dist/web/telegram.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/telegram.js +53 -1
- package/node_modules/@buddi/gateway/dist/web/telegram.js.map +1 -1
- package/node_modules/@buddi/install/dist/.tsbuildinfo +1 -1
- package/node_modules/@buddi/install/dist/launcher.d.ts.map +1 -1
- package/node_modules/@buddi/install/dist/launcher.js +135 -10
- package/node_modules/@buddi/install/dist/launcher.js.map +1 -1
- package/node_modules/@buddi/install/dist/uninstall.d.ts +59 -0
- package/node_modules/@buddi/install/dist/uninstall.d.ts.map +1 -0
- package/node_modules/@buddi/install/dist/uninstall.js +260 -0
- package/node_modules/@buddi/install/dist/uninstall.js.map +1 -0
- package/node_modules/@buddi/runtime/dist/.tsbuildinfo +1 -1
- package/node_modules/@buddi/runtime/dist/codex-direct.d.ts +55 -0
- package/node_modules/@buddi/runtime/dist/codex-direct.d.ts.map +1 -0
- package/node_modules/@buddi/runtime/dist/codex-direct.js +334 -0
- package/node_modules/@buddi/runtime/dist/codex-direct.js.map +1 -0
- package/node_modules/@buddi/runtime/dist/codex-oauth.d.ts +66 -0
- package/node_modules/@buddi/runtime/dist/codex-oauth.d.ts.map +1 -0
- package/node_modules/@buddi/runtime/dist/codex-oauth.js +210 -0
- package/node_modules/@buddi/runtime/dist/codex-oauth.js.map +1 -0
- package/node_modules/@buddi/runtime/dist/codex-profile.d.ts +19 -0
- package/node_modules/@buddi/runtime/dist/codex-profile.d.ts.map +1 -0
- package/node_modules/@buddi/runtime/dist/codex-profile.js +99 -0
- package/node_modules/@buddi/runtime/dist/codex-profile.js.map +1 -0
- package/node_modules/@buddi/runtime/dist/context-window.d.ts.map +1 -1
- package/node_modules/@buddi/runtime/dist/context-window.js +9 -2
- package/node_modules/@buddi/runtime/dist/context-window.js.map +1 -1
- package/node_modules/@buddi/runtime/dist/delegate.d.ts +24 -0
- package/node_modules/@buddi/runtime/dist/delegate.d.ts.map +1 -1
- package/node_modules/@buddi/runtime/dist/delegate.js +87 -11
- package/node_modules/@buddi/runtime/dist/delegate.js.map +1 -1
- package/node_modules/@buddi/runtime/dist/index.d.ts +4 -4
- package/node_modules/@buddi/runtime/dist/index.d.ts.map +1 -1
- package/node_modules/@buddi/runtime/dist/index.js +5 -5
- package/node_modules/@buddi/runtime/dist/index.js.map +1 -1
- package/node_modules/@buddi/runtime/dist/loop.d.ts +7 -0
- package/node_modules/@buddi/runtime/dist/loop.d.ts.map +1 -1
- package/node_modules/@buddi/runtime/dist/loop.js +48 -8
- package/node_modules/@buddi/runtime/dist/loop.js.map +1 -1
- package/node_modules/@buddi/runtime/dist/ollama-signing.d.ts +52 -0
- package/node_modules/@buddi/runtime/dist/ollama-signing.d.ts.map +1 -0
- package/node_modules/@buddi/runtime/dist/ollama-signing.js +154 -0
- package/node_modules/@buddi/runtime/dist/ollama-signing.js.map +1 -0
- package/node_modules/@buddi/runtime/dist/openai.d.ts.map +1 -1
- package/node_modules/@buddi/runtime/dist/openai.js +38 -9
- package/node_modules/@buddi/runtime/dist/openai.js.map +1 -1
- package/node_modules/@buddi/runtime/dist/provider-models.d.ts +2 -0
- package/node_modules/@buddi/runtime/dist/provider-models.d.ts.map +1 -1
- package/node_modules/@buddi/runtime/dist/provider-models.js +8 -3
- package/node_modules/@buddi/runtime/dist/provider-models.js.map +1 -1
- package/node_modules/@buddi/tool-artifacts/dist/.tsbuildinfo +1 -1
- package/node_modules/@buddi/tool-browser/dist/.tsbuildinfo +1 -1
- package/node_modules/@buddi/tool-browser/dist/index.d.ts +1 -1
- package/node_modules/@buddi/tool-browser/dist/index.d.ts.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/index.js +12 -4
- package/node_modules/@buddi/tool-browser/dist/index.js.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/origin-pattern.d.ts +31 -0
- package/node_modules/@buddi/tool-browser/dist/origin-pattern.d.ts.map +1 -0
- package/node_modules/@buddi/tool-browser/dist/origin-pattern.js +61 -0
- package/node_modules/@buddi/tool-browser/dist/origin-pattern.js.map +1 -0
- package/node_modules/@buddi/tool-browser/dist/public-suffixes.d.ts +24 -0
- package/node_modules/@buddi/tool-browser/dist/public-suffixes.d.ts.map +1 -0
- package/node_modules/@buddi/tool-browser/dist/public-suffixes.js +153 -0
- package/node_modules/@buddi/tool-browser/dist/public-suffixes.js.map +1 -0
- package/node_modules/@buddi/tool-browser/dist/secrets.d.ts +45 -9
- package/node_modules/@buddi/tool-browser/dist/secrets.d.ts.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/secrets.js +77 -20
- package/node_modules/@buddi/tool-browser/dist/secrets.js.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/service.d.ts +10 -0
- package/node_modules/@buddi/tool-browser/dist/service.d.ts.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/service.js +84 -20
- package/node_modules/@buddi/tool-browser/dist/service.js.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/types.d.ts +7 -1
- package/node_modules/@buddi/tool-browser/dist/types.d.ts.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/types.js +7 -1
- package/node_modules/@buddi/tool-browser/dist/types.js.map +1 -1
- package/node_modules/@buddi/tool-email/dist/.tsbuildinfo +1 -1
- package/node_modules/@buddi/tool-email/dist/tools/read.js +1 -1
- package/node_modules/@buddi/tool-email/dist/tools/read.js.map +1 -1
- package/node_modules/@buddi/tool-host/dist/.tsbuildinfo +1 -1
- package/node_modules/@buddi/tool-memory/dist/.tsbuildinfo +1 -1
- package/node_modules/@buddi/tool-web/dist/.tsbuildinfo +1 -1
- package/node_modules/ws/lib/websocket.js +17 -3
- package/node_modules/ws/package.json +1 -1
- package/package.json +2 -2
- package/packages/cli/dist/.tsbuildinfo +1 -1
- package/packages/cli/dist/args.d.ts +7 -0
- package/packages/cli/dist/args.d.ts.map +1 -1
- package/packages/cli/dist/args.js +14 -0
- package/packages/cli/dist/args.js.map +1 -1
- package/packages/cli/dist/commands.d.ts.map +1 -1
- package/packages/cli/dist/commands.js +37 -2
- package/packages/cli/dist/commands.js.map +1 -1
- package/packages/cli/dist/dashboard-app.d.ts +6 -0
- package/packages/cli/dist/dashboard-app.d.ts.map +1 -1
- package/packages/cli/dist/dashboard-app.js +16 -1
- package/packages/cli/dist/dashboard-app.js.map +1 -1
- package/packages/cli/dist/doctor-probes.d.ts.map +1 -1
- package/packages/cli/dist/doctor-probes.js +5 -2
- package/packages/cli/dist/doctor-probes.js.map +1 -1
- package/packages/cli/dist/doctor.d.ts +16 -0
- package/packages/cli/dist/doctor.d.ts.map +1 -1
- package/packages/cli/dist/doctor.js +18 -0
- package/packages/cli/dist/doctor.js.map +1 -1
- package/packages/cli/dist/index.d.ts +1 -0
- package/packages/cli/dist/index.d.ts.map +1 -1
- package/packages/cli/dist/index.js +1 -0
- package/packages/cli/dist/index.js.map +1 -1
- package/packages/cli/dist/main.d.ts.map +1 -1
- package/packages/cli/dist/main.js +22 -2
- package/packages/cli/dist/main.js.map +1 -1
- package/packages/cli/dist/status.d.ts +10 -0
- package/packages/cli/dist/status.d.ts.map +1 -1
- package/packages/cli/dist/status.js +4 -0
- package/packages/cli/dist/status.js.map +1 -1
- package/packages/cli/dist/uninstall-cmd.d.ts +42 -0
- package/packages/cli/dist/uninstall-cmd.d.ts.map +1 -0
- package/packages/cli/dist/uninstall-cmd.js +208 -0
- package/packages/cli/dist/uninstall-cmd.js.map +1 -0
- package/packages/core/dist/.tsbuildinfo +1 -1
- package/packages/core/dist/agents/catalog.d.ts.map +1 -1
- package/packages/core/dist/agents/catalog.js +7 -1
- package/packages/core/dist/agents/catalog.js.map +1 -1
- package/packages/core/dist/agents/models.d.ts.map +1 -1
- package/packages/core/dist/agents/models.js +4 -3
- package/packages/core/dist/agents/models.js.map +1 -1
- package/packages/core/dist/agents/skills.d.ts +2 -2
- package/packages/core/dist/goals/frequency.d.ts +51 -0
- package/packages/core/dist/goals/frequency.d.ts.map +1 -0
- package/packages/core/dist/goals/frequency.js +113 -0
- package/packages/core/dist/goals/frequency.js.map +1 -0
- package/packages/core/dist/goals/index.d.ts +2 -0
- package/packages/core/dist/goals/index.d.ts.map +1 -1
- package/packages/core/dist/goals/index.js +2 -0
- package/packages/core/dist/goals/index.js.map +1 -1
- package/packages/core/dist/goals/math.d.ts +8 -2
- package/packages/core/dist/goals/math.d.ts.map +1 -1
- package/packages/core/dist/goals/math.js +17 -4
- package/packages/core/dist/goals/math.js.map +1 -1
- package/packages/core/dist/goals/owner.d.ts +129 -0
- package/packages/core/dist/goals/owner.d.ts.map +1 -0
- package/packages/core/dist/goals/owner.js +239 -0
- package/packages/core/dist/goals/owner.js.map +1 -0
- package/packages/core/dist/goals/store.d.ts.map +1 -1
- package/packages/core/dist/goals/store.js +11 -4
- package/packages/core/dist/goals/store.js.map +1 -1
- package/packages/core/dist/goals/types.d.ts +17 -3
- package/packages/core/dist/goals/types.d.ts.map +1 -1
- package/packages/core/dist/goals/types.js +8 -2
- package/packages/core/dist/goals/types.js.map +1 -1
- package/packages/core/dist/learning/announce.d.ts +29 -0
- package/packages/core/dist/learning/announce.d.ts.map +1 -0
- package/packages/core/dist/learning/announce.js +49 -0
- package/packages/core/dist/learning/announce.js.map +1 -0
- package/packages/core/dist/learning/index.d.ts +1 -0
- package/packages/core/dist/learning/index.d.ts.map +1 -1
- package/packages/core/dist/learning/index.js +1 -0
- package/packages/core/dist/learning/index.js.map +1 -1
- package/packages/core/dist/learning/store.d.ts.map +1 -1
- package/packages/core/dist/learning/store.js +15 -4
- package/packages/core/dist/learning/store.js.map +1 -1
- package/packages/core/dist/notifications/notify.d.ts.map +1 -1
- package/packages/core/dist/notifications/notify.js +1 -0
- package/packages/core/dist/notifications/notify.js.map +1 -1
- package/packages/core/dist/notifications/store.d.ts +2 -0
- package/packages/core/dist/notifications/store.d.ts.map +1 -1
- package/packages/core/dist/notifications/store.js +9 -0
- package/packages/core/dist/notifications/store.js.map +1 -1
- package/packages/core/dist/notifications/types.d.ts +6 -0
- package/packages/core/dist/notifications/types.d.ts.map +1 -1
- package/packages/core/dist/pages.d.ts +2 -2
- package/packages/core/dist/provider-accounts.d.ts +7 -1
- package/packages/core/dist/provider-accounts.d.ts.map +1 -1
- package/packages/core/dist/provider-accounts.js +14 -1
- package/packages/core/dist/provider-accounts.js.map +1 -1
- package/packages/core/dist/provider.d.ts +6 -0
- package/packages/core/dist/provider.d.ts.map +1 -1
- package/packages/core/dist/provider.js.map +1 -1
- package/packages/core/dist/sentinels/run.d.ts.map +1 -1
- package/packages/core/dist/sentinels/run.js +4 -1
- package/packages/core/dist/sentinels/run.js.map +1 -1
- package/packages/core/dist/sentinels/types.d.ts +20 -0
- package/packages/core/dist/sentinels/types.d.ts.map +1 -1
- package/packages/core/dist/sentinels/types.js.map +1 -1
- package/packages/core/dist/surfaces.d.ts +2 -0
- package/packages/core/dist/surfaces.d.ts.map +1 -1
- package/packages/core/dist/surfaces.js +7 -0
- package/packages/core/dist/surfaces.js.map +1 -1
- package/packages/core/dist/uninstall.d.ts +79 -0
- package/packages/core/dist/uninstall.d.ts.map +1 -0
- package/packages/core/dist/uninstall.js +109 -0
- package/packages/core/dist/uninstall.js.map +1 -0
- package/packages/core/dist/views.d.ts +66 -66
- package/packages/core/migrations/044_owner_metrics.sql +45 -0
- package/packages/core/migrations/045_frequency_goals.sql +19 -0
- package/packages/core/migrations/046_ollama_device_key.sql +7 -0
- package/packages/core/migrations/047_reported_context_windows.sql +5 -0
- package/packages/core/package.json +4 -0
- package/packages/gateway/dist/.tsbuildinfo +1 -1
- package/packages/gateway/dist/chat/usage.d.ts.map +1 -1
- package/packages/gateway/dist/chat/usage.js +1 -0
- package/packages/gateway/dist/chat/usage.js.map +1 -1
- package/packages/gateway/dist/codex-accounts.d.ts +24 -11
- package/packages/gateway/dist/codex-accounts.d.ts.map +1 -1
- package/packages/gateway/dist/codex-accounts.js +151 -215
- package/packages/gateway/dist/codex-accounts.js.map +1 -1
- package/packages/gateway/dist/index.d.ts +2 -0
- package/packages/gateway/dist/index.d.ts.map +1 -1
- package/packages/gateway/dist/index.js +2 -0
- package/packages/gateway/dist/index.js.map +1 -1
- package/packages/gateway/dist/missions/agent-run.d.ts.map +1 -1
- package/packages/gateway/dist/missions/agent-run.js +1 -0
- package/packages/gateway/dist/missions/agent-run.js.map +1 -1
- package/packages/gateway/dist/missions/execute.d.ts +7 -0
- package/packages/gateway/dist/missions/execute.d.ts.map +1 -1
- package/packages/gateway/dist/missions/execute.js +3 -1
- package/packages/gateway/dist/missions/execute.js.map +1 -1
- package/packages/gateway/dist/missions/goals-format.d.ts +32 -5
- package/packages/gateway/dist/missions/goals-format.d.ts.map +1 -1
- package/packages/gateway/dist/missions/goals-format.js +59 -2
- package/packages/gateway/dist/missions/goals-format.js.map +1 -1
- package/packages/gateway/dist/missions/goals-page.d.ts +60 -5
- package/packages/gateway/dist/missions/goals-page.d.ts.map +1 -1
- package/packages/gateway/dist/missions/goals-page.js +355 -8
- package/packages/gateway/dist/missions/goals-page.js.map +1 -1
- package/packages/gateway/dist/missions/goals.d.ts +64 -5
- package/packages/gateway/dist/missions/goals.d.ts.map +1 -1
- package/packages/gateway/dist/missions/goals.js +834 -116
- package/packages/gateway/dist/missions/goals.js.map +1 -1
- package/packages/gateway/dist/missions/sentinel-wake.d.ts +5 -0
- package/packages/gateway/dist/missions/sentinel-wake.d.ts.map +1 -1
- package/packages/gateway/dist/missions/sentinel-wake.js +12 -0
- package/packages/gateway/dist/missions/sentinel-wake.js.map +1 -1
- package/packages/gateway/dist/ollama-accounts.d.ts +87 -0
- package/packages/gateway/dist/ollama-accounts.d.ts.map +1 -0
- package/packages/gateway/dist/ollama-accounts.js +179 -0
- package/packages/gateway/dist/ollama-accounts.js.map +1 -0
- package/packages/gateway/dist/owner-notify.d.ts +2 -1
- package/packages/gateway/dist/owner-notify.d.ts.map +1 -1
- package/packages/gateway/dist/owner-notify.js +6 -2
- package/packages/gateway/dist/owner-notify.js.map +1 -1
- package/packages/gateway/dist/provider-accounts.d.ts +47 -1
- package/packages/gateway/dist/provider-accounts.d.ts.map +1 -1
- package/packages/gateway/dist/provider-accounts.js +204 -38
- package/packages/gateway/dist/provider-accounts.js.map +1 -1
- package/packages/gateway/dist/serve.d.ts.map +1 -1
- package/packages/gateway/dist/serve.js +1 -0
- package/packages/gateway/dist/serve.js.map +1 -1
- package/packages/gateway/dist/subscription-signins.d.ts +28 -0
- package/packages/gateway/dist/subscription-signins.d.ts.map +1 -0
- package/packages/gateway/dist/subscription-signins.js +30 -0
- package/packages/gateway/dist/subscription-signins.js.map +1 -0
- package/packages/gateway/dist/surfaces/context-budget.d.ts.map +1 -1
- package/packages/gateway/dist/surfaces/context-budget.js +10 -3
- package/packages/gateway/dist/surfaces/context-budget.js.map +1 -1
- package/packages/gateway/dist/telegram/api.d.ts +43 -9
- package/packages/gateway/dist/telegram/api.d.ts.map +1 -1
- package/packages/gateway/dist/telegram/api.js +80 -21
- package/packages/gateway/dist/telegram/api.js.map +1 -1
- package/packages/gateway/dist/telegram/attachments.d.ts +6 -0
- package/packages/gateway/dist/telegram/attachments.d.ts.map +1 -1
- package/packages/gateway/dist/telegram/attachments.js +1 -0
- package/packages/gateway/dist/telegram/attachments.js.map +1 -1
- package/packages/gateway/dist/telegram/browser-view.d.ts.map +1 -1
- package/packages/gateway/dist/telegram/browser-view.js +2 -1
- package/packages/gateway/dist/telegram/browser-view.js.map +1 -1
- package/packages/gateway/dist/telegram/channel.d.ts +7 -1
- package/packages/gateway/dist/telegram/channel.d.ts.map +1 -1
- package/packages/gateway/dist/telegram/channel.js +24 -2
- package/packages/gateway/dist/telegram/channel.js.map +1 -1
- package/packages/gateway/dist/telegram/commands.d.ts +28 -0
- package/packages/gateway/dist/telegram/commands.d.ts.map +1 -0
- package/packages/gateway/dist/telegram/commands.js +74 -0
- package/packages/gateway/dist/telegram/commands.js.map +1 -0
- package/packages/gateway/dist/telegram/main.d.ts +3 -0
- package/packages/gateway/dist/telegram/main.d.ts.map +1 -1
- package/packages/gateway/dist/telegram/main.js +71 -2
- package/packages/gateway/dist/telegram/main.js.map +1 -1
- package/packages/gateway/dist/telegram/outbound.d.ts +191 -0
- package/packages/gateway/dist/telegram/outbound.d.ts.map +1 -0
- package/packages/gateway/dist/telegram/outbound.js +671 -0
- package/packages/gateway/dist/telegram/outbound.js.map +1 -0
- package/packages/gateway/dist/telegram/proposals.d.ts +65 -0
- package/packages/gateway/dist/telegram/proposals.d.ts.map +1 -0
- package/packages/gateway/dist/telegram/proposals.js +183 -0
- package/packages/gateway/dist/telegram/proposals.js.map +1 -0
- package/packages/gateway/dist/telegram/surface.d.ts +47 -47
- package/packages/gateway/dist/telegram/surface.d.ts.map +1 -1
- package/packages/gateway/dist/telegram/surface.js +99 -290
- package/packages/gateway/dist/telegram/surface.js.map +1 -1
- package/packages/gateway/dist/web/onboarding.d.ts.map +1 -1
- package/packages/gateway/dist/web/onboarding.js +4 -0
- package/packages/gateway/dist/web/onboarding.js.map +1 -1
- package/packages/gateway/dist/web/secrets.d.ts.map +1 -1
- package/packages/gateway/dist/web/secrets.js +3 -2
- package/packages/gateway/dist/web/secrets.js.map +1 -1
- package/packages/gateway/dist/web/server.d.ts.map +1 -1
- package/packages/gateway/dist/web/server.js +36 -14
- package/packages/gateway/dist/web/server.js.map +1 -1
- package/packages/gateway/dist/web/telegram.d.ts +39 -0
- package/packages/gateway/dist/web/telegram.d.ts.map +1 -1
- package/packages/gateway/dist/web/telegram.js +53 -1
- package/packages/gateway/dist/web/telegram.js.map +1 -1
- package/packages/install/dist/.tsbuildinfo +1 -1
- package/packages/install/dist/launcher.d.ts.map +1 -1
- package/packages/install/dist/launcher.js +135 -10
- package/packages/install/dist/launcher.js.map +1 -1
- package/packages/install/dist/uninstall.d.ts +59 -0
- package/packages/install/dist/uninstall.d.ts.map +1 -0
- package/packages/install/dist/uninstall.js +260 -0
- package/packages/install/dist/uninstall.js.map +1 -0
- package/packages/runtime/dist/.tsbuildinfo +1 -1
- package/packages/runtime/dist/codex-direct.d.ts +55 -0
- package/packages/runtime/dist/codex-direct.d.ts.map +1 -0
- package/packages/runtime/dist/codex-direct.js +334 -0
- package/packages/runtime/dist/codex-direct.js.map +1 -0
- package/packages/runtime/dist/codex-oauth.d.ts +66 -0
- package/packages/runtime/dist/codex-oauth.d.ts.map +1 -0
- package/packages/runtime/dist/codex-oauth.js +210 -0
- package/packages/runtime/dist/codex-oauth.js.map +1 -0
- package/packages/runtime/dist/codex-profile.d.ts +19 -0
- package/packages/runtime/dist/codex-profile.d.ts.map +1 -0
- package/packages/runtime/dist/codex-profile.js +99 -0
- package/packages/runtime/dist/codex-profile.js.map +1 -0
- package/packages/runtime/dist/context-window.d.ts.map +1 -1
- package/packages/runtime/dist/context-window.js +9 -2
- package/packages/runtime/dist/context-window.js.map +1 -1
- package/packages/runtime/dist/delegate.d.ts +24 -0
- package/packages/runtime/dist/delegate.d.ts.map +1 -1
- package/packages/runtime/dist/delegate.js +87 -11
- package/packages/runtime/dist/delegate.js.map +1 -1
- package/packages/runtime/dist/index.d.ts +4 -4
- package/packages/runtime/dist/index.d.ts.map +1 -1
- package/packages/runtime/dist/index.js +5 -5
- package/packages/runtime/dist/index.js.map +1 -1
- package/packages/runtime/dist/loop.d.ts +7 -0
- package/packages/runtime/dist/loop.d.ts.map +1 -1
- package/packages/runtime/dist/loop.js +48 -8
- package/packages/runtime/dist/loop.js.map +1 -1
- package/packages/runtime/dist/ollama-signing.d.ts +52 -0
- package/packages/runtime/dist/ollama-signing.d.ts.map +1 -0
- package/packages/runtime/dist/ollama-signing.js +154 -0
- package/packages/runtime/dist/ollama-signing.js.map +1 -0
- package/packages/runtime/dist/openai.d.ts.map +1 -1
- package/packages/runtime/dist/openai.js +38 -9
- package/packages/runtime/dist/openai.js.map +1 -1
- package/packages/runtime/dist/provider-models.d.ts +2 -0
- package/packages/runtime/dist/provider-models.d.ts.map +1 -1
- package/packages/runtime/dist/provider-models.js +8 -3
- package/packages/runtime/dist/provider-models.js.map +1 -1
- package/packages/tools/artifacts/dist/.tsbuildinfo +1 -1
- package/packages/tools/browser/dist/.tsbuildinfo +1 -1
- package/packages/tools/browser/dist/index.d.ts +1 -1
- package/packages/tools/browser/dist/index.d.ts.map +1 -1
- package/packages/tools/browser/dist/index.js +12 -4
- package/packages/tools/browser/dist/index.js.map +1 -1
- package/packages/tools/browser/dist/origin-pattern.d.ts +31 -0
- package/packages/tools/browser/dist/origin-pattern.d.ts.map +1 -0
- package/packages/tools/browser/dist/origin-pattern.js +61 -0
- package/packages/tools/browser/dist/origin-pattern.js.map +1 -0
- package/packages/tools/browser/dist/public-suffixes.d.ts +24 -0
- package/packages/tools/browser/dist/public-suffixes.d.ts.map +1 -0
- package/packages/tools/browser/dist/public-suffixes.js +153 -0
- package/packages/tools/browser/dist/public-suffixes.js.map +1 -0
- package/packages/tools/browser/dist/secrets.d.ts +45 -9
- package/packages/tools/browser/dist/secrets.d.ts.map +1 -1
- package/packages/tools/browser/dist/secrets.js +77 -20
- package/packages/tools/browser/dist/secrets.js.map +1 -1
- package/packages/tools/browser/dist/service.d.ts +10 -0
- package/packages/tools/browser/dist/service.d.ts.map +1 -1
- package/packages/tools/browser/dist/service.js +84 -20
- package/packages/tools/browser/dist/service.js.map +1 -1
- package/packages/tools/browser/dist/types.d.ts +7 -1
- package/packages/tools/browser/dist/types.d.ts.map +1 -1
- package/packages/tools/browser/dist/types.js +7 -1
- package/packages/tools/browser/dist/types.js.map +1 -1
- package/packages/tools/email/dist/.tsbuildinfo +1 -1
- package/packages/tools/email/dist/tools/read.js +1 -1
- package/packages/tools/email/dist/tools/read.js.map +1 -1
- package/packages/tools/host/dist/.tsbuildinfo +1 -1
- package/packages/tools/memory/dist/.tsbuildinfo +1 -1
- package/packages/tools/web/dist/.tsbuildinfo +1 -1
- package/packages/web/dist/assets/index-BRc5auXm.js +123 -0
- package/packages/web/dist/assets/{index-DCn0vN58.css → index-DwUpotTR.css} +1 -1
- package/packages/web/dist/index.html +2 -2
- package/node_modules/@buddi/runtime/dist/codex-app-server.d.ts +0 -20
- package/node_modules/@buddi/runtime/dist/codex-app-server.d.ts.map +0 -1
- package/node_modules/@buddi/runtime/dist/codex-app-server.js +0 -172
- package/node_modules/@buddi/runtime/dist/codex-app-server.js.map +0 -1
- package/node_modules/@buddi/runtime/dist/codex-policy.d.ts +0 -6
- package/node_modules/@buddi/runtime/dist/codex-policy.d.ts.map +0 -1
- package/node_modules/@buddi/runtime/dist/codex-policy.js +0 -79
- package/node_modules/@buddi/runtime/dist/codex-policy.js.map +0 -1
- package/node_modules/@buddi/runtime/dist/codex-rpc.d.ts +0 -34
- package/node_modules/@buddi/runtime/dist/codex-rpc.d.ts.map +0 -1
- package/node_modules/@buddi/runtime/dist/codex-rpc.js +0 -152
- package/node_modules/@buddi/runtime/dist/codex-rpc.js.map +0 -1
- package/node_modules/@buddi/runtime/dist/codex-session.d.ts +0 -30
- package/node_modules/@buddi/runtime/dist/codex-session.d.ts.map +0 -1
- package/node_modules/@buddi/runtime/dist/codex-session.js +0 -169
- package/node_modules/@buddi/runtime/dist/codex-session.js.map +0 -1
- package/packages/runtime/dist/codex-app-server.d.ts +0 -20
- package/packages/runtime/dist/codex-app-server.d.ts.map +0 -1
- package/packages/runtime/dist/codex-app-server.js +0 -172
- package/packages/runtime/dist/codex-app-server.js.map +0 -1
- package/packages/runtime/dist/codex-policy.d.ts +0 -6
- package/packages/runtime/dist/codex-policy.d.ts.map +0 -1
- package/packages/runtime/dist/codex-policy.js +0 -79
- package/packages/runtime/dist/codex-policy.js.map +0 -1
- package/packages/runtime/dist/codex-rpc.d.ts +0 -34
- package/packages/runtime/dist/codex-rpc.d.ts.map +0 -1
- package/packages/runtime/dist/codex-rpc.js +0 -152
- package/packages/runtime/dist/codex-rpc.js.map +0 -1
- package/packages/runtime/dist/codex-session.d.ts +0 -30
- package/packages/runtime/dist/codex-session.d.ts.map +0 -1
- package/packages/runtime/dist/codex-session.js +0 -169
- package/packages/runtime/dist/codex-session.js.map +0 -1
- package/packages/web/dist/assets/index-BRkIoT-q.js +0 -123
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
* `goal.status`; only the holder may update or close, and a non-holder is
|
|
29
29
|
* told `not-yours` in as many words, the way `reminder.cancel` does it.
|
|
30
30
|
*/
|
|
31
|
-
import { GOAL_CADENCES, MAX_GOAL_TITLE, MAX_MILESTONES, MAX_OPEN_GOALS, assertApprovedEffect, closeGoal, createGoal, getGoal, checkMetricParams, STANDING_CHECKS, lastCheck, lastMeasuredCheck, listGoals, localDateString, measureMetricResult, measuredChecks, metricParamsSchema, milestoneValue, milestonesCrossed, paceNeeded, parseReminderWhen, reached, recentChecks, recordCheck, settleGoal, standingOf, targetValue, updateGoal, } from '@buddi/core';
|
|
31
|
+
import { GOAL_CADENCES, MAX_GOAL_TITLE, MAX_MILESTONES, MAX_OPEN_GOALS, assertApprovedEffect, closeGoal, createGoal, getGoal, checkMetricParams, STANDING_CHECKS, lastCheck, lastMeasuredCheck, listGoals, localDateString, measureMetricResult, measuredChecks, metricParamsSchema, milestoneValue, milestonesCrossed, paceNeeded, parseReminderWhen, reached, recentChecks, recordCheck, settleGoal, standingOf, targetValue, updateGoal, FREQUENCY_PERS, MAX_FREQUENCY_COUNT, frequencySettles, frequencyWords, METRIC_DIRECTIONS, METRIC_UNITS, MAX_OWNER_LABEL, MAX_OWNER_UNIT_LABEL, OWNER_SOURCE, createOwnerMetric, cadenceMs, getOwnerMetric, isOwnerMetric, latestOwnerValue, ownerMetricId, ownerSlugOf, recordOwnerValue, refuseOutsideBand, refuseOwnerSlug, } from '@buddi/core';
|
|
32
32
|
import { z } from 'zod';
|
|
33
|
-
import { GOALS_SENTINEL_ID, checkLines, formatPace, formatValue, goalKey, goalLine } from './goals-format.js';
|
|
34
|
-
import { CHART_CHECKS, chartOf, createGoalHome, createGoalOwnerClose, createGoalQueries, goalViews, goalsPage, } from './goals-page.js';
|
|
33
|
+
import { GOALS_SENTINEL_ID, asOwnerSource, frequencyNow, baseUnit, checkLines, formatPace, formatValue, goalKey, goalLine, unitLabelOf, valueUnitOf, } from './goals-format.js';
|
|
34
|
+
import { CHART_CHECKS, chartOf, createGoalHome, createGoalOwnerClose, createGoalQueries, frequencyOf, goalOwnerValues, goalViews, goalsPage, } from './goals-page.js';
|
|
35
35
|
/** Plugin family name for the goal tools. The rows live in core's schema. */
|
|
36
36
|
export const GOAL_PLUGIN = 'goal';
|
|
37
37
|
/**
|
|
@@ -77,6 +77,16 @@ export const GOAL_WAKE_INSTRUCTION = 'Verify with your own tools, then report; t
|
|
|
77
77
|
* they and this one import is what keeps those two out of a cycle.
|
|
78
78
|
* ------------------------------------------------------------------ */
|
|
79
79
|
export { checkLines, formatPace, formatValue, goalLine } from './goals-format.js';
|
|
80
|
+
export { asOwnerSource } from './goals-format.js';
|
|
81
|
+
/** Where a value was said, from the surface the run answers on. */
|
|
82
|
+
export function valueSourceOf(ctx) {
|
|
83
|
+
const surface = ctx.surface?.id;
|
|
84
|
+
if (surface === 'telegram')
|
|
85
|
+
return 'telegram';
|
|
86
|
+
if (surface === 'web' || surface === 'cli')
|
|
87
|
+
return 'chat';
|
|
88
|
+
return 'api';
|
|
89
|
+
}
|
|
80
90
|
/**
|
|
81
91
|
* The sentence the owner approves.
|
|
82
92
|
*
|
|
@@ -105,6 +115,30 @@ export function renderGoalSet(envelope, unit, direction, timezone) {
|
|
|
105
115
|
const stale = readingAsOf !== null && setAt.getTime() - readingAsOf.getTime() > 24 * 60 * 60_000
|
|
106
116
|
? ` (reading as of ${localDateString(readingAsOf, timezone)})`
|
|
107
117
|
: '';
|
|
118
|
+
const measuredBy = envelope.owner !== undefined
|
|
119
|
+
? `Measured by you, when you tell buddi${envelope.owner.isNew ? ` (a new metric, ${envelope.metric})` : ''}, until ${localDateString(asGoal.deadline, timezone)}.`
|
|
120
|
+
: `Measured by ${envelope.metric}${Object.keys(envelope.params).length === 0 ? '' : ` ${JSON.stringify(envelope.params)}`}, every ${envelope.cadence === 'daily' ? 'day' : 'week'}, until ${localDateString(asGoal.deadline, timezone)}.`;
|
|
121
|
+
if (envelope.target.kind === 'frequency') {
|
|
122
|
+
/*
|
|
123
|
+
* "3 times a week until 2026-12-31": a count per window has no "from"
|
|
124
|
+
* and no pace per week — the window is the pace. Milestones are streaks.
|
|
125
|
+
*/
|
|
126
|
+
const per = envelope.target.per;
|
|
127
|
+
const lines = [
|
|
128
|
+
`${envelope.title}`,
|
|
129
|
+
'',
|
|
130
|
+
`${frequencyWords(envelope.target)} until ${localDateString(asGoal.deadline, timezone)}, ` +
|
|
131
|
+
`checked ${envelope.cadence}, held by @${envelope.agentId}`,
|
|
132
|
+
'',
|
|
133
|
+
measuredBy,
|
|
134
|
+
];
|
|
135
|
+
if (envelope.milestones.length > 0) {
|
|
136
|
+
lines.push(`Milestones you will hear about, once each: ${envelope.milestones
|
|
137
|
+
.map((m) => `${m} ${per}s in a row`)
|
|
138
|
+
.join(', ')}.`);
|
|
139
|
+
}
|
|
140
|
+
return lines.join('\n');
|
|
141
|
+
}
|
|
108
142
|
const lines = [
|
|
109
143
|
`${envelope.title}`,
|
|
110
144
|
'',
|
|
@@ -112,7 +146,7 @@ export function renderGoalSet(envelope, unit, direction, timezone) {
|
|
|
112
146
|
`${formatValue(targetValue(asGoal), unit, currency)} by ${localDateString(asGoal.deadline, timezone)}: ` +
|
|
113
147
|
`${formatPace(pace, unit, direction, currency)}, checked ${envelope.cadence}, held by @${envelope.agentId}`,
|
|
114
148
|
'',
|
|
115
|
-
|
|
149
|
+
measuredBy,
|
|
116
150
|
];
|
|
117
151
|
if (envelope.milestones.length > 0) {
|
|
118
152
|
lines.push(`Milestones you will hear about, once each: ${envelope.milestones
|
|
@@ -123,10 +157,12 @@ export function renderGoalSet(envelope, unit, direction, timezone) {
|
|
|
123
157
|
}
|
|
124
158
|
/** The card: one line per field that actually changes, before → after. */
|
|
125
159
|
export function renderGoalUpdate(envelope, unit, timezone, baseline, currency = null) {
|
|
126
|
-
const value = (target) =>
|
|
160
|
+
const value = (target) => target.kind === 'frequency'
|
|
161
|
+
? frequencyWords(target)
|
|
162
|
+
: formatValue(targetValue({ target, baseline: { value: baseline, asOf: new Date(0) } }), unit, currency);
|
|
127
163
|
const { before, after } = envelope;
|
|
128
164
|
const rows = [];
|
|
129
|
-
if (before.target
|
|
165
|
+
if (JSON.stringify(before.target) !== JSON.stringify(after.target)) {
|
|
130
166
|
rows.push(`Target: ${value(before.target)} → ${value(after.target)}`);
|
|
131
167
|
}
|
|
132
168
|
if (before.deadline !== after.deadline) {
|
|
@@ -146,7 +182,7 @@ export function renderGoalUpdate(envelope, unit, timezone, baseline, currency =
|
|
|
146
182
|
/* ------------------------------------------------------------------ *
|
|
147
183
|
* Inputs
|
|
148
184
|
* ------------------------------------------------------------------ */
|
|
149
|
-
const
|
|
185
|
+
const levelTargetInput = z
|
|
150
186
|
.object({
|
|
151
187
|
kind: z
|
|
152
188
|
.enum(['absolute', 'delta'])
|
|
@@ -158,6 +194,42 @@ const targetInput = z
|
|
|
158
194
|
.describe('The number. For a delta it carries its own sign: -40000 to come down by 40,000.'),
|
|
159
195
|
})
|
|
160
196
|
.strict();
|
|
197
|
+
const frequencyTargetInput = z
|
|
198
|
+
.object({
|
|
199
|
+
kind: z
|
|
200
|
+
.literal('frequency')
|
|
201
|
+
.describe('"run three times a week": how many values the owner reports in every week or month.'),
|
|
202
|
+
count: z.number().int().min(1).max(MAX_FREQUENCY_COUNT).describe('How many times in each window: 3.'),
|
|
203
|
+
per: z.enum(FREQUENCY_PERS).describe('The window: a week (Monday to Sunday) or a calendar month.'),
|
|
204
|
+
})
|
|
205
|
+
.strict();
|
|
206
|
+
/**
|
|
207
|
+
* A level target, or a frequency. A frequency goal counts the values of a
|
|
208
|
+
* metric the owner reports; "finish X by Friday" is neither — that is a
|
|
209
|
+
* reminder or a mission.
|
|
210
|
+
*/
|
|
211
|
+
const targetInput = z.union([levelTargetInput, frequencyTargetInput]);
|
|
212
|
+
/** A metric the owner will report, named the first time a goal needs it. */
|
|
213
|
+
const ownerMetricInput = z
|
|
214
|
+
.object({
|
|
215
|
+
slug: z
|
|
216
|
+
.string()
|
|
217
|
+
.min(1)
|
|
218
|
+
.max(40)
|
|
219
|
+
.describe('A short kebab name, unique here: "weight", "resting-heart-rate". The metric becomes owner.<slug>.'),
|
|
220
|
+
label: z.string().min(1).max(MAX_OWNER_LABEL).describe('What it is, in the owner\'s words: "Weight".'),
|
|
221
|
+
unit: z
|
|
222
|
+
.enum(METRIC_UNITS)
|
|
223
|
+
.describe('How the number is formatted. Weight is "number" with a unitLabel; a count of runs is "count".'),
|
|
224
|
+
direction: z.enum(METRIC_DIRECTIONS).describe('Which way is better: "down" for weight to lose, "up" for runs.'),
|
|
225
|
+
unitLabel: z
|
|
226
|
+
.string()
|
|
227
|
+
.min(1)
|
|
228
|
+
.max(MAX_OWNER_UNIT_LABEL)
|
|
229
|
+
.optional()
|
|
230
|
+
.describe('The word after the number, when there is one: "lb", "kg", "km".'),
|
|
231
|
+
})
|
|
232
|
+
.strict();
|
|
161
233
|
const setInput = z
|
|
162
234
|
.object({
|
|
163
235
|
title: z
|
|
@@ -166,9 +238,16 @@ const setInput = z
|
|
|
166
238
|
.max(MAX_GOAL_TITLE)
|
|
167
239
|
.describe('A short name the owner will recognise on a card months from now: "Debt down by 40k".'),
|
|
168
240
|
metric: z
|
|
169
|
-
.string()
|
|
170
|
-
.
|
|
171
|
-
|
|
241
|
+
.union([z.string().min(1), z.object({ owner: ownerMetricInput }).strict()])
|
|
242
|
+
.describe('The metric to watch: an id exactly as goal.metrics lists it ("finance.total_debt", "owner.weight"), ' +
|
|
243
|
+
'or, when no metric measures what the owner named, {"owner": {slug, label, unit, direction, unitLabel?}} ' +
|
|
244
|
+
'to create one the owner reports. Never create an owner metric for something a plugin already measures.'),
|
|
245
|
+
baseline: z
|
|
246
|
+
.object({ value: z.number().finite().describe('The number the owner said: "I\'m 288" is 288.') })
|
|
247
|
+
.strict()
|
|
248
|
+
.optional()
|
|
249
|
+
.describe('Only for a metric the owner reports: where they are today, from the sentence that set the goal, so ' +
|
|
250
|
+
'the goal starts measured. A plugin metric is measured instead.'),
|
|
172
251
|
params: z
|
|
173
252
|
.record(z.unknown())
|
|
174
253
|
.optional()
|
|
@@ -188,7 +267,8 @@ const setInput = z
|
|
|
188
267
|
.max(MAX_MILESTONES)
|
|
189
268
|
.optional()
|
|
190
269
|
.describe('Numbers on the same scale as the target — deltas when the target is a delta — that you want to say ' +
|
|
191
|
-
'something about when they are crossed.
|
|
270
|
+
'something about when they are crossed. For a frequency goal they are streaks: [4, 8] is four and ' +
|
|
271
|
+
'eight windows met in a row. Each fires once, ever.'),
|
|
192
272
|
})
|
|
193
273
|
.strict();
|
|
194
274
|
const updateInput = z
|
|
@@ -223,7 +303,52 @@ const statusInput = z
|
|
|
223
303
|
.describe('One goal, by id — any goal, whoever holds it. Leave it out for all of your own.'),
|
|
224
304
|
})
|
|
225
305
|
.strict();
|
|
306
|
+
const recordInput = z
|
|
307
|
+
.object({
|
|
308
|
+
goal: z
|
|
309
|
+
.string()
|
|
310
|
+
.min(1)
|
|
311
|
+
.optional()
|
|
312
|
+
.describe('The goal the number is for, by id. Name this or metric, not both.'),
|
|
313
|
+
metric: z
|
|
314
|
+
.string()
|
|
315
|
+
.min(1)
|
|
316
|
+
.optional()
|
|
317
|
+
.describe('Or the metric the owner reports: "owner.weight", or just "weight".'),
|
|
318
|
+
value: z.number().finite().describe('The number the owner said. An occurrence ("ran today") is 1.'),
|
|
319
|
+
asOf: z
|
|
320
|
+
.string()
|
|
321
|
+
.min(1)
|
|
322
|
+
.optional()
|
|
323
|
+
.describe("When it was true, in the owner's timezone: 2026-09-26 or 2026-09-26T07:30. Leave it out for now."),
|
|
324
|
+
note: z.string().max(280).optional().describe('A few words the owner added: "after the holidays".'),
|
|
325
|
+
confirmed: z
|
|
326
|
+
.boolean()
|
|
327
|
+
.optional()
|
|
328
|
+
.describe('Set only after the owner confirmed a value this tool refused as far from the last one.'),
|
|
329
|
+
})
|
|
330
|
+
.strict();
|
|
331
|
+
/** A conversation id is a uuid; anything else is not provenance worth keeping. */
|
|
332
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
226
333
|
const refusal = (reason, message) => ({ ok: false, reason, message });
|
|
334
|
+
/**
|
|
335
|
+
* A frequency goal's milestones are streaks — windows met in a row — so they
|
|
336
|
+
* are whole numbers from 1 up, listed in the order they will be reached.
|
|
337
|
+
*/
|
|
338
|
+
function refuseStreaks(milestones) {
|
|
339
|
+
let previous = 0;
|
|
340
|
+
for (const milestone of milestones) {
|
|
341
|
+
if (!Number.isInteger(milestone) || milestone <= previous) {
|
|
342
|
+
return refusal('wrong-direction', `the milestone ${milestone} is not a streak: a frequency goal's milestones are windows met in a row, ` +
|
|
343
|
+
'whole numbers from 1 up, listed from the smallest ([4, 8]).');
|
|
344
|
+
}
|
|
345
|
+
previous = milestone;
|
|
346
|
+
}
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
/** How many owner values and frequency windows `goal.status` prints. */
|
|
350
|
+
export const STATUS_VALUES = 10;
|
|
351
|
+
export const STATUS_WINDOWS = 12;
|
|
227
352
|
/* ------------------------------------------------------------------ *
|
|
228
353
|
* The manifest
|
|
229
354
|
* ------------------------------------------------------------------ */
|
|
@@ -234,9 +359,15 @@ const refusal = (reason, message) => ({ ok: false, reason, message });
|
|
|
234
359
|
* `MetricSource` so a suite can hand it one in-memory metric and exercise the
|
|
235
360
|
* whole surface without building a plugin.
|
|
236
361
|
*/
|
|
237
|
-
export function createGoalManifest(
|
|
362
|
+
export function createGoalManifest(base) {
|
|
363
|
+
/*
|
|
364
|
+
* The plugin metrics and the owner's, behind one `metric(id)`. Every tool
|
|
365
|
+
* refreshes its owner half first — one small `select` — so a metric another
|
|
366
|
+
* conversation created a minute ago is known here.
|
|
367
|
+
*/
|
|
368
|
+
const source = asOwnerSource(base);
|
|
238
369
|
/** The unit a goal's numbers are rendered in, or a safe default. */
|
|
239
|
-
const unitOf = (metric) => source
|
|
370
|
+
const unitOf = (metric) => valueUnitOf(source, metric);
|
|
240
371
|
/** Which way is better, for a goal whose plugin may have been uninstalled. */
|
|
241
372
|
const directionOf = (metric) => source.metric(metric)?.direction ?? 'down';
|
|
242
373
|
/**
|
|
@@ -280,15 +411,96 @@ export function createGoalManifest(source) {
|
|
|
280
411
|
}
|
|
281
412
|
return null;
|
|
282
413
|
}
|
|
414
|
+
function definitionOf(metric) {
|
|
415
|
+
return {
|
|
416
|
+
slug: metric.slug,
|
|
417
|
+
label: metric.label,
|
|
418
|
+
unit: metric.unit,
|
|
419
|
+
direction: metric.direction,
|
|
420
|
+
unitLabel: metric.unitLabel,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Which metric this is, or why there is none.
|
|
425
|
+
*
|
|
426
|
+
* A string names an installed metric (a plugin's, or an owner metric that
|
|
427
|
+
* already exists). An `{ owner }` definition names one to create — or one
|
|
428
|
+
* that exists already, in which case it has to agree about the unit and the
|
|
429
|
+
* direction: two goals reading one series in opposite directions is a
|
|
430
|
+
* contradiction, not a second opinion.
|
|
431
|
+
*/
|
|
432
|
+
async function resolveMetric(input, ctx) {
|
|
433
|
+
if (typeof input.metric === 'string') {
|
|
434
|
+
const found = source.metric(input.metric);
|
|
435
|
+
if (found === undefined) {
|
|
436
|
+
return refusal('unknown-metric', `no metric "${input.metric}" is installed here. Call goal.metrics and name one of those, or pass ` +
|
|
437
|
+
'{"owner": {...}} for a number the owner will report; a goal without a metric is a reminder.');
|
|
438
|
+
}
|
|
439
|
+
if (isOwnerMetric(found)) {
|
|
440
|
+
const slug = ownerSlugOf(found.id);
|
|
441
|
+
return {
|
|
442
|
+
ok: true,
|
|
443
|
+
metric: {
|
|
444
|
+
id: found.id,
|
|
445
|
+
unit: valueUnitOf(source, found.id),
|
|
446
|
+
direction: found.direction,
|
|
447
|
+
owner: {
|
|
448
|
+
metric: { slug, label: found.label, unit: found.unit, direction: found.direction, unitLabel: found.unitLabel },
|
|
449
|
+
isNew: false,
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
return { ok: true, metric: { id: found.id, unit: found.unit, direction: found.direction, owner: null } };
|
|
455
|
+
}
|
|
456
|
+
const def = input.metric.owner;
|
|
457
|
+
const badSlug = refuseOwnerSlug(def.slug);
|
|
458
|
+
if (badSlug !== null)
|
|
459
|
+
return refusal('invalid-metric', badSlug);
|
|
460
|
+
const wanted = {
|
|
461
|
+
slug: def.slug,
|
|
462
|
+
label: def.label.trim(),
|
|
463
|
+
unit: def.unit,
|
|
464
|
+
direction: def.direction,
|
|
465
|
+
unitLabel: def.unitLabel?.trim() || null,
|
|
466
|
+
};
|
|
467
|
+
const existing = await getOwnerMetric(ctx.db, def.slug);
|
|
468
|
+
if (existing !== null) {
|
|
469
|
+
if (existing.unit !== wanted.unit ||
|
|
470
|
+
existing.direction !== wanted.direction ||
|
|
471
|
+
(existing.unitLabel ?? null) !== wanted.unitLabel) {
|
|
472
|
+
return refusal('metric-differs', `owner.${def.slug} already exists as ${existing.unit}${existing.unitLabel ? ` (${existing.unitLabel})` : ''}, better ${existing.direction}. Name it as "owner.${def.slug}" to use it, or choose another slug.`);
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
ok: true,
|
|
476
|
+
metric: {
|
|
477
|
+
id: existing.id,
|
|
478
|
+
unit: existing.unitLabel ? { unit: existing.unit, label: existing.unitLabel } : existing.unit,
|
|
479
|
+
direction: existing.direction,
|
|
480
|
+
owner: { metric: definitionOf(existing), isNew: false },
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
ok: true,
|
|
486
|
+
metric: {
|
|
487
|
+
id: ownerMetricId(def.slug),
|
|
488
|
+
unit: wanted.unitLabel ? { unit: wanted.unit, label: wanted.unitLabel } : wanted.unit,
|
|
489
|
+
direction: wanted.direction,
|
|
490
|
+
owner: { metric: wanted, isNew: true },
|
|
491
|
+
},
|
|
492
|
+
};
|
|
493
|
+
}
|
|
283
494
|
/**
|
|
284
495
|
* Everything `goal.set` refuses before an approval exists, in one place.
|
|
285
496
|
*
|
|
286
497
|
* Read twice on purpose: by `describe`, so the call never becomes a card,
|
|
287
498
|
* and again by `execute`, because the approval was recorded minutes or hours
|
|
288
499
|
* ago and "a delegate may not" is a fact about the run that is executing. It
|
|
289
|
-
* only reads the arguments and the
|
|
500
|
+
* only reads the arguments, the context and the metric definitions, so both
|
|
501
|
+
* readings agree.
|
|
290
502
|
*/
|
|
291
|
-
function refuseSet(input, ctx) {
|
|
503
|
+
async function refuseSet(input, ctx) {
|
|
292
504
|
if (!ctx.agentId) {
|
|
293
505
|
return refusal('no-agent', 'a goal belongs to the agent that holds it, and this run has no agent id, so there is nobody to hold it');
|
|
294
506
|
}
|
|
@@ -296,14 +508,41 @@ export function createGoalManifest(source) {
|
|
|
296
508
|
return refusal('delegate', 'a delegate cannot set a goal: a goal outlives this run by months and belongs to the agent that will ' +
|
|
297
509
|
'be woken about it. Report back instead, and let the agent that asked you propose it.');
|
|
298
510
|
}
|
|
299
|
-
const
|
|
300
|
-
if (
|
|
301
|
-
return
|
|
302
|
-
|
|
511
|
+
const resolved = await resolveMetric(input, ctx);
|
|
512
|
+
if (!resolved.ok)
|
|
513
|
+
return resolved;
|
|
514
|
+
const metric = resolved.metric;
|
|
515
|
+
let params = {};
|
|
516
|
+
if (metric.owner !== null) {
|
|
517
|
+
if (input.params !== undefined && Object.keys(input.params).length > 0) {
|
|
518
|
+
return refusal('invalid-params', `${metric.id} is a number the owner reports; it takes no parameters.`);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
if (input.baseline !== undefined) {
|
|
523
|
+
return refusal('baseline-measured', `${metric.id} is measured by its plugin, so the baseline is measured too; leave baseline out.`);
|
|
524
|
+
}
|
|
525
|
+
const checked = checkMetricParams(source.metric(metric.id), input.params);
|
|
526
|
+
if (!checked.ok)
|
|
527
|
+
return refusal('invalid-params', checked.message);
|
|
528
|
+
params = checked.params;
|
|
529
|
+
}
|
|
530
|
+
if (input.target.kind === 'frequency') {
|
|
531
|
+
/*
|
|
532
|
+
* A frequency goal counts values, and only the owner's metrics have
|
|
533
|
+
* values to count: a plugin's number is a level, read when buddi looks.
|
|
534
|
+
*/
|
|
535
|
+
if (metric.owner === null) {
|
|
536
|
+
return refusal('frequency-needs-owner', `a frequency goal counts the times the owner tells buddi, and ${metric.id} is measured by a plugin. ` +
|
|
537
|
+
'Use an owner metric ({"owner": {...}}) for something the owner does, or a level target for this one.');
|
|
538
|
+
}
|
|
539
|
+
if (input.baseline !== undefined) {
|
|
540
|
+
return refusal('baseline-frequency', 'a frequency goal starts with nothing counted; leave baseline out.');
|
|
541
|
+
}
|
|
542
|
+
const streaks = refuseStreaks(input.milestones ?? []);
|
|
543
|
+
if (streaks !== null)
|
|
544
|
+
return streaks;
|
|
303
545
|
}
|
|
304
|
-
const params = checkMetricParams(metric, input.params);
|
|
305
|
-
if (!params.ok)
|
|
306
|
-
return refusal('invalid-params', params.message);
|
|
307
546
|
const when = parseReminderWhen(input.deadline, ctx.timezone);
|
|
308
547
|
if (!when.ok)
|
|
309
548
|
return refusal('invalid-deadline', when.message);
|
|
@@ -316,26 +555,35 @@ export function createGoalManifest(source) {
|
|
|
316
555
|
return refusal('deadline-too-far', `a goal can run at most ${MAX_GOAL_HORIZON_DAYS} days; that deadline is further out than anyone can ` +
|
|
317
556
|
'hold a plan to.');
|
|
318
557
|
}
|
|
319
|
-
return
|
|
558
|
+
return { ok: true, metric, params };
|
|
320
559
|
}
|
|
321
|
-
/** The envelope, from the arguments and one measurement. */
|
|
322
|
-
function envelopeOf(input, ctx, baseline) {
|
|
560
|
+
/** The envelope, from the arguments, the resolved metric and one measurement. */
|
|
561
|
+
function envelopeOf(input, ctx, metric, params, baseline) {
|
|
323
562
|
const when = parseReminderWhen(input.deadline, ctx.timezone);
|
|
324
|
-
const metric = source.metric(input.metric);
|
|
325
|
-
const params = metric === undefined ? { ok: false } : checkMetricParams(metric, input.params);
|
|
326
563
|
return {
|
|
327
564
|
tool: 'goal.set',
|
|
328
565
|
agentId: ctx.agentId ?? '',
|
|
329
566
|
title: input.title.trim(),
|
|
330
|
-
metric:
|
|
567
|
+
metric: metric.id,
|
|
331
568
|
// What the schema *made of* the input — defaults applied, unknown keys
|
|
332
569
|
// already refused — so the goal row and every later measurement agree.
|
|
333
|
-
params
|
|
334
|
-
target:
|
|
570
|
+
params,
|
|
571
|
+
target: input.target.kind === 'frequency'
|
|
572
|
+
? { kind: 'frequency', count: input.target.count, per: input.target.per }
|
|
573
|
+
: { kind: input.target.kind, value: input.target.value },
|
|
335
574
|
baseline,
|
|
336
575
|
deadline: (when.ok ? when.at : new Date(0)).toISOString(),
|
|
337
576
|
cadence: input.cadence,
|
|
338
577
|
milestones: input.milestones ?? [],
|
|
578
|
+
...(metric.owner === null
|
|
579
|
+
? {}
|
|
580
|
+
: {
|
|
581
|
+
owner: {
|
|
582
|
+
metric: metric.owner.metric,
|
|
583
|
+
isNew: metric.owner.isNew,
|
|
584
|
+
baselineTold: input.baseline !== undefined,
|
|
585
|
+
},
|
|
586
|
+
}),
|
|
339
587
|
};
|
|
340
588
|
}
|
|
341
589
|
/** The baseline an approval already carries, when it carries one. */
|
|
@@ -351,23 +599,50 @@ export function createGoalManifest(source) {
|
|
|
351
599
|
readingAsOf: baseline.readingAsOf ?? null,
|
|
352
600
|
};
|
|
353
601
|
}
|
|
602
|
+
/**
|
|
603
|
+
* The approved envelope's word on whether the owner metric was new.
|
|
604
|
+
*
|
|
605
|
+
* "New" is a fact about the moment the card was drawn: by the time the
|
|
606
|
+
* executor re-describes, another approval may have created the slug, and a
|
|
607
|
+
* card that said "a new metric" must not be voided for that — the definition
|
|
608
|
+
* already had to agree for the second goal to get this far.
|
|
609
|
+
*/
|
|
610
|
+
function approvedIsNew(ctx, metric) {
|
|
611
|
+
const envelope = (ctx.approvedEffect?.envelope ?? null);
|
|
612
|
+
if (metric.owner === null || envelope?.owner === undefined)
|
|
613
|
+
return metric;
|
|
614
|
+
return { ...metric, owner: { ...metric.owner, isNew: envelope.owner.isNew === true } };
|
|
615
|
+
}
|
|
354
616
|
const metrics = {
|
|
355
617
|
name: 'goal.metrics',
|
|
356
618
|
description: 'Every number this installation can actually measure, with what it means, which way is better and the ' +
|
|
357
|
-
'narrowing it takes. A goal watches one of these — name one from here when you propose a goal
|
|
358
|
-
'
|
|
619
|
+
'narrowing it takes. A goal watches one of these — name one from here when you propose a goal. Source ' +
|
|
620
|
+
'"plugin" is measured by a plugin; source "owner" is a number the owner reports with goal.record. When ' +
|
|
621
|
+
'nothing here measures what the owner named, goal.set can create an owner metric.',
|
|
359
622
|
tier: 'auto',
|
|
360
623
|
input: z.object({}).strict(),
|
|
361
|
-
async execute() {
|
|
624
|
+
async execute(_input, ctx) {
|
|
625
|
+
await source.refresh(ctx.db);
|
|
362
626
|
return {
|
|
363
|
-
metrics: source.metrics().map((metric) => (
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
627
|
+
metrics: source.metrics().map((metric) => isOwnerMetric(metric)
|
|
628
|
+
? {
|
|
629
|
+
id: metric.id,
|
|
630
|
+
source: OWNER_SOURCE,
|
|
631
|
+
description: metric.description,
|
|
632
|
+
label: metric.label,
|
|
633
|
+
unit: metric.unit,
|
|
634
|
+
...(metric.unitLabel === null ? {} : { unitLabel: metric.unitLabel }),
|
|
635
|
+
direction: metric.direction,
|
|
636
|
+
}
|
|
637
|
+
: {
|
|
638
|
+
id: metric.id,
|
|
639
|
+
source: 'plugin',
|
|
640
|
+
plugin: metric.plugin,
|
|
641
|
+
description: metric.description,
|
|
642
|
+
unit: metric.unit,
|
|
643
|
+
direction: metric.direction,
|
|
644
|
+
...(metricParamsSchema(metric) === undefined ? {} : { params: metricParamsSchema(metric) }),
|
|
645
|
+
}),
|
|
371
646
|
};
|
|
372
647
|
},
|
|
373
648
|
};
|
|
@@ -379,7 +654,8 @@ export function createGoalManifest(source) {
|
|
|
379
654
|
'deadline gets close. The owner sees the whole shape before they say yes: where the number is today, ' +
|
|
380
655
|
'where it has to get to, by when, what that is per week, and that it is you who will be speaking about ' +
|
|
381
656
|
`it. At most ${MAX_OPEN_GOALS} open goals in an installation. Use it for something measurable that ` +
|
|
382
|
-
'runs for weeks; for one future nudge use reminder.set.'
|
|
657
|
+
'runs for weeks; for one future nudge use reminder.set. When no plugin measures the number (a weight, ' +
|
|
658
|
+
'a time), create an owner metric with {"owner": {...}} and pass the baseline the owner said.',
|
|
383
659
|
tier: 'gated',
|
|
384
660
|
input: setInput,
|
|
385
661
|
/*
|
|
@@ -392,9 +668,11 @@ export function createGoalManifest(source) {
|
|
|
392
668
|
* the owner is never shown a card for a goal that could not exist.
|
|
393
669
|
*/
|
|
394
670
|
async describe(input, ctx) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
671
|
+
await source.refresh(ctx.db);
|
|
672
|
+
const checked = await refuseSet(input, ctx);
|
|
673
|
+
if (!checked.ok)
|
|
674
|
+
throw new Error(checked.message);
|
|
675
|
+
const metric = approvedIsNew(ctx, checked.metric);
|
|
398
676
|
/*
|
|
399
677
|
* Measure **once**, on the first description. The executor re-describes
|
|
400
678
|
* before it dispatches and compares envelope hashes, so a second
|
|
@@ -403,43 +681,57 @@ export function createGoalManifest(source) {
|
|
|
403
681
|
* hopeless for an unread count, and `email.inbox_unread` is one of the
|
|
404
682
|
* metrics this exists for. At re-description the approved envelope is on
|
|
405
683
|
* the context, so the number the owner saw is the number that executes.
|
|
684
|
+
*
|
|
685
|
+
* A number the owner just said is the baseline as it is: there is
|
|
686
|
+
* nothing to measure, and it is the one sentence the goal came from.
|
|
406
687
|
*/
|
|
407
688
|
const baseline = approvedBaseline(ctx) ??
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
689
|
+
// A frequency goal starts with nothing counted: there is no level to measure.
|
|
690
|
+
(input.target.kind === 'frequency'
|
|
691
|
+
? { value: 0, currency: null, asOf: ctx.now().toISOString(), readingAsOf: null }
|
|
692
|
+
: input.baseline !== undefined && metric.owner !== null
|
|
693
|
+
? { value: input.baseline.value, currency: null, asOf: ctx.now().toISOString(), readingAsOf: null }
|
|
694
|
+
: await (async () => {
|
|
695
|
+
const measured = metric.owner !== null && metric.owner.isNew
|
|
696
|
+
? { ok: false, note: 'the owner has told buddi no value yet' }
|
|
697
|
+
: await measureMetricResult(source, metric.id, metric.owner !== null ? { cadence: input.cadence } : checked.params, ctx);
|
|
698
|
+
if (!measured.ok) {
|
|
699
|
+
/*
|
|
700
|
+
* The card is never shown for a metric nobody can read.
|
|
701
|
+
* Approving "from ? today to 47,400" is approving nothing, and
|
|
702
|
+
* the baseline is the one number the whole goal is relative to.
|
|
703
|
+
*/
|
|
704
|
+
throw new Error(metric.owner !== null
|
|
705
|
+
? `${metric.id} has no recent value, so there is no baseline to set a goal against. Ask the ` +
|
|
706
|
+
'owner where they are today and pass it as baseline: {"value": ...}.'
|
|
707
|
+
: `${metric.id} cannot be measured right now, so there is no baseline to set a goal against: ` +
|
|
708
|
+
`${measured.note ?? 'no reason given'}. Fix that first — a goal needs a number to start from.`);
|
|
709
|
+
}
|
|
710
|
+
return {
|
|
711
|
+
value: measured.reading.value,
|
|
712
|
+
currency: measured.reading.currency ?? null,
|
|
713
|
+
asOf: ctx.now().toISOString(),
|
|
714
|
+
readingAsOf: measured.reading.asOf?.toISOString() ?? null,
|
|
715
|
+
};
|
|
716
|
+
})());
|
|
717
|
+
const envelope = envelopeOf(input, ctx, metric, checked.params, baseline);
|
|
718
|
+
const wrongWay = envelope.target.kind === 'frequency' ? null : refuseDirection({ target: envelope.target, baseline: { value: baseline.value, asOf: new Date(baseline.asOf) } }, envelope.milestones, metric.direction, metric.unit, baseline.currency);
|
|
429
719
|
if (wrongWay !== null)
|
|
430
720
|
throw new Error(wrongWay.message);
|
|
431
721
|
return {
|
|
432
722
|
envelope,
|
|
433
|
-
preview: renderGoalSet(envelope,
|
|
723
|
+
preview: renderGoalSet(envelope, metric.unit, metric.direction, ctx.timezone),
|
|
434
724
|
};
|
|
435
725
|
},
|
|
436
726
|
async execute(input, ctx) {
|
|
437
727
|
// Only `executeApproved` reaches this. The checks run again anyway: the
|
|
438
728
|
// approval was recorded minutes or hours ago, and "a delegate may not"
|
|
439
729
|
// is a fact about the run that is executing, not about the one that asked.
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
730
|
+
await source.refresh(ctx.db);
|
|
731
|
+
const checked = await refuseSet(input, ctx);
|
|
732
|
+
if (!checked.ok)
|
|
733
|
+
throw new Error(checked.message);
|
|
734
|
+
const metric = approvedIsNew(ctx, checked.metric);
|
|
443
735
|
/*
|
|
444
736
|
* The baseline is the approval's: it is the number the owner saw, and a
|
|
445
737
|
* goal measured against a different one is a different goal. Everything
|
|
@@ -451,7 +743,7 @@ export function createGoalManifest(source) {
|
|
|
451
743
|
if (baseline === null) {
|
|
452
744
|
throw new Error('the effect no longer matches the approved preview; propose it again');
|
|
453
745
|
}
|
|
454
|
-
const envelope = envelopeOf(input, ctx, baseline);
|
|
746
|
+
const envelope = envelopeOf(input, ctx, metric, checked.params, baseline);
|
|
455
747
|
assertApprovedEffect(ctx, envelope);
|
|
456
748
|
const created = await createGoal(ctx.db, {
|
|
457
749
|
title: envelope.title,
|
|
@@ -467,6 +759,26 @@ export function createGoalManifest(source) {
|
|
|
467
759
|
});
|
|
468
760
|
if (!created.ok)
|
|
469
761
|
return { ok: false, reason: created.reason, message: created.message };
|
|
762
|
+
/*
|
|
763
|
+
* The owner metric is created only now that the goal exists — a goal the
|
|
764
|
+
* budget refused leaves no definition behind — and a baseline the owner
|
|
765
|
+
* said is the metric's first value, so the next thing they tell buddi is
|
|
766
|
+
* measured against it.
|
|
767
|
+
*/
|
|
768
|
+
if (envelope.owner !== undefined) {
|
|
769
|
+
const owned = await createOwnerMetric(ctx.db, envelope.owner.metric);
|
|
770
|
+
source.remember(owned);
|
|
771
|
+
if (envelope.owner.baselineTold) {
|
|
772
|
+
await recordOwnerValue(ctx.db, {
|
|
773
|
+
slug: owned.slug,
|
|
774
|
+
value: envelope.baseline.value,
|
|
775
|
+
at: new Date(envelope.baseline.asOf),
|
|
776
|
+
note: 'baseline, said when the goal was set',
|
|
777
|
+
source: valueSourceOf(ctx),
|
|
778
|
+
conversationId: ctx.conversationId ?? null,
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
}
|
|
470
782
|
// The baseline is also the first check: a goal's history starts where
|
|
471
783
|
// the owner was told it starts, not at the first sentinel tick.
|
|
472
784
|
await recordCheck(ctx.db, {
|
|
@@ -475,12 +787,16 @@ export function createGoalManifest(source) {
|
|
|
475
787
|
asOf: envelope.baseline.readingAsOf === null ? null : new Date(envelope.baseline.readingAsOf),
|
|
476
788
|
value: envelope.baseline.value,
|
|
477
789
|
currency: envelope.baseline.currency,
|
|
478
|
-
note:
|
|
479
|
-
|
|
790
|
+
note: envelope.target.kind === 'frequency'
|
|
791
|
+
? 'set; nothing counted yet'
|
|
792
|
+
: envelope.owner?.baselineTold === true
|
|
793
|
+
? 'baseline, said when the goal was set'
|
|
794
|
+
: 'baseline, measured when the goal was set',
|
|
795
|
+
paceNeeded: envelope.target.kind === 'frequency' ? null : paceNeeded(created.goal, envelope.baseline.value, ctx.now()),
|
|
480
796
|
});
|
|
481
797
|
return {
|
|
482
798
|
ok: true,
|
|
483
|
-
goal: renderGoal(created.goal,
|
|
799
|
+
goal: renderGoal(created.goal, metric.unit, ctx.timezone, unchecked(created.goal, ctx.now()), [], ctx.now()),
|
|
484
800
|
};
|
|
485
801
|
},
|
|
486
802
|
};
|
|
@@ -540,6 +856,18 @@ export function createGoalManifest(source) {
|
|
|
540
856
|
}
|
|
541
857
|
}
|
|
542
858
|
const after = afterOf(input, goal, ctx);
|
|
859
|
+
/*
|
|
860
|
+
* A goal keeps its shape. A level goal's baseline is a number and a
|
|
861
|
+
* frequency goal's is nothing counted; turning one into the other would
|
|
862
|
+
* judge months of history against a baseline that never meant that.
|
|
863
|
+
*/
|
|
864
|
+
if ((goal.target.kind === 'frequency') !== (after.target.kind === 'frequency')) {
|
|
865
|
+
return refusal('shape', goal.target.kind === 'frequency'
|
|
866
|
+
? 'this is a frequency goal and stays one; a level target is a new goal.'
|
|
867
|
+
: 'this goal aims at a level and stays one; a frequency is a new goal on an owner metric.');
|
|
868
|
+
}
|
|
869
|
+
if (after.target.kind === 'frequency')
|
|
870
|
+
return refuseStreaks(after.milestones);
|
|
543
871
|
// Same rule as `goal.set`: an update that puts the target on the wrong
|
|
544
872
|
// side of the baseline would settle the goal `met` on the next tick.
|
|
545
873
|
return refuseDirection({ target: after.target, baseline: goal.baseline }, after.milestones, directionOf(goal.metric), unitOf(goal.metric), goal.currency);
|
|
@@ -556,6 +884,7 @@ export function createGoalManifest(source) {
|
|
|
556
884
|
// any action exists, and the owner is never asked about a change nobody
|
|
557
885
|
// is allowed to make.
|
|
558
886
|
async describe(input, ctx) {
|
|
887
|
+
await source.refresh(ctx.db);
|
|
559
888
|
const mine = await holderOf(input.id, ctx);
|
|
560
889
|
if (!mine.ok)
|
|
561
890
|
throw new Error(mine.message);
|
|
@@ -570,6 +899,7 @@ export function createGoalManifest(source) {
|
|
|
570
899
|
};
|
|
571
900
|
},
|
|
572
901
|
async execute(input, ctx) {
|
|
902
|
+
await source.refresh(ctx.db);
|
|
573
903
|
const mine = await holderOf(input.id, ctx);
|
|
574
904
|
if (!mine.ok)
|
|
575
905
|
throw new Error(mine.message);
|
|
@@ -610,6 +940,7 @@ export function createGoalManifest(source) {
|
|
|
610
940
|
tier: 'auto',
|
|
611
941
|
input: closeInput,
|
|
612
942
|
async execute(input, ctx) {
|
|
943
|
+
await source.refresh(ctx.db);
|
|
613
944
|
const mine = await holderOf(input.id, ctx);
|
|
614
945
|
if (!mine.ok)
|
|
615
946
|
return mine;
|
|
@@ -648,19 +979,63 @@ export function createGoalManifest(source) {
|
|
|
648
979
|
*/
|
|
649
980
|
function renderGoal(goal, unit, timezone, standing, checks,
|
|
650
981
|
/** The run's clock. Never `new Date()`: `goal.status` is read under one. */
|
|
651
|
-
now
|
|
982
|
+
now,
|
|
983
|
+
/** For a goal on an owner metric: its newest values, and a frequency goal's windows. */
|
|
984
|
+
owner = null) {
|
|
652
985
|
const currency = goal.currency;
|
|
653
986
|
const latest = standing.latest;
|
|
654
987
|
const value = latest?.value ?? null;
|
|
655
988
|
const projected = standing.projected;
|
|
656
989
|
void now;
|
|
990
|
+
const ownerFields = owner === null
|
|
991
|
+
? {}
|
|
992
|
+
: {
|
|
993
|
+
values: owner.values.slice(0, STATUS_VALUES).map((v) => ({
|
|
994
|
+
asOf: v.asOf.toISOString(),
|
|
995
|
+
value: v.value,
|
|
996
|
+
valueFormatted: formatValue(v.value, unit, null),
|
|
997
|
+
note: v.note,
|
|
998
|
+
source: v.source,
|
|
999
|
+
})),
|
|
1000
|
+
};
|
|
1001
|
+
const frequency = owner?.frequency ?? null;
|
|
1002
|
+
if (goal.target.kind === 'frequency' && frequency !== null) {
|
|
1003
|
+
/*
|
|
1004
|
+
* A frequency goal's numbers are windows, not a level: the value is the
|
|
1005
|
+
* count so far in this window, the pace is what is left to do in it,
|
|
1006
|
+
* and the verdict is the last window that closed.
|
|
1007
|
+
*/
|
|
1008
|
+
const last = frequency.lastClosed;
|
|
1009
|
+
const onTrackNow = last === null || last.state === 'partial' ? null : last.state === 'met';
|
|
1010
|
+
return {
|
|
1011
|
+
...renderGoal({ ...goal }, unit, timezone, standing, checks, now),
|
|
1012
|
+
...ownerFields,
|
|
1013
|
+
value: frequency.current?.count ?? null,
|
|
1014
|
+
valueFormatted: frequencyNow(goal, frequency),
|
|
1015
|
+
progress: null,
|
|
1016
|
+
paceNeeded: frequency.toGo,
|
|
1017
|
+
paceNeededInWords: frequency.toGo === null ? null : frequency.toGo === 0 ? 'done this ' + goal.target.per : `${frequency.toGo} more this ${goal.target.per}`,
|
|
1018
|
+
projected: null,
|
|
1019
|
+
onTrack: onTrackNow,
|
|
1020
|
+
verdict: onTrackNow === null ? 'no-projection' : onTrackNow ? 'on-track' : 'off-track',
|
|
1021
|
+
streak: frequency.streak,
|
|
1022
|
+
windows: frequency.windows.slice(-STATUS_WINDOWS).map((w) => ({
|
|
1023
|
+
start: w.start,
|
|
1024
|
+
end: w.end,
|
|
1025
|
+
count: w.count,
|
|
1026
|
+
state: w.state,
|
|
1027
|
+
})),
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
657
1030
|
return {
|
|
1031
|
+
...ownerFields,
|
|
658
1032
|
id: goal.id,
|
|
659
1033
|
title: goal.title,
|
|
660
1034
|
agentId: goal.agentId,
|
|
661
1035
|
metric: goal.metric,
|
|
662
1036
|
params: goal.params,
|
|
663
|
-
unit,
|
|
1037
|
+
unit: baseUnit(unit),
|
|
1038
|
+
unitLabel: unitLabelOf(unit),
|
|
664
1039
|
target: goal.target,
|
|
665
1040
|
targetValue: targetValue(goal),
|
|
666
1041
|
baseline: { value: goal.baseline.value, asOf: goal.baseline.asOf.toISOString() },
|
|
@@ -705,6 +1080,7 @@ export function createGoalManifest(source) {
|
|
|
705
1080
|
tier: 'auto',
|
|
706
1081
|
input: statusInput,
|
|
707
1082
|
async execute(input, ctx) {
|
|
1083
|
+
await source.refresh(ctx.db);
|
|
708
1084
|
const goals = input.id === undefined
|
|
709
1085
|
? await listGoals(ctx.db, { agentId: ctx.agentId ?? '', limit: 50 })
|
|
710
1086
|
: await (async () => {
|
|
@@ -735,7 +1111,13 @@ export function createGoalManifest(source) {
|
|
|
735
1111
|
*/
|
|
736
1112
|
const printed = await recentChecks(ctx.db, goal.id, 4);
|
|
737
1113
|
const standing = standingOf(goal, directionOf(goal.metric), await measuredChecks(ctx.db, goal.id, STANDING_CHECKS), ctx.now());
|
|
738
|
-
|
|
1114
|
+
const ownerInfo = ownerSlugOf(goal.metric) !== null
|
|
1115
|
+
? {
|
|
1116
|
+
values: await goalOwnerValues(ctx.db, goal),
|
|
1117
|
+
frequency: await frequencyOf(ctx.db, goal, ctx.now(), ctx.timezone),
|
|
1118
|
+
}
|
|
1119
|
+
: null;
|
|
1120
|
+
out.push(renderGoal(goal, unitOf(goal.metric), ctx.timezone, standing, printed, ctx.now(), ownerInfo));
|
|
739
1121
|
if (goals.length === 1) {
|
|
740
1122
|
/*
|
|
741
1123
|
* The chart is the goal's *history*, not its last four looks: a
|
|
@@ -743,12 +1125,147 @@ export function createGoalManifest(source) {
|
|
|
743
1125
|
* milestone crossed in week two would have no event on it. Its own
|
|
744
1126
|
* read, so the printed rows stay four.
|
|
745
1127
|
*/
|
|
746
|
-
chart = chartOf(goal, unitOf(goal.metric), directionOf(goal.metric), await recentChecks(ctx.db, goal.id, CHART_CHECKS), ctx.timezone);
|
|
1128
|
+
chart = chartOf(goal, unitOf(goal.metric), directionOf(goal.metric), await recentChecks(ctx.db, goal.id, CHART_CHECKS), ctx.timezone, ownerInfo);
|
|
747
1129
|
}
|
|
748
1130
|
}
|
|
749
1131
|
return chart === null ? { goals: out } : { goals: out, chart };
|
|
750
1132
|
},
|
|
751
1133
|
};
|
|
1134
|
+
/**
|
|
1135
|
+
* Where a goal stands, in one sentence: what `goal.record` answers with.
|
|
1136
|
+
*
|
|
1137
|
+
* Read off `standingOf` like every other surface, with the value just said
|
|
1138
|
+
* stood in front of the measured checks — it is not a check yet, and the
|
|
1139
|
+
* owner wants to know what it means now, not after the next tick.
|
|
1140
|
+
*/
|
|
1141
|
+
function paceSentence(goal, standing, timezone) {
|
|
1142
|
+
const unit = unitOf(goal.metric);
|
|
1143
|
+
const direction = directionOf(goal.metric);
|
|
1144
|
+
const value = standing.latest?.value ?? null;
|
|
1145
|
+
const target = formatValue(targetValue(goal), unit, goal.currency);
|
|
1146
|
+
const by = localDateString(goal.deadline, timezone);
|
|
1147
|
+
const pct = standing.progress === null ? '' : `, ${Math.round(standing.progress * 100)}% of the way`;
|
|
1148
|
+
const pace = standing.paceNeeded === null
|
|
1149
|
+
? 'the deadline has passed'
|
|
1150
|
+
: `${formatPace(standing.paceNeeded, unit, direction, goal.currency)} from here reaches ${target} by ${by}`;
|
|
1151
|
+
return `${goal.title}: ${formatValue(value, unit, goal.currency)} now${pct}; ${pace}.`;
|
|
1152
|
+
}
|
|
1153
|
+
const record = {
|
|
1154
|
+
name: 'goal.record',
|
|
1155
|
+
description: 'Write down a number the owner just told you for a metric they report ("285 this morning", "ran today" ' +
|
|
1156
|
+
'is 1). Name the goal or the metric, not whose goal it is — any agent may record, so the owner can say it ' +
|
|
1157
|
+
'to whoever is listening. Answers with where the goal stands now, in one sentence you can repeat. A value ' +
|
|
1158
|
+
'far from the last one is refused with a sentence: ask the owner, then record again with confirmed: true.',
|
|
1159
|
+
tier: 'auto',
|
|
1160
|
+
input: recordInput,
|
|
1161
|
+
async execute(input, ctx) {
|
|
1162
|
+
await source.refresh(ctx.db);
|
|
1163
|
+
if ((input.goal === undefined) === (input.metric === undefined)) {
|
|
1164
|
+
return refusal('name-one', 'name the goal or the metric, exactly one of them');
|
|
1165
|
+
}
|
|
1166
|
+
/*
|
|
1167
|
+
* Which series. A goal names its metric; a metric may be named as
|
|
1168
|
+
* `owner.weight` or just `weight`. Either way it has to be one the owner
|
|
1169
|
+
* reports: a plugin's number is measured, and typing over it would be a
|
|
1170
|
+
* second, disagreeing history.
|
|
1171
|
+
*/
|
|
1172
|
+
let goals = [];
|
|
1173
|
+
let metricId;
|
|
1174
|
+
if (input.goal !== undefined) {
|
|
1175
|
+
const goal = await getGoal(ctx.db, input.goal).catch(() => null);
|
|
1176
|
+
if (goal === null)
|
|
1177
|
+
return refusal('not-found', `no goal ${input.goal}`);
|
|
1178
|
+
metricId = goal.metric;
|
|
1179
|
+
goals = [goal];
|
|
1180
|
+
}
|
|
1181
|
+
else {
|
|
1182
|
+
const raw = input.metric.trim();
|
|
1183
|
+
metricId = raw.includes('.') ? raw : ownerMetricId(raw);
|
|
1184
|
+
}
|
|
1185
|
+
const metric = source.metric(metricId);
|
|
1186
|
+
if (!isOwnerMetric(metric)) {
|
|
1187
|
+
return refusal('not-owner-metric', metric === undefined
|
|
1188
|
+
? `no metric "${metricId}" is kept here. goal.metrics lists the ones the owner reports (source "owner").`
|
|
1189
|
+
: `${metricId} is measured by ${metric.plugin}; there is nothing to record by hand.`);
|
|
1190
|
+
}
|
|
1191
|
+
const slug = ownerSlugOf(metric.id);
|
|
1192
|
+
if (input.goal === undefined) {
|
|
1193
|
+
goals = (await listGoals(ctx.db, { openOnly: true, limit: MAX_OPEN_GOALS })).filter((goal) => goal.metric === metric.id);
|
|
1194
|
+
}
|
|
1195
|
+
const now = ctx.now();
|
|
1196
|
+
let asOf = now;
|
|
1197
|
+
if (input.asOf !== undefined) {
|
|
1198
|
+
const when = parseReminderWhen(input.asOf, ctx.timezone);
|
|
1199
|
+
if (!when.ok)
|
|
1200
|
+
return refusal('invalid-as-of', when.message);
|
|
1201
|
+
asOf = when.at;
|
|
1202
|
+
// "This morning" written as today's date lands at 09:00; before nine
|
|
1203
|
+
// that is a few hours ahead of the clock, and it still means today.
|
|
1204
|
+
if (asOf.getTime() > now.getTime()) {
|
|
1205
|
+
if (localDateString(asOf, ctx.timezone) !== localDateString(now, ctx.timezone)) {
|
|
1206
|
+
return refusal('as-of-future', `${input.asOf} has not happened yet; a value is true of now or of the past`);
|
|
1207
|
+
}
|
|
1208
|
+
asOf = now;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
const unit = unitOf(metric.id);
|
|
1212
|
+
if (input.confirmed !== true) {
|
|
1213
|
+
const last = await latestOwnerValue(ctx.db, slug);
|
|
1214
|
+
const outside = refuseOutsideBand(last?.value ?? null, input.value, (n) => formatValue(n, unit, null));
|
|
1215
|
+
if (outside !== null)
|
|
1216
|
+
return refusal('confirm', outside);
|
|
1217
|
+
}
|
|
1218
|
+
const conversationId = ctx.conversationId !== undefined && UUID.test(ctx.conversationId) ? ctx.conversationId : null;
|
|
1219
|
+
const saved = await recordOwnerValue(ctx.db, {
|
|
1220
|
+
slug,
|
|
1221
|
+
value: input.value,
|
|
1222
|
+
at: now,
|
|
1223
|
+
asOf,
|
|
1224
|
+
note: input.note ?? null,
|
|
1225
|
+
source: valueSourceOf(ctx),
|
|
1226
|
+
conversationId,
|
|
1227
|
+
});
|
|
1228
|
+
const lines = [];
|
|
1229
|
+
for (const goal of goals) {
|
|
1230
|
+
if (goal.state !== 'open')
|
|
1231
|
+
continue;
|
|
1232
|
+
const frequency = await frequencyOf(ctx.db, goal, now, ctx.timezone);
|
|
1233
|
+
if (frequency !== null) {
|
|
1234
|
+
// The value is saved, so the windows already count it.
|
|
1235
|
+
lines.push(`${goal.title}: ${frequencyNow(goal, frequency)}.`);
|
|
1236
|
+
continue;
|
|
1237
|
+
}
|
|
1238
|
+
const pending = {
|
|
1239
|
+
id: '',
|
|
1240
|
+
goalId: goal.id,
|
|
1241
|
+
at: now,
|
|
1242
|
+
asOf,
|
|
1243
|
+
value: input.value,
|
|
1244
|
+
currency: null,
|
|
1245
|
+
note: null,
|
|
1246
|
+
onTrack: null,
|
|
1247
|
+
paceNeeded: null,
|
|
1248
|
+
projected: null,
|
|
1249
|
+
};
|
|
1250
|
+
const standing = standingOf(goal, directionOf(goal.metric), [pending, ...(await measuredChecks(ctx.db, goal.id, STANDING_CHECKS))], now);
|
|
1251
|
+
lines.push(paceSentence(goal, standing, ctx.timezone));
|
|
1252
|
+
}
|
|
1253
|
+
return {
|
|
1254
|
+
ok: true,
|
|
1255
|
+
recorded: {
|
|
1256
|
+
metric: metric.id,
|
|
1257
|
+
value: saved.value,
|
|
1258
|
+
valueFormatted: formatValue(saved.value, unit, null),
|
|
1259
|
+
asOf: saved.asOf.toISOString(),
|
|
1260
|
+
source: saved.source,
|
|
1261
|
+
},
|
|
1262
|
+
// One sentence per open goal on this metric; usually exactly one.
|
|
1263
|
+
pace: lines.length === 0
|
|
1264
|
+
? `Recorded ${formatValue(saved.value, unit, null)}; no open goal watches ${metric.id}.`
|
|
1265
|
+
: lines.join(' '),
|
|
1266
|
+
};
|
|
1267
|
+
},
|
|
1268
|
+
};
|
|
752
1269
|
const list = {
|
|
753
1270
|
name: 'goal.list',
|
|
754
1271
|
description: 'Every open goal in this installation, whoever holds it, one line each. Reading is open to any agent — ' +
|
|
@@ -757,6 +1274,7 @@ export function createGoalManifest(source) {
|
|
|
757
1274
|
tier: 'auto',
|
|
758
1275
|
input: z.object({}).strict(),
|
|
759
1276
|
async execute(_input, ctx) {
|
|
1277
|
+
await source.refresh(ctx.db);
|
|
760
1278
|
const goals = await listGoals(ctx.db, { openOnly: true, limit: MAX_OPEN_GOALS });
|
|
761
1279
|
const out = [];
|
|
762
1280
|
for (const goal of goals) {
|
|
@@ -780,7 +1298,7 @@ export function createGoalManifest(source) {
|
|
|
780
1298
|
// core concept and this manifest only exposes it to a model.
|
|
781
1299
|
schema: 'core',
|
|
782
1300
|
migrationsDir: '',
|
|
783
|
-
tools: [metrics, set, update, close, status, list, createGoalOwnerClose()],
|
|
1301
|
+
tools: [metrics, set, update, close, status, list, record, createGoalOwnerClose()],
|
|
784
1302
|
sentinels: [createGoalsSentinel(source)],
|
|
785
1303
|
// Where a goal shows (§7): a block on Home, a rail page, and one way of
|
|
786
1304
|
// drawing `goal.status` on the canvas. All three are in `goals-page.ts`;
|
|
@@ -794,12 +1312,37 @@ export function createGoalManifest(source) {
|
|
|
794
1312
|
/* ------------------------------------------------------------------ *
|
|
795
1313
|
* The watcher
|
|
796
1314
|
* ------------------------------------------------------------------ */
|
|
1315
|
+
/**
|
|
1316
|
+
* How long an off-track goal stays quiet after it woke its holder: one
|
|
1317
|
+
* cadence. Whole days, not the four-hour-early "due" margin — the margin keeps
|
|
1318
|
+
* checks from drifting; this keeps the same news from arriving twice a week.
|
|
1319
|
+
*/
|
|
1320
|
+
export function offTrackCooldownMs(cadence) {
|
|
1321
|
+
return (cadence === 'daily' ? 1 : 7) * 24 * 60 * 60_000;
|
|
1322
|
+
}
|
|
797
1323
|
/** Is this goal's cadence due again? See `DAILY_DUE_MS` for the margin. */
|
|
798
1324
|
export function cadenceDue(cadence, lastAt, now) {
|
|
799
1325
|
if (lastAt === null)
|
|
800
1326
|
return true;
|
|
801
1327
|
return now.getTime() - lastAt.getTime() >= (cadence === 'daily' ? DAILY_DUE_MS : WEEKLY_DUE_MS);
|
|
802
1328
|
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Which cadence window an owner metric has gone quiet into, or null.
|
|
1331
|
+
*
|
|
1332
|
+
* Windows are counted from `since` — the newest value, or when the goal was
|
|
1333
|
+
* set — one cadence at a time: a weekly goal last told on a Monday morning is
|
|
1334
|
+
* stale from the next Monday morning, and again from the one after. The label
|
|
1335
|
+
* is the owner's day the window began, which is what the finding key and the
|
|
1336
|
+
* notification's dedupe key carry, so each window is said at most once.
|
|
1337
|
+
*/
|
|
1338
|
+
export function staleWindow(since, now, cadence, timezone) {
|
|
1339
|
+
const length = cadenceMs(cadence);
|
|
1340
|
+
const elapsed = now.getTime() - since.getTime();
|
|
1341
|
+
if (elapsed < length)
|
|
1342
|
+
return null;
|
|
1343
|
+
const start = new Date(since.getTime() + Math.floor(elapsed / length) * length);
|
|
1344
|
+
return localDateString(start, timezone);
|
|
1345
|
+
}
|
|
803
1346
|
/**
|
|
804
1347
|
* The `core.goals` watcher.
|
|
805
1348
|
*
|
|
@@ -813,7 +1356,123 @@ export function cadenceDue(cadence, lastAt, now) {
|
|
|
813
1356
|
* and a settled goal is no longer open, so every key under it stops being
|
|
814
1357
|
* returned on the next tick and resolves. The wake it raised has already gone.
|
|
815
1358
|
*/
|
|
816
|
-
export function createGoalsSentinel(
|
|
1359
|
+
export function createGoalsSentinel(base) {
|
|
1360
|
+
const source = asOwnerSource(base);
|
|
1361
|
+
/**
|
|
1362
|
+
* One tick of a frequency goal: its windows, a check on its cadence, and
|
|
1363
|
+
* the three things worth saying — a window that closed short, a streak
|
|
1364
|
+
* milestone, the deadline.
|
|
1365
|
+
*
|
|
1366
|
+
* Each is keyed so it is said once. The short window's key carries the
|
|
1367
|
+
* window, and it is returned only while that window is the newest closed
|
|
1368
|
+
* one; a streak milestone is returned while the window that reached it is
|
|
1369
|
+
* the newest closed one, and never again once raised for another. There is
|
|
1370
|
+
* no staleness line: a week with nothing said is a short week, not silence.
|
|
1371
|
+
*/
|
|
1372
|
+
async function frequencyTick(goal, ctx, now) {
|
|
1373
|
+
if (goal.target.kind !== 'frequency')
|
|
1374
|
+
return [];
|
|
1375
|
+
const { count, per } = goal.target;
|
|
1376
|
+
const unit = valueUnitOf(source, goal.metric);
|
|
1377
|
+
const standing = (await frequencyOf(ctx.db, goal, now, ctx.timezone));
|
|
1378
|
+
const lastClosed = standing.lastClosed;
|
|
1379
|
+
const verdictOf = (w) => w === null || w.state === 'partial' ? null : w.state === 'met';
|
|
1380
|
+
let checks = await recentChecks(ctx.db, goal.id, 4);
|
|
1381
|
+
const lastAt = checks[0]?.at ?? null;
|
|
1382
|
+
const pastDeadline = now.getTime() >= goal.deadline.getTime();
|
|
1383
|
+
const deadlineUnmeasured = pastDeadline && (lastAt === null || lastAt.getTime() < goal.deadline.getTime());
|
|
1384
|
+
if (deadlineUnmeasured || cadenceDue(goal.cadence, lastAt, now)) {
|
|
1385
|
+
// The record of the tally as it stood: the count this window, the
|
|
1386
|
+
// verdict of the last closed one, what is left to do.
|
|
1387
|
+
await recordCheck(ctx.db, {
|
|
1388
|
+
goalId: goal.id,
|
|
1389
|
+
at: now,
|
|
1390
|
+
value: standing.current?.count ?? lastClosed?.count ?? 0,
|
|
1391
|
+
note: frequencyNow(goal, standing),
|
|
1392
|
+
onTrack: verdictOf(lastClosed),
|
|
1393
|
+
paceNeeded: standing.toGo,
|
|
1394
|
+
});
|
|
1395
|
+
checks = await recentChecks(ctx.db, goal.id, 4);
|
|
1396
|
+
}
|
|
1397
|
+
const recent = standing.windows.slice(-6);
|
|
1398
|
+
const detail = (lead) => [
|
|
1399
|
+
lead,
|
|
1400
|
+
'',
|
|
1401
|
+
goalLine(goal, unit, checks[0] ?? null, ctx.timezone),
|
|
1402
|
+
'',
|
|
1403
|
+
`The last ${recent.length} ${per}s:`,
|
|
1404
|
+
...recent.map((w) => ` ${per} of ${w.start}: ${w.count} of ${count} · ${w.state}`),
|
|
1405
|
+
'',
|
|
1406
|
+
GOAL_WAKE_INSTRUCTION,
|
|
1407
|
+
].join('\n');
|
|
1408
|
+
const data = {
|
|
1409
|
+
goalId: goal.id,
|
|
1410
|
+
metric: goal.metric,
|
|
1411
|
+
target: { count, per },
|
|
1412
|
+
deadline: goal.deadline.toISOString(),
|
|
1413
|
+
streak: standing.streak,
|
|
1414
|
+
windows: recent.map((w) => ({ start: w.start, count: w.count, state: w.state })),
|
|
1415
|
+
};
|
|
1416
|
+
const base = { agentId: goal.agentId, data };
|
|
1417
|
+
const out = [];
|
|
1418
|
+
// The deadline: settled by the windows, met when more met than fell short.
|
|
1419
|
+
if (pastDeadline) {
|
|
1420
|
+
const verdict = frequencySettles(standing);
|
|
1421
|
+
await settleGoal(ctx.db, goal.id, verdict, verdict === 'met' ? 'more windows met the count than fell short' : 'more windows fell short than met the count', now);
|
|
1422
|
+
out.push(verdict === 'met'
|
|
1423
|
+
? {
|
|
1424
|
+
...base,
|
|
1425
|
+
key: goalKey(goal.id, 'target-reached'),
|
|
1426
|
+
severity: 'info',
|
|
1427
|
+
wake: true,
|
|
1428
|
+
title: `Goal reached: ${goal.title}`,
|
|
1429
|
+
detail: detail(`${standing.met} ${per}s met the count and ${standing.short} fell short.`),
|
|
1430
|
+
}
|
|
1431
|
+
: {
|
|
1432
|
+
...base,
|
|
1433
|
+
key: goalKey(goal.id, 'deadline-passed'),
|
|
1434
|
+
severity: 'urgent',
|
|
1435
|
+
title: `Goal deadline passed: ${goal.title}`,
|
|
1436
|
+
detail: detail(`The deadline was ${localDateString(goal.deadline, ctx.timezone)}: ${standing.met} ${per}s met ` +
|
|
1437
|
+
`the count and ${standing.short} fell short.`),
|
|
1438
|
+
});
|
|
1439
|
+
return out;
|
|
1440
|
+
}
|
|
1441
|
+
// Drift: the newest closed window fell short. Said once, for that window.
|
|
1442
|
+
if (lastClosed?.state === 'short') {
|
|
1443
|
+
out.push({
|
|
1444
|
+
...base,
|
|
1445
|
+
key: goalKey(goal.id, `short.${lastClosed.start}`),
|
|
1446
|
+
severity: 'info',
|
|
1447
|
+
wake: true,
|
|
1448
|
+
title: `Short last ${per}: ${goal.title}`,
|
|
1449
|
+
detail: detail(`${lastClosed.count} of ${count} in the ${per} of ${lastClosed.start}.`),
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
// Streaks: each milestone once, ever, for the window that reached it.
|
|
1453
|
+
if (lastClosed?.state === 'met' && goal.milestones.length > 0) {
|
|
1454
|
+
const { rows } = await ctx.db.query(`select key, data->>'window' as window from core.sentinel_findings
|
|
1455
|
+
where sentinel_id = $1 and key like $2`, [GOALS_SENTINEL_ID, `goal.${goal.id}.milestone.%`]);
|
|
1456
|
+
const raisedFor = new Map(rows.map((r) => [r.key, r.window]));
|
|
1457
|
+
for (const milestone of goal.milestones) {
|
|
1458
|
+
if (standing.streak < milestone)
|
|
1459
|
+
continue;
|
|
1460
|
+
const key = goalKey(goal.id, `milestone.${milestone}`);
|
|
1461
|
+
if (raisedFor.has(key) && raisedFor.get(key) !== lastClosed.start)
|
|
1462
|
+
continue;
|
|
1463
|
+
out.push({
|
|
1464
|
+
...base,
|
|
1465
|
+
data: { ...data, window: lastClosed.start },
|
|
1466
|
+
key,
|
|
1467
|
+
severity: 'info',
|
|
1468
|
+
wake: true,
|
|
1469
|
+
title: `Milestone on ${goal.title}`,
|
|
1470
|
+
detail: detail(`${milestone} ${per}s in a row at ${frequencyWords(goal.target)}.`),
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
return out;
|
|
1475
|
+
}
|
|
817
1476
|
return {
|
|
818
1477
|
id: GOALS_SENTINEL_ID,
|
|
819
1478
|
description: 'Measures every open goal on its cadence and says when one is off track, past a milestone, out of time or done.',
|
|
@@ -821,11 +1480,17 @@ export function createGoalsSentinel(source) {
|
|
|
821
1480
|
async run(ctx) {
|
|
822
1481
|
const now = ctx.now();
|
|
823
1482
|
const findings = [];
|
|
1483
|
+
await source.refresh(ctx.db);
|
|
824
1484
|
const goals = await listGoals(ctx.db, { openOnly: true, limit: MAX_OPEN_GOALS });
|
|
825
1485
|
for (const goal of goals) {
|
|
826
1486
|
const metric = source.metric(goal.metric);
|
|
827
|
-
const unit = metric
|
|
1487
|
+
const unit = valueUnitOf(source, goal.metric);
|
|
828
1488
|
const direction = metric?.direction ?? 'down';
|
|
1489
|
+
const owned = isOwnerMetric(metric);
|
|
1490
|
+
if (goal.target.kind === 'frequency') {
|
|
1491
|
+
findings.push(...(await frequencyTick(goal, ctx, now)));
|
|
1492
|
+
continue;
|
|
1493
|
+
}
|
|
829
1494
|
const currency = goal.currency;
|
|
830
1495
|
/*
|
|
831
1496
|
* What was true *before* this tick's check, which is what "newly
|
|
@@ -845,44 +1510,62 @@ export function createGoalsSentinel(source) {
|
|
|
845
1510
|
const lastAt = checks[0]?.at ?? null;
|
|
846
1511
|
const deadlineUnmeasured = pastDeadline && (lastAt === null || lastAt.getTime() < goal.deadline.getTime());
|
|
847
1512
|
if (deadlineUnmeasured || cadenceDue(goal.cadence, lastAt, now)) {
|
|
848
|
-
|
|
1513
|
+
// An owner metric is read against *this goal's* cadence: "older than
|
|
1514
|
+
// two cadences" is two weeks for a weekly goal, two days for a daily one.
|
|
1515
|
+
const reading = await measureMetricResult(source, goal.metric, owned ? { ...goal.params, cadence: goal.cadence } : goal.params, goalToolContext(goal, ctx));
|
|
849
1516
|
const value = reading.ok ? reading.reading.value : null;
|
|
850
1517
|
/*
|
|
851
|
-
*
|
|
852
|
-
*
|
|
853
|
-
*
|
|
854
|
-
*
|
|
855
|
-
*
|
|
1518
|
+
* An owner metric is only news when the owner said something new.
|
|
1519
|
+
* Re-recording last week's number under this week's date would draw
|
|
1520
|
+
* a flat line the owner never reported and bend the projection
|
|
1521
|
+
* toward it, so a cadence with no new value records nothing — the
|
|
1522
|
+
* staleness finding below is what says so. The deadline still gets
|
|
1523
|
+
* its row, so the verdict is about a number taken at or after it.
|
|
856
1524
|
*/
|
|
857
|
-
const
|
|
858
|
-
const
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
asOf
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1525
|
+
const lastSeen = checks[0] === undefined ? null : (checks[0].asOf ?? checks[0].at);
|
|
1526
|
+
const nothingNew = owned &&
|
|
1527
|
+
!deadlineUnmeasured &&
|
|
1528
|
+
reading.ok &&
|
|
1529
|
+
lastSeen !== null &&
|
|
1530
|
+
(reading.reading.asOf?.getTime() ?? 0) <= lastSeen.getTime();
|
|
1531
|
+
if (!nothingNew) {
|
|
1532
|
+
/*
|
|
1533
|
+
* The arithmetic of the row about to be written, over the same
|
|
1534
|
+
* window every other surface reads — with this tick's number stood
|
|
1535
|
+
* in front of it, because it is not a row yet. `standingOf` is what
|
|
1536
|
+
* the page and `goal.status` call afterwards, so the number stored
|
|
1537
|
+
* here is the number they will read back.
|
|
1538
|
+
*/
|
|
1539
|
+
const before = await measuredChecks(ctx.db, goal.id, STANDING_CHECKS);
|
|
1540
|
+
const pending = {
|
|
1541
|
+
id: '',
|
|
1542
|
+
goalId: goal.id,
|
|
1543
|
+
at: now,
|
|
1544
|
+
asOf: null,
|
|
1545
|
+
value,
|
|
1546
|
+
currency: null,
|
|
1547
|
+
note: null,
|
|
1548
|
+
onTrack: null,
|
|
1549
|
+
paceNeeded: null,
|
|
1550
|
+
projected: null,
|
|
1551
|
+
};
|
|
1552
|
+
const prospective = standingOf(goal, direction, [pending, ...before], now);
|
|
1553
|
+
await recordCheck(ctx.db, {
|
|
1554
|
+
goalId: goal.id,
|
|
1555
|
+
at: now,
|
|
1556
|
+
asOf: reading.ok ? (reading.reading.asOf ?? null) : null,
|
|
1557
|
+
value,
|
|
1558
|
+
currency: reading.ok ? (reading.reading.currency ?? null) : null,
|
|
1559
|
+
// The reason it could not be measured is the check's note, so
|
|
1560
|
+
// "the plugin is gone" and "the bank has not synced" stay
|
|
1561
|
+
// different facts a week later.
|
|
1562
|
+
note: reading.ok ? (reading.reading.note ?? null) : (reading.note ?? null),
|
|
1563
|
+
onTrack: prospective.onTrack,
|
|
1564
|
+
paceNeeded: value === null ? null : prospective.paceNeeded,
|
|
1565
|
+
projected: prospective.projected,
|
|
1566
|
+
});
|
|
1567
|
+
checks = await recentChecks(ctx.db, goal.id, 4);
|
|
1568
|
+
}
|
|
886
1569
|
}
|
|
887
1570
|
/*
|
|
888
1571
|
* Every verdict below is read off *measured* checks, over the same
|
|
@@ -993,6 +1676,10 @@ export function createGoalsSentinel(source) {
|
|
|
993
1676
|
...base,
|
|
994
1677
|
key: goalKey(goal.id, 'off-track'),
|
|
995
1678
|
severity: 'urgent',
|
|
1679
|
+
// Bound to the goal's cadence: a weekly goal off track wakes its
|
|
1680
|
+
// holder at most once a week for the same drift, a daily one once
|
|
1681
|
+
// a day. The first time still wakes at once.
|
|
1682
|
+
cooldownMs: offTrackCooldownMs(goal.cadence),
|
|
996
1683
|
title: `Off track: ${goal.title}`,
|
|
997
1684
|
detail: detail(`Two checks running, the pace of the last four lands at ` +
|
|
998
1685
|
`${formatValue(newest.projected, unit, currency)} by ` +
|
|
@@ -1018,6 +1705,37 @@ export function createGoalsSentinel(source) {
|
|
|
1018
1705
|
* in a row, and fire on day four of an outage saying a number that
|
|
1019
1706
|
* existed yesterday has been missing for months.
|
|
1020
1707
|
*/
|
|
1708
|
+
if (owned) {
|
|
1709
|
+
/*
|
|
1710
|
+
* 6'. For a number the owner reports, silence is not an outage: it is
|
|
1711
|
+
* a week nobody said anything. Counted from the newest value (or
|
|
1712
|
+
* from when the goal was set, if that is later), once per cadence
|
|
1713
|
+
* that passes: the key and the notification's dedupe key carry
|
|
1714
|
+
* the window, so it is said at most once in it, and the line
|
|
1715
|
+
* waits for the end of the day — never `now`. "Not measurable"
|
|
1716
|
+
* is not raised as well; it would be the same silence twice.
|
|
1717
|
+
*/
|
|
1718
|
+
const slug = ownerSlugOf(goal.metric);
|
|
1719
|
+
const newest = await latestOwnerValue(ctx.db, slug);
|
|
1720
|
+
const since = new Date(Math.max(newest?.asOf.getTime() ?? 0, goal.baseline.asOf.getTime()));
|
|
1721
|
+
const window = staleWindow(since, now, goal.cadence, ctx.timezone);
|
|
1722
|
+
if (window !== null) {
|
|
1723
|
+
const label = isOwnerMetric(metric) ? metric.label.toLowerCase() : goal.metric;
|
|
1724
|
+
const period = goal.cadence === 'daily' ? 'today' : 'this week';
|
|
1725
|
+
findings.push({
|
|
1726
|
+
...base,
|
|
1727
|
+
key: goalKey(goal.id, `stale.${window}`),
|
|
1728
|
+
severity: 'info',
|
|
1729
|
+
wake: true,
|
|
1730
|
+
notify: { urgency: 'today', dedupeKey: `goal:${goal.id}:stale:${window}` },
|
|
1731
|
+
title: `Not told ${period}: ${goal.title}`,
|
|
1732
|
+
detail: detail(`The owner has not told buddi their ${label} since ${localDateString(since, ctx.timezone)}. ` +
|
|
1733
|
+
`Say one line and nothing more, like "You have not told me your ${label} ${period}." ` +
|
|
1734
|
+
'When they answer, record it with goal.record.'),
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
continue;
|
|
1738
|
+
}
|
|
1021
1739
|
const lastMeasuredAt = latest?.at ?? goal.baseline.asOf;
|
|
1022
1740
|
if (now.getTime() - lastMeasuredAt.getTime() >= NOT_MEASURABLE_MS) {
|
|
1023
1741
|
findings.push({
|