crewly 1.20.186 → 1.20.188
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/config/skills/agent/core/project-tickets/SKILL.md +3 -2
- package/config/skills/agent/core/project-tickets/execute.sh +4 -4
- package/config/skills/agent/core/project-tickets/execute.test.sh +2 -2
- package/config/skills/agent/core/remember/SKILL.md +1 -0
- package/config/skills/agent/core/remember/execute.sh +12 -0
- package/config/skills/agent/core/report-status/SKILL.md +7 -1
- package/config/skills/agent/core/report-status/execute.sh +41 -0
- package/config/skills/agent/core/slack-post/SKILL.md +1 -0
- package/config/skills/agent/core/slack-post/execute.sh +9 -2
- package/config/skills/team-leader/_common/lib.sh +45 -0
- package/config/skills/team-leader/start-agent/SKILL.md +6 -6
- package/config/skills/team-leader/start-agent/execute.sh +3 -12
- package/config/skills/team-leader/stop-agent/SKILL.md +6 -6
- package/config/skills/team-leader/stop-agent/execute.sh +3 -12
- package/dist/backend/backend/src/constants.d.ts +114 -2
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +114 -2
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js +62 -0
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js +5 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.d.ts +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.js +1 -1
- package/dist/backend/backend/src/controllers/request/open-items.controller.js +1 -1
- package/dist/backend/backend/src/controllers/request/open-items.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.js +4 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/standing/standing.controller.d.ts +18 -1
- package/dist/backend/backend/src/controllers/standing/standing.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/standing/standing.controller.js +77 -4
- package/dist/backend/backend/src/controllers/standing/standing.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/system/system.controller.d.ts +12 -0
- package/dist/backend/backend/src/controllers/system/system.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/system/system.controller.js +28 -3
- package/dist/backend/backend/src/controllers/system/system.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/system/usage.controller.d.ts +46 -0
- package/dist/backend/backend/src/controllers/system/usage.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/system/usage.controller.js +181 -0
- package/dist/backend/backend/src/controllers/system/usage.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts +7 -0
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js +64 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts +7 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +115 -1
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/routes/modules/system.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/modules/system.routes.js +3 -0
- package/dist/backend/backend/src/routes/modules/system.routes.js.map +1 -1
- package/dist/backend/backend/src/scripts/open-items-stale-followups.d.ts +51 -0
- package/dist/backend/backend/src/scripts/open-items-stale-followups.d.ts.map +1 -0
- package/dist/backend/backend/src/scripts/open-items-stale-followups.js +142 -0
- package/dist/backend/backend/src/scripts/open-items-stale-followups.js.map +1 -0
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.js +32 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/backend/backend/src/services/event-bus/event-bus.service.d.ts +28 -0
- package/dist/backend/backend/src/services/event-bus/event-bus.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/event-bus/event-bus.service.js +83 -2
- package/dist/backend/backend/src/services/event-bus/event-bus.service.js.map +1 -1
- package/dist/backend/backend/src/services/memory/memory.service.d.ts +14 -2
- package/dist/backend/backend/src/services/memory/memory.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/memory/memory.service.js +12 -1
- package/dist/backend/backend/src/services/memory/memory.service.js.map +1 -1
- package/dist/backend/backend/src/services/memory/project-memory.service.d.ts +18 -1
- package/dist/backend/backend/src/services/memory/project-memory.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/memory/project-memory.service.js +38 -1
- package/dist/backend/backend/src/services/memory/project-memory.service.js.map +1 -1
- package/dist/backend/backend/src/services/memory/standing-answers.service.d.ts +11 -0
- package/dist/backend/backend/src/services/memory/standing-answers.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/memory/standing-answers.service.js +40 -2
- package/dist/backend/backend/src/services/memory/standing-answers.service.js.map +1 -1
- package/dist/backend/backend/src/services/messaging/offline-agent-message.d.ts +70 -0
- package/dist/backend/backend/src/services/messaging/offline-agent-message.d.ts.map +1 -0
- package/dist/backend/backend/src/services/messaging/offline-agent-message.js +92 -0
- package/dist/backend/backend/src/services/messaging/offline-agent-message.js.map +1 -0
- package/dist/backend/backend/src/services/messaging/owner-message-watchdog.service.d.ts +7 -1
- package/dist/backend/backend/src/services/messaging/owner-message-watchdog.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/messaging/owner-message-watchdog.service.js +16 -2
- package/dist/backend/backend/src/services/messaging/owner-message-watchdog.service.js.map +1 -1
- package/dist/backend/backend/src/services/messaging/owner-message-watchdog.wiring.d.ts.map +1 -1
- package/dist/backend/backend/src/services/messaging/owner-message-watchdog.wiring.js +2 -0
- package/dist/backend/backend/src/services/messaging/owner-message-watchdog.wiring.js.map +1 -1
- package/dist/backend/backend/src/services/messaging/queue-processor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/messaging/queue-processor.service.js +9 -1
- package/dist/backend/backend/src/services/messaging/queue-processor.service.js.map +1 -1
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.d.ts +41 -1
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.js +87 -2
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.js.map +1 -1
- package/dist/backend/backend/src/services/monitoring/activity-monitor.service.d.ts +30 -12
- package/dist/backend/backend/src/services/monitoring/activity-monitor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/monitoring/activity-monitor.service.js +87 -15
- package/dist/backend/backend/src/services/monitoring/activity-monitor.service.js.map +1 -1
- package/dist/backend/backend/src/services/monitoring/antigravity-usage-sync.service.d.ts +130 -0
- package/dist/backend/backend/src/services/monitoring/antigravity-usage-sync.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/monitoring/antigravity-usage-sync.service.js +274 -0
- package/dist/backend/backend/src/services/monitoring/antigravity-usage-sync.service.js.map +1 -0
- package/dist/backend/backend/src/services/monitoring/codex-rollout-sync.service.d.ts +152 -0
- package/dist/backend/backend/src/services/monitoring/codex-rollout-sync.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/monitoring/codex-rollout-sync.service.js +311 -0
- package/dist/backend/backend/src/services/monitoring/codex-rollout-sync.service.js.map +1 -0
- package/dist/backend/backend/src/services/monitoring/token-usage.service.d.ts +78 -1
- package/dist/backend/backend/src/services/monitoring/token-usage.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/monitoring/token-usage.service.js +96 -4
- package/dist/backend/backend/src/services/monitoring/token-usage.service.js.map +1 -1
- package/dist/backend/backend/src/services/open-items/open-item-extractor.d.ts +10 -1
- package/dist/backend/backend/src/services/open-items/open-item-extractor.d.ts.map +1 -1
- package/dist/backend/backend/src/services/open-items/open-item-extractor.js +86 -5
- package/dist/backend/backend/src/services/open-items/open-item-extractor.js.map +1 -1
- package/dist/backend/backend/src/services/open-items/open-items-backfill.d.ts +2 -1
- package/dist/backend/backend/src/services/open-items/open-items-backfill.d.ts.map +1 -1
- package/dist/backend/backend/src/services/open-items/open-items-backfill.js +17 -6
- package/dist/backend/backend/src/services/open-items/open-items-backfill.js.map +1 -1
- package/dist/backend/backend/src/services/open-items/open-items.service.d.ts +62 -1
- package/dist/backend/backend/src/services/open-items/open-items.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/open-items/open-items.service.js +203 -9
- package/dist/backend/backend/src/services/open-items/open-items.service.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.d.ts +4 -4
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.js +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.d.ts +4 -3
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.js +7 -4
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.d.ts +40 -9
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.js +68 -20
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.js.map +1 -1
- package/dist/backend/backend/src/services/reconciler/idle-holder-surfacer.d.ts +40 -0
- package/dist/backend/backend/src/services/reconciler/idle-holder-surfacer.d.ts.map +1 -0
- package/dist/backend/backend/src/services/reconciler/idle-holder-surfacer.js +82 -0
- package/dist/backend/backend/src/services/reconciler/idle-holder-surfacer.js.map +1 -0
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts +29 -0
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.js +49 -0
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.js.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +6 -11
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts +23 -0
- package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler.service.js +55 -1
- package/dist/backend/backend/src/services/reconciler/reconciler.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-agent-post.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-agent-post.service.js +10 -4
- package/dist/backend/backend/src/services/slack/slack-agent-post.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.d.ts +13 -2
- package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.js +19 -5
- package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.js.map +1 -1
- package/dist/backend/backend/src/services/spend/spend-cap-command.d.ts +129 -0
- package/dist/backend/backend/src/services/spend/spend-cap-command.d.ts.map +1 -0
- package/dist/backend/backend/src/services/spend/spend-cap-command.js +323 -0
- package/dist/backend/backend/src/services/spend/spend-cap-command.js.map +1 -0
- package/dist/backend/backend/src/services/spend/spend-cap.gate.d.ts +60 -0
- package/dist/backend/backend/src/services/spend/spend-cap.gate.d.ts.map +1 -0
- package/dist/backend/backend/src/services/spend/spend-cap.gate.js +66 -0
- package/dist/backend/backend/src/services/spend/spend-cap.gate.js.map +1 -0
- package/dist/backend/backend/src/services/spend/spend-cap.service.d.ts +340 -0
- package/dist/backend/backend/src/services/spend/spend-cap.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/spend/spend-cap.service.js +760 -0
- package/dist/backend/backend/src/services/spend/spend-cap.service.js.map +1 -0
- package/dist/backend/backend/src/services/spend/spend-cap.store.d.ts +126 -0
- package/dist/backend/backend/src/services/spend/spend-cap.store.d.ts.map +1 -0
- package/dist/backend/backend/src/services/spend/spend-cap.store.js +141 -0
- package/dist/backend/backend/src/services/spend/spend-cap.store.js.map +1 -0
- package/dist/backend/backend/src/services/spend/spend-cap.wiring.d.ts +52 -0
- package/dist/backend/backend/src/services/spend/spend-cap.wiring.d.ts.map +1 -0
- package/dist/backend/backend/src/services/spend/spend-cap.wiring.js +112 -0
- package/dist/backend/backend/src/services/spend/spend-cap.wiring.js.map +1 -0
- package/dist/backend/backend/src/services/spend/spend-ledger.service.d.ts +145 -0
- package/dist/backend/backend/src/services/spend/spend-ledger.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/spend/spend-ledger.service.js +214 -0
- package/dist/backend/backend/src/services/spend/spend-ledger.service.js.map +1 -0
- package/dist/backend/backend/src/services/task-pool/claim-target-waker.d.ts +51 -0
- package/dist/backend/backend/src/services/task-pool/claim-target-waker.d.ts.map +1 -0
- package/dist/backend/backend/src/services/task-pool/claim-target-waker.js +43 -0
- package/dist/backend/backend/src/services/task-pool/claim-target-waker.js.map +1 -0
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +38 -0
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js +73 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/backend/backend/src/services/task-pool/work-item-usage.d.ts +53 -0
- package/dist/backend/backend/src/services/task-pool/work-item-usage.d.ts.map +1 -0
- package/dist/backend/backend/src/services/task-pool/work-item-usage.js +39 -0
- package/dist/backend/backend/src/services/task-pool/work-item-usage.js.map +1 -0
- package/dist/backend/backend/src/services/usage/token-format.d.ts +49 -0
- package/dist/backend/backend/src/services/usage/token-format.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage/token-format.js +81 -0
- package/dist/backend/backend/src/services/usage/token-format.js.map +1 -0
- package/dist/backend/backend/src/services/usage/usage-stats.service.d.ts +132 -0
- package/dist/backend/backend/src/services/usage/usage-stats.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage/usage-stats.service.js +226 -0
- package/dist/backend/backend/src/services/usage/usage-stats.service.js.map +1 -0
- package/dist/backend/backend/src/services/v3/v3-data.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/v3-data.service.js +11 -14
- package/dist/backend/backend/src/services/v3/v3-data.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.d.ts +25 -0
- package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.js +50 -0
- package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.js.map +1 -1
- package/dist/backend/backend/src/services/workflow/scheduler.service.d.ts +9 -1
- package/dist/backend/backend/src/services/workflow/scheduler.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/workflow/scheduler.service.js +16 -2
- package/dist/backend/backend/src/services/workflow/scheduler.service.js.map +1 -1
- package/dist/backend/backend/src/types/decision.types.d.ts +3 -1
- package/dist/backend/backend/src/types/decision.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/event-bus.types.d.ts +9 -1
- package/dist/backend/backend/src/types/event-bus.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/event-bus.types.js.map +1 -1
- package/dist/backend/backend/src/types/review-reason.types.d.ts +3 -1
- package/dist/backend/backend/src/types/review-reason.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/review-reason.types.js +1 -0
- package/dist/backend/backend/src/types/review-reason.types.js.map +1 -1
- package/dist/backend/backend/src/types/ticket-autopilot.types.d.ts +24 -7
- package/dist/backend/backend/src/types/ticket-autopilot.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/ticket-autopilot.types.js +29 -13
- package/dist/backend/backend/src/types/ticket-autopilot.types.js.map +1 -1
- package/dist/backend/backend/src/types/v2/open-item.types.d.ts +5 -1
- package/dist/backend/backend/src/types/v2/open-item.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/v2/open-item.types.js +1 -1
- package/dist/backend/backend/src/types/v2/open-item.types.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +114 -2
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +114 -2
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.js +32 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/cli/backend/src/services/event-bus/event-bus.service.d.ts +28 -0
- package/dist/cli/backend/src/services/event-bus/event-bus.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/event-bus/event-bus.service.js +83 -2
- package/dist/cli/backend/src/services/event-bus/event-bus.service.js.map +1 -1
- package/dist/cli/backend/src/services/memory/memory.service.d.ts +14 -2
- package/dist/cli/backend/src/services/memory/memory.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/memory/memory.service.js +12 -1
- package/dist/cli/backend/src/services/memory/memory.service.js.map +1 -1
- package/dist/cli/backend/src/services/memory/project-memory.service.d.ts +18 -1
- package/dist/cli/backend/src/services/memory/project-memory.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/memory/project-memory.service.js +38 -1
- package/dist/cli/backend/src/services/memory/project-memory.service.js.map +1 -1
- package/dist/cli/backend/src/services/memory/standing-answers.service.d.ts +11 -0
- package/dist/cli/backend/src/services/memory/standing-answers.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/memory/standing-answers.service.js +40 -2
- package/dist/cli/backend/src/services/memory/standing-answers.service.js.map +1 -1
- package/dist/cli/backend/src/services/messaging/owner-message-watchdog.service.d.ts +7 -1
- package/dist/cli/backend/src/services/messaging/owner-message-watchdog.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/messaging/owner-message-watchdog.service.js +16 -2
- package/dist/cli/backend/src/services/messaging/owner-message-watchdog.service.js.map +1 -1
- package/dist/cli/backend/src/services/messaging/queue-processor.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/messaging/queue-processor.service.js +9 -1
- package/dist/cli/backend/src/services/messaging/queue-processor.service.js.map +1 -1
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.d.ts +41 -1
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js +87 -2
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js.map +1 -1
- package/dist/cli/backend/src/services/monitoring/activity-monitor.service.d.ts +30 -12
- package/dist/cli/backend/src/services/monitoring/activity-monitor.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/monitoring/activity-monitor.service.js +87 -15
- package/dist/cli/backend/src/services/monitoring/activity-monitor.service.js.map +1 -1
- package/dist/cli/backend/src/services/monitoring/model-pricing.d.ts +97 -0
- package/dist/cli/backend/src/services/monitoring/model-pricing.d.ts.map +1 -0
- package/dist/cli/backend/src/services/monitoring/model-pricing.js +120 -0
- package/dist/cli/backend/src/services/monitoring/model-pricing.js.map +1 -0
- package/dist/cli/backend/src/services/monitoring/token-usage.service.d.ts +78 -1
- package/dist/cli/backend/src/services/monitoring/token-usage.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/monitoring/token-usage.service.js +96 -4
- package/dist/cli/backend/src/services/monitoring/token-usage.service.js.map +1 -1
- package/dist/cli/backend/src/services/open-items/open-item-extractor.d.ts +10 -1
- package/dist/cli/backend/src/services/open-items/open-item-extractor.d.ts.map +1 -1
- package/dist/cli/backend/src/services/open-items/open-item-extractor.js +86 -5
- package/dist/cli/backend/src/services/open-items/open-item-extractor.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.d.ts +13 -2
- package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.js +19 -5
- package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.js.map +1 -1
- package/dist/cli/backend/src/services/spend/spend-cap.gate.d.ts +60 -0
- package/dist/cli/backend/src/services/spend/spend-cap.gate.d.ts.map +1 -0
- package/dist/cli/backend/src/services/spend/spend-cap.gate.js +66 -0
- package/dist/cli/backend/src/services/spend/spend-cap.gate.js.map +1 -0
- package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +38 -0
- package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/task-pool/task-pool.service.js +73 -1
- package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/cli/backend/src/services/task-pool/work-item-usage.d.ts +53 -0
- package/dist/cli/backend/src/services/task-pool/work-item-usage.d.ts.map +1 -0
- package/dist/cli/backend/src/services/task-pool/work-item-usage.js +39 -0
- package/dist/cli/backend/src/services/task-pool/work-item-usage.js.map +1 -0
- package/dist/cli/backend/src/services/usage/token-format.d.ts +49 -0
- package/dist/cli/backend/src/services/usage/token-format.d.ts.map +1 -0
- package/dist/cli/backend/src/services/usage/token-format.js +81 -0
- package/dist/cli/backend/src/services/usage/token-format.js.map +1 -0
- package/dist/cli/backend/src/services/v3/v3-data.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/v3/v3-data.service.js +11 -14
- package/dist/cli/backend/src/services/v3/v3-data.service.js.map +1 -1
- package/dist/cli/backend/src/types/decision.types.d.ts +3 -1
- package/dist/cli/backend/src/types/decision.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/event-bus.types.d.ts +9 -1
- package/dist/cli/backend/src/types/event-bus.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/event-bus.types.js.map +1 -1
- package/dist/cli/backend/src/types/ticket-autopilot.types.d.ts +24 -7
- package/dist/cli/backend/src/types/ticket-autopilot.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/ticket-autopilot.types.js +29 -13
- package/dist/cli/backend/src/types/ticket-autopilot.types.js.map +1 -1
- package/dist/cli/backend/src/types/v2/open-item.types.d.ts +5 -1
- package/dist/cli/backend/src/types/v2/open-item.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/v2/open-item.types.js +1 -1
- package/dist/cli/backend/src/types/v2/open-item.types.js.map +1 -1
- package/frontend/dist/assets/{index-a79c7852.css → index-20a67829.css} +1 -1
- package/frontend/dist/assets/{index-1ee8a9a2.js → index-49f50b45.js} +282 -282
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daily token caps with a hard stop: per agent, per team and all agents
|
|
3
|
+
* together, plus temporary boosts.
|
|
4
|
+
*
|
|
5
|
+
* - The unit is tokens (input incl. cached + output, see `eventTokens`).
|
|
6
|
+
* - Caps are the owner's own limits (unlike the runtime fallback, which
|
|
7
|
+
* reacts to a provider's usage limit). All are OFF until the owner sets
|
|
8
|
+
* one: a default per-agent cap, per-agent overrides, per-team caps, and an
|
|
9
|
+
* optional all-agents total.
|
|
10
|
+
* - At {@link SPEND_CAP_CONSTANTS.WARN_FRACTION} of a cap the owner gets ONE
|
|
11
|
+
* heads-up.
|
|
12
|
+
* - At 100% the agent is hard-stopped: {@link stopOf} answers the delivery
|
|
13
|
+
* and wake gates, so no new turn starts (the current one may finish) and
|
|
14
|
+
* messages stay queued. The owner gets ONE decision card: "Boost +X
|
|
15
|
+
* today" / "Unlimited today" / "Keep stopped".
|
|
16
|
+
* - A boost covers an agent, a team (its members) or everyone. For every
|
|
17
|
+
* agent it covers, it adds `extraTokens` to EVERY cap checked for that
|
|
18
|
+
* agent (its own, its team's, the total), or lifts them all
|
|
19
|
+
* (`unlimited`). It ends at `until` — by default the next local midnight.
|
|
20
|
+
* - Local midnight resets the day; when a stop lifts (midnight, boost, cap
|
|
21
|
+
* change) the queued messages are released.
|
|
22
|
+
*
|
|
23
|
+
* specs/2026-10-02-spend-cap.md
|
|
24
|
+
*
|
|
25
|
+
* @module services/spend/spend-cap.service
|
|
26
|
+
*/
|
|
27
|
+
import { randomUUID } from 'crypto';
|
|
28
|
+
import { ORCHESTRATOR_SESSION_NAME, SPEND_CAP_CONSTANTS as C } from '../../constants.js';
|
|
29
|
+
import { localDateKey, localMidnight } from '../project-tickets/ticket-autopilot-decision.js';
|
|
30
|
+
import { compactTokens, formatTokens, parseTokenAmount } from '../usage/token-format.js';
|
|
31
|
+
import { spendCapReason } from './spend-cap.gate.js';
|
|
32
|
+
import { emptyDay, emptySpendCapFile } from './spend-cap.store.js';
|
|
33
|
+
/** A rejected change. */
|
|
34
|
+
export class SpendCapError extends Error {
|
|
35
|
+
status;
|
|
36
|
+
/**
|
|
37
|
+
* @param status - HTTP status
|
|
38
|
+
* @param message - Owner-facing reason
|
|
39
|
+
*/
|
|
40
|
+
constructor(status, message) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.status = status;
|
|
43
|
+
this.name = 'SpendCapError';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Whether a cap value is usable.
|
|
48
|
+
*
|
|
49
|
+
* @param v - Value
|
|
50
|
+
* @returns True for a finite positive number
|
|
51
|
+
*/
|
|
52
|
+
function isCap(v) {
|
|
53
|
+
return typeof v === 'number' && Number.isFinite(v) && v > 0;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Validate a cap from the API.
|
|
57
|
+
*
|
|
58
|
+
* @param v - Value (number or "20M")
|
|
59
|
+
* @param what - Field name for the error
|
|
60
|
+
* @returns The cap (whole tokens) or null
|
|
61
|
+
* @throws SpendCapError
|
|
62
|
+
*/
|
|
63
|
+
function parseCap(v, what) {
|
|
64
|
+
if (v === null || v === '')
|
|
65
|
+
return null;
|
|
66
|
+
const n = parseTokenAmount(v);
|
|
67
|
+
if (n === null)
|
|
68
|
+
throw new SpendCapError(400, `${what} must be a positive number of tokens (e.g. 5000000 or "5M"), or null to turn it off`);
|
|
69
|
+
return n;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Round up to a whole million (at least 1M).
|
|
73
|
+
*
|
|
74
|
+
* @param tokens - Tokens
|
|
75
|
+
* @returns Rounded tokens
|
|
76
|
+
*/
|
|
77
|
+
export function roundUpMillions(tokens) {
|
|
78
|
+
return Math.max(1_000_000, Math.ceil(tokens / 1_000_000) * 1_000_000);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The "Boost +X today" amount on a cap card: the cap times BOOST_FACTOR,
|
|
82
|
+
* rounded up to a whole million.
|
|
83
|
+
*
|
|
84
|
+
* @param capTokens - Cap in force
|
|
85
|
+
* @returns Tokens
|
|
86
|
+
*/
|
|
87
|
+
export function suggestedBoost(capTokens) {
|
|
88
|
+
return roundUpMillions(capTokens * C.BOOST_FACTOR);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Read the amount out of a "Boost +X tokens today" option label.
|
|
92
|
+
*
|
|
93
|
+
* @param label - Option label
|
|
94
|
+
* @returns Tokens, `unlimited`, or null when the label is not a boost
|
|
95
|
+
*/
|
|
96
|
+
export function boostOfLabel(label) {
|
|
97
|
+
const text = (label ?? '').trim();
|
|
98
|
+
if (text.toLowerCase().startsWith(C.OPTIONS.UNLIMITED.toLowerCase()))
|
|
99
|
+
return 'unlimited';
|
|
100
|
+
const m = /^Boost \+(\S+?)(?: tokens)? today/i.exec(text);
|
|
101
|
+
return m ? parseTokenAmount(m[1]) : null;
|
|
102
|
+
}
|
|
103
|
+
/** Team target key. */
|
|
104
|
+
export function teamTarget(teamId) {
|
|
105
|
+
return `${C.TEAM_TARGET_PREFIX}${teamId}`;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Daily token cap service.
|
|
109
|
+
*/
|
|
110
|
+
export class SpendCapService {
|
|
111
|
+
deps;
|
|
112
|
+
file;
|
|
113
|
+
now;
|
|
114
|
+
logger;
|
|
115
|
+
timer = null;
|
|
116
|
+
/** Sessions stopped at the last evaluation (to release them when the stop lifts) */
|
|
117
|
+
lastStopped = new Set();
|
|
118
|
+
chain = Promise.resolve();
|
|
119
|
+
queuedPass = null;
|
|
120
|
+
/** Team index, refreshed on every evaluation (the gate is synchronous) */
|
|
121
|
+
teamList = [];
|
|
122
|
+
teamsOfSession = new Map();
|
|
123
|
+
/**
|
|
124
|
+
* @param deps - Collaborators
|
|
125
|
+
*/
|
|
126
|
+
constructor(deps) {
|
|
127
|
+
this.deps = deps;
|
|
128
|
+
this.now = deps.now ?? (() => new Date());
|
|
129
|
+
this.logger = deps.logger;
|
|
130
|
+
this.file = deps.store.read() ?? emptySpendCapFile(localDateKey(this.now()));
|
|
131
|
+
}
|
|
132
|
+
/** Start the enforcement tick. */
|
|
133
|
+
start() {
|
|
134
|
+
if (this.timer)
|
|
135
|
+
return;
|
|
136
|
+
this.timer = setInterval(() => void this.evaluate(), C.TICK_MS);
|
|
137
|
+
this.timer.unref?.();
|
|
138
|
+
void this.evaluate();
|
|
139
|
+
}
|
|
140
|
+
/** Stop the tick. */
|
|
141
|
+
stop() {
|
|
142
|
+
if (this.timer)
|
|
143
|
+
clearInterval(this.timer);
|
|
144
|
+
this.timer = null;
|
|
145
|
+
}
|
|
146
|
+
/** Re-read the team list now (tests; the tick does it too). */
|
|
147
|
+
async refreshTeams() {
|
|
148
|
+
const teams = (await this.deps.teams?.().catch(() => [])) ?? [];
|
|
149
|
+
this.teamList = teams;
|
|
150
|
+
const index = new Map();
|
|
151
|
+
for (const t of teams) {
|
|
152
|
+
for (const s of t.members)
|
|
153
|
+
index.set(s, [...(index.get(s) ?? []), t.id]);
|
|
154
|
+
}
|
|
155
|
+
this.teamsOfSession = index;
|
|
156
|
+
}
|
|
157
|
+
// ---------------------------------------------------------------- caps
|
|
158
|
+
/** @returns The owner's caps (a copy) */
|
|
159
|
+
getConfig() {
|
|
160
|
+
return JSON.parse(JSON.stringify(this.file.config));
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Change caps. Takes effect at once (a stop can lift or start).
|
|
164
|
+
*
|
|
165
|
+
* @param patch - Changes
|
|
166
|
+
* @returns The new caps
|
|
167
|
+
* @throws SpendCapError on a bad value
|
|
168
|
+
*/
|
|
169
|
+
async setCaps(patch) {
|
|
170
|
+
if (!patch || typeof patch !== 'object')
|
|
171
|
+
throw new SpendCapError(400, 'Send the caps to change as JSON');
|
|
172
|
+
const next = this.getConfig();
|
|
173
|
+
if ('defaultAgentCapTokens' in patch)
|
|
174
|
+
next.defaultAgentCapTokens = parseCap(patch.defaultAgentCapTokens, 'defaultAgentCapTokens');
|
|
175
|
+
if ('totalCapTokens' in patch)
|
|
176
|
+
next.totalCapTokens = parseCap(patch.totalCapTokens, 'totalCapTokens');
|
|
177
|
+
if (patch.agents !== undefined) {
|
|
178
|
+
if (!patch.agents || typeof patch.agents !== 'object')
|
|
179
|
+
throw new SpendCapError(400, 'agents must be an object: { "<session>": 5000000 | null | "default" }');
|
|
180
|
+
for (const [session, value] of Object.entries(patch.agents)) {
|
|
181
|
+
if (!session.trim())
|
|
182
|
+
throw new SpendCapError(400, 'agents keys must be agent session names');
|
|
183
|
+
if (value === 'default')
|
|
184
|
+
delete next.agentCapsTokens[session];
|
|
185
|
+
else
|
|
186
|
+
next.agentCapsTokens[session] = parseCap(value, `agents.${session}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (patch.teams !== undefined) {
|
|
190
|
+
if (!patch.teams || typeof patch.teams !== 'object')
|
|
191
|
+
throw new SpendCapError(400, 'teams must be an object: { "<teamId>": 50000000 | null }');
|
|
192
|
+
for (const [teamId, value] of Object.entries(patch.teams)) {
|
|
193
|
+
if (!teamId.trim())
|
|
194
|
+
throw new SpendCapError(400, 'teams keys must be team ids');
|
|
195
|
+
const cap = parseCap(value, `teams.${teamId}`);
|
|
196
|
+
if (cap === null)
|
|
197
|
+
delete next.teamCapsTokens[teamId];
|
|
198
|
+
else
|
|
199
|
+
next.teamCapsTokens[teamId] = cap;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
next.updatedAt = this.now().toISOString();
|
|
203
|
+
this.file.config = next;
|
|
204
|
+
this.persist();
|
|
205
|
+
this.logger?.info('Token caps changed', { caps: next });
|
|
206
|
+
await this.evaluate();
|
|
207
|
+
return this.getConfig();
|
|
208
|
+
}
|
|
209
|
+
// ---------------------------------------------------------------- boosts
|
|
210
|
+
/** @returns Boosts in force (expired ones are dropped) */
|
|
211
|
+
activeBoosts() {
|
|
212
|
+
this.pruneBoosts();
|
|
213
|
+
return this.file.boosts.map((b) => ({ ...b }));
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Add a temporary boost.
|
|
217
|
+
*
|
|
218
|
+
* @param input - Scope, target, extra tokens or unlimited, optional end
|
|
219
|
+
* @returns The boost
|
|
220
|
+
* @throws SpendCapError on a bad request
|
|
221
|
+
*/
|
|
222
|
+
async boost(input) {
|
|
223
|
+
if (!input || typeof input !== 'object')
|
|
224
|
+
throw new SpendCapError(400, 'Send the boost as JSON');
|
|
225
|
+
const target = await this.resolveBoostTarget(input.scope, input.id);
|
|
226
|
+
const unlimited = input.unlimited === true;
|
|
227
|
+
const extra = unlimited ? null : parseTokenAmount(input.extraTokens);
|
|
228
|
+
if (!unlimited && extra === null)
|
|
229
|
+
throw new SpendCapError(400, 'Send extraTokens (a positive number of tokens, e.g. 20000000 or "20M") or unlimited: true');
|
|
230
|
+
const now = this.now();
|
|
231
|
+
let until = this.nextMidnight();
|
|
232
|
+
if (input.until !== undefined && input.until !== null && input.until !== '') {
|
|
233
|
+
const t = new Date(String(input.until));
|
|
234
|
+
if (Number.isNaN(t.getTime()) || t.getTime() <= now.getTime())
|
|
235
|
+
throw new SpendCapError(400, 'until must be a future time (ISO 8601)');
|
|
236
|
+
until = t;
|
|
237
|
+
}
|
|
238
|
+
const boost = {
|
|
239
|
+
id: randomUUID(),
|
|
240
|
+
target,
|
|
241
|
+
...(unlimited ? { unlimited: true } : { extraTokens: extra }),
|
|
242
|
+
until: until.toISOString(),
|
|
243
|
+
createdAt: now.toISOString(),
|
|
244
|
+
...(input.by ? { by: input.by } : {}),
|
|
245
|
+
};
|
|
246
|
+
this.pruneBoosts();
|
|
247
|
+
this.file.boosts.push(boost);
|
|
248
|
+
this.persist();
|
|
249
|
+
this.logger?.info('Usage boost added', { target, extraTokens: boost.extraTokens, unlimited: boost.unlimited, until: boost.until });
|
|
250
|
+
await this.evaluate();
|
|
251
|
+
return { ...boost };
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* End a boost early.
|
|
255
|
+
*
|
|
256
|
+
* @param id - Boost id
|
|
257
|
+
* @returns True when it existed
|
|
258
|
+
*/
|
|
259
|
+
async removeBoost(id) {
|
|
260
|
+
const before = this.file.boosts.length;
|
|
261
|
+
this.file.boosts = this.file.boosts.filter((b) => b.id !== id);
|
|
262
|
+
if (this.file.boosts.length === before)
|
|
263
|
+
return false;
|
|
264
|
+
this.persist();
|
|
265
|
+
await this.evaluate();
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Owner-facing description of a boost target.
|
|
270
|
+
*
|
|
271
|
+
* @param target - Boost target
|
|
272
|
+
* @returns e.g. `everyone`, `team CE`, `Ella`
|
|
273
|
+
*/
|
|
274
|
+
describeTarget(target) {
|
|
275
|
+
if (target === C.TOTAL_TARGET)
|
|
276
|
+
return 'everyone';
|
|
277
|
+
if (target.startsWith(C.TEAM_TARGET_PREFIX)) {
|
|
278
|
+
const id = target.slice(C.TEAM_TARGET_PREFIX.length);
|
|
279
|
+
return `team ${this.teamList.find((t) => t.id === id)?.name ?? id}`;
|
|
280
|
+
}
|
|
281
|
+
return this.nameOf(target);
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Extra tokens / unlimited a boost set gives for one target set.
|
|
285
|
+
*
|
|
286
|
+
* @param covers - Which boost targets apply
|
|
287
|
+
* @returns Sum of extras and whether any is unlimited
|
|
288
|
+
*/
|
|
289
|
+
boostFor(covers) {
|
|
290
|
+
let extra = 0;
|
|
291
|
+
let unlimited = false;
|
|
292
|
+
for (const b of this.activeBoostsRaw()) {
|
|
293
|
+
if (!covers(b.target))
|
|
294
|
+
continue;
|
|
295
|
+
if (b.unlimited)
|
|
296
|
+
unlimited = true;
|
|
297
|
+
else
|
|
298
|
+
extra += b.extraTokens ?? 0;
|
|
299
|
+
}
|
|
300
|
+
return { extra, unlimited };
|
|
301
|
+
}
|
|
302
|
+
/** Boosts covering an agent: its own, its teams', everyone's. */
|
|
303
|
+
boostForSession(session) {
|
|
304
|
+
const teams = new Set((this.teamsOfSession.get(session) ?? []).map(teamTarget));
|
|
305
|
+
return this.boostFor((t) => t === C.TOTAL_TARGET || t === session || teams.has(t));
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Boosts covering a team (team and everyone boosts) — what the ticket
|
|
309
|
+
* autopilot budget of the team's projects honours.
|
|
310
|
+
*
|
|
311
|
+
* @param teamId - Team id
|
|
312
|
+
* @returns Extra tokens and unlimited
|
|
313
|
+
*/
|
|
314
|
+
boostForTeam(teamId) {
|
|
315
|
+
return this.boostForTeams([teamId]);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Boosts covering any of several teams, each boost counted once (a
|
|
319
|
+
* project worked on by two teams).
|
|
320
|
+
*
|
|
321
|
+
* @param teamIds - Team ids
|
|
322
|
+
* @returns Extra tokens and unlimited
|
|
323
|
+
*/
|
|
324
|
+
boostForTeams(teamIds) {
|
|
325
|
+
const keys = new Set(teamIds.map(teamTarget));
|
|
326
|
+
return this.boostFor((t) => t === C.TOTAL_TARGET || keys.has(t));
|
|
327
|
+
}
|
|
328
|
+
// ---------------------------------------------------------------- gate
|
|
329
|
+
/**
|
|
330
|
+
* The stop in force for a session (the delivery / wake gate). Synchronous
|
|
331
|
+
* and live: it reads today's usage now, not at the last tick.
|
|
332
|
+
*
|
|
333
|
+
* @param session - Agent session
|
|
334
|
+
* @returns Stop, or null when the agent may run
|
|
335
|
+
*/
|
|
336
|
+
stopOf(session) {
|
|
337
|
+
this.rollDay();
|
|
338
|
+
const stop = this.computeStop(session);
|
|
339
|
+
if (stop && !this.file.day.stopped.includes(this.stopKey(stop)))
|
|
340
|
+
this.scheduleEvaluate();
|
|
341
|
+
return stop;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Owner-facing name.
|
|
345
|
+
*
|
|
346
|
+
* @param session - Agent session
|
|
347
|
+
* @returns Name
|
|
348
|
+
*/
|
|
349
|
+
displayNameOf(session) {
|
|
350
|
+
return this.nameOf(session);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* An agent's own cap before boosts, and where it comes from.
|
|
354
|
+
*
|
|
355
|
+
* @param session - Agent session
|
|
356
|
+
* @returns Cap (null = none) and source
|
|
357
|
+
*/
|
|
358
|
+
ownCapOf(session) {
|
|
359
|
+
const cfg = this.file.config;
|
|
360
|
+
if (Object.prototype.hasOwnProperty.call(cfg.agentCapsTokens, session)) {
|
|
361
|
+
const v = cfg.agentCapsTokens[session];
|
|
362
|
+
return isCap(v) ? { capTokens: v, source: 'override' } : { capTokens: null, source: 'exempt' };
|
|
363
|
+
}
|
|
364
|
+
return isCap(cfg.defaultAgentCapTokens) ? { capTokens: cfg.defaultAgentCapTokens, source: 'default' } : { capTokens: null, source: 'none' };
|
|
365
|
+
}
|
|
366
|
+
// ---------------------------------------------------------------- view
|
|
367
|
+
/**
|
|
368
|
+
* Usage plus caps for the API / Settings panel.
|
|
369
|
+
*
|
|
370
|
+
* @param days - Window
|
|
371
|
+
* @returns View
|
|
372
|
+
*/
|
|
373
|
+
async view(days = C.DEFAULT_DAYS) {
|
|
374
|
+
this.rollDay();
|
|
375
|
+
await this.refreshTeams();
|
|
376
|
+
const summary = this.deps.ledger.summarize(days);
|
|
377
|
+
const known = (await this.deps.knownSessions?.().catch(() => [])) ?? [];
|
|
378
|
+
const rows = new Map(summary.agents.map((a) => [a.session, a]));
|
|
379
|
+
for (const s of [...known, ...this.teamList.flatMap((t) => t.members)]) {
|
|
380
|
+
if (!rows.has(s))
|
|
381
|
+
rows.set(s, { session: s, runtimes: [], todayTokens: 0, windowTokens: 0, windowCachedTokens: 0, daily: summary.days.map(() => 0) });
|
|
382
|
+
}
|
|
383
|
+
const agents = [...rows.values()].map((a) => {
|
|
384
|
+
const own = this.ownCapOf(a.session);
|
|
385
|
+
const b = this.boostForSession(a.session);
|
|
386
|
+
const stop = this.computeStop(a.session);
|
|
387
|
+
return {
|
|
388
|
+
session: a.session,
|
|
389
|
+
name: this.nameOf(a.session),
|
|
390
|
+
...(this.teamsOfSession.get(a.session)?.[0] ? { teamId: this.teamsOfSession.get(a.session)?.[0] } : {}),
|
|
391
|
+
runtimes: a.runtimes,
|
|
392
|
+
todayTokens: a.todayTokens,
|
|
393
|
+
windowTokens: a.windowTokens,
|
|
394
|
+
daily: a.daily,
|
|
395
|
+
baseCapTokens: own.capTokens,
|
|
396
|
+
capTokens: own.capTokens === null || b.unlimited ? null : own.capTokens + b.extra,
|
|
397
|
+
capSource: own.source,
|
|
398
|
+
boosted: b.unlimited || b.extra > 0,
|
|
399
|
+
unlimited: b.unlimited,
|
|
400
|
+
stopped: !!stop,
|
|
401
|
+
...(stop ? { stopReason: spendCapReason(stop, this.nameOf(a.session)) } : {}),
|
|
402
|
+
};
|
|
403
|
+
});
|
|
404
|
+
const teams = this.teamList.map((t) => {
|
|
405
|
+
const base = this.file.config.teamCapsTokens[t.id];
|
|
406
|
+
const b = this.boostForTeam(t.id);
|
|
407
|
+
const cap = isCap(base) && !b.unlimited ? base + b.extra : null;
|
|
408
|
+
const used = this.deps.ledger.groupToday(t.members);
|
|
409
|
+
return {
|
|
410
|
+
teamId: t.id,
|
|
411
|
+
name: t.name,
|
|
412
|
+
members: t.members,
|
|
413
|
+
todayTokens: used,
|
|
414
|
+
baseCapTokens: isCap(base) ? base : null,
|
|
415
|
+
capTokens: cap,
|
|
416
|
+
extraTokens: b.extra,
|
|
417
|
+
unlimited: b.unlimited,
|
|
418
|
+
boosts: this.activeBoostsRaw().filter((x) => x.target === teamTarget(t.id)),
|
|
419
|
+
stopped: cap !== null && used >= cap,
|
|
420
|
+
};
|
|
421
|
+
});
|
|
422
|
+
const totalBoost = this.boostFor((t) => t === C.TOTAL_TARGET);
|
|
423
|
+
const total = isCap(this.file.config.totalCapTokens) && !totalBoost.unlimited ? this.file.config.totalCapTokens + totalBoost.extra : null;
|
|
424
|
+
const { agents: _omit, ...rest } = summary;
|
|
425
|
+
void _omit;
|
|
426
|
+
return {
|
|
427
|
+
...rest,
|
|
428
|
+
agents,
|
|
429
|
+
teams,
|
|
430
|
+
caps: this.getConfig(),
|
|
431
|
+
boosts: this.activeBoosts(),
|
|
432
|
+
totalCapTodayTokens: total,
|
|
433
|
+
suggestedAgentCapTokens: summary.p90AgentDayTokens > 0 ? roundUpMillions(summary.p90AgentDayTokens) : null,
|
|
434
|
+
totalStopped: total !== null && this.deps.ledger.totalToday() >= total,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
// ---------------------------------------------------------------- enforcement
|
|
438
|
+
/**
|
|
439
|
+
* One enforcement pass: day roll, boost expiry, 80% notices, stop
|
|
440
|
+
* announcements (one card per stop), and releasing agents whose stop
|
|
441
|
+
* lifted. Passes run one at a time; a call during a pass gets another
|
|
442
|
+
* pass after it.
|
|
443
|
+
*/
|
|
444
|
+
evaluate() {
|
|
445
|
+
if (this.queuedPass)
|
|
446
|
+
return this.queuedPass;
|
|
447
|
+
const pass = this.chain.then(async () => {
|
|
448
|
+
this.queuedPass = null;
|
|
449
|
+
try {
|
|
450
|
+
await this.evaluateOnce();
|
|
451
|
+
}
|
|
452
|
+
catch (err) {
|
|
453
|
+
this.logger?.warn('Token cap evaluation failed', { error: err instanceof Error ? err.message : String(err) });
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
this.queuedPass = pass;
|
|
457
|
+
this.chain = pass;
|
|
458
|
+
return pass;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* React to a settled "cap reached" card (decision kind `spend_cap`).
|
|
462
|
+
*
|
|
463
|
+
* @param d - The settled decision
|
|
464
|
+
* @returns Always null (nobody else is told)
|
|
465
|
+
*/
|
|
466
|
+
async onSettled(d) {
|
|
467
|
+
if (d.kind !== C.DECISION_KIND || !d.system)
|
|
468
|
+
return null;
|
|
469
|
+
const target = d.system.key;
|
|
470
|
+
this.rollDay();
|
|
471
|
+
if (this.file.day.cards[target] !== d.id) {
|
|
472
|
+
if (d.status === 'resolved')
|
|
473
|
+
await this.reply(d.id, 'This card is from an earlier day; the cap has already reset at midnight.');
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
const who = this.describeTarget(target);
|
|
477
|
+
if (d.status !== 'resolved')
|
|
478
|
+
return null;
|
|
479
|
+
const label = d.options.find((o) => o.key === d.chosenKey)?.label;
|
|
480
|
+
const boost = boostOfLabel(label);
|
|
481
|
+
if (boost === null) {
|
|
482
|
+
await this.reply(d.id, `OK, ${who} stays stopped until midnight. Messages stay queued.`);
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
try {
|
|
486
|
+
const scope = target === C.TOTAL_TARGET ? 'all' : target.startsWith(C.TEAM_TARGET_PREFIX) ? 'team' : 'agent';
|
|
487
|
+
const id = scope === 'team' ? target.slice(C.TEAM_TARGET_PREFIX.length) : scope === 'agent' ? target : undefined;
|
|
488
|
+
await this.boost(boost === 'unlimited' ? { scope, id, unlimited: true, by: 'card' } : { scope, id, extraTokens: boost, by: 'card' });
|
|
489
|
+
const what = boost === 'unlimited' ? 'no cap' : `+${formatTokens(boost)}`;
|
|
490
|
+
await this.reply(d.id, `Done: ${what} for ${who} until midnight. Queued messages are being delivered.`);
|
|
491
|
+
}
|
|
492
|
+
catch (err) {
|
|
493
|
+
await this.reply(d.id, `Could not boost: ${err instanceof Error ? err.message : String(err)}`);
|
|
494
|
+
}
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
// ---------------------------------------------------------------- internals
|
|
498
|
+
async evaluateOnce() {
|
|
499
|
+
this.rollDay();
|
|
500
|
+
const hadBoosts = this.file.boosts.length;
|
|
501
|
+
this.pruneBoosts();
|
|
502
|
+
if (hadBoosts !== this.file.boosts.length)
|
|
503
|
+
this.persist();
|
|
504
|
+
if (!this.anyCap() && this.lastStopped.size === 0)
|
|
505
|
+
return;
|
|
506
|
+
await this.refreshTeams();
|
|
507
|
+
this.deps.ledger.invalidate();
|
|
508
|
+
const summary = this.deps.ledger.summarize(1);
|
|
509
|
+
const known = (await this.deps.knownSessions?.().catch(() => [])) ?? [];
|
|
510
|
+
const sessions = new Set([...known, ...summary.agents.map((a) => a.session), ...this.teamList.flatMap((t) => t.members)]);
|
|
511
|
+
for (const s of this.lastStopped)
|
|
512
|
+
sessions.add(s);
|
|
513
|
+
// The all-agents total first: its notice names the whole crew once.
|
|
514
|
+
const totalCap = this.capForTarget(C.TOTAL_TARGET);
|
|
515
|
+
if (totalCap !== null)
|
|
516
|
+
await this.checkThresholds(C.TOTAL_TARGET, totalCap, this.deps.ledger.totalToday());
|
|
517
|
+
for (const t of this.teamList) {
|
|
518
|
+
const cap = this.capForTarget(teamTarget(t.id));
|
|
519
|
+
if (cap !== null)
|
|
520
|
+
await this.checkThresholds(teamTarget(t.id), cap, this.deps.ledger.groupToday(t.members));
|
|
521
|
+
}
|
|
522
|
+
const stoppedNow = new Set();
|
|
523
|
+
for (const session of sessions) {
|
|
524
|
+
const cap = this.capForTarget(session);
|
|
525
|
+
if (cap !== null)
|
|
526
|
+
await this.checkThresholds(session, cap, this.deps.ledger.usedToday(session));
|
|
527
|
+
if (this.computeStop(session))
|
|
528
|
+
stoppedNow.add(session);
|
|
529
|
+
}
|
|
530
|
+
const released = [...this.lastStopped].filter((s) => !stoppedNow.has(s));
|
|
531
|
+
this.lastStopped = stoppedNow;
|
|
532
|
+
if (released.length > 0) {
|
|
533
|
+
this.logger?.info('Token cap stop lifted — releasing queued messages', { sessions: released });
|
|
534
|
+
await this.deps.onReleased?.(released).catch((err) => this.logger?.warn('Releasing queued messages failed', { sessions: released, error: err instanceof Error ? err.message : String(err) }));
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* The cap in force for a cap target, with the boosts that cover the
|
|
539
|
+
* target itself (for notices and cards).
|
|
540
|
+
*/
|
|
541
|
+
capForTarget(target) {
|
|
542
|
+
const cfg = this.file.config;
|
|
543
|
+
if (target === C.TOTAL_TARGET) {
|
|
544
|
+
const b = this.boostFor((t) => t === C.TOTAL_TARGET);
|
|
545
|
+
return isCap(cfg.totalCapTokens) && !b.unlimited ? cfg.totalCapTokens + b.extra : null;
|
|
546
|
+
}
|
|
547
|
+
if (target.startsWith(C.TEAM_TARGET_PREFIX)) {
|
|
548
|
+
const id = target.slice(C.TEAM_TARGET_PREFIX.length);
|
|
549
|
+
const base = cfg.teamCapsTokens[id];
|
|
550
|
+
const b = this.boostForTeam(id);
|
|
551
|
+
return isCap(base) && !b.unlimited ? base + b.extra : null;
|
|
552
|
+
}
|
|
553
|
+
const own = this.ownCapOf(target).capTokens;
|
|
554
|
+
const b = this.boostForSession(target);
|
|
555
|
+
return own !== null && !b.unlimited ? own + b.extra : null;
|
|
556
|
+
}
|
|
557
|
+
async checkThresholds(target, cap, used) {
|
|
558
|
+
const key = `${target}@${cap}`;
|
|
559
|
+
const day = this.file.day;
|
|
560
|
+
const who = this.describeTarget(target);
|
|
561
|
+
if (used >= cap) {
|
|
562
|
+
if (day.stopped.includes(key))
|
|
563
|
+
return;
|
|
564
|
+
day.stopped.push(key);
|
|
565
|
+
if (!day.warned.includes(key))
|
|
566
|
+
day.warned.push(key);
|
|
567
|
+
this.persist();
|
|
568
|
+
this.logger?.warn('Daily token cap reached — hard stop (no new turns until midnight)', { target, capTokens: cap, usedTokens: used });
|
|
569
|
+
await this.announceStop(target, who, cap, used);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
if (used >= cap * C.WARN_FRACTION && !day.warned.includes(key)) {
|
|
573
|
+
day.warned.push(key);
|
|
574
|
+
this.persist();
|
|
575
|
+
const pct = Math.floor((used / cap) * 100);
|
|
576
|
+
const subject = target === C.TOTAL_TARGET ? 'All agents together have' : target.startsWith(C.TEAM_TARGET_PREFIX) ? `Team ${who.replace(/^team /, '')} has` : `${who} has`;
|
|
577
|
+
const stops = target === C.TOTAL_TARGET ? 'every agent stops' : target.startsWith(C.TEAM_TARGET_PREFIX) ? 'its members stop' : 'it stops';
|
|
578
|
+
await this.notify(`Heads-up: ${subject} used ${formatTokens(used)} of the ${formatTokens(cap)} daily token cap today (${pct}%). At ${compactTokens(cap)} ${stops} taking new turns until midnight.`);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
async announceStop(target, who, cap, used) {
|
|
582
|
+
const extra = suggestedBoost(cap);
|
|
583
|
+
const dmWho = target === C.TOTAL_TARGET ? 'everyone' : target === ORCHESTRATOR_SESSION_NAME ? 'orc' : who.replace(/^team /, '');
|
|
584
|
+
const headline = target === C.TOTAL_TARGET
|
|
585
|
+
? `All agents together hit the daily token cap (${formatTokens(cap)}); every agent is stopped until midnight.`
|
|
586
|
+
: target.startsWith(C.TEAM_TARGET_PREFIX)
|
|
587
|
+
? `Team ${who.replace(/^team /, '')} hit its daily token cap (${formatTokens(cap)}); its members are stopped until midnight.`
|
|
588
|
+
: `${who} hit its daily token cap (${formatTokens(cap)}) and is stopped until midnight.`;
|
|
589
|
+
const decisions = this.deps.decisions?.() ?? null;
|
|
590
|
+
if (decisions) {
|
|
591
|
+
try {
|
|
592
|
+
const d = await decisions.askSystem({
|
|
593
|
+
kind: C.DECISION_KIND,
|
|
594
|
+
system: { key: target, defaultIsDecline: true },
|
|
595
|
+
title: 'Daily token cap reached',
|
|
596
|
+
question: `${headline} Boost it for today?`,
|
|
597
|
+
body: [
|
|
598
|
+
`Used today: ${formatTokens(used)}. The current turn was allowed to finish; no new turns start. Messages are kept and delivered when it runs again.`,
|
|
599
|
+
],
|
|
600
|
+
options: [
|
|
601
|
+
`Boost +${compactTokens(extra)} tokens today — runs again until midnight`,
|
|
602
|
+
`${C.OPTIONS.UNLIMITED} — no cap until midnight`,
|
|
603
|
+
`${C.OPTIONS.KEEP} — resets at midnight`,
|
|
604
|
+
],
|
|
605
|
+
default: C.OPTIONS.KEEP,
|
|
606
|
+
deadline: this.nextMidnight(),
|
|
607
|
+
sensitive: 'spend',
|
|
608
|
+
});
|
|
609
|
+
this.file.day.cards[target] = d.id;
|
|
610
|
+
this.persist();
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
catch (err) {
|
|
614
|
+
this.logger?.warn('Could not post the token cap card — sending a notice instead', { target, error: err instanceof Error ? err.message : String(err) });
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
await this.notify(`${headline} Messages are queued. Reply \`boost ${dmWho} by ${compactTokens(extra)} today\` or \`unlimited today for ${dmWho}\`.`);
|
|
618
|
+
}
|
|
619
|
+
anyCap() {
|
|
620
|
+
const cfg = this.file.config;
|
|
621
|
+
return (isCap(cfg.defaultAgentCapTokens) ||
|
|
622
|
+
isCap(cfg.totalCapTokens) ||
|
|
623
|
+
Object.values(cfg.agentCapsTokens).some(isCap) ||
|
|
624
|
+
Object.values(cfg.teamCapsTokens).some(isCap));
|
|
625
|
+
}
|
|
626
|
+
computeStop(session) {
|
|
627
|
+
const b = this.boostForSession(session);
|
|
628
|
+
if (b.unlimited)
|
|
629
|
+
return null;
|
|
630
|
+
const cfg = this.file.config;
|
|
631
|
+
const own = this.ownCapOf(session).capTokens;
|
|
632
|
+
if (own !== null) {
|
|
633
|
+
const used = this.deps.ledger.usedToday(session);
|
|
634
|
+
if (used >= own + b.extra)
|
|
635
|
+
return { session, scope: 'agent', capTokens: own + b.extra, usedTokens: used };
|
|
636
|
+
}
|
|
637
|
+
for (const teamId of this.teamsOfSession.get(session) ?? []) {
|
|
638
|
+
const base = cfg.teamCapsTokens[teamId];
|
|
639
|
+
if (!isCap(base))
|
|
640
|
+
continue;
|
|
641
|
+
const team = this.teamList.find((t) => t.id === teamId);
|
|
642
|
+
const used = this.deps.ledger.groupToday(team?.members ?? [session]);
|
|
643
|
+
if (used >= base + b.extra)
|
|
644
|
+
return { session, scope: 'team', capTokens: base + b.extra, usedTokens: used, teamId, teamName: team?.name ?? teamId };
|
|
645
|
+
}
|
|
646
|
+
if (isCap(cfg.totalCapTokens)) {
|
|
647
|
+
const used = this.deps.ledger.totalToday();
|
|
648
|
+
if (used >= cfg.totalCapTokens + b.extra)
|
|
649
|
+
return { session, scope: 'total', capTokens: cfg.totalCapTokens + b.extra, usedTokens: used };
|
|
650
|
+
}
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
stopKey(stop) {
|
|
654
|
+
const target = stop.scope === 'total' ? C.TOTAL_TARGET : stop.scope === 'team' ? teamTarget(stop.teamId ?? '') : stop.session;
|
|
655
|
+
return `${target}@${stop.capTokens}`;
|
|
656
|
+
}
|
|
657
|
+
scheduleEvaluate() {
|
|
658
|
+
void this.evaluate();
|
|
659
|
+
}
|
|
660
|
+
activeBoostsRaw() {
|
|
661
|
+
const now = this.now().getTime();
|
|
662
|
+
return this.file.boosts.filter((b) => new Date(b.until).getTime() > now);
|
|
663
|
+
}
|
|
664
|
+
pruneBoosts() {
|
|
665
|
+
const live = this.activeBoostsRaw();
|
|
666
|
+
if (live.length !== this.file.boosts.length) {
|
|
667
|
+
const ended = this.file.boosts.filter((b) => !live.includes(b));
|
|
668
|
+
this.file.boosts = live;
|
|
669
|
+
this.logger?.info('Usage boosts ended', { boosts: ended.map((b) => ({ target: b.target, until: b.until })) });
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
async resolveBoostTarget(scope, id) {
|
|
673
|
+
if (scope === 'all')
|
|
674
|
+
return C.TOTAL_TARGET;
|
|
675
|
+
const raw = typeof id === 'string' ? id.trim() : '';
|
|
676
|
+
if (scope === 'team') {
|
|
677
|
+
if (!raw)
|
|
678
|
+
throw new SpendCapError(400, 'id is required for a team boost (team id or name)');
|
|
679
|
+
await this.refreshTeams();
|
|
680
|
+
const w = raw.toLowerCase();
|
|
681
|
+
const hit = this.teamList.find((t) => t.id === raw) ?? this.teamList.filter((t) => t.name.toLowerCase() === w).at(0);
|
|
682
|
+
if (!hit)
|
|
683
|
+
throw new SpendCapError(404, `No team called "${raw}"`);
|
|
684
|
+
return teamTarget(hit.id);
|
|
685
|
+
}
|
|
686
|
+
if (scope === 'agent') {
|
|
687
|
+
if (!raw)
|
|
688
|
+
throw new SpendCapError(400, 'id is required for an agent boost (session or name)');
|
|
689
|
+
await this.refreshTeams();
|
|
690
|
+
const known = new Set([...((await this.deps.knownSessions?.().catch(() => [])) ?? []), ...this.teamList.flatMap((t) => t.members)]);
|
|
691
|
+
if (known.has(raw))
|
|
692
|
+
return raw;
|
|
693
|
+
const byName = [...known].filter((s) => this.nameOf(s).toLowerCase() === raw.toLowerCase());
|
|
694
|
+
if (byName.length === 1)
|
|
695
|
+
return byName[0];
|
|
696
|
+
if (byName.length > 1)
|
|
697
|
+
throw new SpendCapError(400, `More than one agent is called "${raw}"; use its session name`);
|
|
698
|
+
return raw;
|
|
699
|
+
}
|
|
700
|
+
throw new SpendCapError(400, 'scope must be team, agent or all');
|
|
701
|
+
}
|
|
702
|
+
/** Reset today's bookkeeping when the local day changed. */
|
|
703
|
+
rollDay() {
|
|
704
|
+
const today = localDateKey(this.now());
|
|
705
|
+
if (this.file.day.date === today)
|
|
706
|
+
return;
|
|
707
|
+
const had = this.file.day.stopped.length;
|
|
708
|
+
this.file.day = emptyDay(today);
|
|
709
|
+
this.pruneBoosts();
|
|
710
|
+
this.deps.ledger.invalidate();
|
|
711
|
+
this.persist();
|
|
712
|
+
if (had > 0)
|
|
713
|
+
this.logger?.info('Token caps reset for the new day', { date: today });
|
|
714
|
+
}
|
|
715
|
+
nextMidnight() {
|
|
716
|
+
const next = localMidnight(this.now());
|
|
717
|
+
next.setDate(next.getDate() + 1);
|
|
718
|
+
// A card must have a future deadline even in the last minute of the day.
|
|
719
|
+
return next.getTime() - this.now().getTime() < 60_000 ? new Date(this.now().getTime() + 60_000) : next;
|
|
720
|
+
}
|
|
721
|
+
nameOf(target) {
|
|
722
|
+
if (target === C.TOTAL_TARGET)
|
|
723
|
+
return 'All agents';
|
|
724
|
+
try {
|
|
725
|
+
return this.deps.displayNameOf?.(target) || target;
|
|
726
|
+
}
|
|
727
|
+
catch {
|
|
728
|
+
return target;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
async notify(text) {
|
|
732
|
+
try {
|
|
733
|
+
await this.deps.notifyOwner(text);
|
|
734
|
+
}
|
|
735
|
+
catch (err) {
|
|
736
|
+
this.logger?.warn('Could not send the token cap notice', { error: err instanceof Error ? err.message : String(err) });
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
async reply(decisionId, text) {
|
|
740
|
+
await this.deps.decisions?.()?.replyInThread(decisionId, text).catch(() => undefined);
|
|
741
|
+
}
|
|
742
|
+
persist() {
|
|
743
|
+
try {
|
|
744
|
+
this.deps.store.write(this.file);
|
|
745
|
+
}
|
|
746
|
+
catch (err) {
|
|
747
|
+
this.logger?.warn('Could not save token caps', { error: err instanceof Error ? err.message : String(err) });
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
let instance = null;
|
|
752
|
+
/** @returns The backend's service, or null before it is wired */
|
|
753
|
+
export function getSpendCapService() {
|
|
754
|
+
return instance;
|
|
755
|
+
}
|
|
756
|
+
/** @param service - Instance (null clears) */
|
|
757
|
+
export function setSpendCapService(service) {
|
|
758
|
+
instance = service;
|
|
759
|
+
}
|
|
760
|
+
//# sourceMappingURL=spend-cap.service.js.map
|