crewly 1.20.362 → 1.20.364
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/hooks/destructive-git-guard/guard.mjs +96 -0
- package/config/roles/orchestrator/prompt.md +3 -3
- package/config/roles/team-leader/prompt.md +2 -0
- package/config/skills/agent/core/ask-owner/SKILL.md +31 -0
- package/config/skills/agent/core/ask-owner/execute.sh +14 -3
- package/config/skills/agent/core/ask-owner/execute.test.sh +8 -0
- package/config/skills/agent/core/gmail-read/SKILL.md +8 -0
- package/config/skills/agent/core/gmail-send/SKILL.md +10 -3
- package/config/skills/agent/core/gmail-send/execute.sh +2 -1
- package/config/skills/agent/core/gmail-watch-thread/SKILL.md +69 -0
- package/config/skills/agent/core/gmail-watch-thread/execute.sh +51 -0
- package/config/skills/agent/core/project-tickets/SKILL.md +12 -0
- package/config/skills/agent/core/project-tickets/execute.sh +13 -3
- package/config/skills/agent/core/project-tickets/execute.test.sh +4 -0
- package/config/skills/agent/core/watch-for-event/SKILL.md +22 -0
- package/config/skills/orchestrator/delegate-task/SKILL.md +14 -0
- package/config/skills/orchestrator/delegate-task/execute.sh +19 -2
- package/config/skills/orchestrator/delegate-task/execute.test.sh +30 -0
- package/config/skills/orchestrator/harness-login/SKILL.md +1 -1
- package/config/skills/team-leader/delegate-task/execute.sh +6 -0
- package/dist/backend/backend/src/constants.d.ts +74 -2
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +75 -3
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/chat/chat.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/chat/chat.controller.js +14 -1
- package/dist/backend/backend/src/controllers/chat/chat.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/google/google.controller.d.ts +44 -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 +121 -3
- 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 +5 -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 +1 -1
- package/dist/backend/backend/src/controllers/harness/owner-login.controller.js +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js +2 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js.map +1 -1
- 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 +7 -0
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +61 -0
- package/dist/backend/backend/src/index.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 +2 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/backend/backend/src/services/decisions/decision-card.d.ts +46 -13
- package/dist/backend/backend/src/services/decisions/decision-card.d.ts.map +1 -1
- package/dist/backend/backend/src/services/decisions/decision-card.js +100 -50
- package/dist/backend/backend/src/services/decisions/decision-card.js.map +1 -1
- package/dist/backend/backend/src/services/decisions/decision-contract.d.ts +23 -1
- package/dist/backend/backend/src/services/decisions/decision-contract.d.ts.map +1 -1
- package/dist/backend/backend/src/services/decisions/decision-contract.js +76 -4
- package/dist/backend/backend/src/services/decisions/decision-contract.js.map +1 -1
- package/dist/backend/backend/src/services/decisions/decision-wording.d.ts +167 -0
- package/dist/backend/backend/src/services/decisions/decision-wording.d.ts.map +1 -0
- package/dist/backend/backend/src/services/decisions/decision-wording.js +406 -0
- package/dist/backend/backend/src/services/decisions/decision-wording.js.map +1 -0
- package/dist/backend/backend/src/services/decisions/decision.service.d.ts +30 -1
- package/dist/backend/backend/src/services/decisions/decision.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/decisions/decision.service.js +44 -8
- package/dist/backend/backend/src/services/decisions/decision.service.js.map +1 -1
- package/dist/backend/backend/src/services/decisions/decision.wiring.d.ts.map +1 -1
- package/dist/backend/backend/src/services/decisions/decision.wiring.js +17 -0
- package/dist/backend/backend/src/services/decisions/decision.wiring.js.map +1 -1
- package/dist/backend/backend/src/services/event-bus/event-bus.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/event-bus/event-bus.service.js +6 -1
- package/dist/backend/backend/src/services/event-bus/event-bus.service.js.map +1 -1
- package/dist/backend/backend/src/services/google/gmail-reply-watch.service.d.ts +142 -0
- package/dist/backend/backend/src/services/google/gmail-reply-watch.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/google/gmail-reply-watch.service.js +304 -0
- package/dist/backend/backend/src/services/google/gmail-reply-watch.service.js.map +1 -0
- package/dist/backend/backend/src/services/google/gmail-send-approval.service.d.ts +130 -0
- package/dist/backend/backend/src/services/google/gmail-send-approval.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/google/gmail-send-approval.service.js +216 -0
- package/dist/backend/backend/src/services/google/gmail-send-approval.service.js.map +1 -0
- package/dist/backend/backend/src/services/google/gmail-send-gate.d.ts +28 -13
- package/dist/backend/backend/src/services/google/gmail-send-gate.d.ts.map +1 -1
- package/dist/backend/backend/src/services/google/gmail-send-gate.js +82 -29
- package/dist/backend/backend/src/services/google/gmail-send-gate.js.map +1 -1
- package/dist/backend/backend/src/services/google/gmail.service.d.ts +90 -0
- package/dist/backend/backend/src/services/google/gmail.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/google/gmail.service.js +122 -0
- package/dist/backend/backend/src/services/google/gmail.service.js.map +1 -1
- package/dist/backend/backend/src/services/harness/claude-accounts.d.ts +24 -14
- package/dist/backend/backend/src/services/harness/claude-accounts.d.ts.map +1 -1
- package/dist/backend/backend/src/services/harness/claude-accounts.js +35 -21
- package/dist/backend/backend/src/services/harness/claude-accounts.js.map +1 -1
- package/dist/backend/backend/src/services/harness/claude-native-login.d.ts +106 -0
- package/dist/backend/backend/src/services/harness/claude-native-login.d.ts.map +1 -0
- package/dist/backend/backend/src/services/harness/claude-native-login.js +174 -0
- package/dist/backend/backend/src/services/harness/claude-native-login.js.map +1 -0
- package/dist/backend/backend/src/services/harness/claude-token-migration.service.d.ts +158 -0
- package/dist/backend/backend/src/services/harness/claude-token-migration.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/harness/claude-token-migration.service.js +283 -0
- package/dist/backend/backend/src/services/harness/claude-token-migration.service.js.map +1 -0
- package/dist/backend/backend/src/services/harness/harness-credentials.store.d.ts +44 -24
- 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 +96 -39
- package/dist/backend/backend/src/services/harness/harness-credentials.store.js.map +1 -1
- package/dist/backend/backend/src/services/harness/harness-registry.js +1 -1
- package/dist/backend/backend/src/services/harness/harness-registry.js.map +1 -1
- package/dist/backend/backend/src/services/harness/harness-relogin.service.d.ts +2 -2
- package/dist/backend/backend/src/services/harness/harness-relogin.service.js +2 -2
- package/dist/backend/backend/src/services/harness/harness.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/harness/harness.service.js +10 -1
- package/dist/backend/backend/src/services/harness/harness.service.js.map +1 -1
- package/dist/backend/backend/src/services/harness/login-broker.service.d.ts +24 -21
- 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 +50 -64
- package/dist/backend/backend/src/services/harness/login-broker.service.js.map +1 -1
- package/dist/backend/backend/src/services/harness/login-rules.d.ts +17 -11
- package/dist/backend/backend/src/services/harness/login-rules.d.ts.map +1 -1
- package/dist/backend/backend/src/services/harness/login-rules.js +22 -25
- package/dist/backend/backend/src/services/harness/login-rules.js.map +1 -1
- package/dist/backend/backend/src/services/open-items/open-item-extractor.d.ts +36 -0
- package/dist/backend/backend/src/services/open-items/open-item-extractor.d.ts.map +1 -1
- package/dist/backend/backend/src/services/open-items/open-item-extractor.js +70 -0
- package/dist/backend/backend/src/services/open-items/open-item-extractor.js.map +1 -1
- package/dist/backend/backend/src/services/open-items/open-items.service.d.ts +25 -0
- package/dist/backend/backend/src/services/open-items/open-items.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/open-items/open-items.service.js +65 -5
- package/dist/backend/backend/src/services/open-items/open-items.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 +5 -2
- package/dist/backend/backend/src/services/orc/orc-status-router.service.js.map +1 -1
- package/dist/backend/backend/src/services/orc/owner-completion-report.service.d.ts +50 -2
- package/dist/backend/backend/src/services/orc/owner-completion-report.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orc/owner-completion-report.service.js +103 -11
- package/dist/backend/backend/src/services/orc/owner-completion-report.service.js.map +1 -1
- package/dist/backend/backend/src/services/orc/owner-completion-report.text.d.ts +27 -0
- package/dist/backend/backend/src/services/orc/owner-completion-report.text.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orc/owner-completion-report.text.js +25 -0
- package/dist/backend/backend/src/services/orc/owner-completion-report.text.js.map +1 -1
- package/dist/backend/backend/src/services/orc/reply-destination-resolver.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orc/reply-destination-resolver.js +4 -2
- package/dist/backend/backend/src/services/orc/reply-destination-resolver.js.map +1 -1
- package/dist/backend/backend/src/services/orc/team-lead-report.d.ts +120 -0
- package/dist/backend/backend/src/services/orc/team-lead-report.d.ts.map +1 -0
- package/dist/backend/backend/src/services/orc/team-lead-report.js +198 -0
- package/dist/backend/backend/src/services/orc/team-lead-report.js.map +1 -0
- package/dist/backend/backend/src/services/orc/team-lead-report.wiring.d.ts +53 -0
- package/dist/backend/backend/src/services/orc/team-lead-report.wiring.d.ts.map +1 -0
- package/dist/backend/backend/src/services/orc/team-lead-report.wiring.js +85 -0
- package/dist/backend/backend/src/services/orc/team-lead-report.wiring.js.map +1 -0
- package/dist/backend/backend/src/services/orc/work-item-destination.d.ts +35 -0
- package/dist/backend/backend/src/services/orc/work-item-destination.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orc/work-item-destination.js +38 -1
- package/dist/backend/backend/src/services/orc/work-item-destination.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/project-ticket.service.d.ts +5 -0
- package/dist/backend/backend/src/services/project-tickets/project-ticket.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/project-ticket.service.js +30 -6
- package/dist/backend/backend/src/services/project-tickets/project-ticket.service.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-file.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-file.js +6 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-file.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-hold-registry.d.ts +64 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-hold-registry.d.ts.map +1 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-hold-registry.js +93 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-hold-registry.js.map +1 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-hold.d.ts +176 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-hold.d.ts.map +1 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-hold.js +301 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-hold.js.map +1 -0
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts +33 -3
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.js +50 -4
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.js.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts +8 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +26 -0
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts +6 -1
- package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler.service.js +3 -2
- package/dist/backend/backend/src/services/reconciler/reconciler.service.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 +4 -1
- package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.wiring.js.map +1 -1
- package/dist/backend/backend/src/services/stall/stall-watcher.service.d.ts +34 -0
- package/dist/backend/backend/src/services/stall/stall-watcher.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/stall/stall-watcher.service.js +70 -2
- package/dist/backend/backend/src/services/stall/stall-watcher.service.js.map +1 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +36 -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 +72 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/backend/backend/src/services/worktree/worktree-janitor.service.d.ts +3 -0
- package/dist/backend/backend/src/services/worktree/worktree-janitor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/worktree/worktree-janitor.service.js +5 -2
- package/dist/backend/backend/src/services/worktree/worktree-janitor.service.js.map +1 -1
- package/dist/backend/backend/src/services/worktree/worktree-unmerged-notice.d.ts +7 -2
- package/dist/backend/backend/src/services/worktree/worktree-unmerged-notice.d.ts.map +1 -1
- package/dist/backend/backend/src/services/worktree/worktree-unmerged-notice.js +11 -2
- package/dist/backend/backend/src/services/worktree/worktree-unmerged-notice.js.map +1 -1
- package/dist/backend/backend/src/types/decision.types.d.ts +53 -1
- package/dist/backend/backend/src/types/decision.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/event-bus.types.d.ts +10 -2
- package/dist/backend/backend/src/types/event-bus.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/event-bus.types.js +8 -0
- package/dist/backend/backend/src/types/event-bus.types.js.map +1 -1
- package/dist/backend/backend/src/types/project-ticket.types.d.ts +7 -0
- package/dist/backend/backend/src/types/project-ticket.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/project-ticket.types.js +1 -0
- package/dist/backend/backend/src/types/project-ticket.types.js.map +1 -1
- package/dist/backend/backend/src/types/v2/open-item.types.d.ts +8 -1
- package/dist/backend/backend/src/types/v2/open-item.types.d.ts.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +74 -2
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +75 -3
- package/dist/cli/backend/src/constants.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 +2 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/cli/backend/src/services/decisions/decision-card.d.ts +46 -13
- package/dist/cli/backend/src/services/decisions/decision-card.d.ts.map +1 -1
- package/dist/cli/backend/src/services/decisions/decision-card.js +100 -50
- package/dist/cli/backend/src/services/decisions/decision-card.js.map +1 -1
- package/dist/cli/backend/src/services/decisions/decision-contract.d.ts +23 -1
- package/dist/cli/backend/src/services/decisions/decision-contract.d.ts.map +1 -1
- package/dist/cli/backend/src/services/decisions/decision-contract.js +76 -4
- package/dist/cli/backend/src/services/decisions/decision-contract.js.map +1 -1
- package/dist/cli/backend/src/services/decisions/decision-wording.d.ts +167 -0
- package/dist/cli/backend/src/services/decisions/decision-wording.d.ts.map +1 -0
- package/dist/cli/backend/src/services/decisions/decision-wording.js +406 -0
- package/dist/cli/backend/src/services/decisions/decision-wording.js.map +1 -0
- package/dist/cli/backend/src/services/decisions/decision.service.d.ts +30 -1
- package/dist/cli/backend/src/services/decisions/decision.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/decisions/decision.service.js +44 -8
- package/dist/cli/backend/src/services/decisions/decision.service.js.map +1 -1
- package/dist/cli/backend/src/services/event-bus/event-bus.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/event-bus/event-bus.service.js +6 -1
- package/dist/cli/backend/src/services/event-bus/event-bus.service.js.map +1 -1
- package/dist/cli/backend/src/services/harness/claude-accounts.d.ts +24 -14
- package/dist/cli/backend/src/services/harness/claude-accounts.d.ts.map +1 -1
- package/dist/cli/backend/src/services/harness/claude-accounts.js +35 -21
- package/dist/cli/backend/src/services/harness/claude-accounts.js.map +1 -1
- package/dist/cli/backend/src/services/harness/claude-native-login.d.ts +106 -0
- package/dist/cli/backend/src/services/harness/claude-native-login.d.ts.map +1 -0
- package/dist/cli/backend/src/services/harness/claude-native-login.js +174 -0
- package/dist/cli/backend/src/services/harness/claude-native-login.js.map +1 -0
- package/dist/cli/backend/src/services/harness/claude-token-migration.service.d.ts +158 -0
- package/dist/cli/backend/src/services/harness/claude-token-migration.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/harness/claude-token-migration.service.js +283 -0
- package/dist/cli/backend/src/services/harness/claude-token-migration.service.js.map +1 -0
- package/dist/cli/backend/src/services/harness/harness-credentials.store.d.ts +44 -24
- 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 +96 -39
- package/dist/cli/backend/src/services/harness/harness-credentials.store.js.map +1 -1
- package/dist/cli/backend/src/services/harness/harness-registry.js +1 -1
- package/dist/cli/backend/src/services/harness/harness-registry.js.map +1 -1
- package/dist/cli/backend/src/services/harness/harness-relogin.service.d.ts +2 -2
- package/dist/cli/backend/src/services/harness/harness-relogin.service.js +2 -2
- package/dist/cli/backend/src/services/harness/harness.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/harness/harness.service.js +10 -1
- package/dist/cli/backend/src/services/harness/harness.service.js.map +1 -1
- package/dist/cli/backend/src/services/harness/login-broker.service.d.ts +24 -21
- 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 +50 -64
- package/dist/cli/backend/src/services/harness/login-broker.service.js.map +1 -1
- package/dist/cli/backend/src/services/harness/login-rules.d.ts +17 -11
- package/dist/cli/backend/src/services/harness/login-rules.d.ts.map +1 -1
- package/dist/cli/backend/src/services/harness/login-rules.js +22 -25
- package/dist/cli/backend/src/services/harness/login-rules.js.map +1 -1
- package/dist/cli/backend/src/services/open-items/open-item-extractor.d.ts +36 -0
- package/dist/cli/backend/src/services/open-items/open-item-extractor.d.ts.map +1 -1
- package/dist/cli/backend/src/services/open-items/open-item-extractor.js +70 -0
- package/dist/cli/backend/src/services/open-items/open-item-extractor.js.map +1 -1
- package/dist/cli/backend/src/services/open-items/open-items.service.d.ts +25 -0
- package/dist/cli/backend/src/services/open-items/open-items.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/open-items/open-items.service.js +65 -5
- package/dist/cli/backend/src/services/open-items/open-items.service.js.map +1 -1
- package/dist/cli/backend/src/services/orc/owner-completion-report.text.d.ts +113 -0
- package/dist/cli/backend/src/services/orc/owner-completion-report.text.d.ts.map +1 -0
- package/dist/cli/backend/src/services/orc/owner-completion-report.text.js +195 -0
- package/dist/cli/backend/src/services/orc/owner-completion-report.text.js.map +1 -0
- package/dist/cli/backend/src/services/orc/team-lead-report.d.ts +120 -0
- package/dist/cli/backend/src/services/orc/team-lead-report.d.ts.map +1 -0
- package/dist/cli/backend/src/services/orc/team-lead-report.js +198 -0
- package/dist/cli/backend/src/services/orc/team-lead-report.js.map +1 -0
- package/dist/cli/backend/src/services/orc/team-lead-report.wiring.d.ts +53 -0
- package/dist/cli/backend/src/services/orc/team-lead-report.wiring.d.ts.map +1 -0
- package/dist/cli/backend/src/services/orc/team-lead-report.wiring.js +85 -0
- package/dist/cli/backend/src/services/orc/team-lead-report.wiring.js.map +1 -0
- package/dist/cli/backend/src/services/orc/work-item-destination.d.ts +35 -0
- package/dist/cli/backend/src/services/orc/work-item-destination.d.ts.map +1 -1
- package/dist/cli/backend/src/services/orc/work-item-destination.js +38 -1
- package/dist/cli/backend/src/services/orc/work-item-destination.js.map +1 -1
- package/dist/cli/backend/src/services/project-tickets/project-ticket.service.d.ts +5 -0
- package/dist/cli/backend/src/services/project-tickets/project-ticket.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/project-tickets/project-ticket.service.js +30 -6
- package/dist/cli/backend/src/services/project-tickets/project-ticket.service.js.map +1 -1
- package/dist/cli/backend/src/services/project-tickets/ticket-file.d.ts.map +1 -1
- package/dist/cli/backend/src/services/project-tickets/ticket-file.js +6 -1
- package/dist/cli/backend/src/services/project-tickets/ticket-file.js.map +1 -1
- package/dist/cli/backend/src/services/project-tickets/ticket-hold.d.ts +176 -0
- package/dist/cli/backend/src/services/project-tickets/ticket-hold.d.ts.map +1 -0
- package/dist/cli/backend/src/services/project-tickets/ticket-hold.js +301 -0
- package/dist/cli/backend/src/services/project-tickets/ticket-hold.js.map +1 -0
- package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +36 -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 +72 -1
- package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/cli/backend/src/types/decision.types.d.ts +53 -1
- package/dist/cli/backend/src/types/decision.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/event-bus.types.d.ts +10 -2
- package/dist/cli/backend/src/types/event-bus.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/event-bus.types.js +8 -0
- package/dist/cli/backend/src/types/event-bus.types.js.map +1 -1
- package/dist/cli/backend/src/types/project-ticket.types.d.ts +7 -0
- package/dist/cli/backend/src/types/project-ticket.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/project-ticket.types.js +1 -0
- package/dist/cli/backend/src/types/project-ticket.types.js.map +1 -1
- package/dist/cli/backend/src/types/v2/open-item.types.d.ts +8 -1
- package/dist/cli/backend/src/types/v2/open-item.types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gmail Send Approval
|
|
3
|
+
*
|
|
4
|
+
* Puts a drafted email in front of the owner as a decision card and sends
|
|
5
|
+
* it only when the owner taps Send (CREW-257,
|
|
6
|
+
* specs/2026-10-06-gmail-approve-send-and-reply-wake.md).
|
|
7
|
+
*
|
|
8
|
+
* The send happens in exactly one place, {@link GmailSendApprovalService.onSettled},
|
|
9
|
+
* which the decision service calls when an owner answer settles a
|
|
10
|
+
* `gmail_send` card (button, reaction, thread reply, dashboard). Nothing an
|
|
11
|
+
* agent can call reaches it, so an agent cannot approve its own mail.
|
|
12
|
+
*
|
|
13
|
+
* Each approval is bound to one hold (`agent:draftId`) and to a fingerprint
|
|
14
|
+
* of the draft as the owner saw it. A hold settles once; a draft that was
|
|
15
|
+
* edited after the card was raised is not sent, a new card is raised for the
|
|
16
|
+
* edited text instead.
|
|
17
|
+
*
|
|
18
|
+
* @module services/google/gmail-send-approval
|
|
19
|
+
*/
|
|
20
|
+
import type { OwnerDecision, DecisionOption, GmailSendSubject } from '../../types/decision.types.js';
|
|
21
|
+
import type { DecisionKindHandler } from '../decisions/decision.service.js';
|
|
22
|
+
import type { GmailDraftContent, GmailSendResult } from './gmail.service.js';
|
|
23
|
+
import { type HeldSend } from './gmail-send-gate.js';
|
|
24
|
+
/** Option keys on the card. */
|
|
25
|
+
export declare const GMAIL_SEND_KEYS: {
|
|
26
|
+
readonly SEND: "send";
|
|
27
|
+
readonly NOT_NOW: "not_now";
|
|
28
|
+
};
|
|
29
|
+
/** How long the card waits before the (safe) default, Discard, applies. */
|
|
30
|
+
export declare const GMAIL_SEND_DEADLINE_MS: number;
|
|
31
|
+
/** The Gmail calls the approval needs. */
|
|
32
|
+
export interface GmailDraftApi {
|
|
33
|
+
getDraft(draftId: string): Promise<GmailDraftContent>;
|
|
34
|
+
sendDraft(draftId: string): Promise<GmailSendResult>;
|
|
35
|
+
}
|
|
36
|
+
/** The decision-service surface used here. */
|
|
37
|
+
export interface GmailApprovalDecisions {
|
|
38
|
+
askPrebuilt(ask: {
|
|
39
|
+
kind: 'gmail_send';
|
|
40
|
+
asker: string;
|
|
41
|
+
question: string;
|
|
42
|
+
options: DecisionOption[];
|
|
43
|
+
defaultKey: string;
|
|
44
|
+
yesKey: string;
|
|
45
|
+
deadline: Date;
|
|
46
|
+
sensitive: 'email';
|
|
47
|
+
gmail: GmailSendSubject;
|
|
48
|
+
}): Promise<OwnerDecision>;
|
|
49
|
+
chooseFromDashboard(id: string, optionKey: string): Promise<OwnerDecision>;
|
|
50
|
+
replyInThread?(id: string, text: string): Promise<boolean>;
|
|
51
|
+
cancelWhere?(predicate: (d: OwnerDecision) => boolean, reason: string): Promise<unknown>;
|
|
52
|
+
}
|
|
53
|
+
/** Dependencies, injected so tests need no Slack or Google. */
|
|
54
|
+
export interface GmailSendApprovalDeps {
|
|
55
|
+
/** Gmail access for the account the draft lives in */
|
|
56
|
+
gmailFor: (account?: string) => GmailDraftApi;
|
|
57
|
+
/** The decision service, once cards run */
|
|
58
|
+
decisions: () => GmailApprovalDecisions | null;
|
|
59
|
+
/** Whether a card can be posted at all */
|
|
60
|
+
canAskInSlack: () => boolean;
|
|
61
|
+
/** Tell the drafting agent what happened */
|
|
62
|
+
tellAgent: (session: string, message: string) => Promise<unknown>;
|
|
63
|
+
/** Display name for the card */
|
|
64
|
+
agentNameOf?: (session: string) => Promise<string | undefined>;
|
|
65
|
+
/** Called after a send so replies on the thread wake the agent */
|
|
66
|
+
onSent?: (info: {
|
|
67
|
+
threadId: string;
|
|
68
|
+
agentSession: string;
|
|
69
|
+
account?: string;
|
|
70
|
+
}) => Promise<void> | void;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* sha256 of everything that gets sent: sender, every recipient (including
|
|
74
|
+
* Bcc, a recipient the other parties never see), subject, both bodies and
|
|
75
|
+
* the attachments' names and sizes. A change to any of them is a different
|
|
76
|
+
* email from the one the owner approved.
|
|
77
|
+
*
|
|
78
|
+
* @param d - Draft content
|
|
79
|
+
* @returns Hex digest
|
|
80
|
+
*/
|
|
81
|
+
export declare function fingerprintDraft(d: GmailDraftContent): string;
|
|
82
|
+
/**
|
|
83
|
+
* The card question: who it goes to (all of them, Bcc included), what it says.
|
|
84
|
+
*
|
|
85
|
+
* @param agentName - Drafting agent
|
|
86
|
+
* @param d - The draft
|
|
87
|
+
* @returns mrkdwn
|
|
88
|
+
*/
|
|
89
|
+
export declare function approvalQuestion(agentName: string, d: GmailDraftContent): string;
|
|
90
|
+
/**
|
|
91
|
+
* Raises the send card and applies the owner's answer.
|
|
92
|
+
*/
|
|
93
|
+
export declare class GmailSendApprovalService implements DecisionKindHandler {
|
|
94
|
+
private readonly deps;
|
|
95
|
+
private readonly logger;
|
|
96
|
+
/** Holds being discarded by the owner right now (their settle note says the draft is gone) */
|
|
97
|
+
private readonly discarding;
|
|
98
|
+
constructor(deps: GmailSendApprovalDeps);
|
|
99
|
+
/**
|
|
100
|
+
* A draft was held: read it, remember what the owner will be shown and ask.
|
|
101
|
+
*
|
|
102
|
+
* @param hold - The held send
|
|
103
|
+
* @returns True when a card was raised
|
|
104
|
+
*/
|
|
105
|
+
onHeld(hold: HeldSend): Promise<boolean>;
|
|
106
|
+
/**
|
|
107
|
+
* The owner answered on the dashboard route: settle the card so it and the
|
|
108
|
+
* send agree.
|
|
109
|
+
*
|
|
110
|
+
* @param holdId - Hold id
|
|
111
|
+
* @param answer - What the owner chose
|
|
112
|
+
* @returns True when a card carried the answer
|
|
113
|
+
*/
|
|
114
|
+
answerFromOwner(holdId: string, answer: 'send' | 'discard'): Promise<boolean>;
|
|
115
|
+
/**
|
|
116
|
+
* The owner discarded the draft on the owner-only route (which deletes it).
|
|
117
|
+
* Settles the card as "not now" and tells the agent the draft is gone.
|
|
118
|
+
*
|
|
119
|
+
* @param holdId - Hold id
|
|
120
|
+
*/
|
|
121
|
+
discarded(holdId: string): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* A `gmail_send` card settled. The only place a held draft is sent.
|
|
124
|
+
*
|
|
125
|
+
* @param decision - The settled decision
|
|
126
|
+
* @returns Note for the drafting agent, or null
|
|
127
|
+
*/
|
|
128
|
+
onSettled(decision: OwnerDecision): Promise<string | null>;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=gmail-send-approval.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gmail-send-approval.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/google/gmail-send-approval.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gmail Send Approval
|
|
3
|
+
*
|
|
4
|
+
* Puts a drafted email in front of the owner as a decision card and sends
|
|
5
|
+
* it only when the owner taps Send (CREW-257,
|
|
6
|
+
* specs/2026-10-06-gmail-approve-send-and-reply-wake.md).
|
|
7
|
+
*
|
|
8
|
+
* The send happens in exactly one place, {@link GmailSendApprovalService.onSettled},
|
|
9
|
+
* which the decision service calls when an owner answer settles a
|
|
10
|
+
* `gmail_send` card (button, reaction, thread reply, dashboard). Nothing an
|
|
11
|
+
* agent can call reaches it, so an agent cannot approve its own mail.
|
|
12
|
+
*
|
|
13
|
+
* Each approval is bound to one hold (`agent:draftId`) and to a fingerprint
|
|
14
|
+
* of the draft as the owner saw it. A hold settles once; a draft that was
|
|
15
|
+
* edited after the card was raised is not sent, a new card is raised for the
|
|
16
|
+
* edited text instead.
|
|
17
|
+
*
|
|
18
|
+
* @module services/google/gmail-send-approval
|
|
19
|
+
*/
|
|
20
|
+
import { createHash } from 'node:crypto';
|
|
21
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
22
|
+
import { clearHeldSend, getHeldSend, holdGmailSend, markSettled, updateHeldSend, wasSettled } from './gmail-send-gate.js';
|
|
23
|
+
/** Option keys on the card. */
|
|
24
|
+
export const GMAIL_SEND_KEYS = { SEND: 'send', NOT_NOW: 'not_now' };
|
|
25
|
+
/** How long the card waits before the (safe) default, Discard, applies. */
|
|
26
|
+
export const GMAIL_SEND_DEADLINE_MS = 24 * 60 * 60 * 1000;
|
|
27
|
+
/** Body characters shown on the card. */
|
|
28
|
+
const PREVIEW_CHARS = 700;
|
|
29
|
+
/**
|
|
30
|
+
* sha256 of everything that gets sent: sender, every recipient (including
|
|
31
|
+
* Bcc, a recipient the other parties never see), subject, both bodies and
|
|
32
|
+
* the attachments' names and sizes. A change to any of them is a different
|
|
33
|
+
* email from the one the owner approved.
|
|
34
|
+
*
|
|
35
|
+
* @param d - Draft content
|
|
36
|
+
* @returns Hex digest
|
|
37
|
+
*/
|
|
38
|
+
export function fingerprintDraft(d) {
|
|
39
|
+
return createHash('sha256')
|
|
40
|
+
.update(JSON.stringify([d.from, d.to, d.cc, d.bcc, d.subject, d.body, d.html, d.attachments.map((a) => [a.filename, a.size])]))
|
|
41
|
+
.digest('hex');
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The card question: who it goes to (all of them, Bcc included), what it says.
|
|
45
|
+
*
|
|
46
|
+
* @param agentName - Drafting agent
|
|
47
|
+
* @param d - The draft
|
|
48
|
+
* @returns mrkdwn
|
|
49
|
+
*/
|
|
50
|
+
export function approvalQuestion(agentName, d) {
|
|
51
|
+
const body = d.body.length > PREVIEW_CHARS ? `${d.body.slice(0, PREVIEW_CHARS)}…` : d.body;
|
|
52
|
+
const lines = [`Send this to ${d.to}?`, `${agentName} drafted an email and wants it sent.`];
|
|
53
|
+
if (d.from)
|
|
54
|
+
lines.push(`*From:* ${d.from}`);
|
|
55
|
+
lines.push(`*To:* ${d.to}`);
|
|
56
|
+
if (d.cc)
|
|
57
|
+
lines.push(`*Cc:* ${d.cc}`);
|
|
58
|
+
if (d.bcc)
|
|
59
|
+
lines.push(`*Bcc:* ${d.bcc} _(the other recipients will not see this)_`);
|
|
60
|
+
lines.push(`*Subject:* ${d.subject}`);
|
|
61
|
+
if (d.attachments.length)
|
|
62
|
+
lines.push(`*Attachments:* ${d.attachments.map((a) => `${a.filename} (${a.size} B)`).join(', ')}`);
|
|
63
|
+
return `${lines.join('\n')}\n\n${body || '(no plain-text body)'}`;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Raises the send card and applies the owner's answer.
|
|
67
|
+
*/
|
|
68
|
+
export class GmailSendApprovalService {
|
|
69
|
+
deps;
|
|
70
|
+
logger;
|
|
71
|
+
/** Holds being discarded by the owner right now (their settle note says the draft is gone) */
|
|
72
|
+
discarding = new Set();
|
|
73
|
+
constructor(deps) {
|
|
74
|
+
this.deps = deps;
|
|
75
|
+
this.logger = LoggerService.getInstance().createComponentLogger('GmailSendApproval');
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A draft was held: read it, remember what the owner will be shown and ask.
|
|
79
|
+
*
|
|
80
|
+
* @param hold - The held send
|
|
81
|
+
* @returns True when a card was raised
|
|
82
|
+
*/
|
|
83
|
+
async onHeld(hold) {
|
|
84
|
+
const decisions = this.deps.decisions();
|
|
85
|
+
if (!decisions || !this.deps.canAskInSlack())
|
|
86
|
+
return false;
|
|
87
|
+
const gmail = this.deps.gmailFor(hold.account);
|
|
88
|
+
const draft = await gmail.getDraft(hold.draftId);
|
|
89
|
+
const fingerprint = fingerprintDraft(draft);
|
|
90
|
+
const agentName = (await this.deps.agentNameOf?.(hold.agentSession).catch(() => undefined)) ?? hold.agentSession;
|
|
91
|
+
const decision = await decisions.askPrebuilt({
|
|
92
|
+
kind: 'gmail_send',
|
|
93
|
+
asker: hold.agentSession,
|
|
94
|
+
question: approvalQuestion(agentName, draft),
|
|
95
|
+
options: [
|
|
96
|
+
{ key: GMAIL_SEND_KEYS.SEND, label: 'Yes', detail: 'send exactly this email, once' },
|
|
97
|
+
{ key: GMAIL_SEND_KEYS.NOT_NOW, label: 'Not now', detail: 'nothing is sent; the draft stays in Gmail' },
|
|
98
|
+
],
|
|
99
|
+
defaultKey: GMAIL_SEND_KEYS.NOT_NOW,
|
|
100
|
+
yesKey: GMAIL_SEND_KEYS.SEND,
|
|
101
|
+
deadline: new Date(Date.now() + GMAIL_SEND_DEADLINE_MS),
|
|
102
|
+
sensitive: 'email',
|
|
103
|
+
gmail: { agentSession: hold.agentSession, agentName, holdId: hold.id, draftId: hold.draftId, to: hold.to, subject: hold.subject },
|
|
104
|
+
});
|
|
105
|
+
updateHeldSend(hold.id, { fingerprint, decisionId: decision.id });
|
|
106
|
+
this.logger.info('Asked the owner about a drafted email', { holdId: hold.id, decisionId: decision.id });
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The owner answered on the dashboard route: settle the card so it and the
|
|
111
|
+
* send agree.
|
|
112
|
+
*
|
|
113
|
+
* @param holdId - Hold id
|
|
114
|
+
* @param answer - What the owner chose
|
|
115
|
+
* @returns True when a card carried the answer
|
|
116
|
+
*/
|
|
117
|
+
async answerFromOwner(holdId, answer) {
|
|
118
|
+
const hold = getHeldSend(holdId);
|
|
119
|
+
const decisions = this.deps.decisions();
|
|
120
|
+
if (!hold?.decisionId || !decisions)
|
|
121
|
+
return false;
|
|
122
|
+
await decisions.chooseFromDashboard(hold.decisionId, answer === 'send' ? GMAIL_SEND_KEYS.SEND : GMAIL_SEND_KEYS.NOT_NOW);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The owner discarded the draft on the owner-only route (which deletes it).
|
|
127
|
+
* Settles the card as "not now" and tells the agent the draft is gone.
|
|
128
|
+
*
|
|
129
|
+
* @param holdId - Hold id
|
|
130
|
+
*/
|
|
131
|
+
async discarded(holdId) {
|
|
132
|
+
this.discarding.add(holdId);
|
|
133
|
+
try {
|
|
134
|
+
await this.answerFromOwner(holdId, 'discard');
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
this.discarding.delete(holdId);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* A `gmail_send` card settled. The only place a held draft is sent.
|
|
142
|
+
*
|
|
143
|
+
* @param decision - The settled decision
|
|
144
|
+
* @returns Note for the drafting agent, or null
|
|
145
|
+
*/
|
|
146
|
+
async onSettled(decision) {
|
|
147
|
+
const subject = decision.gmail;
|
|
148
|
+
if (!subject)
|
|
149
|
+
return null;
|
|
150
|
+
const hold = getHeldSend(subject.holdId);
|
|
151
|
+
const sendChosen = decision.status === 'resolved' && decision.chosenKey === GMAIL_SEND_KEYS.SEND;
|
|
152
|
+
if (!hold || hold.decisionId !== decision.id) {
|
|
153
|
+
// Nothing to send. A Not now / expiry with no hold needs no news, but a
|
|
154
|
+
// Yes must never vanish: the card shows Yes, so say it was not sent.
|
|
155
|
+
if (!sendChosen || wasSettled(decision.id))
|
|
156
|
+
return null;
|
|
157
|
+
const why = !hold ? 'the pending send was lost (the draft is no longer held)' : 'this card was replaced by a newer one for the same draft';
|
|
158
|
+
const note = `Not sent: ${why}. The draft is still in Gmail.`;
|
|
159
|
+
this.logger.warn('Send approved but no matching hold — not sent', { holdId: subject.holdId, decisionId: decision.id, held: !!hold });
|
|
160
|
+
await this.deps.decisions()?.replyInThread?.(decision.id, note).catch(() => false);
|
|
161
|
+
return `[GMAIL] The owner tapped Yes on "${subject.subject}" to ${subject.to}, but it was NOT sent: ${why}. The draft is still in Gmail; ask the owner whether to try again.`;
|
|
162
|
+
}
|
|
163
|
+
markSettled(decision.id);
|
|
164
|
+
if (decision.status !== 'resolved' || decision.chosenKey !== GMAIL_SEND_KEYS.SEND) {
|
|
165
|
+
clearHeldSend(hold.id);
|
|
166
|
+
this.logger.info('Drafted email not sent', { holdId: hold.id, status: decision.status });
|
|
167
|
+
// Never deletes the draft: only an owner tap on Discard does (via discarded()).
|
|
168
|
+
if (this.discarding.has(hold.id))
|
|
169
|
+
return `[GMAIL] The owner discarded "${hold.subject}" to ${hold.to}; the draft was deleted. Do not send it another way.`;
|
|
170
|
+
if (decision.status === 'defaulted' || decision.status === 'expired') {
|
|
171
|
+
return `[GMAIL] "${hold.subject}" to ${hold.to} was not approved in 24h; nothing was sent and the draft is left in Gmail.`;
|
|
172
|
+
}
|
|
173
|
+
if (decision.status === 'resolved')
|
|
174
|
+
return `[GMAIL] The owner said not now to "${hold.subject}" to ${hold.to}. Nothing was sent; the draft stays in Gmail. Do not send it another way.`;
|
|
175
|
+
return `[GMAIL] The approval card for "${hold.subject}" to ${hold.to} was withdrawn. Nothing was sent; the draft is left in Gmail.`;
|
|
176
|
+
}
|
|
177
|
+
const gmail = this.deps.gmailFor(hold.account);
|
|
178
|
+
const current = await gmail.getDraft(hold.draftId);
|
|
179
|
+
if (!hold.fingerprint || fingerprintDraft(current) !== hold.fingerprint) {
|
|
180
|
+
// The text changed after the owner looked at it: that approval is for
|
|
181
|
+
// a different email. Ask again, with what is there now.
|
|
182
|
+
clearHeldSend(hold.id);
|
|
183
|
+
const reheld = holdGmailSend({
|
|
184
|
+
agentSession: hold.agentSession,
|
|
185
|
+
draftId: hold.draftId,
|
|
186
|
+
to: hold.to,
|
|
187
|
+
subject: hold.subject,
|
|
188
|
+
...(hold.claim ? { claim: hold.claim } : {}),
|
|
189
|
+
...(hold.account ? { account: hold.account } : {}),
|
|
190
|
+
});
|
|
191
|
+
await this.onHeld(reheld);
|
|
192
|
+
this.logger.warn('Drafted email changed after approval — not sent, asked again', { holdId: hold.id });
|
|
193
|
+
return `[GMAIL] "${hold.subject}" was edited after the owner approved it, so it was NOT sent. A new card with the edited text is waiting for the owner.`;
|
|
194
|
+
}
|
|
195
|
+
const sent = await gmail.sendDraft(hold.draftId);
|
|
196
|
+
clearHeldSend(hold.id);
|
|
197
|
+
// Audit line: who, what, to whom, on which owner decision.
|
|
198
|
+
this.logger.info('Owner approved a drafted email — sent', {
|
|
199
|
+
holdId: hold.id,
|
|
200
|
+
decisionId: decision.id,
|
|
201
|
+
to: hold.to,
|
|
202
|
+
subject: hold.subject,
|
|
203
|
+
messageId: sent.id,
|
|
204
|
+
threadId: sent.threadId,
|
|
205
|
+
agentSession: hold.agentSession,
|
|
206
|
+
});
|
|
207
|
+
try {
|
|
208
|
+
await this.deps.onSent?.({ threadId: sent.threadId, agentSession: hold.agentSession, ...(hold.account ? { account: hold.account } : {}) });
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
this.logger.warn('Could not watch the thread after sending', { threadId: sent.threadId, error: err instanceof Error ? err.message : String(err) });
|
|
212
|
+
}
|
|
213
|
+
return `[GMAIL] The owner approved and "${hold.subject}" was sent to ${hold.to} (thread ${sent.threadId}). Replies on that thread will wake you.`;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=gmail-send-approval.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gmail-send-approval.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/google/gmail-send-approval.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -57,7 +57,25 @@ export interface HeldSend {
|
|
|
57
57
|
claim?: string;
|
|
58
58
|
/** When it was held (epoch ms) */
|
|
59
59
|
heldAt: number;
|
|
60
|
+
/** Google account the draft lives in (X-Google-Account), when not the default */
|
|
61
|
+
account?: string;
|
|
62
|
+
/** sha256 of the draft's to/cc/subject/body when the owner was asked; an edit changes it */
|
|
63
|
+
fingerprint?: string;
|
|
64
|
+
/** The decision card the owner answers */
|
|
65
|
+
decisionId?: string;
|
|
60
66
|
}
|
|
67
|
+
/** Remember that a card's answer has been acted on. */
|
|
68
|
+
export declare function markSettled(decisionId: string): void;
|
|
69
|
+
/** True when this card's answer was already acted on. */
|
|
70
|
+
export declare function wasSettled(decisionId: string): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Persist holds to `file` and load whatever is already there (call once at
|
|
73
|
+
* startup, before cards can settle).
|
|
74
|
+
*
|
|
75
|
+
* @param file - JSON file, e.g. ~/.crewly/gmail-send-holds.json
|
|
76
|
+
* @returns Number of holds loaded
|
|
77
|
+
*/
|
|
78
|
+
export declare function loadHeldSends(file: string): number;
|
|
61
79
|
/**
|
|
62
80
|
* Record an attempted send and the instruction the agent cites for it.
|
|
63
81
|
*
|
|
@@ -70,20 +88,8 @@ export declare function holdGmailSend(input: {
|
|
|
70
88
|
to: string;
|
|
71
89
|
subject: string;
|
|
72
90
|
claim?: string;
|
|
91
|
+
account?: string;
|
|
73
92
|
}): HeldSend;
|
|
74
|
-
/**
|
|
75
|
-
* Spend an approval the owner gave for this agent.
|
|
76
|
-
*
|
|
77
|
-
* @param agentSession - The agent attempting to send
|
|
78
|
-
* @returns True when an approval was available and has now been used up
|
|
79
|
-
*/
|
|
80
|
-
export declare function consumeSendApproval(agentSession: string): boolean;
|
|
81
|
-
/**
|
|
82
|
-
* Grant one send to an agent.
|
|
83
|
-
*
|
|
84
|
-
* @param agentSession - The agent allowed to send
|
|
85
|
-
*/
|
|
86
|
-
export declare function grantSendApproval(agentSession: string): void;
|
|
87
93
|
/**
|
|
88
94
|
* Everything waiting on the owner, oldest first.
|
|
89
95
|
*
|
|
@@ -97,6 +103,15 @@ export declare function listHeldSends(): HeldSend[];
|
|
|
97
103
|
* @returns The hold, or undefined
|
|
98
104
|
*/
|
|
99
105
|
export declare function getHeldSend(id: string): HeldSend | undefined;
|
|
106
|
+
/**
|
|
107
|
+
* Merge fields into a hold (the fingerprint and card id once the owner has
|
|
108
|
+
* been asked).
|
|
109
|
+
*
|
|
110
|
+
* @param id - Hold id
|
|
111
|
+
* @param patch - Fields to set
|
|
112
|
+
* @returns The updated hold, or undefined when there is none
|
|
113
|
+
*/
|
|
114
|
+
export declare function updateHeldSend(id: string, patch: Partial<Pick<HeldSend, 'fingerprint' | 'decisionId'>>): HeldSend | undefined;
|
|
100
115
|
/**
|
|
101
116
|
* Drop a hold once it has been answered.
|
|
102
117
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gmail-send-gate.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/google/gmail-send-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;
|
|
1
|
+
{"version":3,"file":"gmail-send-gate.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/google/gmail-send-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAKH,8CAA8C;AAC9C,MAAM,WAAW,QAAQ;IACxB,8CAA8C;IAC9C,EAAE,EAAE,MAAM,CAAC;IACX,gCAAgC;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,EAAE,EAAE,MAAM,CAAC;IACX,mBAAmB;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4FAA4F;IAC5F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAYD,uDAAuD;AACvD,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAGpD;AAED,yDAAyD;AACzD,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAEtD;AAoBD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAclD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,QAAQ,CAcX;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,QAAQ,EAAE,CAE1C;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAE5D;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,aAAa,GAAG,YAAY,CAAC,CAAC,GAAG,QAAQ,GAAG,SAAS,CAO7H;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAIjD;AAED,+BAA+B;AAC/B,wBAAgB,kBAAkB,IAAI,IAAI,CAIzC"}
|
|
@@ -36,17 +36,68 @@
|
|
|
36
36
|
*
|
|
37
37
|
* @module services/google/gmail-send-gate
|
|
38
38
|
*/
|
|
39
|
-
import
|
|
40
|
-
|
|
39
|
+
import fs from 'node:fs';
|
|
40
|
+
import path from 'node:path';
|
|
41
41
|
/** Held sends, newest last, keyed by hold id. */
|
|
42
42
|
const held = new Map();
|
|
43
|
+
/** Where holds are persisted (set at startup); undefined = memory only. */
|
|
44
|
+
let storeFile;
|
|
45
|
+
/** Decision ids already acted on (bounded, persisted) so a repeat settle is not mistaken for a lost hold. */
|
|
46
|
+
const SETTLED_MAX = 200;
|
|
47
|
+
let settled = [];
|
|
48
|
+
/** Remember that a card's answer has been acted on. */
|
|
49
|
+
export function markSettled(decisionId) {
|
|
50
|
+
settled = [...settled.filter((d) => d !== decisionId), decisionId].slice(-SETTLED_MAX);
|
|
51
|
+
persist();
|
|
52
|
+
}
|
|
53
|
+
/** True when this card's answer was already acted on. */
|
|
54
|
+
export function wasSettled(decisionId) {
|
|
55
|
+
return settled.includes(decisionId);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Write the holds to disk (tmp + rename). A card waits up to 24 h and every
|
|
59
|
+
* release restarts the backend, so a hold kept only in memory would be lost
|
|
60
|
+
* while its card still shows Yes. Failure is swallowed: the in-memory hold
|
|
61
|
+
* still works until the next restart.
|
|
62
|
+
*/
|
|
63
|
+
function persist() {
|
|
64
|
+
if (!storeFile)
|
|
65
|
+
return;
|
|
66
|
+
try {
|
|
67
|
+
fs.mkdirSync(path.dirname(storeFile), { recursive: true });
|
|
68
|
+
const tmp = `${storeFile}.${process.pid}.tmp`;
|
|
69
|
+
fs.writeFileSync(tmp, JSON.stringify({ holds: Array.from(held.values()), settled }, null, 2));
|
|
70
|
+
fs.renameSync(tmp, storeFile);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
/* best effort */
|
|
74
|
+
}
|
|
75
|
+
}
|
|
43
76
|
/**
|
|
44
|
-
*
|
|
77
|
+
* Persist holds to `file` and load whatever is already there (call once at
|
|
78
|
+
* startup, before cards can settle).
|
|
45
79
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
80
|
+
* @param file - JSON file, e.g. ~/.crewly/gmail-send-holds.json
|
|
81
|
+
* @returns Number of holds loaded
|
|
48
82
|
*/
|
|
49
|
-
|
|
83
|
+
export function loadHeldSends(file) {
|
|
84
|
+
storeFile = file;
|
|
85
|
+
held.clear();
|
|
86
|
+
settled = [];
|
|
87
|
+
try {
|
|
88
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
89
|
+
if (Array.isArray(parsed.settled))
|
|
90
|
+
settled = parsed.settled.filter((x) => typeof x === 'string');
|
|
91
|
+
for (const h of Array.isArray(parsed.holds) ? parsed.holds : []) {
|
|
92
|
+
if (h && typeof h.id === 'string' && typeof h.draftId === 'string')
|
|
93
|
+
held.set(h.id, h);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
/* no file yet */
|
|
98
|
+
}
|
|
99
|
+
return held.size;
|
|
100
|
+
}
|
|
50
101
|
/**
|
|
51
102
|
* Record an attempted send and the instruction the agent cites for it.
|
|
52
103
|
*
|
|
@@ -61,32 +112,13 @@ export function holdGmailSend(input) {
|
|
|
61
112
|
to: input.to,
|
|
62
113
|
subject: input.subject,
|
|
63
114
|
...(input.claim ? { claim: input.claim } : {}),
|
|
115
|
+
...(input.account ? { account: input.account } : {}),
|
|
64
116
|
heldAt: Date.now(),
|
|
65
117
|
};
|
|
66
118
|
held.set(entry.id, entry);
|
|
119
|
+
persist();
|
|
67
120
|
return entry;
|
|
68
121
|
}
|
|
69
|
-
/**
|
|
70
|
-
* Spend an approval the owner gave for this agent.
|
|
71
|
-
*
|
|
72
|
-
* @param agentSession - The agent attempting to send
|
|
73
|
-
* @returns True when an approval was available and has now been used up
|
|
74
|
-
*/
|
|
75
|
-
export function consumeSendApproval(agentSession) {
|
|
76
|
-
if (!approved.has(agentSession))
|
|
77
|
-
return false;
|
|
78
|
-
approved.delete(agentSession);
|
|
79
|
-
logger.info('Agent spent an owner approval to send mail', { agentSession });
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Grant one send to an agent.
|
|
84
|
-
*
|
|
85
|
-
* @param agentSession - The agent allowed to send
|
|
86
|
-
*/
|
|
87
|
-
export function grantSendApproval(agentSession) {
|
|
88
|
-
approved.add(agentSession);
|
|
89
|
-
}
|
|
90
122
|
/**
|
|
91
123
|
* Everything waiting on the owner, oldest first.
|
|
92
124
|
*
|
|
@@ -104,6 +136,23 @@ export function listHeldSends() {
|
|
|
104
136
|
export function getHeldSend(id) {
|
|
105
137
|
return held.get(id);
|
|
106
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Merge fields into a hold (the fingerprint and card id once the owner has
|
|
141
|
+
* been asked).
|
|
142
|
+
*
|
|
143
|
+
* @param id - Hold id
|
|
144
|
+
* @param patch - Fields to set
|
|
145
|
+
* @returns The updated hold, or undefined when there is none
|
|
146
|
+
*/
|
|
147
|
+
export function updateHeldSend(id, patch) {
|
|
148
|
+
const cur = held.get(id);
|
|
149
|
+
if (!cur)
|
|
150
|
+
return undefined;
|
|
151
|
+
const next = { ...cur, ...patch };
|
|
152
|
+
held.set(id, next);
|
|
153
|
+
persist();
|
|
154
|
+
return next;
|
|
155
|
+
}
|
|
107
156
|
/**
|
|
108
157
|
* Drop a hold once it has been answered.
|
|
109
158
|
*
|
|
@@ -111,11 +160,15 @@ export function getHeldSend(id) {
|
|
|
111
160
|
* @returns True when there was one to drop
|
|
112
161
|
*/
|
|
113
162
|
export function clearHeldSend(id) {
|
|
114
|
-
|
|
163
|
+
const had = held.delete(id);
|
|
164
|
+
if (had)
|
|
165
|
+
persist();
|
|
166
|
+
return had;
|
|
115
167
|
}
|
|
116
168
|
/** Drops all state (tests). */
|
|
117
169
|
export function resetGmailSendGate() {
|
|
118
170
|
held.clear();
|
|
119
|
-
|
|
171
|
+
settled = [];
|
|
172
|
+
storeFile = undefined;
|
|
120
173
|
}
|
|
121
174
|
//# sourceMappingURL=gmail-send-gate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gmail-send-gate.js","sourceRoot":"","sources":["../../../../../../backend/src/services/google/gmail-send-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"gmail-send-gate.js","sourceRoot":"","sources":["../../../../../../backend/src/services/google/gmail-send-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AA+B7B,iDAAiD;AACjD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAoB,CAAC;AAEzC,2EAA2E;AAC3E,IAAI,SAA6B,CAAC;AAElC,6GAA6G;AAC7G,MAAM,WAAW,GAAG,GAAG,CAAC;AACxB,IAAI,OAAO,GAAa,EAAE,CAAC;AAE3B,uDAAuD;AACvD,MAAM,UAAU,WAAW,CAAC,UAAkB;IAC7C,OAAO,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,UAAU,CAAC,EAAE,UAAU,CAAC,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC,CAAC;IACvF,OAAO,EAAE,CAAC;AACX,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,UAAU,CAAC,UAAkB;IAC5C,OAAO,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;AACrC,CAAC;AAED;;;;;GAKG;AACH,SAAS,OAAO;IACf,IAAI,CAAC,SAAS;QAAE,OAAO;IACvB,IAAI,CAAC;QACJ,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,MAAM,GAAG,GAAG,GAAG,SAAS,IAAI,OAAO,CAAC,GAAG,MAAM,CAAC;QAC9C,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC9F,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACR,iBAAiB;IAClB,CAAC;AACF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACzC,SAAS,GAAG,IAAI,CAAC;IACjB,IAAI,CAAC,KAAK,EAAE,CAAC;IACb,OAAO,GAAG,EAAE,CAAC;IACb,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAA+C,CAAC;QACxG,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC;YAAE,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC;QACjG,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;YACjE,IAAI,CAAC,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ;gBAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QACvF,CAAC;IACF,CAAC;IAAC,MAAM,CAAC;QACR,iBAAiB;IAClB,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC;AAClB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,KAO7B;IACA,MAAM,KAAK,GAAa;QACvB,EAAE,EAAE,GAAG,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,OAAO,EAAE;QAC5C,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9C,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpD,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE;KAClB,CAAC;IACF,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC1B,OAAO,EAAE,CAAC;IACV,OAAO,KAAK,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa;IAC5B,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;AACtE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,EAAU;IACrC,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACrB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,EAAU,EAAE,KAA4D;IACtG,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACzB,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAC3B,MAAM,IAAI,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;IAClC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACnB,OAAO,EAAE,CAAC;IACV,OAAO,IAAI,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,EAAU;IACvC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC5B,IAAI,GAAG;QAAE,OAAO,EAAE,CAAC;IACnB,OAAO,GAAG,CAAC;AACZ,CAAC;AAED,+BAA+B;AAC/B,MAAM,UAAU,kBAAkB;IACjC,IAAI,CAAC,KAAK,EAAE,CAAC;IACb,OAAO,GAAG,EAAE,CAAC;IACb,SAAS,GAAG,SAAS,CAAC;AACvB,CAAC"}
|