crewly 1.20.302 → 1.20.304
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/roles/developer/prompt.md +7 -0
- package/config/roles/fullstack-dev/prompt.md +7 -0
- package/config/roles/product-manager/prompt.md +7 -0
- package/config/skills/orchestrator/move-team/SKILL.md +53 -0
- package/config/skills/orchestrator/move-team/execute.sh +42 -0
- package/config/skills/orchestrator/usage-categories/SKILL.md +59 -0
- package/config/skills/orchestrator/usage-categories/execute.sh +81 -0
- package/config/skills/orchestrator/usage-categories/execute.test.sh +45 -0
- package/config/skills/orchestrator/usage-report/SKILL.md +71 -0
- package/config/skills/orchestrator/usage-report/execute.sh +84 -0
- package/config/skills/orchestrator/usage-report/execute.test.sh +39 -0
- package/config/skills/team-leader/usage-report/SKILL.md +71 -0
- package/config/skills/team-leader/usage-report/execute.sh +84 -0
- package/config/skills/team-leader/usage-report/execute.test.sh +39 -0
- package/dist/backend/backend/src/constants.d.ts +80 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +80 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/team-move/team-move.controller.d.ts +45 -0
- package/dist/backend/backend/src/controllers/team-move/team-move.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/team-move/team-move.controller.js +214 -0
- package/dist/backend/backend/src/controllers/team-move/team-move.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.controller.d.ts +36 -0
- package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.controller.js +158 -0
- package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.routes.d.ts +15 -0
- package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.routes.js +26 -0
- package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.routes.js.map +1 -0
- package/dist/backend/backend/src/index.d.ts +10 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +97 -20
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +6 -0
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/agent/follow-through.wiring.d.ts +32 -0
- package/dist/backend/backend/src/services/agent/follow-through.wiring.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/follow-through.wiring.js +42 -1
- package/dist/backend/backend/src/services/agent/follow-through.wiring.js.map +1 -1
- package/dist/backend/backend/src/services/agent/resource-mode.service.d.ts +26 -6
- package/dist/backend/backend/src/services/agent/resource-mode.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/resource-mode.service.js +59 -5
- package/dist/backend/backend/src/services/agent/resource-mode.service.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.js +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
- package/dist/backend/backend/src/services/core/storage.service.d.ts +18 -0
- package/dist/backend/backend/src/services/core/storage.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/core/storage.service.js +16 -0
- package/dist/backend/backend/src/services/core/storage.service.js.map +1 -1
- package/dist/backend/backend/src/services/decisions/decision.service.d.ts +19 -0
- package/dist/backend/backend/src/services/decisions/decision.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/decisions/decision.service.js +46 -0
- package/dist/backend/backend/src/services/decisions/decision.service.js.map +1 -1
- package/dist/backend/backend/src/services/monitoring/agent-turn-state.d.ts +35 -0
- package/dist/backend/backend/src/services/monitoring/agent-turn-state.d.ts.map +1 -1
- package/dist/backend/backend/src/services/monitoring/agent-turn-state.js +101 -0
- package/dist/backend/backend/src/services/monitoring/agent-turn-state.js.map +1 -1
- package/dist/backend/backend/src/services/monitoring/background-work-watchdog.d.ts +58 -0
- package/dist/backend/backend/src/services/monitoring/background-work-watchdog.d.ts.map +1 -0
- package/dist/backend/backend/src/services/monitoring/background-work-watchdog.js +100 -0
- package/dist/backend/backend/src/services/monitoring/background-work-watchdog.js.map +1 -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 +3 -0
- package/dist/backend/backend/src/services/monitoring/token-usage.service.js.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts +7 -0
- 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 +15 -0
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.js +2 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +9 -0
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +25 -0
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.d.ts +24 -0
- 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 +63 -1
- package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.js.map +1 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +19 -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 +41 -0
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/backend/backend/src/services/team-move/team-move-archive.d.ts +122 -0
- package/dist/backend/backend/src/services/team-move/team-move-archive.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-archive.js +202 -0
- package/dist/backend/backend/src/services/team-move/team-move-archive.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-chat.d.ts +59 -0
- package/dist/backend/backend/src/services/team-move/team-move-chat.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-chat.js +133 -0
- package/dist/backend/backend/src/services/team-move/team-move-chat.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-cloud.client.d.ts +102 -0
- package/dist/backend/backend/src/services/team-move/team-move-cloud.client.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-cloud.client.js +217 -0
- package/dist/backend/backend/src/services/team-move/team-move-cloud.client.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-crypto.d.ts +102 -0
- package/dist/backend/backend/src/services/team-move/team-move-crypto.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-crypto.js +348 -0
- package/dist/backend/backend/src/services/team-move/team-move-crypto.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-driver.service.d.ts +209 -0
- package/dist/backend/backend/src/services/team-move/team-move-driver.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-driver.service.js +504 -0
- package/dist/backend/backend/src/services/team-move/team-move-driver.service.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-exporter.d.ts +156 -0
- package/dist/backend/backend/src/services/team-move/team-move-exporter.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-exporter.js +462 -0
- package/dist/backend/backend/src/services/team-move/team-move-exporter.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-fs.d.ts +157 -0
- package/dist/backend/backend/src/services/team-move/team-move-fs.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-fs.js +298 -0
- package/dist/backend/backend/src/services/team-move/team-move-fs.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-git.d.ts +69 -0
- package/dist/backend/backend/src/services/team-move/team-move-git.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-git.js +198 -0
- package/dist/backend/backend/src/services/team-move/team-move-git.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-importer.d.ts +121 -0
- package/dist/backend/backend/src/services/team-move/team-move-importer.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-importer.js +462 -0
- package/dist/backend/backend/src/services/team-move/team-move-importer.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-journal.d.ts +110 -0
- package/dist/backend/backend/src/services/team-move/team-move-journal.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-journal.js +198 -0
- package/dist/backend/backend/src/services/team-move/team-move-journal.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-preflight.d.ts +71 -0
- package/dist/backend/backend/src/services/team-move/team-move-preflight.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-preflight.js +102 -0
- package/dist/backend/backend/src/services/team-move/team-move-preflight.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-runtime.d.ts +29 -0
- package/dist/backend/backend/src/services/team-move/team-move-runtime.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-runtime.js +173 -0
- package/dist/backend/backend/src/services/team-move/team-move-runtime.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-secret-scan.d.ts +50 -0
- package/dist/backend/backend/src/services/team-move/team-move-secret-scan.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-secret-scan.js +96 -0
- package/dist/backend/backend/src/services/team-move/team-move-secret-scan.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-select.d.ts +94 -0
- package/dist/backend/backend/src/services/team-move/team-move-select.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-select.js +124 -0
- package/dist/backend/backend/src/services/team-move/team-move-select.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move.fixture.d.ts +46 -0
- package/dist/backend/backend/src/services/team-move/team-move.fixture.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move.fixture.js +202 -0
- package/dist/backend/backend/src/services/team-move/team-move.fixture.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move.types.d.ts +269 -0
- package/dist/backend/backend/src/services/team-move/team-move.types.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move.types.js +44 -0
- package/dist/backend/backend/src/services/team-move/team-move.types.js.map +1 -0
- package/dist/backend/backend/src/services/trace/trace-context.service.d.ts +42 -2
- package/dist/backend/backend/src/services/trace/trace-context.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/trace/trace-context.service.js +66 -5
- package/dist/backend/backend/src/services/trace/trace-context.service.js.map +1 -1
- package/dist/backend/backend/src/services/trace/trace-recorder.d.ts.map +1 -1
- package/dist/backend/backend/src/services/trace/trace-recorder.js +11 -0
- package/dist/backend/backend/src/services/trace/trace-recorder.js.map +1 -1
- package/dist/backend/backend/src/services/usage-ledger/categories-param.d.ts +28 -0
- package/dist/backend/backend/src/services/usage-ledger/categories-param.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/categories-param.js +67 -0
- package/dist/backend/backend/src/services/usage-ledger/categories-param.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/excerpt.d.ts +37 -0
- package/dist/backend/backend/src/services/usage-ledger/excerpt.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/excerpt.js +49 -0
- package/dist/backend/backend/src/services/usage-ledger/excerpt.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/index.d.ts +14 -0
- package/dist/backend/backend/src/services/usage-ledger/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/index.js +14 -0
- package/dist/backend/backend/src/services/usage-ledger/index.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/ledger-hooks.d.ts +44 -0
- package/dist/backend/backend/src/services/usage-ledger/ledger-hooks.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/ledger-hooks.js +52 -0
- package/dist/backend/backend/src/services/usage-ledger/ledger-hooks.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/ledger-rollup.d.ts +100 -0
- package/dist/backend/backend/src/services/usage-ledger/ledger-rollup.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/ledger-rollup.js +189 -0
- package/dist/backend/backend/src/services/usage-ledger/ledger-rollup.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/owner-spans.d.ts +41 -0
- package/dist/backend/backend/src/services/usage-ledger/owner-spans.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/owner-spans.js +71 -0
- package/dist/backend/backend/src/services/usage-ledger/owner-spans.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/team-directory.d.ts +47 -0
- package/dist/backend/backend/src/services/usage-ledger/team-directory.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/team-directory.js +106 -0
- package/dist/backend/backend/src/services/usage-ledger/team-directory.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/unit-classifier.d.ts +101 -0
- package/dist/backend/backend/src/services/usage-ledger/unit-classifier.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/unit-classifier.js +263 -0
- package/dist/backend/backend/src/services/usage-ledger/unit-classifier.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-categories.store.d.ts +96 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-categories.store.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-categories.store.js +166 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-categories.store.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.backfill.d.ts +90 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.backfill.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.backfill.js +294 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.backfill.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.recorder.d.ts +102 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.recorder.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.recorder.js +314 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.recorder.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.runtime.d.ts +62 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.runtime.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.runtime.js +106 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.runtime.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.store.d.ts +52 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.store.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.store.js +95 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.store.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.types.d.ts +127 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.types.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.types.js +19 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.types.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-report.service.d.ts +124 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-report.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-report.service.js +372 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-report.service.js.map +1 -0
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +80 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +80 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/agent/resource-mode.service.d.ts +26 -6
- package/dist/cli/backend/src/services/agent/resource-mode.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/resource-mode.service.js +59 -5
- package/dist/cli/backend/src/services/agent/resource-mode.service.js.map +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.js +1 -0
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
- package/dist/cli/backend/src/services/core/storage.service.d.ts +18 -0
- package/dist/cli/backend/src/services/core/storage.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/core/storage.service.js +16 -0
- package/dist/cli/backend/src/services/core/storage.service.js.map +1 -1
- package/dist/cli/backend/src/services/decisions/decision.service.d.ts +19 -0
- package/dist/cli/backend/src/services/decisions/decision.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/decisions/decision.service.js +46 -0
- package/dist/cli/backend/src/services/decisions/decision.service.js.map +1 -1
- package/dist/cli/backend/src/services/monitoring/agent-turn-state.d.ts +35 -0
- package/dist/cli/backend/src/services/monitoring/agent-turn-state.d.ts.map +1 -1
- package/dist/cli/backend/src/services/monitoring/agent-turn-state.js +101 -0
- package/dist/cli/backend/src/services/monitoring/agent-turn-state.js.map +1 -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 +3 -0
- package/dist/cli/backend/src/services/monitoring/token-usage.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +9 -0
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +25 -0
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.d.ts +24 -0
- 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 +63 -1
- package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.js.map +1 -1
- package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +19 -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 +41 -0
- package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/cli/backend/src/services/trace/trace-context.service.d.ts +42 -2
- package/dist/cli/backend/src/services/trace/trace-context.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/trace/trace-context.service.js +66 -5
- package/dist/cli/backend/src/services/trace/trace-context.service.js.map +1 -1
- package/dist/cli/backend/src/services/trace/trace-recorder.d.ts.map +1 -1
- package/dist/cli/backend/src/services/trace/trace-recorder.js +11 -0
- package/dist/cli/backend/src/services/trace/trace-recorder.js.map +1 -1
- package/dist/cli/backend/src/services/usage-ledger/ledger-hooks.d.ts +44 -0
- package/dist/cli/backend/src/services/usage-ledger/ledger-hooks.d.ts.map +1 -0
- package/dist/cli/backend/src/services/usage-ledger/ledger-hooks.js +52 -0
- package/dist/cli/backend/src/services/usage-ledger/ledger-hooks.js.map +1 -0
- package/dist/cli/cli/src/commands/team-move.d.ts +68 -0
- package/dist/cli/cli/src/commands/team-move.d.ts.map +1 -0
- package/dist/cli/cli/src/commands/team-move.js +155 -0
- package/dist/cli/cli/src/commands/team-move.js.map +1 -0
- package/dist/cli/cli/src/index.js +33 -0
- package/dist/cli/cli/src/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory fold of the ledger files into per-unit rollups.
|
|
3
|
+
*
|
|
4
|
+
* Reads are incremental: each file's byte offset is remembered, so a refresh
|
|
5
|
+
* only parses what was appended since the last one (the first refresh after a
|
|
6
|
+
* restart reads everything once). The rollup is derived data; the files stay
|
|
7
|
+
* the source of truth.
|
|
8
|
+
*
|
|
9
|
+
* @module services/usage-ledger/ledger-rollup
|
|
10
|
+
*/
|
|
11
|
+
import { promises as fs } from 'fs';
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
import { UNTICKETED_PREFIX } from './usage-ledger.types.js';
|
|
14
|
+
const READ_CHUNK = 1 << 20;
|
|
15
|
+
/**
|
|
16
|
+
* Per-unit rollups of the ledger.
|
|
17
|
+
*/
|
|
18
|
+
export class LedgerRollup {
|
|
19
|
+
units = new Map();
|
|
20
|
+
offsets = new Map();
|
|
21
|
+
running = Promise.resolve();
|
|
22
|
+
/** Facts folded in so far */
|
|
23
|
+
factCount = 0;
|
|
24
|
+
/**
|
|
25
|
+
* Fold in whatever was appended since the last refresh. Calls are serialised.
|
|
26
|
+
*
|
|
27
|
+
* @param store - The ledger store
|
|
28
|
+
*/
|
|
29
|
+
refresh(store) {
|
|
30
|
+
this.running = this.running.then(() => this.run(store)).catch(() => undefined);
|
|
31
|
+
return this.running;
|
|
32
|
+
}
|
|
33
|
+
async run(store) {
|
|
34
|
+
const files = await store.listFiles();
|
|
35
|
+
for (const name of files) {
|
|
36
|
+
const file = path.join(store.dir, name);
|
|
37
|
+
let size;
|
|
38
|
+
try {
|
|
39
|
+
size = (await fs.stat(file)).size;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const known = this.offsets.get(name) ?? 0;
|
|
45
|
+
if (size < known) {
|
|
46
|
+
// A file got shorter: the ledger was edited from outside. Start over.
|
|
47
|
+
this.units.clear();
|
|
48
|
+
this.offsets.clear();
|
|
49
|
+
this.factCount = 0;
|
|
50
|
+
await this.run(store);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (size === known)
|
|
54
|
+
continue;
|
|
55
|
+
this.offsets.set(name, known + (await this.readNew(file, known, size)));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Read complete lines from `from` to `size`.
|
|
60
|
+
*
|
|
61
|
+
* @returns Bytes consumed (up to and including the last newline)
|
|
62
|
+
*/
|
|
63
|
+
async readNew(file, from, size) {
|
|
64
|
+
const handle = await fs.open(file, 'r');
|
|
65
|
+
try {
|
|
66
|
+
let consumed = 0;
|
|
67
|
+
let pending = Buffer.alloc(0);
|
|
68
|
+
let position = from;
|
|
69
|
+
while (position < size) {
|
|
70
|
+
const buf = Buffer.alloc(Math.min(READ_CHUNK, size - position));
|
|
71
|
+
const { bytesRead } = await handle.read(buf, 0, buf.length, position);
|
|
72
|
+
if (bytesRead === 0)
|
|
73
|
+
break;
|
|
74
|
+
position += bytesRead;
|
|
75
|
+
pending = Buffer.concat([pending, buf.subarray(0, bytesRead)]);
|
|
76
|
+
const lastNewline = pending.lastIndexOf(0x0a);
|
|
77
|
+
if (lastNewline < 0)
|
|
78
|
+
continue;
|
|
79
|
+
const complete = pending.subarray(0, lastNewline + 1).toString('utf-8');
|
|
80
|
+
pending = pending.subarray(lastNewline + 1);
|
|
81
|
+
consumed += Buffer.byteLength(complete);
|
|
82
|
+
for (const line of complete.split('\n'))
|
|
83
|
+
this.foldLine(line);
|
|
84
|
+
}
|
|
85
|
+
return consumed;
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
await handle.close();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
foldLine(line) {
|
|
92
|
+
if (!line.trim())
|
|
93
|
+
return;
|
|
94
|
+
try {
|
|
95
|
+
const fact = JSON.parse(line);
|
|
96
|
+
if (fact && typeof fact === 'object' && typeof fact.unitId === 'string')
|
|
97
|
+
this.fold(fact);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// a torn or foreign line
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
unit(unitId, ms) {
|
|
104
|
+
let u = this.units.get(unitId);
|
|
105
|
+
if (!u) {
|
|
106
|
+
u = {
|
|
107
|
+
unitId,
|
|
108
|
+
synthetic: unitId.startsWith(UNTICKETED_PREFIX),
|
|
109
|
+
firstMs: ms,
|
|
110
|
+
lastMs: ms,
|
|
111
|
+
agents: new Map(),
|
|
112
|
+
usage: [],
|
|
113
|
+
busy: [],
|
|
114
|
+
ownerMessages: [],
|
|
115
|
+
inferred: false,
|
|
116
|
+
};
|
|
117
|
+
this.units.set(unitId, u);
|
|
118
|
+
}
|
|
119
|
+
if (Number.isFinite(ms)) {
|
|
120
|
+
u.firstMs = Math.min(u.firstMs, ms);
|
|
121
|
+
u.lastMs = Math.max(u.lastMs, ms);
|
|
122
|
+
}
|
|
123
|
+
return u;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Fold one fact in.
|
|
127
|
+
*
|
|
128
|
+
* @param fact - A ledger fact
|
|
129
|
+
*/
|
|
130
|
+
fold(fact) {
|
|
131
|
+
const ms = Date.parse(fact.ts);
|
|
132
|
+
const u = this.unit(fact.unitId, ms);
|
|
133
|
+
this.factCount += 1;
|
|
134
|
+
if (fact.attribution === 'inferred')
|
|
135
|
+
u.inferred = true;
|
|
136
|
+
switch (fact.kind) {
|
|
137
|
+
case 'unit.meta': {
|
|
138
|
+
// First value wins; later facts only fill gaps.
|
|
139
|
+
u.ticketId ??= fact.ticketId;
|
|
140
|
+
u.requestId ??= fact.requestId;
|
|
141
|
+
u.rootKind ??= fact.rootKind;
|
|
142
|
+
u.title ||= fact.title;
|
|
143
|
+
u.excerpt ||= fact.excerpt;
|
|
144
|
+
u.project ??= fact.project;
|
|
145
|
+
u.team ??= fact.team;
|
|
146
|
+
u.channel ??= fact.channel;
|
|
147
|
+
u.threadRef ??= fact.threadRef;
|
|
148
|
+
u.openedAt ??= fact.openedAt;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case 'unit.agent': {
|
|
152
|
+
const known = u.agents.get(fact.agent);
|
|
153
|
+
u.agents.set(fact.agent, { team: known?.team ?? fact.team, project: known?.project ?? fact.project });
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
case 'unit.closed': {
|
|
157
|
+
if (!u.closedAt || fact.ts > u.closedAt) {
|
|
158
|
+
u.closedAt = fact.ts;
|
|
159
|
+
u.closedStatus = fact.status;
|
|
160
|
+
}
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
case 'usage':
|
|
164
|
+
u.usage.push({
|
|
165
|
+
hourMs: ms,
|
|
166
|
+
agent: fact.agent,
|
|
167
|
+
team: fact.team,
|
|
168
|
+
model: fact.model,
|
|
169
|
+
share: fact.share,
|
|
170
|
+
input: fact.input,
|
|
171
|
+
cachedInput: fact.cachedInput,
|
|
172
|
+
output: fact.output,
|
|
173
|
+
cacheWrite: fact.cacheWrite,
|
|
174
|
+
budget: fact.budget,
|
|
175
|
+
costUsd: fact.costUsd,
|
|
176
|
+
attribution: fact.attribution,
|
|
177
|
+
});
|
|
178
|
+
break;
|
|
179
|
+
case 'busy':
|
|
180
|
+
u.busy.push({ hourMs: ms, agent: fact.agent, team: fact.team, busyMs: fact.busyMs, share: fact.share, attribution: fact.attribution });
|
|
181
|
+
break;
|
|
182
|
+
case 'owner.message':
|
|
183
|
+
if (Number.isFinite(ms))
|
|
184
|
+
u.ownerMessages.push(ms);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=ledger-rollup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger-rollup.js","sourceRoot":"","sources":["../../../../../../backend/src/services/usage-ledger/ledger-rollup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owner time per ticket, from owner message times.
|
|
3
|
+
*
|
|
4
|
+
* Heuristic (specs/2026-10-11-usage-ledger.md): owner messages on one ticket
|
|
5
|
+
* that follow each other within {@link OWNER_SPAN_GAP_MS} form one active
|
|
6
|
+
* span. A span runs from its first message to its last message plus
|
|
7
|
+
* {@link OWNER_MESSAGE_CREDIT_MS} (the time spent writing that last message),
|
|
8
|
+
* so a single message counts for one minute. When spans of different tickets
|
|
9
|
+
* overlap in time, the overlap is split evenly between them, so the owner's
|
|
10
|
+
* minutes are never counted twice.
|
|
11
|
+
*
|
|
12
|
+
* @module services/usage-ledger/owner-spans
|
|
13
|
+
*/
|
|
14
|
+
/** Messages at most this far apart belong to the same span. */
|
|
15
|
+
export declare const OWNER_SPAN_GAP_MS: number;
|
|
16
|
+
/** Time credited to the last (or only) message of a span. */
|
|
17
|
+
export declare const OWNER_MESSAGE_CREDIT_MS = 60000;
|
|
18
|
+
/** One stretch of owner activity on a ticket. */
|
|
19
|
+
export interface OwnerSpan {
|
|
20
|
+
unitId: string;
|
|
21
|
+
startMs: number;
|
|
22
|
+
endMs: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Group one ticket's owner message times into spans.
|
|
26
|
+
*
|
|
27
|
+
* @param unitId - Ticket (unit) id
|
|
28
|
+
* @param times - Message times (ms), any order, duplicates allowed
|
|
29
|
+
* @returns Spans, oldest first
|
|
30
|
+
*/
|
|
31
|
+
export declare function buildOwnerSpans(unitId: string, times: number[]): OwnerSpan[];
|
|
32
|
+
/**
|
|
33
|
+
* Owner minutes per ticket inside a time window, with overlaps split evenly.
|
|
34
|
+
*
|
|
35
|
+
* @param spans - Spans of any tickets
|
|
36
|
+
* @param fromMs - Window start (ms)
|
|
37
|
+
* @param toMs - Window end (ms)
|
|
38
|
+
* @returns Ticket id → minutes
|
|
39
|
+
*/
|
|
40
|
+
export declare function ownerMinutesByUnit(spans: OwnerSpan[], fromMs: number, toMs: number): Map<string, number>;
|
|
41
|
+
//# sourceMappingURL=owner-spans.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"owner-spans.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/usage-ledger/owner-spans.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,QAAc,CAAC;AAC7C,6DAA6D;AAC7D,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C,iDAAiD;AACjD,MAAM,WAAW,SAAS;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACd;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,CAgB5E;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAgBxG"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owner time per ticket, from owner message times.
|
|
3
|
+
*
|
|
4
|
+
* Heuristic (specs/2026-10-11-usage-ledger.md): owner messages on one ticket
|
|
5
|
+
* that follow each other within {@link OWNER_SPAN_GAP_MS} form one active
|
|
6
|
+
* span. A span runs from its first message to its last message plus
|
|
7
|
+
* {@link OWNER_MESSAGE_CREDIT_MS} (the time spent writing that last message),
|
|
8
|
+
* so a single message counts for one minute. When spans of different tickets
|
|
9
|
+
* overlap in time, the overlap is split evenly between them, so the owner's
|
|
10
|
+
* minutes are never counted twice.
|
|
11
|
+
*
|
|
12
|
+
* @module services/usage-ledger/owner-spans
|
|
13
|
+
*/
|
|
14
|
+
/** Messages at most this far apart belong to the same span. */
|
|
15
|
+
export const OWNER_SPAN_GAP_MS = 10 * 60_000;
|
|
16
|
+
/** Time credited to the last (or only) message of a span. */
|
|
17
|
+
export const OWNER_MESSAGE_CREDIT_MS = 60_000;
|
|
18
|
+
/**
|
|
19
|
+
* Group one ticket's owner message times into spans.
|
|
20
|
+
*
|
|
21
|
+
* @param unitId - Ticket (unit) id
|
|
22
|
+
* @param times - Message times (ms), any order, duplicates allowed
|
|
23
|
+
* @returns Spans, oldest first
|
|
24
|
+
*/
|
|
25
|
+
export function buildOwnerSpans(unitId, times) {
|
|
26
|
+
const sorted = [...new Set(times.filter((t) => Number.isFinite(t)))].sort((a, b) => a - b);
|
|
27
|
+
const spans = [];
|
|
28
|
+
let start = null;
|
|
29
|
+
let last = 0;
|
|
30
|
+
for (const t of sorted) {
|
|
31
|
+
if (start !== null && t - last <= OWNER_SPAN_GAP_MS) {
|
|
32
|
+
last = t;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (start !== null)
|
|
36
|
+
spans.push({ unitId, startMs: start, endMs: last + OWNER_MESSAGE_CREDIT_MS });
|
|
37
|
+
start = t;
|
|
38
|
+
last = t;
|
|
39
|
+
}
|
|
40
|
+
if (start !== null)
|
|
41
|
+
spans.push({ unitId, startMs: start, endMs: last + OWNER_MESSAGE_CREDIT_MS });
|
|
42
|
+
return spans;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Owner minutes per ticket inside a time window, with overlaps split evenly.
|
|
46
|
+
*
|
|
47
|
+
* @param spans - Spans of any tickets
|
|
48
|
+
* @param fromMs - Window start (ms)
|
|
49
|
+
* @param toMs - Window end (ms)
|
|
50
|
+
* @returns Ticket id → minutes
|
|
51
|
+
*/
|
|
52
|
+
export function ownerMinutesByUnit(spans, fromMs, toMs) {
|
|
53
|
+
const clipped = spans
|
|
54
|
+
.map((s) => ({ unitId: s.unitId, startMs: Math.max(s.startMs, fromMs), endMs: Math.min(s.endMs, toMs) }))
|
|
55
|
+
.filter((s) => s.endMs > s.startMs);
|
|
56
|
+
const points = [...new Set(clipped.flatMap((s) => [s.startMs, s.endMs]))].sort((a, b) => a - b);
|
|
57
|
+
const minutes = new Map();
|
|
58
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
59
|
+
const a = points[i];
|
|
60
|
+
const b = points[i + 1];
|
|
61
|
+
const active = clipped.filter((s) => s.startMs <= a && s.endMs >= b);
|
|
62
|
+
if (active.length === 0)
|
|
63
|
+
continue;
|
|
64
|
+
const units = [...new Set(active.map((s) => s.unitId))];
|
|
65
|
+
const each = (b - a) / 60_000 / units.length;
|
|
66
|
+
for (const u of units)
|
|
67
|
+
minutes.set(u, (minutes.get(u) ?? 0) + each);
|
|
68
|
+
}
|
|
69
|
+
return minutes;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=owner-spans.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"owner-spans.js","sourceRoot":"","sources":["../../../../../../backend/src/services/usage-ledger/owner-spans.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which team and project an agent session belongs to, read from the team
|
|
3
|
+
* configs under CREWLY_HOME (`teams/<id>/config.json`, `projects.json`).
|
|
4
|
+
* Cached; a stale cache is refreshed in the background so hot paths stay
|
|
5
|
+
* synchronous and cheap.
|
|
6
|
+
*
|
|
7
|
+
* @module services/usage-ledger/team-directory
|
|
8
|
+
*/
|
|
9
|
+
/** Team and project of a session. */
|
|
10
|
+
export interface TeamInfo {
|
|
11
|
+
team?: string;
|
|
12
|
+
project?: string;
|
|
13
|
+
}
|
|
14
|
+
/** What the ledger asks of a directory. */
|
|
15
|
+
export interface TeamLookup {
|
|
16
|
+
/** Team / project of an agent session (sync, from cache) */
|
|
17
|
+
lookup(session: string): TeamInfo | null;
|
|
18
|
+
/** Project name for a project id */
|
|
19
|
+
projectName(projectId: string): string | undefined;
|
|
20
|
+
/** Wait until the cache has been loaded at least once */
|
|
21
|
+
ready(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Reads team and project names from disk.
|
|
25
|
+
*/
|
|
26
|
+
export declare class TeamDirectory implements TeamLookup {
|
|
27
|
+
private readonly home;
|
|
28
|
+
private readonly now;
|
|
29
|
+
private sessions;
|
|
30
|
+
private projects;
|
|
31
|
+
private loadedAt;
|
|
32
|
+
private loading;
|
|
33
|
+
/**
|
|
34
|
+
* @param home - Crewly home (default CREWLY_HOME)
|
|
35
|
+
* @param now - Clock (ms)
|
|
36
|
+
*/
|
|
37
|
+
constructor(home?: string, now?: () => number);
|
|
38
|
+
lookup(session: string): TeamInfo | null;
|
|
39
|
+
projectName(projectId: string): string | undefined;
|
|
40
|
+
ready(): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Reload the cache (one load at a time).
|
|
43
|
+
*/
|
|
44
|
+
refresh(): Promise<void>;
|
|
45
|
+
private load;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=team-directory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"team-directory.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/usage-ledger/team-directory.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,qCAAqC;AACrC,MAAM,WAAW,QAAQ;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,2CAA2C;AAC3C,MAAM,WAAW,UAAU;IAC1B,4DAA4D;IAC5D,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAAC;IACzC,oCAAoC;IACpC,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACnD,yDAAyD;IACzD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAmBD;;GAEG;AACH,qBAAa,aAAc,YAAW,UAAU;IAW9C,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAXrB,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,OAAO,CAA8B;IAE7C;;;OAGG;gBAEe,IAAI,GAAE,MAA4B,EAClC,GAAG,GAAE,MAAM,MAAyB;IAGtD,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IAMxC,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI5C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B;;OAEG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAWV,IAAI;CAiClB"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which team and project an agent session belongs to, read from the team
|
|
3
|
+
* configs under CREWLY_HOME (`teams/<id>/config.json`, `projects.json`).
|
|
4
|
+
* Cached; a stale cache is refreshed in the background so hot paths stay
|
|
5
|
+
* synchronous and cheap.
|
|
6
|
+
*
|
|
7
|
+
* @module services/usage-ledger/team-directory
|
|
8
|
+
*/
|
|
9
|
+
import { promises as fs } from 'fs';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import { getCrewlyHomePath } from '../core/crewly-home.utils.js';
|
|
12
|
+
/** How long a loaded cache is trusted. */
|
|
13
|
+
const TTL_MS = 60_000;
|
|
14
|
+
/** The orchestrator's session is not in any team. */
|
|
15
|
+
const ORCHESTRATOR_SESSION = /^crewly-orc(?:$|-)/;
|
|
16
|
+
/**
|
|
17
|
+
* Reads team and project names from disk.
|
|
18
|
+
*/
|
|
19
|
+
export class TeamDirectory {
|
|
20
|
+
home;
|
|
21
|
+
now;
|
|
22
|
+
sessions = new Map();
|
|
23
|
+
projects = new Map();
|
|
24
|
+
loadedAt = 0;
|
|
25
|
+
loading = null;
|
|
26
|
+
/**
|
|
27
|
+
* @param home - Crewly home (default CREWLY_HOME)
|
|
28
|
+
* @param now - Clock (ms)
|
|
29
|
+
*/
|
|
30
|
+
constructor(home = getCrewlyHomePath(), now = () => Date.now()) {
|
|
31
|
+
this.home = home;
|
|
32
|
+
this.now = now;
|
|
33
|
+
}
|
|
34
|
+
lookup(session) {
|
|
35
|
+
if (this.now() - this.loadedAt > TTL_MS)
|
|
36
|
+
void this.refresh();
|
|
37
|
+
if (ORCHESTRATOR_SESSION.test(session))
|
|
38
|
+
return { team: 'Orchestrator' };
|
|
39
|
+
return this.sessions.get(session) ?? null;
|
|
40
|
+
}
|
|
41
|
+
projectName(projectId) {
|
|
42
|
+
return this.projects.get(projectId);
|
|
43
|
+
}
|
|
44
|
+
async ready() {
|
|
45
|
+
if (this.loadedAt === 0)
|
|
46
|
+
await this.refresh();
|
|
47
|
+
else if (this.loading)
|
|
48
|
+
await this.loading;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Reload the cache (one load at a time).
|
|
52
|
+
*/
|
|
53
|
+
refresh() {
|
|
54
|
+
if (!this.loading) {
|
|
55
|
+
this.loading = this.load()
|
|
56
|
+
.catch(() => undefined)
|
|
57
|
+
.finally(() => {
|
|
58
|
+
this.loading = null;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return this.loading;
|
|
62
|
+
}
|
|
63
|
+
async load() {
|
|
64
|
+
const projects = new Map();
|
|
65
|
+
try {
|
|
66
|
+
const list = JSON.parse(await fs.readFile(path.join(this.home, 'projects.json'), 'utf-8'));
|
|
67
|
+
for (const p of Array.isArray(list) ? list : [])
|
|
68
|
+
if (p?.id && p.name)
|
|
69
|
+
projects.set(p.id, p.name);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// no projects file
|
|
73
|
+
}
|
|
74
|
+
const sessions = new Map();
|
|
75
|
+
try {
|
|
76
|
+
const teamsDir = path.join(this.home, 'teams');
|
|
77
|
+
for (const entry of await fs.readdir(teamsDir, { withFileTypes: true })) {
|
|
78
|
+
if (!entry.isDirectory())
|
|
79
|
+
continue;
|
|
80
|
+
try {
|
|
81
|
+
const team = JSON.parse(await fs.readFile(path.join(teamsDir, entry.name, 'config.json'), 'utf-8'));
|
|
82
|
+
if (!team?.name)
|
|
83
|
+
continue;
|
|
84
|
+
const project = team.projectIds?.map((id) => projects.get(id)).find((n) => !!n);
|
|
85
|
+
const info = { team: team.name, ...(project ? { project } : {}) };
|
|
86
|
+
for (const m of team.members ?? []) {
|
|
87
|
+
if (m.sessionName)
|
|
88
|
+
sessions.set(m.sessionName, info);
|
|
89
|
+
if (m.agentId)
|
|
90
|
+
sessions.set(m.agentId, info);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// unreadable team
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// no teams dir
|
|
100
|
+
}
|
|
101
|
+
this.sessions = sessions;
|
|
102
|
+
this.projects = projects;
|
|
103
|
+
this.loadedAt = this.now();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=team-directory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"team-directory.js","sourceRoot":"","sources":["../../../../../../backend/src/services/usage-ledger/team-directory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Puts tickets into categories at report time.
|
|
3
|
+
*
|
|
4
|
+
* Order of attempts, cheapest first:
|
|
5
|
+
* 1. Deterministic: keywords (or the comma-separated phrases of the
|
|
6
|
+
* description), team and project hints.
|
|
7
|
+
* 2. Only for tickets nothing matched, and only when a model is available: one
|
|
8
|
+
* small batched question to the default cheap model.
|
|
9
|
+
* 3. Otherwise `other`.
|
|
10
|
+
*
|
|
11
|
+
* With no definitions the category is the ticket's team (else its project).
|
|
12
|
+
*
|
|
13
|
+
* Results are cached per (category-set id, ticket); a fallback `other` that
|
|
14
|
+
* came from having no model is not cached, so it is retried later. The cache
|
|
15
|
+
* lives beside the ledger and is never part of the ticket facts.
|
|
16
|
+
*
|
|
17
|
+
* @module services/usage-ledger/unit-classifier
|
|
18
|
+
*/
|
|
19
|
+
import { type CategoryDef } from './usage-categories.store.js';
|
|
20
|
+
/** Category of tickets that match nothing. */
|
|
21
|
+
export declare const OTHER_CATEGORY = "other";
|
|
22
|
+
/** Category of usage that belongs to no ticket. */
|
|
23
|
+
export declare const NO_TICKET_CATEGORY = "(no ticket)";
|
|
24
|
+
/** File with the classification cache. */
|
|
25
|
+
export declare const CLASSIFICATION_CACHE_FILE = "classification-cache.json";
|
|
26
|
+
/** What the classifier reads about a ticket. */
|
|
27
|
+
export interface ClassifiableUnit {
|
|
28
|
+
unitId: string;
|
|
29
|
+
ticketId?: string;
|
|
30
|
+
title?: string;
|
|
31
|
+
excerpt?: string;
|
|
32
|
+
team?: string;
|
|
33
|
+
project?: string;
|
|
34
|
+
}
|
|
35
|
+
/** Asks a small model one question; null when none is available or it failed. */
|
|
36
|
+
export type CategoryLlm = (prompt: string) => Promise<string | null>;
|
|
37
|
+
/** Where tickets ended up. */
|
|
38
|
+
export interface ClassifyResult {
|
|
39
|
+
/** Ticket → category */
|
|
40
|
+
categories: Map<string, string>;
|
|
41
|
+
/** Tickets answered by the model in this call */
|
|
42
|
+
llmClassified: number;
|
|
43
|
+
/** Tickets that matched nothing and had no model to ask */
|
|
44
|
+
unclassified: number;
|
|
45
|
+
}
|
|
46
|
+
/** Default classification (no user categories): the ticket's team, else its project. */
|
|
47
|
+
export declare function defaultCategory(unit: ClassifiableUnit): string;
|
|
48
|
+
/**
|
|
49
|
+
* Keywords of a definition: its explicit keywords, else the comma / semicolon
|
|
50
|
+
* separated phrases of its description (so "Xiaohongshu, Substack, LinkedIn"
|
|
51
|
+
* works as typed).
|
|
52
|
+
*
|
|
53
|
+
* @param def - Category definition
|
|
54
|
+
* @returns Lower-case keywords
|
|
55
|
+
*/
|
|
56
|
+
export declare function keywordsOf(def: CategoryDef): string[];
|
|
57
|
+
/**
|
|
58
|
+
* Best category by keyword / team / project hints.
|
|
59
|
+
*
|
|
60
|
+
* @param unit - The ticket
|
|
61
|
+
* @param defs - Definitions
|
|
62
|
+
* @returns Category name, or null when nothing matched
|
|
63
|
+
*/
|
|
64
|
+
export declare function matchCategory(unit: ClassifiableUnit, defs: CategoryDef[]): string | null;
|
|
65
|
+
/**
|
|
66
|
+
* Classifies tickets for reports.
|
|
67
|
+
*/
|
|
68
|
+
export declare class UnitClassifier {
|
|
69
|
+
private readonly llm;
|
|
70
|
+
private readonly cacheFile;
|
|
71
|
+
private cache;
|
|
72
|
+
/**
|
|
73
|
+
* @param dir - Ledger directory
|
|
74
|
+
* @param llm - Optional model (null: deterministic only)
|
|
75
|
+
*/
|
|
76
|
+
constructor(dir: string, llm?: CategoryLlm | null);
|
|
77
|
+
private loadCache;
|
|
78
|
+
/**
|
|
79
|
+
* Classify tickets.
|
|
80
|
+
*
|
|
81
|
+
* @param units - Tickets (never the "(no ticket)" pseudo-units)
|
|
82
|
+
* @param defs - The user's categories; empty means the default (team / project)
|
|
83
|
+
* @returns Category per ticket
|
|
84
|
+
*/
|
|
85
|
+
classify(units: ClassifiableUnit[], defs: CategoryDef[]): Promise<ClassifyResult>;
|
|
86
|
+
private saveCache;
|
|
87
|
+
/**
|
|
88
|
+
* Ask the model about tickets nothing matched.
|
|
89
|
+
*
|
|
90
|
+
* @returns Ticket → category (only tickets the model answered validly)
|
|
91
|
+
*/
|
|
92
|
+
private askModel;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The default cheap model: a small Anthropic model when `ANTHROPIC_API_KEY`
|
|
96
|
+
* is set; none otherwise.
|
|
97
|
+
*
|
|
98
|
+
* @returns A question function, or null
|
|
99
|
+
*/
|
|
100
|
+
export declare function defaultCategoryLlm(): CategoryLlm | null;
|
|
101
|
+
//# sourceMappingURL=unit-classifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unit-classifier.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/usage-ledger/unit-classifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAE9E,8CAA8C;AAC9C,eAAO,MAAM,cAAc,UAAU,CAAC;AACtC,mDAAmD;AACnD,eAAO,MAAM,kBAAkB,gBAAgB,CAAC;AAEhD,0CAA0C;AAC1C,eAAO,MAAM,yBAAyB,8BAA8B,CAAC;AAQrE,gDAAgD;AAChD,MAAM,WAAW,gBAAgB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,iFAAiF;AACjF,MAAM,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAErE,8BAA8B;AAC9B,MAAM,WAAW,cAAc;IAC9B,wBAAwB;IACxB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,iDAAiD;IACjD,aAAa,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC;CACrB;AAMD,wFAAwF;AACxF,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAE9D;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,EAAE,CAUrD;AAaD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,MAAM,GAAG,IAAI,CAaxF;AAED;;GAEG;AACH,qBAAa,cAAc;IAUzB,OAAO,CAAC,QAAQ,CAAC,GAAG;IATrB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,KAAK,CAA0B;IAEvC;;;OAGG;gBAEF,GAAG,EAAE,MAAM,EACM,GAAG,GAAE,WAAW,GAAG,IAAW;YAKlC,SAAS;IAWvB;;;;;;OAMG;IACG,QAAQ,CAAC,KAAK,EAAE,gBAAgB,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC;YA2CzE,SAAS;IAcvB;;;;OAIG;YACW,QAAQ;CAsCtB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,WAAW,GAAG,IAAI,CAcvD"}
|