crewly 1.20.192 → 1.20.194
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/constants.ts +11 -0
- package/config/hooks/subagent-guard/subagent.sh +113 -0
- package/config/roles/architect/prompt.md +1 -1
- package/config/roles/auditor/prompt.md +1 -1
- package/config/roles/backend-developer/prompt.md +1 -1
- package/config/roles/content-strategist/prompt.md +2 -2
- package/config/roles/designer/prompt.md +1 -1
- package/config/roles/developer/prompt.md +2 -2
- package/config/roles/frontend-developer/prompt.md +1 -1
- package/config/roles/fullstack-dev/prompt.md +1 -1
- package/config/roles/generalist/prompt.md +1 -1
- package/config/roles/ops/prompt.md +1 -1
- package/config/roles/orchestrator/prompt.md +2 -2
- package/config/roles/product-manager/prompt.md +1 -1
- package/config/roles/qa/prompt.md +1 -1
- package/config/roles/qa-engineer/prompt.md +1 -1
- package/config/roles/researcher/prompt.md +1 -1
- package/config/roles/sales/prompt.md +1 -1
- package/config/roles/support/prompt.md +1 -1
- package/config/roles/team-leader/prompt.md +1 -1
- package/config/roles/tpm/prompt.md +1 -1
- package/config/roles/ux-designer/prompt.md +1 -1
- package/config/skills/_common/complete-body-shape.test.sh +17 -0
- package/config/skills/agent/core/complete-task/SKILL.md +27 -4
- package/config/skills/agent/core/complete-task/execute.sh +23 -3
- package/config/skills/agent/core/complete-task/execute.test.sh +34 -0
- package/config/skills/agent/core/experiment-card/SKILL.md +96 -0
- package/config/skills/agent/core/experiment-card/execute.sh +147 -0
- package/config/skills/agent/core/experiment-card/execute.test.sh +91 -0
- package/config/skills/agent/core/handoff-task/execute.sh +1 -1
- package/config/skills/agent/core/report-status/SKILL.md +27 -4
- package/config/skills/agent/core/report-status/execute.sh +104 -4
- package/config/skills/agent/core/todo-add/SKILL.md +16 -2
- package/config/skills/agent/core/todo-add/execute.sh +21 -5
- package/config/skills/agent/core/todo-add/execute.test.sh +14 -0
- package/config/skills/agent/core/todo-tasks/SKILL.md +4 -3
- package/config/skills/agent/core/todo-update/SKILL.md +13 -2
- package/config/skills/agent/core/todo-update/execute.sh +41 -3
- package/config/skills/agent/core/todo-update/execute.test.sh +7 -0
- package/config/skills/marketplace-registry.ts +4 -4
- package/config/skills/orchestrator/complete-task/SKILL.md +13 -6
- package/config/skills/orchestrator/complete-task/execute.sh +22 -3
- package/config/skills/orchestrator/delegate-task/execute.sh +2 -2
- package/config/skills/orchestrator/harness-login/SKILL.md +9 -1
- package/config/skills/orchestrator/harness-login/execute.sh +12 -4
- package/config/skills/orchestrator/harness-login/execute.test.sh +7 -0
- package/config/skills/registry.json +6 -5
- package/config/skills/team-leader/delegate-task/execute.sh +1 -1
- package/config/skills/team-leader/verify-output/SKILL.md +18 -1
- package/config/skills/team-leader/verify-output/execute.sh +28 -0
- package/config/skills/team-leader/verify-output/execute.test.sh +27 -0
- package/dist/backend/backend/src/constants.d.ts +192 -2
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +196 -1
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/canva/canva.controller.d.ts +6 -0
- package/dist/backend/backend/src/controllers/canva/canva.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/canva/canva.controller.js +13 -2
- package/dist/backend/backend/src/controllers/canva/canva.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/canva/canva.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/canva/canva.routes.js +3 -1
- package/dist/backend/backend/src/controllers/canva/canva.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.js +4 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/connector/connector.controller.d.ts +4 -0
- package/dist/backend/backend/src/controllers/connector/connector.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/connector/connector.controller.js +32 -3
- package/dist/backend/backend/src/controllers/connector/connector.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/connector/grant-sharing.handler.d.ts +41 -0
- package/dist/backend/backend/src/controllers/connector/grant-sharing.handler.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/connector/grant-sharing.handler.js +75 -0
- package/dist/backend/backend/src/controllers/connector/grant-sharing.handler.js.map +1 -0
- package/dist/backend/backend/src/controllers/experiments/experiments.routes.d.ts +27 -0
- package/dist/backend/backend/src/controllers/experiments/experiments.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/experiments/experiments.routes.js +96 -0
- package/dist/backend/backend/src/controllers/experiments/experiments.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/google/google.controller.d.ts +6 -0
- package/dist/backend/backend/src/controllers/google/google.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/google/google.controller.js +19 -0
- package/dist/backend/backend/src/controllers/google/google.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/google/google.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/google/google.routes.js +3 -1
- package/dist/backend/backend/src/controllers/google/google.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/harness/owner-login.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/harness/owner-login.controller.js +22 -3
- package/dist/backend/backend/src/controllers/harness/owner-login.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/microsoft/microsoft-todo.controller.d.ts +19 -2
- package/dist/backend/backend/src/controllers/microsoft/microsoft-todo.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/microsoft/microsoft-todo.controller.js +37 -4
- package/dist/backend/backend/src/controllers/microsoft/microsoft-todo.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/microsoft/microsoft-todo.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/microsoft/microsoft-todo.routes.js +3 -1
- package/dist/backend/backend/src/controllers/microsoft/microsoft-todo.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.d.ts +9 -0
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js +26 -0
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/people/people.routes.d.ts +25 -0
- package/dist/backend/backend/src/controllers/people/people.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/people/people.routes.js +90 -0
- package/dist/backend/backend/src/controllers/people/people.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/request-types.d.ts +2 -0
- package/dist/backend/backend/src/controllers/request-types.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/system/runtime-fallback.controller.d.ts +22 -1
- package/dist/backend/backend/src/controllers/system/runtime-fallback.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/system/runtime-fallback.controller.js +143 -3
- package/dist/backend/backend/src/controllers/system/runtime-fallback.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts +25 -3
- 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 +125 -19
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/team/team.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/team/team.controller.js +14 -0
- package/dist/backend/backend/src/controllers/team/team.controller.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +82 -3
- 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/agent-registration.service.d.ts +13 -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 +28 -3
- package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/auditor-scheduler.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/auditor-scheduler.service.js +3 -0
- package/dist/backend/backend/src/services/agent/auditor-scheduler.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/control-plane-guard.service.d.ts +3 -2
- package/dist/backend/backend/src/services/agent/control-plane-guard.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/control-plane-guard.service.js +14 -3
- package/dist/backend/backend/src/services/agent/control-plane-guard.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/fresh-task-conversation.service.d.ts +28 -1
- package/dist/backend/backend/src/services/agent/fresh-task-conversation.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/fresh-task-conversation.service.js +48 -4
- package/dist/backend/backend/src/services/agent/fresh-task-conversation.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/idle-detection.service.js +1 -1
- package/dist/backend/backend/src/services/agent/idle-detection.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/oauth-relogin-monitor.service.d.ts +2 -1
- package/dist/backend/backend/src/services/agent/oauth-relogin-monitor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/oauth-relogin-monitor.service.js +17 -4
- package/dist/backend/backend/src/services/agent/oauth-relogin-monitor.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.d.ts +14 -0
- package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.js +38 -0
- package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-session-recovery.d.ts +34 -0
- package/dist/backend/backend/src/services/agent/runtime-session-recovery.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-session-recovery.js +49 -5
- package/dist/backend/backend/src/services/agent/runtime-session-recovery.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/communication.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/communication.module.js +8 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/communication.module.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/lazy-anti-patterns.module.js +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/lazy-anti-patterns.module.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -0
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/backend/backend/src/services/canva/canva-token.service.d.ts +17 -4
- package/dist/backend/backend/src/services/canva/canva-token.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/canva/canva-token.service.js +54 -17
- package/dist/backend/backend/src/services/canva/canva-token.service.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +35 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +77 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-disconnect-notice.service.d.ts +17 -2
- package/dist/backend/backend/src/services/cloud/cloud-disconnect-notice.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-disconnect-notice.service.js +30 -6
- package/dist/backend/backend/src/services/cloud/cloud-disconnect-notice.service.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-disconnect-notice.utils.d.ts +38 -2
- package/dist/backend/backend/src/services/cloud/cloud-disconnect-notice.utils.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-disconnect-notice.utils.js +63 -1
- package/dist/backend/backend/src/services/cloud/cloud-disconnect-notice.utils.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.service.d.ts +74 -4
- package/dist/backend/backend/src/services/cloud/cloud-sync.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.service.js +204 -56
- package/dist/backend/backend/src/services/cloud/cloud-sync.service.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts +19 -0
- 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.map +1 -1
- package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.js +3 -0
- package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.js.map +1 -1
- package/dist/backend/backend/src/services/experiments/experiment-verdict.d.ts +88 -0
- package/dist/backend/backend/src/services/experiments/experiment-verdict.d.ts.map +1 -0
- package/dist/backend/backend/src/services/experiments/experiment-verdict.js +226 -0
- package/dist/backend/backend/src/services/experiments/experiment-verdict.js.map +1 -0
- package/dist/backend/backend/src/services/experiments/experiment.service.d.ts +301 -0
- package/dist/backend/backend/src/services/experiments/experiment.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/experiments/experiment.service.js +754 -0
- package/dist/backend/backend/src/services/experiments/experiment.service.js.map +1 -0
- package/dist/backend/backend/src/services/experiments/experiment.wiring.d.ts +73 -0
- package/dist/backend/backend/src/services/experiments/experiment.wiring.d.ts.map +1 -0
- package/dist/backend/backend/src/services/experiments/experiment.wiring.js +142 -0
- package/dist/backend/backend/src/services/experiments/experiment.wiring.js.map +1 -0
- package/dist/backend/backend/src/services/experiments/seo-ops-metric.fetcher.d.ts +60 -0
- package/dist/backend/backend/src/services/experiments/seo-ops-metric.fetcher.d.ts.map +1 -0
- package/dist/backend/backend/src/services/experiments/seo-ops-metric.fetcher.js +113 -0
- package/dist/backend/backend/src/services/experiments/seo-ops-metric.fetcher.js.map +1 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts +25 -2
- package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/google/google-workspace-token.service.js +53 -7
- package/dist/backend/backend/src/services/google/google-workspace-token.service.js.map +1 -1
- package/dist/backend/backend/src/services/harness/claude-accounts.d.ts +207 -0
- package/dist/backend/backend/src/services/harness/claude-accounts.d.ts.map +1 -0
- package/dist/backend/backend/src/services/harness/claude-accounts.js +306 -0
- package/dist/backend/backend/src/services/harness/claude-accounts.js.map +1 -0
- package/dist/backend/backend/src/services/harness/harness-credentials.store.d.ts +35 -0
- package/dist/backend/backend/src/services/harness/harness-credentials.store.d.ts.map +1 -1
- package/dist/backend/backend/src/services/harness/harness-credentials.store.js +48 -0
- package/dist/backend/backend/src/services/harness/harness-credentials.store.js.map +1 -1
- package/dist/backend/backend/src/services/harness/harness-relogin.service.d.ts +70 -1
- package/dist/backend/backend/src/services/harness/harness-relogin.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/harness/harness-relogin.service.js +156 -19
- package/dist/backend/backend/src/services/harness/harness-relogin.service.js.map +1 -1
- package/dist/backend/backend/src/services/harness/harness.types.d.ts +2 -0
- package/dist/backend/backend/src/services/harness/harness.types.d.ts.map +1 -1
- package/dist/backend/backend/src/services/harness/harness.types.js.map +1 -1
- package/dist/backend/backend/src/services/harness/login-broker.service.d.ts +19 -4
- package/dist/backend/backend/src/services/harness/login-broker.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/harness/login-broker.service.js +46 -7
- package/dist/backend/backend/src/services/harness/login-broker.service.js.map +1 -1
- package/dist/backend/backend/src/services/harness/owner-login-request.d.ts +18 -2
- package/dist/backend/backend/src/services/harness/owner-login-request.d.ts.map +1 -1
- package/dist/backend/backend/src/services/harness/owner-login-request.js +31 -7
- package/dist/backend/backend/src/services/harness/owner-login-request.js.map +1 -1
- package/dist/backend/backend/src/services/marketplace/marketplace-installer.service.d.ts +21 -0
- package/dist/backend/backend/src/services/marketplace/marketplace-installer.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/marketplace/marketplace-installer.service.js +39 -1
- package/dist/backend/backend/src/services/marketplace/marketplace-installer.service.js.map +1 -1
- package/dist/backend/backend/src/services/messaging/queue-processor.service.d.ts +19 -0
- 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 +46 -0
- 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 +5 -0
- 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 +13 -0
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.js.map +1 -1
- package/dist/backend/backend/src/services/microsoft/microsoft-todo.service.d.ts +87 -5
- package/dist/backend/backend/src/services/microsoft/microsoft-todo.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/microsoft/microsoft-todo.service.js +151 -12
- package/dist/backend/backend/src/services/microsoft/microsoft-todo.service.js.map +1 -1
- package/dist/backend/backend/src/services/microsoft/microsoft-token.service.d.ts +17 -4
- package/dist/backend/backend/src/services/microsoft/microsoft-token.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/microsoft/microsoft-token.service.js +50 -17
- package/dist/backend/backend/src/services/microsoft/microsoft-token.service.js.map +1 -1
- package/dist/backend/backend/src/services/monitoring/claude-session-tokens.service.d.ts +53 -7
- package/dist/backend/backend/src/services/monitoring/claude-session-tokens.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/monitoring/claude-session-tokens.service.js +77 -14
- package/dist/backend/backend/src/services/monitoring/claude-session-tokens.service.js.map +1 -1
- package/dist/backend/backend/src/services/monitoring/claude-transcript-sync.service.d.ts +53 -1
- package/dist/backend/backend/src/services/monitoring/claude-transcript-sync.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/monitoring/claude-transcript-sync.service.js +130 -29
- package/dist/backend/backend/src/services/monitoring/claude-transcript-sync.service.js.map +1 -1
- package/dist/backend/backend/src/services/orc/orc-status-router.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orc/orc-status-router.service.js +3 -2
- package/dist/backend/backend/src/services/orc/orc-status-router.service.js.map +1 -1
- package/dist/backend/backend/src/services/people/acting-for.service.d.ts +188 -0
- package/dist/backend/backend/src/services/people/acting-for.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/people/acting-for.service.js +299 -0
- package/dist/backend/backend/src/services/people/acting-for.service.js.map +1 -0
- package/dist/backend/backend/src/services/people/dedicated-agent.d.ts +89 -0
- package/dist/backend/backend/src/services/people/dedicated-agent.d.ts.map +1 -0
- package/dist/backend/backend/src/services/people/dedicated-agent.js +113 -0
- package/dist/backend/backend/src/services/people/dedicated-agent.js.map +1 -0
- package/dist/backend/backend/src/services/people/grant-sharing.d.ts +80 -0
- package/dist/backend/backend/src/services/people/grant-sharing.d.ts.map +1 -0
- package/dist/backend/backend/src/services/people/grant-sharing.js +143 -0
- package/dist/backend/backend/src/services/people/grant-sharing.js.map +1 -0
- package/dist/backend/backend/src/services/people/people-directory.service.d.ts +239 -0
- package/dist/backend/backend/src/services/people/people-directory.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/people/people-directory.service.js +454 -0
- package/dist/backend/backend/src/services/people/people-directory.service.js.map +1 -0
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
- package/dist/backend/backend/src/services/runtime-fallback/effective-runtime.d.ts +29 -1
- package/dist/backend/backend/src/services/runtime-fallback/effective-runtime.d.ts.map +1 -1
- package/dist/backend/backend/src/services/runtime-fallback/effective-runtime.js +40 -0
- package/dist/backend/backend/src/services/runtime-fallback/effective-runtime.js.map +1 -1
- package/dist/backend/backend/src/services/runtime-fallback/runtime-availability.d.ts +8 -1
- package/dist/backend/backend/src/services/runtime-fallback/runtime-availability.d.ts.map +1 -1
- package/dist/backend/backend/src/services/runtime-fallback/runtime-availability.js +16 -2
- package/dist/backend/backend/src/services/runtime-fallback/runtime-availability.js.map +1 -1
- package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.service.d.ts +60 -1
- package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.service.js +140 -8
- package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.service.js.map +1 -1
- package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.store.js +1 -1
- package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.store.js.map +1 -1
- package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.types.d.ts +20 -7
- package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.types.d.ts.map +1 -1
- package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.types.js +24 -7
- package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.types.js.map +1 -1
- package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.wiring.d.ts.map +1 -1
- package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.wiring.js +12 -2
- package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.wiring.js.map +1 -1
- package/dist/backend/backend/src/services/runtime-fallback/runtime-usage-probe.d.ts +7 -1
- package/dist/backend/backend/src/services/runtime-fallback/runtime-usage-probe.d.ts.map +1 -1
- package/dist/backend/backend/src/services/runtime-fallback/runtime-usage-probe.js +18 -3
- package/dist/backend/backend/src/services/runtime-fallback/runtime-usage-probe.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-agent-dm.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-agent-dm.service.js +3 -0
- package/dist/backend/backend/src/services/slack/slack-agent-dm.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-bot-ids.d.ts +33 -0
- package/dist/backend/backend/src/services/slack/slack-bot-ids.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-bot-ids.js +56 -0
- package/dist/backend/backend/src/services/slack/slack-bot-ids.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts +22 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +70 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.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 +46 -0
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.d.ts +6 -0
- package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.js +8 -0
- package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/backend/backend/src/services/task-pool/completion-evidence.service.d.ts +105 -0
- package/dist/backend/backend/src/services/task-pool/completion-evidence.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/task-pool/completion-evidence.service.js +238 -0
- package/dist/backend/backend/src/services/task-pool/completion-evidence.service.js.map +1 -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 +4 -0
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/backend/backend/src/services/usage/usage-stats.service.d.ts +39 -12
- package/dist/backend/backend/src/services/usage/usage-stats.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/usage/usage-stats.service.js +61 -20
- package/dist/backend/backend/src/services/usage/usage-stats.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/agent-auto-claim.service.d.ts +2 -0
- package/dist/backend/backend/src/services/v3/agent-auto-claim.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/agent-auto-claim.service.js +9 -0
- package/dist/backend/backend/src/services/v3/agent-auto-claim.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.d.ts +10 -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 +45 -2
- package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-ingest.service.d.ts +3 -1
- package/dist/backend/backend/src/services/wiki/wiki-ingest.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-ingest.service.js.map +1 -1
- package/dist/backend/backend/src/services/workflow/message-scheduler.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/workflow/message-scheduler.service.js +3 -0
- package/dist/backend/backend/src/services/workflow/message-scheduler.service.js.map +1 -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 +3 -0
- package/dist/backend/backend/src/services/workflow/scheduler.service.js.map +1 -1
- package/dist/backend/backend/src/types/experiment.types.d.ts +153 -0
- package/dist/backend/backend/src/types/experiment.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/experiment.types.js +32 -0
- package/dist/backend/backend/src/types/experiment.types.js.map +1 -0
- package/dist/backend/backend/src/types/index.d.ts +12 -1
- package/dist/backend/backend/src/types/index.d.ts.map +1 -1
- package/dist/backend/backend/src/types/index.js.map +1 -1
- package/dist/backend/backend/src/types/messaging.types.d.ts +11 -0
- package/dist/backend/backend/src/types/messaging.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/messaging.types.js.map +1 -1
- package/dist/backend/backend/src/types/v2/completion-evidence.types.d.ts +77 -0
- package/dist/backend/backend/src/types/v2/completion-evidence.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/v2/completion-evidence.types.js +119 -0
- package/dist/backend/backend/src/types/v2/completion-evidence.types.js.map +1 -0
- package/dist/backend/backend/src/types/v2/work-item.types.d.ts +13 -0
- package/dist/backend/backend/src/types/v2/work-item.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/v2/work-item.types.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/backend/config/constants.d.ts +11 -0
- package/dist/backend/config/constants.d.ts.map +1 -1
- package/dist/backend/config/constants.js +11 -0
- package/dist/backend/config/constants.js.map +1 -1
- package/dist/cli/backend/src/constants.d.ts +192 -2
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +196 -1
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts +13 -0
- 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 +28 -3
- package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/auditor-scheduler.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/auditor-scheduler.service.js +3 -0
- package/dist/cli/backend/src/services/agent/auditor-scheduler.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/control-plane-guard.service.d.ts +3 -2
- package/dist/cli/backend/src/services/agent/control-plane-guard.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/control-plane-guard.service.js +14 -3
- package/dist/cli/backend/src/services/agent/control-plane-guard.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/fresh-task-conversation.service.d.ts +28 -1
- package/dist/cli/backend/src/services/agent/fresh-task-conversation.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/fresh-task-conversation.service.js +48 -4
- package/dist/cli/backend/src/services/agent/fresh-task-conversation.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.d.ts +2 -1
- package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.js +17 -4
- package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.d.ts +14 -0
- package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.js +38 -0
- package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-session-recovery.d.ts +34 -0
- package/dist/cli/backend/src/services/agent/runtime-session-recovery.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-session-recovery.js +49 -5
- package/dist/cli/backend/src/services/agent/runtime-session-recovery.js.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/communication.module.d.ts.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/communication.module.js +8 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/communication.module.js.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/lazy-anti-patterns.module.js +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/lazy-anti-patterns.module.js.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +35 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +77 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.service.d.ts +74 -4
- package/dist/cli/backend/src/services/cloud/cloud-sync.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.service.js +204 -56
- package/dist/cli/backend/src/services/cloud/cloud-sync.service.js.map +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts +19 -0
- 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.map +1 -1
- package/dist/cli/backend/src/services/harness/claude-accounts.d.ts +207 -0
- package/dist/cli/backend/src/services/harness/claude-accounts.d.ts.map +1 -0
- package/dist/cli/backend/src/services/harness/claude-accounts.js +306 -0
- package/dist/cli/backend/src/services/harness/claude-accounts.js.map +1 -0
- package/dist/cli/backend/src/services/harness/harness-credentials.store.d.ts +35 -0
- package/dist/cli/backend/src/services/harness/harness-credentials.store.d.ts.map +1 -1
- package/dist/cli/backend/src/services/harness/harness-credentials.store.js +48 -0
- package/dist/cli/backend/src/services/harness/harness-credentials.store.js.map +1 -1
- package/dist/cli/backend/src/services/harness/harness-relogin.service.d.ts +70 -1
- package/dist/cli/backend/src/services/harness/harness-relogin.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/harness/harness-relogin.service.js +156 -19
- package/dist/cli/backend/src/services/harness/harness-relogin.service.js.map +1 -1
- package/dist/cli/backend/src/services/harness/harness.types.d.ts +2 -0
- package/dist/cli/backend/src/services/harness/harness.types.d.ts.map +1 -1
- package/dist/cli/backend/src/services/harness/harness.types.js.map +1 -1
- package/dist/cli/backend/src/services/harness/login-broker.service.d.ts +19 -4
- package/dist/cli/backend/src/services/harness/login-broker.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/harness/login-broker.service.js +46 -7
- package/dist/cli/backend/src/services/harness/login-broker.service.js.map +1 -1
- package/dist/cli/backend/src/services/harness/owner-login-request.d.ts +18 -2
- package/dist/cli/backend/src/services/harness/owner-login-request.d.ts.map +1 -1
- package/dist/cli/backend/src/services/harness/owner-login-request.js +31 -7
- package/dist/cli/backend/src/services/harness/owner-login-request.js.map +1 -1
- package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.d.ts +21 -0
- package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.js +39 -1
- package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.js.map +1 -1
- package/dist/cli/backend/src/services/messaging/queue-processor.service.d.ts +19 -0
- 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 +46 -0
- 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 +5 -0
- 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 +13 -0
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js.map +1 -1
- package/dist/cli/backend/src/services/monitoring/claude-session-tokens.service.d.ts +197 -0
- package/dist/cli/backend/src/services/monitoring/claude-session-tokens.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/monitoring/claude-session-tokens.service.js +455 -0
- package/dist/cli/backend/src/services/monitoring/claude-session-tokens.service.js.map +1 -0
- package/dist/cli/backend/src/services/people/acting-for.service.d.ts +188 -0
- package/dist/cli/backend/src/services/people/acting-for.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/people/acting-for.service.js +299 -0
- package/dist/cli/backend/src/services/people/acting-for.service.js.map +1 -0
- package/dist/cli/backend/src/services/people/dedicated-agent.d.ts +89 -0
- package/dist/cli/backend/src/services/people/dedicated-agent.d.ts.map +1 -0
- package/dist/cli/backend/src/services/people/dedicated-agent.js +113 -0
- package/dist/cli/backend/src/services/people/dedicated-agent.js.map +1 -0
- package/dist/cli/backend/src/services/people/people-directory.service.d.ts +239 -0
- package/dist/cli/backend/src/services/people/people-directory.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/people/people-directory.service.js +454 -0
- package/dist/cli/backend/src/services/people/people-directory.service.js.map +1 -0
- package/dist/cli/backend/src/services/runtime-fallback/effective-runtime.d.ts +29 -1
- package/dist/cli/backend/src/services/runtime-fallback/effective-runtime.d.ts.map +1 -1
- package/dist/cli/backend/src/services/runtime-fallback/effective-runtime.js +40 -0
- package/dist/cli/backend/src/services/runtime-fallback/effective-runtime.js.map +1 -1
- package/dist/cli/backend/src/services/runtime-fallback/runtime-fallback.types.d.ts +20 -7
- package/dist/cli/backend/src/services/runtime-fallback/runtime-fallback.types.d.ts.map +1 -1
- package/dist/cli/backend/src/services/runtime-fallback/runtime-fallback.types.js +24 -7
- package/dist/cli/backend/src/services/runtime-fallback/runtime-fallback.types.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-agent-dm.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-agent-dm.service.js +3 -0
- package/dist/cli/backend/src/services/slack/slack-agent-dm.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts +22 -0
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js +70 -0
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.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 +46 -0
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.d.ts +6 -0
- package/dist/cli/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.js +8 -0
- package/dist/cli/backend/src/services/slack/slack.service.js.map +1 -1
- 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 +4 -0
- package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/cli/backend/src/services/wiki/wiki-ingest.service.d.ts +3 -1
- package/dist/cli/backend/src/services/wiki/wiki-ingest.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/wiki/wiki-ingest.service.js.map +1 -1
- package/dist/cli/backend/src/types/index.d.ts +12 -1
- package/dist/cli/backend/src/types/index.d.ts.map +1 -1
- package/dist/cli/backend/src/types/index.js.map +1 -1
- package/dist/cli/backend/src/types/messaging.types.d.ts +11 -0
- package/dist/cli/backend/src/types/messaging.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/messaging.types.js.map +1 -1
- package/dist/cli/backend/src/types/v2/work-item.types.d.ts +13 -0
- package/dist/cli/backend/src/types/v2/work-item.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/v2/work-item.types.js.map +1 -1
- package/dist/cli/cli/src/utils/marketplace.d.ts +21 -0
- package/dist/cli/cli/src/utils/marketplace.d.ts.map +1 -1
- package/dist/cli/cli/src/utils/marketplace.js +35 -1
- package/dist/cli/cli/src/utils/marketplace.js.map +1 -1
- package/dist/cli/config/constants.d.ts +11 -0
- package/dist/cli/config/constants.d.ts.map +1 -1
- package/dist/cli/config/constants.js +11 -0
- package/dist/cli/config/constants.js.map +1 -1
- package/dist/cli/config/skills/marketplace-registry.d.ts +4 -4
- package/dist/cli/config/skills/marketplace-registry.js +4 -4
- package/frontend/dist/assets/{index-fc715ec3.css → index-5e967795.css} +1 -1
- package/frontend/dist/assets/{index-5b7af029.js → index-800118ca.js} +296 -296
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/packages/crewly-agent/src/runtime/tool-registry.test.ts +45 -0
- package/packages/crewly-agent/src/runtime/tool-registry.ts +40 -5
- package/packages/crewly-agent/src/test/isolated-home.test.ts +24 -0
|
@@ -0,0 +1,754 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Experiment cards (issue #986, epic #982).
|
|
3
|
+
*
|
|
4
|
+
* An optimisation ticket carries an experiment: a hypothesis ("change X →
|
|
5
|
+
* metric Y from a to b"), a metric (Search Console / GA4 through the seo-ops
|
|
6
|
+
* skill), a baseline captured when the change ships and an observation
|
|
7
|
+
* window (default 14 days). When the window has settled the harness fetches
|
|
8
|
+
* the metric, labels the result worked / didn't / inconclusive (simple
|
|
9
|
+
* significance rules plus a minimum volume, see experiment-verdict.ts),
|
|
10
|
+
* resolves the agent's prediction, appends the result to the wiki
|
|
11
|
+
* experiment log and tells the owner.
|
|
12
|
+
*
|
|
13
|
+
* The experiment is a trace root: `traceId` (`exp:EXP-n`) names the run, and
|
|
14
|
+
* the timeline records every step (created, shipped, baseline, each fetch
|
|
15
|
+
* failure, measured, logged, reported), so the whole run can be reviewed.
|
|
16
|
+
*
|
|
17
|
+
* State: `~/.crewly/experiments.json`. A tick (every 15 min) moves
|
|
18
|
+
* experiments forward, so restarts lose nothing.
|
|
19
|
+
*
|
|
20
|
+
* specs/experiment-cards.md
|
|
21
|
+
*
|
|
22
|
+
* @module services/experiments/experiment.service
|
|
23
|
+
*/
|
|
24
|
+
import { promises as fs } from 'fs';
|
|
25
|
+
import path from 'path';
|
|
26
|
+
import { EXPERIMENT_CONSTANTS } from '../../constants.js';
|
|
27
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
28
|
+
import { isExperimentSource, isExperimentStatus, } from '../../types/experiment.types.js';
|
|
29
|
+
import { decideVerdict, defaultDirection, experimentWindows, formatValue } from './experiment-verdict.js';
|
|
30
|
+
/** An error with an HTTP status. */
|
|
31
|
+
export class ExperimentError extends Error {
|
|
32
|
+
status;
|
|
33
|
+
/**
|
|
34
|
+
* @param status - HTTP status
|
|
35
|
+
* @param message - What went wrong
|
|
36
|
+
*/
|
|
37
|
+
constructor(status, message) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.status = status;
|
|
40
|
+
this.name = 'ExperimentError';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Error text.
|
|
45
|
+
*
|
|
46
|
+
* @param err - Anything thrown
|
|
47
|
+
* @returns Message
|
|
48
|
+
*/
|
|
49
|
+
function errText(err) {
|
|
50
|
+
return err instanceof Error ? err.message : String(err);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Trimmed, length-capped string, or undefined.
|
|
54
|
+
*
|
|
55
|
+
* @param v - Value
|
|
56
|
+
* @returns Text
|
|
57
|
+
*/
|
|
58
|
+
function text(v) {
|
|
59
|
+
if (typeof v !== 'string')
|
|
60
|
+
return undefined;
|
|
61
|
+
const t = v.trim();
|
|
62
|
+
return t ? t.slice(0, EXPERIMENT_CONSTANTS.MAX_TEXT_LENGTH) : undefined;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Validate a metric definition.
|
|
66
|
+
*
|
|
67
|
+
* @param raw - Body
|
|
68
|
+
* @returns Metric
|
|
69
|
+
* @throws ExperimentError(400) with what is wrong
|
|
70
|
+
*/
|
|
71
|
+
export function validateMetric(raw) {
|
|
72
|
+
if (!raw || typeof raw !== 'object')
|
|
73
|
+
throw new ExperimentError(400, 'metric is required: {source, measure, config, …}');
|
|
74
|
+
const m = raw;
|
|
75
|
+
if (!isExperimentSource(m.source))
|
|
76
|
+
throw new ExperimentError(400, 'metric.source must be gsc or ga4');
|
|
77
|
+
const measures = EXPERIMENT_CONSTANTS.SOURCE_MEASURES[m.source] ?? [];
|
|
78
|
+
if (typeof m.measure !== 'string' || !measures.includes(m.measure)) {
|
|
79
|
+
throw new ExperimentError(400, `metric.measure for ${m.source} must be one of: ${measures.join(', ')}`);
|
|
80
|
+
}
|
|
81
|
+
const config = text(m.config);
|
|
82
|
+
if (!config || !path.isAbsolute(config))
|
|
83
|
+
throw new ExperimentError(400, 'metric.config must be the absolute path of the seo-ops site config');
|
|
84
|
+
const out = { source: m.source, measure: m.measure, config };
|
|
85
|
+
const page = text(m.page);
|
|
86
|
+
if (page)
|
|
87
|
+
out.page = page;
|
|
88
|
+
if (m.pageMatch !== undefined) {
|
|
89
|
+
if (m.pageMatch !== 'exact' && m.pageMatch !== 'contains')
|
|
90
|
+
throw new ExperimentError(400, 'metric.pageMatch must be exact or contains');
|
|
91
|
+
out.pageMatch = m.pageMatch;
|
|
92
|
+
}
|
|
93
|
+
const query = text(m.query);
|
|
94
|
+
if (query) {
|
|
95
|
+
if (m.source !== 'gsc')
|
|
96
|
+
throw new ExperimentError(400, 'metric.query is a Search Console filter (source gsc)');
|
|
97
|
+
out.query = query;
|
|
98
|
+
}
|
|
99
|
+
if (m.queryMatch !== undefined) {
|
|
100
|
+
if (m.queryMatch !== 'exact' && m.queryMatch !== 'contains')
|
|
101
|
+
throw new ExperimentError(400, 'metric.queryMatch must be exact or contains');
|
|
102
|
+
out.queryMatch = m.queryMatch;
|
|
103
|
+
}
|
|
104
|
+
const event = text(m.event);
|
|
105
|
+
if (out.measure === 'events' && !event)
|
|
106
|
+
throw new ExperimentError(400, 'metric.event is required for ga4 events (e.g. generate_lead)');
|
|
107
|
+
if (event)
|
|
108
|
+
out.event = event;
|
|
109
|
+
const channel = text(m.channel);
|
|
110
|
+
if (channel) {
|
|
111
|
+
if (m.source !== 'ga4')
|
|
112
|
+
throw new ExperimentError(400, 'metric.channel is a GA4 filter (source ga4)');
|
|
113
|
+
out.channel = channel;
|
|
114
|
+
}
|
|
115
|
+
const label = text(m.label);
|
|
116
|
+
if (label)
|
|
117
|
+
out.label = label;
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Validate a ticket link.
|
|
122
|
+
*
|
|
123
|
+
* @param raw - Body
|
|
124
|
+
* @returns Link or undefined
|
|
125
|
+
* @throws ExperimentError(400)
|
|
126
|
+
*/
|
|
127
|
+
export function validateTicketLink(raw) {
|
|
128
|
+
if (raw === undefined || raw === null || raw === '')
|
|
129
|
+
return undefined;
|
|
130
|
+
if (typeof raw !== 'object')
|
|
131
|
+
throw new ExperimentError(400, 'ticket must be {kind: "project", project, id} or {kind: "harness", id}');
|
|
132
|
+
const t = raw;
|
|
133
|
+
const id = text(t.id);
|
|
134
|
+
if (!id)
|
|
135
|
+
throw new ExperimentError(400, 'ticket.id is required');
|
|
136
|
+
if (t.kind === 'harness')
|
|
137
|
+
return { kind: 'harness', id };
|
|
138
|
+
if (t.kind === 'project') {
|
|
139
|
+
const project = text(t.project);
|
|
140
|
+
if (!project)
|
|
141
|
+
throw new ExperimentError(400, 'ticket.project is required for a project ticket');
|
|
142
|
+
return { kind: 'project', project, id };
|
|
143
|
+
}
|
|
144
|
+
throw new ExperimentError(400, 'ticket.kind must be project or harness');
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* How a ticket link reads.
|
|
148
|
+
*
|
|
149
|
+
* @param link - Link
|
|
150
|
+
* @returns e.g. "ce-site/T-12" or "TKT-40"
|
|
151
|
+
*/
|
|
152
|
+
export function ticketLabel(link) {
|
|
153
|
+
return link.kind === 'project' ? `${link.project}/${link.id}` : link.id;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The verdict word for people.
|
|
157
|
+
*
|
|
158
|
+
* @param e - Experiment
|
|
159
|
+
* @returns worked / didn't work / inconclusive
|
|
160
|
+
*/
|
|
161
|
+
function verdictWord(e) {
|
|
162
|
+
return e.verdict === 'worked' ? 'worked' : e.verdict === 'didnt' ? "didn't work" : 'inconclusive';
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The metric as one line.
|
|
166
|
+
*
|
|
167
|
+
* @param m - Metric
|
|
168
|
+
* @returns e.g. "gsc clicks · page https://… · query visa"
|
|
169
|
+
*/
|
|
170
|
+
export function metricLabel(m) {
|
|
171
|
+
const parts = [m.label ?? `${m.source} ${m.measure}`];
|
|
172
|
+
if (m.page)
|
|
173
|
+
parts.push(`page ${m.page}`);
|
|
174
|
+
if (m.query)
|
|
175
|
+
parts.push(`query ${m.query}`);
|
|
176
|
+
if (m.event)
|
|
177
|
+
parts.push(`event ${m.event}`);
|
|
178
|
+
return parts.join(' · ');
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* The result as markdown (wiki log entry and owner message share it).
|
|
182
|
+
*
|
|
183
|
+
* @param e - A measured experiment
|
|
184
|
+
* @returns Markdown
|
|
185
|
+
*/
|
|
186
|
+
export function resultSummary(e) {
|
|
187
|
+
const lines = [
|
|
188
|
+
`**${e.id} ${verdictWord(e)}: ${e.title}**`,
|
|
189
|
+
`Hypothesis: ${e.hypothesis}`,
|
|
190
|
+
`Metric: ${metricLabel(e.metric)}`,
|
|
191
|
+
`Result: ${e.verdictReason ?? 'n/a'}`,
|
|
192
|
+
];
|
|
193
|
+
if (e.expected?.to !== undefined && e.result) {
|
|
194
|
+
const reached = e.direction === 'increase' ? (e.result.total ?? -Infinity) >= e.expected.to : (e.result.total ?? Infinity) <= e.expected.to;
|
|
195
|
+
lines.push(`Target ${formatValue(e.expected.to, e.metric.measure)}: ${reached ? 'reached' : 'not reached'}`);
|
|
196
|
+
}
|
|
197
|
+
if (e.baseline && e.result)
|
|
198
|
+
lines.push(`Baseline ${e.baseline.start}..${e.baseline.end} · Result ${e.result.start}..${e.result.end}`);
|
|
199
|
+
if (e.ticket)
|
|
200
|
+
lines.push(`Ticket: ${ticketLabel(e.ticket)}`);
|
|
201
|
+
lines.push(`Trace: ${e.traceId}`);
|
|
202
|
+
return lines.join('\n');
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Experiment cards: store, lifecycle and the measuring tick.
|
|
206
|
+
*/
|
|
207
|
+
export class ExperimentService {
|
|
208
|
+
deps;
|
|
209
|
+
static instance = null;
|
|
210
|
+
logger;
|
|
211
|
+
now;
|
|
212
|
+
chain = Promise.resolve();
|
|
213
|
+
timer = null;
|
|
214
|
+
/** A tick is running (fetches can be slow; ticks never overlap) */
|
|
215
|
+
ticking = false;
|
|
216
|
+
/**
|
|
217
|
+
* @param deps - Dependencies
|
|
218
|
+
*/
|
|
219
|
+
constructor(deps) {
|
|
220
|
+
this.deps = deps;
|
|
221
|
+
this.logger = deps.logger ?? LoggerService.getInstance().createComponentLogger('ExperimentService');
|
|
222
|
+
this.now = deps.now ?? (() => new Date());
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* The installed service (null before boot).
|
|
226
|
+
*
|
|
227
|
+
* @returns Service or null
|
|
228
|
+
*/
|
|
229
|
+
static getInstance() {
|
|
230
|
+
return ExperimentService.instance;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Install the process service.
|
|
234
|
+
*
|
|
235
|
+
* @param service - Service, or null to clear
|
|
236
|
+
*/
|
|
237
|
+
static setInstance(service) {
|
|
238
|
+
ExperimentService.instance = service;
|
|
239
|
+
}
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
// Lifecycle
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
/**
|
|
244
|
+
* Start the periodic tick (runs once right away).
|
|
245
|
+
*
|
|
246
|
+
* @param intervalMs - Interval (0 = no timer; tests call {@link tick})
|
|
247
|
+
*/
|
|
248
|
+
start(intervalMs = EXPERIMENT_CONSTANTS.TICK_INTERVAL_MS) {
|
|
249
|
+
if (intervalMs > 0 && !this.timer) {
|
|
250
|
+
const run = () => {
|
|
251
|
+
void this.tick().catch((err) => this.logger.warn('Experiment tick failed (non-fatal)', { error: errText(err) }));
|
|
252
|
+
};
|
|
253
|
+
this.timer = setInterval(run, intervalMs);
|
|
254
|
+
this.timer.unref?.();
|
|
255
|
+
run();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/** Stop the tick. */
|
|
259
|
+
stop() {
|
|
260
|
+
if (this.timer)
|
|
261
|
+
clearInterval(this.timer);
|
|
262
|
+
this.timer = null;
|
|
263
|
+
}
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
// Store
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
/**
|
|
268
|
+
* Run one store operation at a time.
|
|
269
|
+
*
|
|
270
|
+
* @param fn - Operation
|
|
271
|
+
* @returns Its result
|
|
272
|
+
*/
|
|
273
|
+
serial(fn) {
|
|
274
|
+
const next = this.chain.then(fn, fn);
|
|
275
|
+
this.chain = next.catch(() => undefined);
|
|
276
|
+
return next;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Read the store.
|
|
280
|
+
*
|
|
281
|
+
* @returns Store data (empty when missing)
|
|
282
|
+
*/
|
|
283
|
+
async load() {
|
|
284
|
+
try {
|
|
285
|
+
const data = JSON.parse(await fs.readFile(this.deps.storeFile, 'utf-8'));
|
|
286
|
+
return { version: 1, nextNumber: data.nextNumber ?? 1, experiments: Array.isArray(data.experiments) ? data.experiments : [] };
|
|
287
|
+
}
|
|
288
|
+
catch (err) {
|
|
289
|
+
if (err.code !== 'ENOENT')
|
|
290
|
+
this.logger.warn('Experiment store unreadable; starting empty', { error: errText(err) });
|
|
291
|
+
return { version: 1, nextNumber: 1, experiments: [] };
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Write the store atomically.
|
|
296
|
+
*
|
|
297
|
+
* @param data - Store data
|
|
298
|
+
*/
|
|
299
|
+
async save(data) {
|
|
300
|
+
await fs.mkdir(path.dirname(this.deps.storeFile), { recursive: true });
|
|
301
|
+
const tmp = `${this.deps.storeFile}.${process.pid}.tmp`;
|
|
302
|
+
await fs.writeFile(tmp, JSON.stringify(data, null, 2));
|
|
303
|
+
await fs.rename(tmp, this.deps.storeFile);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Load, change one experiment, save.
|
|
307
|
+
*
|
|
308
|
+
* @param id - Experiment id
|
|
309
|
+
* @param fn - Change (mutates the experiment)
|
|
310
|
+
* @returns The experiment after the change
|
|
311
|
+
* @throws ExperimentError(404) when not found
|
|
312
|
+
*/
|
|
313
|
+
async mutate(id, fn) {
|
|
314
|
+
return this.serial(async () => {
|
|
315
|
+
const data = await this.load();
|
|
316
|
+
const e = data.experiments.find((x) => x.id.toLowerCase() === String(id).trim().toLowerCase());
|
|
317
|
+
if (!e)
|
|
318
|
+
throw new ExperimentError(404, `Experiment not found: ${id}`);
|
|
319
|
+
await fn(e);
|
|
320
|
+
e.updatedAt = this.now().toISOString();
|
|
321
|
+
await this.save(data);
|
|
322
|
+
return e;
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Add a timeline entry.
|
|
327
|
+
*
|
|
328
|
+
* @param e - Experiment
|
|
329
|
+
* @param event - Event name
|
|
330
|
+
* @param detail - Optional detail
|
|
331
|
+
*/
|
|
332
|
+
record(e, event, detail) {
|
|
333
|
+
e.timeline.push({ at: this.now().toISOString(), event, ...(detail ? { detail: detail.slice(0, 500) } : {}) });
|
|
334
|
+
if (e.timeline.length > EXPERIMENT_CONSTANTS.MAX_TIMELINE)
|
|
335
|
+
e.timeline.splice(0, e.timeline.length - EXPERIMENT_CONSTANTS.MAX_TIMELINE);
|
|
336
|
+
}
|
|
337
|
+
// ---------------------------------------------------------------------------
|
|
338
|
+
// Reads
|
|
339
|
+
// ---------------------------------------------------------------------------
|
|
340
|
+
/**
|
|
341
|
+
* All experiments, newest first.
|
|
342
|
+
*
|
|
343
|
+
* @param filter - Optional status / ticket filter
|
|
344
|
+
* @returns Experiments
|
|
345
|
+
*/
|
|
346
|
+
async list(filter = {}) {
|
|
347
|
+
const data = await this.serial(() => this.load());
|
|
348
|
+
return data.experiments
|
|
349
|
+
.filter((e) => !filter.status || e.status === filter.status)
|
|
350
|
+
.filter((e) => !filter.ticket || (e.ticket && (e.ticket.id === filter.ticket || ticketLabel(e.ticket) === filter.ticket)))
|
|
351
|
+
.reverse();
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* One experiment.
|
|
355
|
+
*
|
|
356
|
+
* @param id - EXP-n
|
|
357
|
+
* @returns Experiment or null
|
|
358
|
+
*/
|
|
359
|
+
async get(id) {
|
|
360
|
+
const data = await this.serial(() => this.load());
|
|
361
|
+
return data.experiments.find((e) => e.id.toLowerCase() === String(id).trim().toLowerCase()) ?? null;
|
|
362
|
+
}
|
|
363
|
+
// ---------------------------------------------------------------------------
|
|
364
|
+
// Writes
|
|
365
|
+
// ---------------------------------------------------------------------------
|
|
366
|
+
/**
|
|
367
|
+
* Create an experiment card.
|
|
368
|
+
*
|
|
369
|
+
* @param input - Hypothesis, metric, window, ticket, confidence
|
|
370
|
+
* @param caller - Agent session, or `owner`
|
|
371
|
+
* @returns The new experiment (status planned)
|
|
372
|
+
* @throws ExperimentError(400) on invalid input
|
|
373
|
+
*/
|
|
374
|
+
async create(input, caller) {
|
|
375
|
+
const C = EXPERIMENT_CONSTANTS;
|
|
376
|
+
const hypothesis = text(input.hypothesis);
|
|
377
|
+
if (!hypothesis)
|
|
378
|
+
throw new ExperimentError(400, 'hypothesis is required: "change X → metric Y from a to b"');
|
|
379
|
+
const metric = validateMetric(input.metric);
|
|
380
|
+
if (this.deps.fileExists && !(await this.deps.fileExists(metric.config))) {
|
|
381
|
+
throw new ExperimentError(400, `seo-ops config not found: ${metric.config}`);
|
|
382
|
+
}
|
|
383
|
+
let windowDays = C.DEFAULT_WINDOW_DAYS;
|
|
384
|
+
if (input.windowDays !== undefined && input.windowDays !== null && input.windowDays !== '') {
|
|
385
|
+
const n = Number(input.windowDays);
|
|
386
|
+
if (!Number.isInteger(n) || n < C.MIN_WINDOW_DAYS || n > C.MAX_WINDOW_DAYS) {
|
|
387
|
+
throw new ExperimentError(400, `windowDays must be a whole number from ${C.MIN_WINDOW_DAYS} to ${C.MAX_WINDOW_DAYS}`);
|
|
388
|
+
}
|
|
389
|
+
windowDays = n;
|
|
390
|
+
}
|
|
391
|
+
let direction = defaultDirection(metric.measure);
|
|
392
|
+
if (input.direction !== undefined && input.direction !== null && input.direction !== '') {
|
|
393
|
+
if (input.direction !== 'increase' && input.direction !== 'decrease')
|
|
394
|
+
throw new ExperimentError(400, 'direction must be increase or decrease');
|
|
395
|
+
direction = input.direction;
|
|
396
|
+
}
|
|
397
|
+
let expected;
|
|
398
|
+
if (input.expected && typeof input.expected === 'object') {
|
|
399
|
+
const raw = input.expected;
|
|
400
|
+
const from = raw.from === undefined || raw.from === null || raw.from === '' ? undefined : Number(raw.from);
|
|
401
|
+
const to = raw.to === undefined || raw.to === null || raw.to === '' ? undefined : Number(raw.to);
|
|
402
|
+
if ((from !== undefined && !Number.isFinite(from)) || (to !== undefined && !Number.isFinite(to))) {
|
|
403
|
+
throw new ExperimentError(400, 'expected.from / expected.to must be numbers');
|
|
404
|
+
}
|
|
405
|
+
if (from !== undefined || to !== undefined)
|
|
406
|
+
expected = { ...(from !== undefined ? { from } : {}), ...(to !== undefined ? { to } : {}) };
|
|
407
|
+
}
|
|
408
|
+
let confidence = C.DEFAULT_CONFIDENCE;
|
|
409
|
+
if (input.confidence !== undefined && input.confidence !== null && input.confidence !== '') {
|
|
410
|
+
const c = Number(input.confidence);
|
|
411
|
+
if (!Number.isFinite(c) || c < 0 || c > 1)
|
|
412
|
+
throw new ExperimentError(400, 'confidence must be between 0 and 1');
|
|
413
|
+
confidence = c;
|
|
414
|
+
}
|
|
415
|
+
const ticket = validateTicketLink(input.ticket);
|
|
416
|
+
const title = text(input.title) ?? hypothesis.slice(0, 80);
|
|
417
|
+
const created = await this.serial(async () => {
|
|
418
|
+
const data = await this.load();
|
|
419
|
+
const id = `${C.ID_PREFIX}${data.nextNumber}`;
|
|
420
|
+
data.nextNumber += 1;
|
|
421
|
+
const at = this.now().toISOString();
|
|
422
|
+
const e = {
|
|
423
|
+
id,
|
|
424
|
+
traceId: `exp:${id}`,
|
|
425
|
+
title,
|
|
426
|
+
hypothesis,
|
|
427
|
+
direction,
|
|
428
|
+
...(expected ? { expected } : {}),
|
|
429
|
+
metric,
|
|
430
|
+
windowDays,
|
|
431
|
+
...(ticket ? { ticket } : {}),
|
|
432
|
+
createdBy: caller,
|
|
433
|
+
confidence,
|
|
434
|
+
status: 'planned',
|
|
435
|
+
createdAt: at,
|
|
436
|
+
updatedAt: at,
|
|
437
|
+
timeline: [],
|
|
438
|
+
};
|
|
439
|
+
this.record(e, 'created', `by ${caller}${ticket ? ` on ${ticketLabel(ticket)}` : ''}`);
|
|
440
|
+
data.experiments.push(e);
|
|
441
|
+
await this.save(data);
|
|
442
|
+
return e;
|
|
443
|
+
});
|
|
444
|
+
if (ticket && this.deps.noteOnTicket) {
|
|
445
|
+
await this.deps.noteOnTicket(ticket, `Experiment ${created.id}: ${hypothesis} (metric: ${metricLabel(metric)}, ${windowDays}-day window; measured automatically after ship)`).catch((err) => this.logger.debug('Could not note the experiment on its ticket (non-fatal)', { id: created.id, error: errText(err) }));
|
|
446
|
+
}
|
|
447
|
+
this.logger.info('Experiment created', { id: created.id, caller, ticket: ticket ? ticketLabel(ticket) : undefined });
|
|
448
|
+
return created;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Mark an experiment shipped: fix its windows, record the prediction and
|
|
452
|
+
* capture the baseline.
|
|
453
|
+
*
|
|
454
|
+
* @param id - EXP-n
|
|
455
|
+
* @param caller - Who shipped it
|
|
456
|
+
* @param shippedAt - When it went live (default now; not in the future)
|
|
457
|
+
* @returns The experiment
|
|
458
|
+
* @throws ExperimentError(404 / 409 / 400)
|
|
459
|
+
*/
|
|
460
|
+
async ship(id, caller, shippedAt) {
|
|
461
|
+
const at = shippedAt ?? this.now().toISOString();
|
|
462
|
+
const t = Date.parse(at);
|
|
463
|
+
if (Number.isNaN(t))
|
|
464
|
+
throw new ExperimentError(400, 'shippedAt must be an ISO time');
|
|
465
|
+
if (t > this.now().getTime())
|
|
466
|
+
throw new ExperimentError(400, 'shippedAt is in the future');
|
|
467
|
+
await this.mutate(id, (e) => {
|
|
468
|
+
if (e.status !== 'planned')
|
|
469
|
+
throw new ExperimentError(409, `${e.id} is ${e.status}; only a planned experiment can ship`);
|
|
470
|
+
const w = experimentWindows(new Date(t).toISOString(), e.windowDays, e.metric.source);
|
|
471
|
+
e.status = 'running';
|
|
472
|
+
e.shippedAt = new Date(t).toISOString();
|
|
473
|
+
e.dueAt = w.dueAt;
|
|
474
|
+
e.fetchAttempts = 0;
|
|
475
|
+
e.stuckReported = false;
|
|
476
|
+
this.record(e, 'shipped', `by ${caller}; baseline ${w.baseline.start}..${w.baseline.end}, result ${w.observation.start}..${w.observation.end}, due ${w.dueAt.slice(0, 10)}`);
|
|
477
|
+
});
|
|
478
|
+
await this.recordPrediction(id);
|
|
479
|
+
await this.captureBaseline(id);
|
|
480
|
+
return (await this.get(id));
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Cancel an experiment that has not been measured.
|
|
484
|
+
*
|
|
485
|
+
* @param id - EXP-n
|
|
486
|
+
* @param caller - Who
|
|
487
|
+
* @param reason - Why
|
|
488
|
+
* @returns The experiment
|
|
489
|
+
* @throws ExperimentError(404 / 409)
|
|
490
|
+
*/
|
|
491
|
+
async cancel(id, caller, reason) {
|
|
492
|
+
return this.mutate(id, (e) => {
|
|
493
|
+
if (e.status === 'done' || e.status === 'cancelled')
|
|
494
|
+
throw new ExperimentError(409, `${e.id} is already ${e.status}`);
|
|
495
|
+
e.status = 'cancelled';
|
|
496
|
+
this.record(e, 'cancelled', `by ${caller}${reason ? `: ${reason}` : ''}`);
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Measure a due experiment now (instead of waiting for the tick).
|
|
501
|
+
*
|
|
502
|
+
* @param id - EXP-n
|
|
503
|
+
* @returns The experiment
|
|
504
|
+
* @throws ExperimentError(404 / 409) when it is not running or not yet due
|
|
505
|
+
*/
|
|
506
|
+
async measureNow(id) {
|
|
507
|
+
const e = await this.get(id);
|
|
508
|
+
if (!e)
|
|
509
|
+
throw new ExperimentError(404, `Experiment not found: ${id}`);
|
|
510
|
+
if (e.status !== 'running')
|
|
511
|
+
throw new ExperimentError(409, `${e.id} is ${e.status}; only a running experiment can be measured`);
|
|
512
|
+
if (e.dueAt && Date.parse(e.dueAt) > this.now().getTime()) {
|
|
513
|
+
throw new ExperimentError(409, `${e.id} is not due until ${e.dueAt.slice(0, 10)} (the window's numbers have not settled)`);
|
|
514
|
+
}
|
|
515
|
+
if (!e.baseline)
|
|
516
|
+
await this.captureBaseline(id);
|
|
517
|
+
await this.measure(id);
|
|
518
|
+
await this.followUp(id);
|
|
519
|
+
return (await this.get(id));
|
|
520
|
+
}
|
|
521
|
+
// ---------------------------------------------------------------------------
|
|
522
|
+
// The tick
|
|
523
|
+
// ---------------------------------------------------------------------------
|
|
524
|
+
/**
|
|
525
|
+
* Move every experiment forward: ship planned ones whose ticket shipped,
|
|
526
|
+
* retry missing baselines, measure due ones, finish follow-ups.
|
|
527
|
+
*
|
|
528
|
+
* @returns Counts of what happened (zeros when a tick is already running)
|
|
529
|
+
*/
|
|
530
|
+
async tick() {
|
|
531
|
+
if (this.ticking)
|
|
532
|
+
return { shipped: 0, measured: 0 };
|
|
533
|
+
this.ticking = true;
|
|
534
|
+
try {
|
|
535
|
+
return await this.runTick();
|
|
536
|
+
}
|
|
537
|
+
finally {
|
|
538
|
+
this.ticking = false;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* One pass over all experiments (see {@link tick}).
|
|
543
|
+
*
|
|
544
|
+
* @returns Counts of what happened
|
|
545
|
+
*/
|
|
546
|
+
async runTick() {
|
|
547
|
+
const all = await this.serial(() => this.load());
|
|
548
|
+
let shipped = 0;
|
|
549
|
+
let measured = 0;
|
|
550
|
+
const nowMs = this.now().getTime();
|
|
551
|
+
for (const e of all.experiments) {
|
|
552
|
+
try {
|
|
553
|
+
if (e.status === 'planned' && e.ticket && this.deps.ticketShippedAt) {
|
|
554
|
+
const at = await this.deps.ticketShippedAt(e.ticket);
|
|
555
|
+
if (at) {
|
|
556
|
+
const t = Math.min(Date.parse(at) || nowMs, nowMs);
|
|
557
|
+
await this.ship(e.id, `ticket ${ticketLabel(e.ticket)} done`, new Date(t).toISOString());
|
|
558
|
+
shipped += 1;
|
|
559
|
+
}
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
if (e.status === 'running') {
|
|
563
|
+
if (!e.baseline)
|
|
564
|
+
await this.captureBaseline(e.id);
|
|
565
|
+
if (e.dueAt && Date.parse(e.dueAt) <= nowMs) {
|
|
566
|
+
const after = await this.get(e.id);
|
|
567
|
+
if (after?.baseline) {
|
|
568
|
+
await this.measure(e.id);
|
|
569
|
+
if ((await this.get(e.id))?.status === 'done')
|
|
570
|
+
measured += 1;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (e.status === 'done' || (await this.get(e.id))?.status === 'done')
|
|
575
|
+
await this.followUp(e.id);
|
|
576
|
+
}
|
|
577
|
+
catch (err) {
|
|
578
|
+
this.logger.warn('Experiment step failed (non-fatal)', { id: e.id, error: errText(err) });
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return { shipped, measured };
|
|
582
|
+
}
|
|
583
|
+
// ---------------------------------------------------------------------------
|
|
584
|
+
// Steps
|
|
585
|
+
// ---------------------------------------------------------------------------
|
|
586
|
+
/**
|
|
587
|
+
* Record the creating agent's prediction that the hypothesis holds.
|
|
588
|
+
*
|
|
589
|
+
* @param id - EXP-n
|
|
590
|
+
*/
|
|
591
|
+
async recordPrediction(id) {
|
|
592
|
+
const e = await this.get(id);
|
|
593
|
+
if (!e || !this.deps.predictions || e.predictionId || e.createdBy === 'owner' || !e.dueAt)
|
|
594
|
+
return;
|
|
595
|
+
try {
|
|
596
|
+
const p = await this.deps.predictions.make(e.createdBy, `${e.id}: ${e.hypothesis}`, e.confidence, e.dueAt.slice(0, 10));
|
|
597
|
+
await this.mutate(id, (x) => {
|
|
598
|
+
x.predictionId = p.id;
|
|
599
|
+
this.record(x, 'prediction_recorded', `${p.id} for ${x.createdBy} at ${Math.round(x.confidence * 100)}%`);
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
catch (err) {
|
|
603
|
+
this.logger.debug('Could not record the experiment prediction (non-fatal)', { id, error: errText(err) });
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* A fetch failed: count it, and tell the owner once when it keeps failing.
|
|
608
|
+
*
|
|
609
|
+
* @param id - EXP-n
|
|
610
|
+
* @param step - baseline / result
|
|
611
|
+
* @param err - The error
|
|
612
|
+
*/
|
|
613
|
+
async fetchFailed(id, step, err) {
|
|
614
|
+
let tell = null;
|
|
615
|
+
await this.mutate(id, (e) => {
|
|
616
|
+
e.fetchAttempts = (e.fetchAttempts ?? 0) + 1;
|
|
617
|
+
e.lastError = errText(err).slice(0, 500);
|
|
618
|
+
this.record(e, 'fetch_failed', `${step}: ${e.lastError}`);
|
|
619
|
+
if (e.fetchAttempts >= EXPERIMENT_CONSTANTS.MAX_FETCH_ATTEMPTS && !e.stuckReported) {
|
|
620
|
+
e.stuckReported = true;
|
|
621
|
+
tell = { ...e };
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
if (tell && this.deps.notifyOwner) {
|
|
625
|
+
const e = tell;
|
|
626
|
+
await this.deps
|
|
627
|
+
.notifyOwner({
|
|
628
|
+
title: `Experiment ${e.id} can't fetch its ${step}`,
|
|
629
|
+
message: `${e.title}\nMetric: ${metricLabel(e.metric)}\nThe last ${EXPERIMENT_CONSTANTS.MAX_FETCH_ATTEMPTS} tries failed: ${e.lastError}\nIt keeps retrying; fix the seo-ops config or credentials (${e.metric.config}).`,
|
|
630
|
+
urgent: false,
|
|
631
|
+
})
|
|
632
|
+
.catch(() => false);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Fetch the baseline window.
|
|
637
|
+
*
|
|
638
|
+
* @param id - EXP-n
|
|
639
|
+
*/
|
|
640
|
+
async captureBaseline(id) {
|
|
641
|
+
const e = await this.get(id);
|
|
642
|
+
if (!e || e.status !== 'running' || e.baseline || !e.shippedAt)
|
|
643
|
+
return;
|
|
644
|
+
const w = experimentWindows(e.shippedAt, e.windowDays, e.metric.source);
|
|
645
|
+
let m;
|
|
646
|
+
try {
|
|
647
|
+
m = await this.deps.fetchMetric(e.metric, w.baseline);
|
|
648
|
+
}
|
|
649
|
+
catch (err) {
|
|
650
|
+
await this.fetchFailed(id, 'baseline', err);
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
await this.mutate(id, (x) => {
|
|
654
|
+
x.baseline = m;
|
|
655
|
+
x.fetchAttempts = 0;
|
|
656
|
+
x.stuckReported = false;
|
|
657
|
+
delete x.lastError;
|
|
658
|
+
this.record(x, 'baseline_captured', `${formatValue(m.total, x.metric.measure)} over ${m.start}..${m.end} (volume ${m.volume})`);
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Fetch the observation window and label the result.
|
|
663
|
+
*
|
|
664
|
+
* @param id - EXP-n
|
|
665
|
+
*/
|
|
666
|
+
async measure(id) {
|
|
667
|
+
const e = await this.get(id);
|
|
668
|
+
if (!e || e.status !== 'running' || !e.baseline || !e.shippedAt)
|
|
669
|
+
return;
|
|
670
|
+
const w = experimentWindows(e.shippedAt, e.windowDays, e.metric.source);
|
|
671
|
+
let m;
|
|
672
|
+
try {
|
|
673
|
+
m = await this.deps.fetchMetric(e.metric, w.observation);
|
|
674
|
+
}
|
|
675
|
+
catch (err) {
|
|
676
|
+
await this.fetchFailed(id, 'result', err);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
await this.mutate(id, (x) => {
|
|
680
|
+
if (!x.baseline)
|
|
681
|
+
return;
|
|
682
|
+
const v = decideVerdict(x.metric.measure, x.direction, x.baseline, m);
|
|
683
|
+
x.result = m;
|
|
684
|
+
x.verdict = v.verdict;
|
|
685
|
+
x.verdictReason = v.reason;
|
|
686
|
+
x.status = 'done';
|
|
687
|
+
x.fetchAttempts = 0;
|
|
688
|
+
delete x.lastError;
|
|
689
|
+
this.record(x, 'measured', `${v.verdict}: ${v.reason}`);
|
|
690
|
+
});
|
|
691
|
+
this.logger.info('Experiment measured', { id });
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* After a result: resolve the prediction, write the wiki log, tell the
|
|
695
|
+
* owner. Each step is retried by later ticks until it lands.
|
|
696
|
+
*
|
|
697
|
+
* @param id - EXP-n
|
|
698
|
+
*/
|
|
699
|
+
async followUp(id) {
|
|
700
|
+
const e = await this.get(id);
|
|
701
|
+
if (!e || e.status !== 'done' || !e.verdict)
|
|
702
|
+
return;
|
|
703
|
+
const summary = resultSummary(e);
|
|
704
|
+
if (e.predictionId && this.deps.predictions && !e.timeline.some((t) => t.event === 'prediction_resolved') && e.verdict !== 'inconclusive') {
|
|
705
|
+
try {
|
|
706
|
+
await this.deps.predictions.resolve(e.createdBy, e.predictionId, `${verdictWord(e)}: ${e.verdictReason ?? ''}`, e.verdict === 'worked');
|
|
707
|
+
await this.mutate(id, (x) => this.record(x, 'prediction_resolved', `${x.predictionId}: ${x.verdict === 'worked' ? 'accurate' : 'inaccurate'}`));
|
|
708
|
+
}
|
|
709
|
+
catch (err) {
|
|
710
|
+
this.logger.debug('Could not resolve the experiment prediction (non-fatal)', { id, error: errText(err) });
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
if (!e.loggedAt && this.deps.writeLog) {
|
|
714
|
+
const ok = await this.deps.writeLog(e, summary).catch((err) => {
|
|
715
|
+
this.logger.debug('Wiki experiment log write failed (non-fatal)', { id, error: errText(err) });
|
|
716
|
+
return false;
|
|
717
|
+
});
|
|
718
|
+
if (ok)
|
|
719
|
+
await this.mutate(id, (x) => {
|
|
720
|
+
x.loggedAt = this.now().toISOString();
|
|
721
|
+
this.record(x, 'logged', EXPERIMENT_CONSTANTS.WIKI_LOG_PATH);
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
if (!e.reportedAt && this.deps.notifyOwner) {
|
|
725
|
+
const sent = await this.deps
|
|
726
|
+
.notifyOwner({ title: `Experiment ${e.id} ${verdictWord(e)}`, message: summary, urgent: false })
|
|
727
|
+
.catch(() => false);
|
|
728
|
+
if (sent)
|
|
729
|
+
await this.mutate(id, (x) => {
|
|
730
|
+
x.reportedAt = this.now().toISOString();
|
|
731
|
+
this.record(x, 'reported', 'owner');
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
if (e.ticket && this.deps.noteOnTicket && !e.timeline.some((t) => t.event === 'ticket_noted')) {
|
|
735
|
+
try {
|
|
736
|
+
await this.deps.noteOnTicket(e.ticket, `Experiment ${e.id} ${verdictWord(e)}: ${e.verdictReason ?? ''}`);
|
|
737
|
+
await this.mutate(id, (x) => this.record(x, 'ticket_noted', ticketLabel(e.ticket)));
|
|
738
|
+
}
|
|
739
|
+
catch (err) {
|
|
740
|
+
this.logger.debug('Could not note the result on the ticket (non-fatal)', { id, error: errText(err) });
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Is this a status filter value?
|
|
747
|
+
*
|
|
748
|
+
* @param v - Query value
|
|
749
|
+
* @returns The status or undefined
|
|
750
|
+
*/
|
|
751
|
+
export function statusFilter(v) {
|
|
752
|
+
return isExperimentStatus(v) ? v : undefined;
|
|
753
|
+
}
|
|
754
|
+
//# sourceMappingURL=experiment.service.js.map
|