crewly 1.20.186 → 1.20.187
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/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 +54 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +54 -0
- 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/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/spend.controller.d.ts +32 -0
- package/dist/backend/backend/src/controllers/system/spend.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/system/spend.controller.js +100 -0
- package/dist/backend/backend/src/controllers/system/spend.controller.js.map +1 -0
- 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/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.map +1 -1
- package/dist/backend/backend/src/index.js +59 -0
- 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 +5 -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 +11 -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 +83 -13
- package/dist/backend/backend/src/services/monitoring/activity-monitor.service.js.map +1 -1
- package/dist/backend/backend/src/services/monitoring/token-usage.service.d.ts +25 -0
- 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 +45 -1
- 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/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 +99 -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 +190 -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 +64 -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 +71 -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 +225 -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 +503 -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 +85 -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 +95 -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 +50 -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 +98 -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 +124 -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 +189 -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 +44 -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 +37 -0
- package/dist/backend/backend/src/services/task-pool/work-item-usage.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/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 +54 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +54 -0
- 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 +5 -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 +11 -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 +83 -13
- 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 +25 -0
- 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 +45 -1
- 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 +64 -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 +71 -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 +44 -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 +37 -0
- package/dist/cli/backend/src/services/task-pool/work-item-usage.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/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-1ee8a9a2.js → index-15c2faf2.js} +279 -279
- package/frontend/dist/assets/{index-a79c7852.css → index-20a67829.css} +1 -1
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spend caps from the owner's DM with the orc. Handled by the backend, not
|
|
3
|
+
* the orc — so it works while the orc itself is stopped by its cap:
|
|
4
|
+
*
|
|
5
|
+
* - `set daily cap for crewly-orc to $5` / `set daily cap for Ella to 3`
|
|
6
|
+
* - `set daily cap to $5` (the default for every agent)
|
|
7
|
+
* - `set daily total cap to $20` (all agents together)
|
|
8
|
+
* - `remove daily cap for Ella` / `remove daily cap` / `remove daily total cap`
|
|
9
|
+
* - `raise cap for orc to $10 today` / `raise total cap to $40 today`
|
|
10
|
+
*
|
|
11
|
+
* While the orc is stopped by a cap, every other owner DM to it gets one
|
|
12
|
+
* harness line saying so (the message itself stays queued for the orc).
|
|
13
|
+
*
|
|
14
|
+
* specs/2026-10-02-spend-cap.md
|
|
15
|
+
*
|
|
16
|
+
* @module services/spend/spend-cap-command
|
|
17
|
+
*/
|
|
18
|
+
import type { SlackIncomingMessage } from '../../types/slack.types.js';
|
|
19
|
+
import { type SpendStop } from './spend-cap.gate.js';
|
|
20
|
+
import { type SpendCapPatch } from './spend-cap.service.js';
|
|
21
|
+
/** A parsed command. */
|
|
22
|
+
export type SpendCapCommand = {
|
|
23
|
+
kind: 'set';
|
|
24
|
+
target: string | null;
|
|
25
|
+
usd: number;
|
|
26
|
+
} | {
|
|
27
|
+
kind: 'set_total';
|
|
28
|
+
usd: number;
|
|
29
|
+
} | {
|
|
30
|
+
kind: 'remove';
|
|
31
|
+
target: string | null;
|
|
32
|
+
} | {
|
|
33
|
+
kind: 'remove_total';
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'raise';
|
|
36
|
+
target: string;
|
|
37
|
+
usd: number;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Parse a spend cap command.
|
|
41
|
+
*
|
|
42
|
+
* @param text - Message text
|
|
43
|
+
* @returns The command, or null when the message is not one
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* parseSpendCapCommand('set daily cap for crewly-orc to $5') // { kind: 'set', target: 'crewly-orc', usd: 5 }
|
|
47
|
+
* parseSpendCapCommand('raise cap for orc to $10 today') // { kind: 'raise', target: 'orc', usd: 10 }
|
|
48
|
+
*/
|
|
49
|
+
export declare function parseSpendCapCommand(text: string | undefined): SpendCapCommand | null;
|
|
50
|
+
/** An agent the owner can name. */
|
|
51
|
+
export interface NamedAgent {
|
|
52
|
+
session: string;
|
|
53
|
+
name: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Resolve the agent the owner named.
|
|
57
|
+
*
|
|
58
|
+
* @param who - Session name, member name, or "orc" / "orchestrator"
|
|
59
|
+
* @param agents - Configured agents
|
|
60
|
+
* @returns Session, or null when unknown / ambiguous
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveAgent(who: string, agents: NamedAgent[]): string | null;
|
|
63
|
+
/** What the interceptor needs. */
|
|
64
|
+
export interface SpendCapCommandDeps {
|
|
65
|
+
/** `orc` when the message is the owner writing in their DM with the orc */
|
|
66
|
+
ownerDmScope: (message: SlackIncomingMessage) => 'orc' | 'agent' | null;
|
|
67
|
+
replyTargetOf: (message: SlackIncomingMessage) => unknown;
|
|
68
|
+
reply: (text: string, target: unknown) => Promise<unknown>;
|
|
69
|
+
/** Configured agents (orc included) */
|
|
70
|
+
agents: () => Promise<NamedAgent[]>;
|
|
71
|
+
setCaps: (patch: SpendCapPatch) => Promise<unknown>;
|
|
72
|
+
raiseToday: (target: string, usd: number) => Promise<number>;
|
|
73
|
+
/** The orc's stop, or null */
|
|
74
|
+
orcStop: () => SpendStop | null;
|
|
75
|
+
onError?: (err: unknown) => void;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The orc's harness line while it is stopped by a cap.
|
|
79
|
+
*
|
|
80
|
+
* @param stop - The orc's stop
|
|
81
|
+
* @returns One line
|
|
82
|
+
*/
|
|
83
|
+
export declare function orcCappedReply(stop: SpendStop): string;
|
|
84
|
+
/**
|
|
85
|
+
* Run a parsed command.
|
|
86
|
+
*
|
|
87
|
+
* @param cmd - Command
|
|
88
|
+
* @param deps - Collaborators
|
|
89
|
+
* @returns The owner's one-line answer
|
|
90
|
+
*/
|
|
91
|
+
export declare function runSpendCapCommand(cmd: SpendCapCommand, deps: Pick<SpendCapCommandDeps, 'agents' | 'setCaps' | 'raiseToday'>): Promise<string>;
|
|
92
|
+
/**
|
|
93
|
+
* The Slack bridge interceptor for the owner's orc DM.
|
|
94
|
+
*
|
|
95
|
+
* @param deps - Collaborators
|
|
96
|
+
* @returns Interceptor: true when the message was a cap command (consumed)
|
|
97
|
+
*/
|
|
98
|
+
export declare function createSpendCapInterceptor(deps: SpendCapCommandDeps): (message: SlackIncomingMessage) => boolean;
|
|
99
|
+
//# sourceMappingURL=spend-cap-command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spend-cap-command.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/spend/spend-cap-command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChF,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE5E,wBAAwB;AACxB,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,GACxB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAsBnD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,eAAe,GAAG,IAAI,CAmBrF;AAED,mCAAmC;AACnC,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,GAAG,IAAI,CAO7E;AAED,kCAAkC;AAClC,MAAM,WAAW,mBAAmB;IAClC,2EAA2E;IAC3E,YAAY,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,KAAK,GAAG,OAAO,GAAG,IAAI,CAAC;IACxE,aAAa,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC;IAC1D,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3D,uCAAuC;IACvC,MAAM,EAAE,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACpC,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACpD,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7D,8BAA8B;IAC9B,OAAO,EAAE,MAAM,SAAS,GAAG,IAAI,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;CAClC;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAItD;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,IAAI,CAAC,mBAAmB,EAAE,QAAQ,GAAG,SAAS,GAAG,YAAY,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CA8CpJ;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,mBAAmB,GAAG,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAqB/G"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spend caps from the owner's DM with the orc. Handled by the backend, not
|
|
3
|
+
* the orc — so it works while the orc itself is stopped by its cap:
|
|
4
|
+
*
|
|
5
|
+
* - `set daily cap for crewly-orc to $5` / `set daily cap for Ella to 3`
|
|
6
|
+
* - `set daily cap to $5` (the default for every agent)
|
|
7
|
+
* - `set daily total cap to $20` (all agents together)
|
|
8
|
+
* - `remove daily cap for Ella` / `remove daily cap` / `remove daily total cap`
|
|
9
|
+
* - `raise cap for orc to $10 today` / `raise total cap to $40 today`
|
|
10
|
+
*
|
|
11
|
+
* While the orc is stopped by a cap, every other owner DM to it gets one
|
|
12
|
+
* harness line saying so (the message itself stays queued for the orc).
|
|
13
|
+
*
|
|
14
|
+
* specs/2026-10-02-spend-cap.md
|
|
15
|
+
*
|
|
16
|
+
* @module services/spend/spend-cap-command
|
|
17
|
+
*/
|
|
18
|
+
import { ORCHESTRATOR_SESSION_NAME, SPEND_CAP_CONSTANTS as C } from '../../constants.js';
|
|
19
|
+
import { formatUsd, spendCapReason } from './spend-cap.gate.js';
|
|
20
|
+
import { suggestedRaise } from './spend-cap.service.js';
|
|
21
|
+
const AMOUNT = String.raw `\$?\s*(\d+(?:\.\d{1,2})?)\s*(?:usd|dollars?)?`;
|
|
22
|
+
const CAP = String.raw `(?:daily\s+)?(?:spend(?:ing)?\s+)?cap`;
|
|
23
|
+
/**
|
|
24
|
+
* Normalise a message for matching.
|
|
25
|
+
*
|
|
26
|
+
* @param text - Raw text
|
|
27
|
+
* @returns Lower-cased, mentions and trailing punctuation removed
|
|
28
|
+
*/
|
|
29
|
+
function norm(text) {
|
|
30
|
+
return text
|
|
31
|
+
.replace(/<@[A-Z0-9]+>/g, '')
|
|
32
|
+
.replace(/[`*_]/g, '')
|
|
33
|
+
.toLowerCase()
|
|
34
|
+
.replace(/[\s.。!!~~]+$/u, '')
|
|
35
|
+
.replace(/^(?:please|pls)\s+/, '')
|
|
36
|
+
.replace(/\s+/g, ' ')
|
|
37
|
+
.trim();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Parse a spend cap command.
|
|
41
|
+
*
|
|
42
|
+
* @param text - Message text
|
|
43
|
+
* @returns The command, or null when the message is not one
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* parseSpendCapCommand('set daily cap for crewly-orc to $5') // { kind: 'set', target: 'crewly-orc', usd: 5 }
|
|
47
|
+
* parseSpendCapCommand('raise cap for orc to $10 today') // { kind: 'raise', target: 'orc', usd: 10 }
|
|
48
|
+
*/
|
|
49
|
+
export function parseSpendCapCommand(text) {
|
|
50
|
+
if (!text)
|
|
51
|
+
return null;
|
|
52
|
+
const t = norm(text);
|
|
53
|
+
let m = new RegExp(String.raw `^set (?:the )?(?:daily total|total daily|total)(?: spend(?:ing)?)? cap to ${AMOUNT}$`).exec(t);
|
|
54
|
+
if (m)
|
|
55
|
+
return { kind: 'set_total', usd: Number(m[1]) };
|
|
56
|
+
m = new RegExp(String.raw `^set (?:the )?${CAP}(?: for (.+?))? to ${AMOUNT}(?: (?:a|per) day)?$`).exec(t);
|
|
57
|
+
if (m)
|
|
58
|
+
return { kind: 'set', target: m[1]?.trim() || null, usd: Number(m[2]) };
|
|
59
|
+
m = /^(?:remove|clear|turn off|disable) (?:the )?(?:daily total|total daily|total)(?: spend(?:ing)?)? cap$/.exec(t);
|
|
60
|
+
if (m)
|
|
61
|
+
return { kind: 'remove_total' };
|
|
62
|
+
m = new RegExp(String.raw `^(?:remove|clear|turn off|disable) (?:the )?${CAP}(?: for (.+))?$`).exec(t);
|
|
63
|
+
if (m)
|
|
64
|
+
return { kind: 'remove', target: m[1]?.trim() || null };
|
|
65
|
+
m = new RegExp(String.raw `^raise (?:the )?(?:daily total|total daily|total)(?: spend(?:ing)?)? cap to ${AMOUNT}(?: (?:for )?today)?$`).exec(t);
|
|
66
|
+
if (m)
|
|
67
|
+
return { kind: 'raise', target: C.TOTAL_TARGET, usd: Number(m[1]) };
|
|
68
|
+
m = new RegExp(String.raw `^raise (?:the )?${CAP} for (.+?) to ${AMOUNT}(?: (?:for )?today)?$`).exec(t);
|
|
69
|
+
if (m) {
|
|
70
|
+
const target = m[1].trim();
|
|
71
|
+
return { kind: 'raise', target: ['total', 'all', 'all agents'].includes(target) ? C.TOTAL_TARGET : target, usd: Number(m[2]) };
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Resolve the agent the owner named.
|
|
77
|
+
*
|
|
78
|
+
* @param who - Session name, member name, or "orc" / "orchestrator"
|
|
79
|
+
* @param agents - Configured agents
|
|
80
|
+
* @returns Session, or null when unknown / ambiguous
|
|
81
|
+
*/
|
|
82
|
+
export function resolveAgent(who, agents) {
|
|
83
|
+
const w = who.trim().toLowerCase().replace(/^@/, '');
|
|
84
|
+
if (['orc', 'the orc', 'orchestrator', 'the orchestrator', 'crewly orc', ORCHESTRATOR_SESSION_NAME].includes(w))
|
|
85
|
+
return ORCHESTRATOR_SESSION_NAME;
|
|
86
|
+
const bySession = agents.find((a) => a.session.toLowerCase() === w);
|
|
87
|
+
if (bySession)
|
|
88
|
+
return bySession.session;
|
|
89
|
+
const byName = agents.filter((a) => a.name.toLowerCase() === w);
|
|
90
|
+
return byName.length === 1 ? byName[0].session : null;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The orc's harness line while it is stopped by a cap.
|
|
94
|
+
*
|
|
95
|
+
* @param stop - The orc's stop
|
|
96
|
+
* @returns One line
|
|
97
|
+
*/
|
|
98
|
+
export function orcCappedReply(stop) {
|
|
99
|
+
const raise = suggestedRaise(stop.capUsd, stop.spentUsd);
|
|
100
|
+
const target = stop.scope === 'total' ? 'total' : 'orc';
|
|
101
|
+
return `${spendCapReason(stop, 'Orc')}. No new turns start until midnight; your message is queued. To raise it for today, reply \`raise cap for ${target} to $${raise} today\` (or Settings → System → Spend).`;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Run a parsed command.
|
|
105
|
+
*
|
|
106
|
+
* @param cmd - Command
|
|
107
|
+
* @param deps - Collaborators
|
|
108
|
+
* @returns The owner's one-line answer
|
|
109
|
+
*/
|
|
110
|
+
export async function runSpendCapCommand(cmd, deps) {
|
|
111
|
+
const agents = await deps.agents();
|
|
112
|
+
const who = (target) => {
|
|
113
|
+
const session = resolveAgent(target, agents);
|
|
114
|
+
if (!session)
|
|
115
|
+
return `I don't know an agent called "${target}". Use its name or session name (e.g. crewly-orc).`;
|
|
116
|
+
const name = session === ORCHESTRATOR_SESSION_NAME ? 'Orc' : agents.find((a) => a.session === session)?.name ?? session;
|
|
117
|
+
return { session, name };
|
|
118
|
+
};
|
|
119
|
+
switch (cmd.kind) {
|
|
120
|
+
case 'set_total':
|
|
121
|
+
await deps.setCaps({ totalCapUsd: cmd.usd });
|
|
122
|
+
return `Daily total spend cap set to ${formatUsd(cmd.usd)} for all agents together.`;
|
|
123
|
+
case 'remove_total':
|
|
124
|
+
await deps.setCaps({ totalCapUsd: null });
|
|
125
|
+
return 'Daily total spend cap removed.';
|
|
126
|
+
case 'set': {
|
|
127
|
+
if (!cmd.target) {
|
|
128
|
+
await deps.setCaps({ defaultAgentCapUsd: cmd.usd });
|
|
129
|
+
return `Daily spend cap set to ${formatUsd(cmd.usd)} per agent (agents with their own cap keep it).`;
|
|
130
|
+
}
|
|
131
|
+
const a = who(cmd.target);
|
|
132
|
+
if (typeof a === 'string')
|
|
133
|
+
return a;
|
|
134
|
+
await deps.setCaps({ agents: { [a.session]: cmd.usd } });
|
|
135
|
+
return `Daily spend cap for ${a.name} set to ${formatUsd(cmd.usd)}.`;
|
|
136
|
+
}
|
|
137
|
+
case 'remove': {
|
|
138
|
+
if (!cmd.target) {
|
|
139
|
+
await deps.setCaps({ defaultAgentCapUsd: null });
|
|
140
|
+
return 'Default daily spend cap removed (agents with their own cap keep it).';
|
|
141
|
+
}
|
|
142
|
+
const a = who(cmd.target);
|
|
143
|
+
if (typeof a === 'string')
|
|
144
|
+
return a;
|
|
145
|
+
await deps.setCaps({ agents: { [a.session]: null } });
|
|
146
|
+
return `${a.name} has no daily spend cap now.`;
|
|
147
|
+
}
|
|
148
|
+
case 'raise': {
|
|
149
|
+
if (cmd.target === C.TOTAL_TARGET) {
|
|
150
|
+
const cap = await deps.raiseToday(C.TOTAL_TARGET, cmd.usd);
|
|
151
|
+
return `Daily total spend cap raised to ${formatUsd(cap)} for today.`;
|
|
152
|
+
}
|
|
153
|
+
const a = who(cmd.target);
|
|
154
|
+
if (typeof a === 'string')
|
|
155
|
+
return a;
|
|
156
|
+
const cap = await deps.raiseToday(a.session, cmd.usd);
|
|
157
|
+
return `${a.name}'s daily spend cap raised to ${formatUsd(cap)} for today. Queued messages are being delivered.`;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The Slack bridge interceptor for the owner's orc DM.
|
|
163
|
+
*
|
|
164
|
+
* @param deps - Collaborators
|
|
165
|
+
* @returns Interceptor: true when the message was a cap command (consumed)
|
|
166
|
+
*/
|
|
167
|
+
export function createSpendCapInterceptor(deps) {
|
|
168
|
+
return (message) => {
|
|
169
|
+
if (deps.ownerDmScope(message) !== 'orc')
|
|
170
|
+
return false;
|
|
171
|
+
const target = deps.replyTargetOf(message);
|
|
172
|
+
const cmd = message.hasFiles ? null : parseSpendCapCommand(message.text);
|
|
173
|
+
if (cmd) {
|
|
174
|
+
void runSpendCapCommand(cmd, deps)
|
|
175
|
+
.then((text) => deps.reply(text, target))
|
|
176
|
+
.catch((err) => {
|
|
177
|
+
deps.onError?.(err);
|
|
178
|
+
return deps.reply(`Couldn't change the cap: ${err instanceof Error ? err.message : String(err)}`, target).catch(() => undefined);
|
|
179
|
+
});
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
const stop = deps.orcStop();
|
|
183
|
+
if (stop) {
|
|
184
|
+
// Not consumed: the message goes on to the orc's queue and waits there.
|
|
185
|
+
void deps.reply(orcCappedReply(stop), target).catch((err) => deps.onError?.(err));
|
|
186
|
+
}
|
|
187
|
+
return false;
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=spend-cap-command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spend-cap-command.js","sourceRoot":"","sources":["../../../../../../backend/src/services/spend/spend-cap-command.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spend cap gate — the process-wide hook the delivery and wake paths ask
|
|
3
|
+
* "may this agent start a new turn?". Kept apart from the service so the
|
|
4
|
+
* hot paths (agent registration, the orc queue processor, the owner-message
|
|
5
|
+
* watchdog) import a tiny module with no dependencies of its own.
|
|
6
|
+
*
|
|
7
|
+
* specs/2026-10-02-spend-cap.md
|
|
8
|
+
*
|
|
9
|
+
* @module services/spend/spend-cap.gate
|
|
10
|
+
*/
|
|
11
|
+
/** Why an agent may not start a new turn today. */
|
|
12
|
+
export interface SpendStop {
|
|
13
|
+
session: string;
|
|
14
|
+
/** `agent`: its own cap; `total`: the all-agents daily total cap */
|
|
15
|
+
scope: 'agent' | 'total';
|
|
16
|
+
/** The cap in force (USD) */
|
|
17
|
+
capUsd: number;
|
|
18
|
+
/** What was spent today against it (USD) */
|
|
19
|
+
spentUsd: number;
|
|
20
|
+
}
|
|
21
|
+
/** What the service exposes to the gate. */
|
|
22
|
+
export interface SpendCapGate {
|
|
23
|
+
/** The stop in force for a session, or null when it may run */
|
|
24
|
+
stopOf(session: string): SpendStop | null;
|
|
25
|
+
/** Owner-facing name of a session ("Orc", "Ella") */
|
|
26
|
+
displayNameOf?(session: string): string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Install (or clear) the gate.
|
|
30
|
+
*
|
|
31
|
+
* @param next - Gate, or null
|
|
32
|
+
*/
|
|
33
|
+
export declare function setSpendCapGate(next: SpendCapGate | null): void;
|
|
34
|
+
/**
|
|
35
|
+
* The stop in force for a session. Never throws: a broken gate lets the
|
|
36
|
+
* agent run rather than silently dropping its work.
|
|
37
|
+
*
|
|
38
|
+
* @param session - Agent session
|
|
39
|
+
* @returns Stop, or null
|
|
40
|
+
*/
|
|
41
|
+
export declare function spendCapStopOf(session: string): SpendStop | null;
|
|
42
|
+
/**
|
|
43
|
+
* Owner-facing name of a session.
|
|
44
|
+
*
|
|
45
|
+
* @param session - Agent session
|
|
46
|
+
* @returns Name (the session name when unknown)
|
|
47
|
+
*/
|
|
48
|
+
export declare function spendCapNameOf(session: string): string;
|
|
49
|
+
/**
|
|
50
|
+
* USD for owner text.
|
|
51
|
+
*
|
|
52
|
+
* @param usd - Amount
|
|
53
|
+
* @returns e.g. `$5.00`
|
|
54
|
+
*/
|
|
55
|
+
export declare function formatUsd(usd: number): string;
|
|
56
|
+
/**
|
|
57
|
+
* The one-line reason, e.g. "Orc hit its daily spend cap ($5.00)".
|
|
58
|
+
*
|
|
59
|
+
* @param stop - The stop
|
|
60
|
+
* @param name - Owner-facing name (default: looked up)
|
|
61
|
+
* @returns Reason text
|
|
62
|
+
*/
|
|
63
|
+
export declare function spendCapReason(stop: SpendStop, name?: string): string;
|
|
64
|
+
//# sourceMappingURL=spend-cap.gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spend-cap.gate.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/spend/spend-cap.gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,mDAAmD;AACnD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IACzB,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,4CAA4C;AAC5C,MAAM,WAAW,YAAY;IAC3B,+DAA+D;IAC/D,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;IAC1C,qDAAqD;IACrD,aAAa,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;CACzC;AAID;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI,CAE/D;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAMhE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMtD;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,GAAE,MAAqC,GAAG,MAAM,CAKnG"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spend cap gate — the process-wide hook the delivery and wake paths ask
|
|
3
|
+
* "may this agent start a new turn?". Kept apart from the service so the
|
|
4
|
+
* hot paths (agent registration, the orc queue processor, the owner-message
|
|
5
|
+
* watchdog) import a tiny module with no dependencies of its own.
|
|
6
|
+
*
|
|
7
|
+
* specs/2026-10-02-spend-cap.md
|
|
8
|
+
*
|
|
9
|
+
* @module services/spend/spend-cap.gate
|
|
10
|
+
*/
|
|
11
|
+
let gate = null;
|
|
12
|
+
/**
|
|
13
|
+
* Install (or clear) the gate.
|
|
14
|
+
*
|
|
15
|
+
* @param next - Gate, or null
|
|
16
|
+
*/
|
|
17
|
+
export function setSpendCapGate(next) {
|
|
18
|
+
gate = next;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The stop in force for a session. Never throws: a broken gate lets the
|
|
22
|
+
* agent run rather than silently dropping its work.
|
|
23
|
+
*
|
|
24
|
+
* @param session - Agent session
|
|
25
|
+
* @returns Stop, or null
|
|
26
|
+
*/
|
|
27
|
+
export function spendCapStopOf(session) {
|
|
28
|
+
try {
|
|
29
|
+
return gate?.stopOf(session) ?? null;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Owner-facing name of a session.
|
|
37
|
+
*
|
|
38
|
+
* @param session - Agent session
|
|
39
|
+
* @returns Name (the session name when unknown)
|
|
40
|
+
*/
|
|
41
|
+
export function spendCapNameOf(session) {
|
|
42
|
+
try {
|
|
43
|
+
return gate?.displayNameOf?.(session) || session;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return session;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* USD for owner text.
|
|
51
|
+
*
|
|
52
|
+
* @param usd - Amount
|
|
53
|
+
* @returns e.g. `$5.00`
|
|
54
|
+
*/
|
|
55
|
+
export function formatUsd(usd) {
|
|
56
|
+
return `$${(Math.round(usd * 100) / 100).toFixed(2)}`;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The one-line reason, e.g. "Orc hit its daily spend cap ($5.00)".
|
|
60
|
+
*
|
|
61
|
+
* @param stop - The stop
|
|
62
|
+
* @param name - Owner-facing name (default: looked up)
|
|
63
|
+
* @returns Reason text
|
|
64
|
+
*/
|
|
65
|
+
export function spendCapReason(stop, name = spendCapNameOf(stop.session)) {
|
|
66
|
+
if (stop.scope === 'total') {
|
|
67
|
+
return `${name} is stopped: all agents together hit the daily total spend cap (${formatUsd(stop.capUsd)})`;
|
|
68
|
+
}
|
|
69
|
+
return `${name} hit its daily spend cap (${formatUsd(stop.capUsd)})`;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=spend-cap.gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spend-cap.gate.js","sourceRoot":"","sources":["../../../../../../backend/src/services/spend/spend-cap.gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAqBH,IAAI,IAAI,GAAwB,IAAI,CAAC;AAErC;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,IAAyB;IACvD,IAAI,GAAG,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,IAAI,CAAC;QACH,OAAO,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,IAAI,CAAC;QACH,OAAO,IAAI,EAAE,aAAa,EAAE,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;AACxD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,IAAe,EAAE,OAAe,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC;IACzF,IAAI,IAAI,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC;QAC3B,OAAO,GAAG,IAAI,mEAAmE,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC;IAC7G,CAAC;IACD,OAAO,GAAG,IAAI,6BAA6B,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC;AACvE,CAAC"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-agent daily spend cap with a hard stop.
|
|
3
|
+
*
|
|
4
|
+
* - Caps are the owner's own limits (unlike the runtime fallback, which
|
|
5
|
+
* reacts to a provider's usage limit). All are OFF until the owner sets
|
|
6
|
+
* one: a default per-agent cap, per-agent overrides, and an optional
|
|
7
|
+
* all-agents daily total.
|
|
8
|
+
* - At {@link SPEND_CAP_CONSTANTS.WARN_FRACTION} of a cap the owner gets ONE
|
|
9
|
+
* heads-up.
|
|
10
|
+
* - At 100% the agent is hard-stopped: {@link stopOf} answers the delivery
|
|
11
|
+
* and wake gates, so no new turn starts (the current one may finish) and
|
|
12
|
+
* messages stay queued. The owner gets ONE decision card: "Raise to $Y
|
|
13
|
+
* today" / "Keep stopped".
|
|
14
|
+
* - Local midnight resets everything; raises last for the day they were
|
|
15
|
+
* made. When a stop lifts (midnight, raise, cap change) the queued
|
|
16
|
+
* messages are released.
|
|
17
|
+
*
|
|
18
|
+
* Spend comes from {@link SpendLedger} (the shared token ledger and its one
|
|
19
|
+
* cost computation).
|
|
20
|
+
*
|
|
21
|
+
* specs/2026-10-02-spend-cap.md
|
|
22
|
+
*
|
|
23
|
+
* @module services/spend/spend-cap.service
|
|
24
|
+
*/
|
|
25
|
+
import type { OwnerDecision } from '../../types/decision.types.js';
|
|
26
|
+
import type { SystemAskInput } from '../decisions/decision.service.js';
|
|
27
|
+
import { type SpendCapGate, type SpendStop } from './spend-cap.gate.js';
|
|
28
|
+
import { type SpendLedger, type SpendSummary } from './spend-ledger.service.js';
|
|
29
|
+
import { type SpendCapConfig, type SpendCapStoreLike } from './spend-cap.store.js';
|
|
30
|
+
/** Minimal logger. */
|
|
31
|
+
export interface SpendCapLogger {
|
|
32
|
+
info(message: string, meta?: Record<string, unknown>): void;
|
|
33
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
34
|
+
}
|
|
35
|
+
/** The decision-card calls the service makes. */
|
|
36
|
+
export interface SpendCapDecisions {
|
|
37
|
+
askSystem(input: SystemAskInput): Promise<OwnerDecision>;
|
|
38
|
+
replyInThread(id: string, text: string): Promise<boolean>;
|
|
39
|
+
}
|
|
40
|
+
/** Collaborators. */
|
|
41
|
+
export interface SpendCapServiceDeps {
|
|
42
|
+
store: SpendCapStoreLike;
|
|
43
|
+
ledger: Pick<SpendLedger, 'spentToday' | 'totalToday' | 'summarize' | 'invalidate'>;
|
|
44
|
+
/** One line to the owner (Slack DM from this machine's orc bot); false/throw = not sent */
|
|
45
|
+
notifyOwner: (text: string) => Promise<unknown>;
|
|
46
|
+
/** Decision cards (null before they are wired: the stop is then a plain notice) */
|
|
47
|
+
decisions?: () => SpendCapDecisions | null;
|
|
48
|
+
/** Owner-facing name of a session ("Orc", "Ella") */
|
|
49
|
+
displayNameOf?: (session: string) => string;
|
|
50
|
+
/** Sessions the owner configured (orc + team members), for the caps view */
|
|
51
|
+
knownSessions?: () => Promise<string[]>;
|
|
52
|
+
/** Stops lifted: deliver what was queued for these sessions */
|
|
53
|
+
onReleased?: (sessions: string[]) => Promise<void>;
|
|
54
|
+
now?: () => Date;
|
|
55
|
+
logger?: SpendCapLogger;
|
|
56
|
+
}
|
|
57
|
+
/** Body of `PUT /api/system/spend/caps`. */
|
|
58
|
+
export interface SpendCapPatch {
|
|
59
|
+
/** Default per-agent cap (USD); null turns it off */
|
|
60
|
+
defaultAgentCapUsd?: number | null;
|
|
61
|
+
/** All-agents total cap (USD); null turns it off */
|
|
62
|
+
totalCapUsd?: number | null;
|
|
63
|
+
/** Session → cap (USD), `null` = no cap for this agent, `"default"` = drop the override */
|
|
64
|
+
agents?: Record<string, number | null | 'default'>;
|
|
65
|
+
}
|
|
66
|
+
/** A rejected cap change. */
|
|
67
|
+
export declare class SpendCapError extends Error {
|
|
68
|
+
readonly status: number;
|
|
69
|
+
/**
|
|
70
|
+
* @param status - HTTP status
|
|
71
|
+
* @param message - Owner-facing reason
|
|
72
|
+
*/
|
|
73
|
+
constructor(status: number, message: string);
|
|
74
|
+
}
|
|
75
|
+
/** One agent in the view. */
|
|
76
|
+
export interface SpendCapAgentView {
|
|
77
|
+
session: string;
|
|
78
|
+
name: string;
|
|
79
|
+
runtimes: string[];
|
|
80
|
+
todayUsd: number;
|
|
81
|
+
windowUsd: number;
|
|
82
|
+
daily: number[];
|
|
83
|
+
/** Cap in force today (USD), null = none */
|
|
84
|
+
capUsd: number | null;
|
|
85
|
+
/** Where the cap comes from */
|
|
86
|
+
capSource: 'override' | 'default' | 'raised' | 'none' | 'exempt';
|
|
87
|
+
/** Stopped right now, and why */
|
|
88
|
+
stopped: boolean;
|
|
89
|
+
stopReason?: string;
|
|
90
|
+
}
|
|
91
|
+
/** What `GET /api/system/spend` returns. */
|
|
92
|
+
export interface SpendView extends Omit<SpendSummary, 'agents'> {
|
|
93
|
+
agents: SpendCapAgentView[];
|
|
94
|
+
caps: SpendCapConfig;
|
|
95
|
+
/** Raises in force today: target → USD */
|
|
96
|
+
raisedToday: Record<string, number>;
|
|
97
|
+
/** Total cap in force today (raise included), null = off */
|
|
98
|
+
totalCapTodayUsd: number | null;
|
|
99
|
+
/** Suggested default per-agent cap: last window's p90 agent-day, rounded up to whole dollars (null = no data) */
|
|
100
|
+
suggestedAgentCapUsd: number | null;
|
|
101
|
+
/** All agents together stopped by the total cap */
|
|
102
|
+
totalStopped: boolean;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The "Raise to $Y today" amount: the cap times RAISE_FACTOR, rounded up to
|
|
106
|
+
* whole dollars, and always above what was already spent.
|
|
107
|
+
*
|
|
108
|
+
* @param capUsd - Cap in force
|
|
109
|
+
* @param spentUsd - Spent today
|
|
110
|
+
* @returns USD
|
|
111
|
+
*/
|
|
112
|
+
export declare function suggestedRaise(capUsd: number, spentUsd: number): number;
|
|
113
|
+
/**
|
|
114
|
+
* Read the amount out of a "Raise to $Y today" option label.
|
|
115
|
+
*
|
|
116
|
+
* @param label - Option label
|
|
117
|
+
* @returns USD, or null when the label is not a raise
|
|
118
|
+
*/
|
|
119
|
+
export declare function raiseAmountOf(label: string | undefined): number | null;
|
|
120
|
+
/**
|
|
121
|
+
* Per-agent daily spend cap service.
|
|
122
|
+
*/
|
|
123
|
+
export declare class SpendCapService implements SpendCapGate {
|
|
124
|
+
private readonly deps;
|
|
125
|
+
private file;
|
|
126
|
+
private readonly now;
|
|
127
|
+
private readonly logger;
|
|
128
|
+
private timer;
|
|
129
|
+
/** Sessions stopped at the last evaluation (to release them when the stop lifts) */
|
|
130
|
+
private lastStopped;
|
|
131
|
+
/** Evaluation passes run one after another */
|
|
132
|
+
private chain;
|
|
133
|
+
private queuedPass;
|
|
134
|
+
/**
|
|
135
|
+
* @param deps - Collaborators
|
|
136
|
+
*/
|
|
137
|
+
constructor(deps: SpendCapServiceDeps);
|
|
138
|
+
/** Start the enforcement tick. */
|
|
139
|
+
start(): void;
|
|
140
|
+
/** Stop the tick. */
|
|
141
|
+
stop(): void;
|
|
142
|
+
/** @returns The owner's caps (a copy) */
|
|
143
|
+
getConfig(): SpendCapConfig;
|
|
144
|
+
/**
|
|
145
|
+
* Change caps. Takes effect at once (a stop can lift or start).
|
|
146
|
+
*
|
|
147
|
+
* @param patch - Changes
|
|
148
|
+
* @returns The new caps
|
|
149
|
+
* @throws SpendCapError on a bad value
|
|
150
|
+
*/
|
|
151
|
+
setCaps(patch: SpendCapPatch): Promise<SpendCapConfig>;
|
|
152
|
+
/**
|
|
153
|
+
* Lift a cap for today only.
|
|
154
|
+
*
|
|
155
|
+
* @param target - Agent session, or `*` for the all-agents total
|
|
156
|
+
* @param usd - New cap for today (must be above today's spend)
|
|
157
|
+
* @returns The new cap for today
|
|
158
|
+
* @throws SpendCapError when the amount is not above today's spend
|
|
159
|
+
*/
|
|
160
|
+
raiseToday(target: string, usd: unknown): Promise<number>;
|
|
161
|
+
/**
|
|
162
|
+
* The stop in force for a session (the delivery / wake gate). Synchronous
|
|
163
|
+
* and live: it reads today's spend now, not at the last tick.
|
|
164
|
+
*
|
|
165
|
+
* @param session - Agent session
|
|
166
|
+
* @returns Stop, or null when the agent may run
|
|
167
|
+
*/
|
|
168
|
+
stopOf(session: string): SpendStop | null;
|
|
169
|
+
/**
|
|
170
|
+
* Owner-facing name.
|
|
171
|
+
*
|
|
172
|
+
* @param session - Agent session
|
|
173
|
+
* @returns Name
|
|
174
|
+
*/
|
|
175
|
+
displayNameOf(session: string): string;
|
|
176
|
+
/**
|
|
177
|
+
* Cap in force today for a target, and where it comes from.
|
|
178
|
+
*
|
|
179
|
+
* @param target - Agent session, or `*`
|
|
180
|
+
* @returns Cap (null = none) and source
|
|
181
|
+
*/
|
|
182
|
+
capOf(target: string): {
|
|
183
|
+
capUsd: number | null;
|
|
184
|
+
source: SpendCapAgentView['capSource'];
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* Spend plus caps for the API / Settings panel.
|
|
188
|
+
*
|
|
189
|
+
* @param days - Window
|
|
190
|
+
* @returns View
|
|
191
|
+
*/
|
|
192
|
+
view(days?: number): Promise<SpendView>;
|
|
193
|
+
/**
|
|
194
|
+
* One enforcement pass: day roll, 80% notices, stop announcements (one
|
|
195
|
+
* card per stop), and releasing agents whose stop lifted. Passes run
|
|
196
|
+
* one at a time; a call during a pass gets another pass after it.
|
|
197
|
+
*/
|
|
198
|
+
evaluate(): Promise<void>;
|
|
199
|
+
/**
|
|
200
|
+
* React to a settled "cap reached" card (decision kind `spend_cap`).
|
|
201
|
+
*
|
|
202
|
+
* @param d - The settled decision
|
|
203
|
+
* @returns Always null (nobody else is told)
|
|
204
|
+
*/
|
|
205
|
+
onSettled(d: OwnerDecision): Promise<null>;
|
|
206
|
+
private evaluateOnce;
|
|
207
|
+
private checkThresholds;
|
|
208
|
+
private announceStop;
|
|
209
|
+
private anyCap;
|
|
210
|
+
private computeStop;
|
|
211
|
+
private stopKey;
|
|
212
|
+
private scheduleEvaluate;
|
|
213
|
+
/** Reset today's bookkeeping when the local day changed. */
|
|
214
|
+
private rollDay;
|
|
215
|
+
private nextMidnight;
|
|
216
|
+
private nameOf;
|
|
217
|
+
private notify;
|
|
218
|
+
private reply;
|
|
219
|
+
private persist;
|
|
220
|
+
}
|
|
221
|
+
/** @returns The backend's service, or null before it is wired */
|
|
222
|
+
export declare function getSpendCapService(): SpendCapService | null;
|
|
223
|
+
/** @param service - Instance (null clears) */
|
|
224
|
+
export declare function setSpendCapService(service: SpendCapService | null): void;
|
|
225
|
+
//# sourceMappingURL=spend-cap.service.d.ts.map
|