@rubytech/create-realagent-code 0.1.592 → 0.1.594
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/package.json +2 -2
- package/payload/platform/package.json +1 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +11 -5
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +11 -0
- package/payload/platform/plugins/cloudflare/PLUGIN.md +2 -0
- package/payload/platform/plugins/docs/references/internals.md +5 -3
- package/payload/platform/plugins/docs/references/telegram-guide.md +5 -1
- package/payload/platform/plugins/scheduling/PLUGIN.md +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js +15 -2
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +7 -2
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js +125 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts +28 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js +72 -12
- package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js.map +1 -1
- package/payload/platform/scripts/__tests__/agent-identity-reconcile.test.sh +110 -0
- package/payload/platform/scripts/__tests__/check-orphan-dist.test.sh +87 -0
- package/payload/platform/scripts/check-orphan-dist.mjs +143 -0
- package/payload/platform/scripts/lib/agent-identity-reconcile.sh +122 -0
- package/payload/platform/scripts/lib/provision-account-dir.sh +8 -26
- package/payload/platform/scripts/setup-account.sh +15 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +37 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +178 -13
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +5 -0
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.d.ts +5 -0
- package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.js +42 -1
- package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-public-lockdown.d.ts +13 -5
- package/payload/platform/services/claude-session-manager/dist/rc-public-lockdown.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-public-lockdown.js +23 -9
- package/payload/platform/services/claude-session-manager/dist/rc-public-lockdown.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/telegram-channel-mcp.d.ts +7 -4
- package/payload/platform/services/claude-session-manager/dist/telegram-channel-mcp.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/telegram-channel-mcp.js +5 -3
- package/payload/platform/services/claude-session-manager/dist/telegram-channel-mcp.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/telegram-channel-store.d.ts +15 -1
- package/payload/platform/services/claude-session-manager/dist/telegram-channel-store.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/telegram-channel-store.js +16 -7
- package/payload/platform/services/claude-session-manager/dist/telegram-channel-store.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/telegram-plugin-mcp.d.ts +28 -0
- package/payload/platform/services/claude-session-manager/dist/telegram-plugin-mcp.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/telegram-plugin-mcp.js +53 -0
- package/payload/platform/services/claude-session-manager/dist/telegram-plugin-mcp.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.d.ts +9 -5
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.js +12 -6
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.d.ts +7 -3
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.js +5 -1
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.js.map +1 -1
- package/payload/platform/services/telegram-channel/dist/instructions.d.ts +14 -2
- package/payload/platform/services/telegram-channel/dist/instructions.d.ts.map +1 -1
- package/payload/platform/services/telegram-channel/dist/instructions.js +34 -3
- package/payload/platform/services/telegram-channel/dist/instructions.js.map +1 -1
- package/payload/platform/templates/agents/public/IDENTITY.md +3 -3
- package/payload/server/server.js +74 -9
- package/payload/platform/lib/anthropic-key/dist/index.d.ts +0 -22
- package/payload/platform/lib/anthropic-key/dist/index.d.ts.map +0 -1
- package/payload/platform/lib/anthropic-key/dist/index.js +0 -232
- package/payload/platform/lib/anthropic-key/dist/index.js.map +0 -1
- package/payload/platform/lib/graph-search/dist/query-expansion.d.ts +0 -37
- package/payload/platform/lib/graph-search/dist/query-expansion.d.ts.map +0 -1
- package/payload/platform/lib/graph-search/dist/query-expansion.js +0 -101
- package/payload/platform/lib/graph-search/dist/query-expansion.js.map +0 -1
- package/payload/platform/lib/oauth-llm/dist/index.d.ts +0 -116
- package/payload/platform/lib/oauth-llm/dist/index.d.ts.map +0 -1
- package/payload/platform/lib/oauth-llm/dist/index.js +0 -386
- package/payload/platform/lib/oauth-llm/dist/index.js.map +0 -1
- package/payload/platform/lib/screening-patterns/dist/index.d.ts +0 -29
- package/payload/platform/lib/screening-patterns/dist/index.d.ts.map +0 -1
- package/payload/platform/lib/screening-patterns/dist/index.js +0 -48
- package/payload/platform/lib/screening-patterns/dist/index.js.map +0 -1
- package/payload/platform/plugins/admin/mcp/dist/__tests__/admin-identity-authenticate.test.d.ts +0 -2
- package/payload/platform/plugins/admin/mcp/dist/__tests__/admin-identity-authenticate.test.d.ts.map +0 -1
- package/payload/platform/plugins/admin/mcp/dist/__tests__/admin-identity-authenticate.test.js +0 -34
- package/payload/platform/plugins/admin/mcp/dist/__tests__/admin-identity-authenticate.test.js.map +0 -1
- package/payload/platform/plugins/admin/mcp/dist/lib/onboarding.d.ts +0 -39
- package/payload/platform/plugins/admin/mcp/dist/lib/onboarding.d.ts.map +0 -1
- package/payload/platform/plugins/admin/mcp/dist/lib/onboarding.js +0 -249
- package/payload/platform/plugins/admin/mcp/dist/lib/onboarding.js.map +0 -1
- package/payload/platform/plugins/admin/mcp/dist/tools/admin-identity-authenticate.d.ts +0 -6
- package/payload/platform/plugins/admin/mcp/dist/tools/admin-identity-authenticate.d.ts.map +0 -1
- package/payload/platform/plugins/admin/mcp/dist/tools/admin-identity-authenticate.js +0 -32
- package/payload/platform/plugins/admin/mcp/dist/tools/admin-identity-authenticate.js.map +0 -1
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts +0 -283
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts.map +0 -1
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js +0 -1155
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js.map +0 -1
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.d.ts +0 -90
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.d.ts.map +0 -1
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.js +0 -551
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.js.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/lib/claude-bridge.d.ts +0 -17
- package/payload/platform/plugins/email/mcp/dist/lib/claude-bridge.d.ts.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/lib/claude-bridge.js +0 -186
- package/payload/platform/plugins/email/mcp/dist/lib/claude-bridge.js.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/lib/screening.d.ts +0 -29
- package/payload/platform/plugins/email/mcp/dist/lib/screening.d.ts.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/lib/screening.js +0 -105
- package/payload/platform/plugins/email/mcp/dist/lib/screening.js.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.d.ts +0 -38
- package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.d.ts.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.js +0 -817
- package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.js.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.d.ts +0 -30
- package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.d.ts.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.js +0 -215
- package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.js.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-auto-respond-config.d.ts +0 -19
- package/payload/platform/plugins/email/mcp/dist/tools/email-auto-respond-config.d.ts.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-auto-respond-config.js +0 -151
- package/payload/platform/plugins/email/mcp/dist/tools/email-auto-respond-config.js.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-classify.d.ts +0 -6
- package/payload/platform/plugins/email/mcp/dist/tools/email-classify.d.ts.map +0 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-classify.js +0 -89
- package/payload/platform/plugins/email/mcp/dist/tools/email-classify.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-rewriter-operator-hint.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-rewriter-operator-hint.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-rewriter-operator-hint.test.js +0 -90
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-rewriter-operator-hint.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js +0 -226
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.d.ts +0 -60
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.js +0 -169
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts +0 -248
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js +0 -824
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.d.ts +0 -63
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.js +0 -210
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.d.ts +0 -35
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.js +0 -28
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights-emit.test.js +0 -68
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write-emit.test.js +0 -88
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-classify-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-classify-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-classify-emit.test.js +0 -58
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-classify-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-delete-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-delete-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-delete-emit.test.js +0 -64
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-delete-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-edit-attachment-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-edit-attachment-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-edit-attachment-emit.test.js +0 -70
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-edit-attachment-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-emit.test.js +0 -48
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-emit.test.js +0 -74
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-extract-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-extract-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-extract-emit.test.js +0 -47
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-extract-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-web-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-web-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-web-emit.test.js +0 -65
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-web-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-reindex-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-reindex-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-reindex-emit.test.js +0 -43
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-reindex-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-rename-attachment-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-rename-attachment-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-rename-attachment-emit.test.js +0 -65
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-rename-attachment-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-restore-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-restore-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-restore-emit.test.js +0 -49
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-restore-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.js +0 -79
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-operator-hint.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-operator-hint.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-operator-hint.test.js +0 -329
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-operator-hint.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.d.ts +0 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.js +0 -81
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.d.ts +0 -34
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.js +0 -62
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-rank.d.ts +0 -61
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-rank.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-rank.js +0 -102
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-rank.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-signals-recent.d.ts +0 -35
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-signals-recent.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-signals-recent.js +0 -73
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-signals-recent.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.d.ts +0 -29
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.js +0 -22
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.d.ts +0 -30
- package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.js +0 -130
- package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.js.map +0 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.d.ts +0 -65
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.d.ts.map +0 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.js +0 -262
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.js.map +0 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/normalise-start-date.test.d.ts +0 -2
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/normalise-start-date.test.d.ts.map +0 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/normalise-start-date.test.js +0 -100
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/normalise-start-date.test.js.map +0 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/scheduled-seat-id.test.d.ts +0 -2
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/scheduled-seat-id.test.d.ts.map +0 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/scheduled-seat-id.test.js +0 -22
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/scheduled-seat-id.test.js.map +0 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/scheduled-seat-id.d.ts +0 -11
- package/payload/platform/plugins/scheduling/mcp/dist/lib/scheduled-seat-id.d.ts.map +0 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/scheduled-seat-id.js +0 -19
- package/payload/platform/plugins/scheduling/mcp/dist/lib/scheduled-seat-id.js.map +0 -1
- package/payload/platform/plugins/url-get/mcp/dist/lib/summarise.d.ts +0 -8
- package/payload/platform/plugins/url-get/mcp/dist/lib/summarise.d.ts.map +0 -1
- package/payload/platform/plugins/url-get/mcp/dist/lib/summarise.js +0 -83
- package/payload/platform/plugins/url-get/mcp/dist/lib/summarise.js.map +0 -1
- package/payload/platform/plugins/workflows/mcp/dist/lib/llm-call.d.ts +0 -151
- package/payload/platform/plugins/workflows/mcp/dist/lib/llm-call.d.ts.map +0 -1
- package/payload/platform/plugins/workflows/mcp/dist/lib/llm-call.js +0 -299
- package/payload/platform/plugins/workflows/mcp/dist/lib/llm-call.js.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/account-admin-census.d.ts +0 -39
- package/payload/platform/services/claude-session-manager/dist/account-admin-census.d.ts.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/account-admin-census.js +0 -122
- package/payload/platform/services/claude-session-manager/dist/account-admin-census.js.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/admin-identity-audit.d.ts +0 -39
- package/payload/platform/services/claude-session-manager/dist/admin-identity-audit.d.ts.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/admin-identity-audit.js +0 -133
- package/payload/platform/services/claude-session-manager/dist/admin-identity-audit.js.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/input-postcondition.d.ts +0 -22
- package/payload/platform/services/claude-session-manager/dist/input-postcondition.d.ts.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/input-postcondition.js +0 -50
- package/payload/platform/services/claude-session-manager/dist/input-postcondition.js.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-observer.d.ts +0 -30
- package/payload/platform/services/claude-session-manager/dist/jsonl-observer.d.ts.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-observer.js +0 -204
- package/payload/platform/services/claude-session-manager/dist/jsonl-observer.js.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.d.ts +0 -80
- package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.d.ts.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.js +0 -306
- package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.js.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/session-store.d.ts +0 -49
- package/payload/platform/services/claude-session-manager/dist/session-store.d.ts.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/session-store.js +0 -52
- package/payload/platform/services/claude-session-manager/dist/session-store.js.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/systemd-spawn.d.ts +0 -83
- package/payload/platform/services/claude-session-manager/dist/systemd-spawn.d.ts.map +0 -1
- package/payload/platform/services/claude-session-manager/dist/systemd-spawn.js +0 -345
- package/payload/platform/services/claude-session-manager/dist/systemd-spawn.js.map +0 -1
- package/payload/platform/services/telegram-channel/dist/turn-follow.d.ts +0 -104
- package/payload/platform/services/telegram-channel/dist/turn-follow.d.ts.map +0 -1
- package/payload/platform/services/telegram-channel/dist/turn-follow.js +0 -408
- package/payload/platform/services/telegram-channel/dist/turn-follow.js.map +0 -1
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.d.ts +0 -104
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.d.ts.map +0 -1
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.js +0 -393
- package/payload/platform/services/whatsapp-channel/dist/turn-follow.js.map +0 -1
|
@@ -1,817 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cron script: email-auto-respond
|
|
3
|
-
*
|
|
4
|
-
* Polls IMAP for new messages on EmailAccounts with autoRespond enabled.
|
|
5
|
-
* For each new message, invokes the assigned public agent via the Anthropic
|
|
6
|
-
* API and sends the reply via SMTP with threading headers.
|
|
7
|
-
*
|
|
8
|
-
* PID lock at $HOME/{configDir}/email-auto-respond.lock prevents overlapping runs.
|
|
9
|
-
*
|
|
10
|
-
* PLATFORM_ROOT=... node dist/scripts/email-auto-respond.js
|
|
11
|
-
*
|
|
12
|
-
* ┌─────────────────────────────────────────────────────────────────┐
|
|
13
|
-
* │ CRON (every 1 min) │
|
|
14
|
-
* │ \-> email-auto-respond.ts │
|
|
15
|
-
* │ ├── PID lock │
|
|
16
|
-
* │ ├── Neo4j: EmailAccounts with autoRespond=true │
|
|
17
|
-
* │ │ └── Skip if pollInterval hasn't elapsed │
|
|
18
|
-
* │ └── For each account: │
|
|
19
|
-
* │ ├── Read rate limit counters from Neo4j │
|
|
20
|
-
* │ │ └── Reset if clock hour/day boundary passed │
|
|
21
|
-
* │ ├── IMAP: fetch UID > lastRespondedUid │
|
|
22
|
-
* │ ├── For each new message: │
|
|
23
|
-
* │ │ ├── Skip auto-replies (RFC 3834) │
|
|
24
|
-
* │ │ ├── Skip own address │
|
|
25
|
-
* │ │ ├── Skip duplicate sender (1 reply/sender/poll) │
|
|
26
|
-
* │ │ ├── Check hourly/daily cap → break if hit │
|
|
27
|
-
* │ │ │ └── Log + Task node (does NOT disable) │
|
|
28
|
-
* │ │ ├── Assemble system prompt (IDENTITY+SOUL+KNOW) │
|
|
29
|
-
* │ │ ├── Anthropic API → generate reply │
|
|
30
|
-
* │ │ ├── SMTP: send with In-Reply-To/References │
|
|
31
|
-
* │ │ ├── Increment send counters │
|
|
32
|
-
* │ │ └── Update lastRespondedUid (per-email) │
|
|
33
|
-
* │ ├── Update lastPollAt │
|
|
34
|
-
* │ └── Circuit breaker (3 fails → disable + Task node) │
|
|
35
|
-
* └─────────────────────────────────────────────────────────────────┘
|
|
36
|
-
*/
|
|
37
|
-
import { dispatchEmailToClaude, emailTurnTimeoutMs } from "../lib/claude-bridge.js";
|
|
38
|
-
import neo4j from "neo4j-driver";
|
|
39
|
-
import { randomUUID } from "node:crypto";
|
|
40
|
-
import { readFileSync, writeFileSync, existsSync, unlinkSync, mkdirSync, appendFileSync } from "node:fs";
|
|
41
|
-
import { resolve } from "node:path";
|
|
42
|
-
import { homedir } from "node:os";
|
|
43
|
-
import { fetchForAutoRespond } from "../lib/imap.js";
|
|
44
|
-
import { sendEmail } from "../lib/smtp.js";
|
|
45
|
-
import { readPassword } from "../lib/credentials.js";
|
|
46
|
-
import { storeOutboundEmail, stripAngleBrackets } from "../lib/graph.js";
|
|
47
|
-
import { writeNodeWithEdges } from "../../../../../lib/graph-write/dist/index.js";
|
|
48
|
-
/**
|
|
49
|
-
* Look up the screening verdict for an email by its Message-ID.
|
|
50
|
-
* Returns { found: false } if the Email node doesn't exist yet
|
|
51
|
-
* (email-fetch hasn't processed it) or if messageId is null.
|
|
52
|
-
*/
|
|
53
|
-
async function lookupScreening(driver, messageId, accountId) {
|
|
54
|
-
if (!messageId)
|
|
55
|
-
return { found: false, screening: null, promptInjectionFlag: false };
|
|
56
|
-
const normalised = stripAngleBrackets(messageId);
|
|
57
|
-
if (!normalised)
|
|
58
|
-
return { found: false, screening: null, promptInjectionFlag: false };
|
|
59
|
-
const session = driver.session();
|
|
60
|
-
try {
|
|
61
|
-
const result = await session.run(`MATCH (e:Email {messageId: $messageId, accountId: $accountId})
|
|
62
|
-
RETURN e.screening AS screening, e.promptInjectionFlag AS promptInjectionFlag
|
|
63
|
-
LIMIT 1`, { messageId: normalised, accountId });
|
|
64
|
-
if (result.records.length === 0) {
|
|
65
|
-
return { found: false, screening: null, promptInjectionFlag: false };
|
|
66
|
-
}
|
|
67
|
-
const rec = result.records[0];
|
|
68
|
-
return {
|
|
69
|
-
found: true,
|
|
70
|
-
screening: rec.get("screening") ?? null,
|
|
71
|
-
promptInjectionFlag: rec.get("promptInjectionFlag") === true,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
finally {
|
|
75
|
-
await session.close();
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
// ---------------------------------------------------------------------------
|
|
79
|
-
// Constants
|
|
80
|
-
// ---------------------------------------------------------------------------
|
|
81
|
-
const CIRCUIT_BREAKER_THRESHOLD = 3;
|
|
82
|
-
const MAX_EMAILS_PER_POLL = 10;
|
|
83
|
-
const DEFAULT_HOURLY_LIMIT = 20;
|
|
84
|
-
const DEFAULT_DAILY_LIMIT = 100;
|
|
85
|
-
// Brand-aware config directory — reads configDir from brand.json stamped at install time.
|
|
86
|
-
// No fallback: if brand.json is missing or incomplete, the platform wasn't properly installed.
|
|
87
|
-
function resolveConfigDir() {
|
|
88
|
-
const platformRoot = process.env.PLATFORM_ROOT;
|
|
89
|
-
if (!platformRoot) {
|
|
90
|
-
throw new Error("PLATFORM_ROOT environment variable is required");
|
|
91
|
-
}
|
|
92
|
-
const brandPath = resolve(platformRoot, "config", "brand.json");
|
|
93
|
-
if (!existsSync(brandPath)) {
|
|
94
|
-
throw new Error(`brand.json not found at ${brandPath} — platform not properly installed`);
|
|
95
|
-
}
|
|
96
|
-
try {
|
|
97
|
-
const brand = JSON.parse(readFileSync(brandPath, "utf-8"));
|
|
98
|
-
if (!brand.configDir) {
|
|
99
|
-
throw new Error(`brand.json at ${brandPath} is missing the configDir field`);
|
|
100
|
-
}
|
|
101
|
-
return brand.configDir;
|
|
102
|
-
}
|
|
103
|
-
catch (err) {
|
|
104
|
-
if (err instanceof SyntaxError) {
|
|
105
|
-
throw new Error(`brand.json at ${brandPath} is not valid JSON: ${err.message}`);
|
|
106
|
-
}
|
|
107
|
-
throw err;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
const CONFIG_DIR = resolve(homedir(), resolveConfigDir());
|
|
111
|
-
const LOCK_DIR = CONFIG_DIR;
|
|
112
|
-
const LOCK_FILE = resolve(LOCK_DIR, "email-auto-respond.lock");
|
|
113
|
-
const LOG_DIR = resolve(CONFIG_DIR, "logs");
|
|
114
|
-
const LOG_FILE = resolve(LOG_DIR, "email-auto-respond.log");
|
|
115
|
-
// ---------------------------------------------------------------------------
|
|
116
|
-
// Logging
|
|
117
|
-
// ---------------------------------------------------------------------------
|
|
118
|
-
function log(level, message) {
|
|
119
|
-
const ts = new Date().toISOString();
|
|
120
|
-
const line = `[${ts}] [${level}] ${message}\n`;
|
|
121
|
-
mkdirSync(LOG_DIR, { recursive: true });
|
|
122
|
-
appendFileSync(LOG_FILE, line);
|
|
123
|
-
if (level === "error") {
|
|
124
|
-
console.error(line.trim());
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
// ---------------------------------------------------------------------------
|
|
128
|
-
// Lock file
|
|
129
|
-
// ---------------------------------------------------------------------------
|
|
130
|
-
function acquireLock() {
|
|
131
|
-
mkdirSync(LOCK_DIR, { recursive: true });
|
|
132
|
-
if (existsSync(LOCK_FILE)) {
|
|
133
|
-
const storedPid = readFileSync(LOCK_FILE, "utf-8").trim();
|
|
134
|
-
try {
|
|
135
|
-
process.kill(Number(storedPid), 0);
|
|
136
|
-
return false; // Process still running
|
|
137
|
-
}
|
|
138
|
-
catch {
|
|
139
|
-
unlinkSync(LOCK_FILE); // Stale lock
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
writeFileSync(LOCK_FILE, String(process.pid));
|
|
143
|
-
return true;
|
|
144
|
-
}
|
|
145
|
-
function releaseLock() {
|
|
146
|
-
try {
|
|
147
|
-
if (existsSync(LOCK_FILE)) {
|
|
148
|
-
const storedPid = readFileSync(LOCK_FILE, "utf-8").trim();
|
|
149
|
-
if (storedPid === String(process.pid)) {
|
|
150
|
-
unlinkSync(LOCK_FILE);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
catch {
|
|
155
|
-
// Best-effort cleanup
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
// ---------------------------------------------------------------------------
|
|
159
|
-
// Neo4j connection (standalone)
|
|
160
|
-
// ---------------------------------------------------------------------------
|
|
161
|
-
function connectNeo4j() {
|
|
162
|
-
const platformRoot = process.env.PLATFORM_ROOT;
|
|
163
|
-
if (!platformRoot) {
|
|
164
|
-
throw new Error("PLATFORM_ROOT environment variable is required");
|
|
165
|
-
}
|
|
166
|
-
let password;
|
|
167
|
-
if (process.env.NEO4J_PASSWORD) {
|
|
168
|
-
password = process.env.NEO4J_PASSWORD;
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
const passwordFile = resolve(platformRoot, "config/.neo4j-password");
|
|
172
|
-
try {
|
|
173
|
-
password = readFileSync(passwordFile, "utf-8").trim();
|
|
174
|
-
}
|
|
175
|
-
catch {
|
|
176
|
-
throw new Error(`Neo4j password not found at ${passwordFile}`);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
// NEO4J_URI must be explicit. Crontab installer prefixes
|
|
180
|
-
// NEO4J_URI=bolt://localhost:${NEO4J_PORT} so the cron path is safe, but the
|
|
181
|
-
// throw-on-unset catches manual runs that forget to source .env.
|
|
182
|
-
const uri = process.env.NEO4J_URI;
|
|
183
|
-
if (!uri) {
|
|
184
|
-
throw new Error("[email/auto-respond] NEO4J_URI unset — refusing to default to bolt://localhost:7687");
|
|
185
|
-
}
|
|
186
|
-
const user = process.env.NEO4J_USER ?? "neo4j";
|
|
187
|
-
console.error(`[email/auto-respond] resolved neo4j_uri=${uri}`);
|
|
188
|
-
return neo4j.driver(uri, neo4j.auth.basic(user, password));
|
|
189
|
-
}
|
|
190
|
-
async function getAutoRespondAccounts(driver) {
|
|
191
|
-
const session = driver.session();
|
|
192
|
-
try {
|
|
193
|
-
const result = await session.run(`MATCH (e:EmailAccount {autoRespond: true}) RETURN e`);
|
|
194
|
-
return result.records.map((record) => {
|
|
195
|
-
const props = record.get("e").properties;
|
|
196
|
-
return {
|
|
197
|
-
accountId: props.accountId,
|
|
198
|
-
config: {
|
|
199
|
-
email: props.email,
|
|
200
|
-
agentAddress: props.agentAddress ?? props.email,
|
|
201
|
-
imapHost: props.imapHost,
|
|
202
|
-
imapPort: neo4j.integer.toNumber(props.imapPort),
|
|
203
|
-
imapSecurity: props.imapSecurity,
|
|
204
|
-
smtpHost: props.smtpHost,
|
|
205
|
-
smtpPort: neo4j.integer.toNumber(props.smtpPort),
|
|
206
|
-
smtpSecurity: props.smtpSecurity,
|
|
207
|
-
},
|
|
208
|
-
autoRespond: {
|
|
209
|
-
autoRespond: true,
|
|
210
|
-
respondingAgentSlug: props.respondingAgentSlug ?? null,
|
|
211
|
-
pollIntervalMinutes: props.pollIntervalMinutes
|
|
212
|
-
? neo4j.integer.toNumber(props.pollIntervalMinutes)
|
|
213
|
-
: 5,
|
|
214
|
-
lastRespondedUid: props.lastRespondedUid
|
|
215
|
-
? neo4j.integer.toNumber(props.lastRespondedUid)
|
|
216
|
-
: 0,
|
|
217
|
-
lastPollAt: props.lastPollAt ?? null,
|
|
218
|
-
consecutiveFailures: props.consecutiveFailures
|
|
219
|
-
? neo4j.integer.toNumber(props.consecutiveFailures)
|
|
220
|
-
: 0,
|
|
221
|
-
autoRespondHourlyLimit: props.autoRespondHourlyLimit
|
|
222
|
-
? neo4j.integer.toNumber(props.autoRespondHourlyLimit)
|
|
223
|
-
: DEFAULT_HOURLY_LIMIT,
|
|
224
|
-
autoRespondDailyLimit: props.autoRespondDailyLimit
|
|
225
|
-
? neo4j.integer.toNumber(props.autoRespondDailyLimit)
|
|
226
|
-
: DEFAULT_DAILY_LIMIT,
|
|
227
|
-
},
|
|
228
|
-
};
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
finally {
|
|
232
|
-
await session.close();
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Extract the bare email address from a From header. RFC 5322 allows
|
|
237
|
-
* `"Display Name" <addr@example.com>` and `addr@example.com` forms; the
|
|
238
|
-
* bridge keys sessions by the bare address only.
|
|
239
|
-
*/
|
|
240
|
-
function stripSenderAddress(from) {
|
|
241
|
-
const m = from.match(/<([^>]+)>/);
|
|
242
|
-
if (m)
|
|
243
|
-
return m[1].trim().toLowerCase();
|
|
244
|
-
return from.trim().toLowerCase();
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Build a threaded Re: subject line.
|
|
248
|
-
* Adds "Re: " prefix only if not already present.
|
|
249
|
-
*/
|
|
250
|
-
function replySubject(originalSubject) {
|
|
251
|
-
const trimmed = originalSubject.trim();
|
|
252
|
-
if (/^re:\s/i.test(trimmed))
|
|
253
|
-
return trimmed;
|
|
254
|
-
return `Re: ${trimmed}`;
|
|
255
|
-
}
|
|
256
|
-
// ---------------------------------------------------------------------------
|
|
257
|
-
// Neo4j update helpers
|
|
258
|
-
// ---------------------------------------------------------------------------
|
|
259
|
-
async function updateLastRespondedUid(driver, accountId, uid) {
|
|
260
|
-
const session = driver.session();
|
|
261
|
-
try {
|
|
262
|
-
await session.run(`MATCH (e:EmailAccount {accountId: $accountId})
|
|
263
|
-
SET e.lastRespondedUid = $uid`, { accountId, uid: neo4j.int(uid) });
|
|
264
|
-
}
|
|
265
|
-
finally {
|
|
266
|
-
await session.close();
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
async function updateLastPollAt(driver, accountId) {
|
|
270
|
-
const session = driver.session();
|
|
271
|
-
try {
|
|
272
|
-
await session.run(`MATCH (e:EmailAccount {accountId: $accountId})
|
|
273
|
-
SET e.lastPollAt = $now, e.consecutiveFailures = 0`, { accountId, now: new Date().toISOString() });
|
|
274
|
-
}
|
|
275
|
-
finally {
|
|
276
|
-
await session.close();
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
async function incrementFailures(driver, accountId) {
|
|
280
|
-
const session = driver.session();
|
|
281
|
-
try {
|
|
282
|
-
const result = await session.run(`MATCH (e:EmailAccount {accountId: $accountId})
|
|
283
|
-
SET e.consecutiveFailures = COALESCE(e.consecutiveFailures, 0) + 1
|
|
284
|
-
RETURN e.consecutiveFailures AS failures`, { accountId });
|
|
285
|
-
return neo4j.integer.toNumber(result.records[0]?.get("failures") ?? 0);
|
|
286
|
-
}
|
|
287
|
-
finally {
|
|
288
|
-
await session.close();
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
async function disableAutoRespond(driver, accountId, reason) {
|
|
292
|
-
const session = driver.session();
|
|
293
|
-
try {
|
|
294
|
-
await session.run(`MATCH (e:EmailAccount {accountId: $accountId})
|
|
295
|
-
SET e.autoRespond = false`, { accountId });
|
|
296
|
-
}
|
|
297
|
-
finally {
|
|
298
|
-
await session.close();
|
|
299
|
-
}
|
|
300
|
-
// Write doctrine: admin notification Task must
|
|
301
|
-
// be created with at least one adjacency. The Task is semantically
|
|
302
|
-
// about the EmailAccount it disabled — link AFFECTS → EmailAccount.
|
|
303
|
-
// If the EmailAccount was deleted between circuit-breaker-trip and
|
|
304
|
-
// this write, writeNodeWithEdges throws 'unresolved-target'; log and
|
|
305
|
-
// continue — the cron loop must survive a missing target node.
|
|
306
|
-
const taskSession = driver.session();
|
|
307
|
-
try {
|
|
308
|
-
const eaResult = await taskSession.run(`MATCH (ea:EmailAccount {accountId: $accountId}) RETURN elementId(ea) AS id LIMIT 1`, { accountId });
|
|
309
|
-
if (eaResult.records.length === 0) {
|
|
310
|
-
log("warn", `Account ${accountId}: EmailAccount not found — skipping admin Task creation for disabled auto-respond`);
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
await writeNodeWithEdges({
|
|
314
|
-
session: taskSession,
|
|
315
|
-
labels: ["Task"],
|
|
316
|
-
props: {
|
|
317
|
-
taskId: randomUUID(),
|
|
318
|
-
accountId,
|
|
319
|
-
title: "Email auto-respond disabled",
|
|
320
|
-
description: `Auto-respond was automatically disabled after ${CIRCUIT_BREAKER_THRESHOLD} consecutive failures. Reason: ${reason}. Re-enable via "enable auto-respond" after resolving the issue.`,
|
|
321
|
-
status: "open",
|
|
322
|
-
priority: "high",
|
|
323
|
-
createdAt: new Date().toISOString(),
|
|
324
|
-
scope: "admin",
|
|
325
|
-
},
|
|
326
|
-
relationships: [
|
|
327
|
-
{
|
|
328
|
-
type: "AFFECTS",
|
|
329
|
-
direction: "outgoing",
|
|
330
|
-
targetNodeId: eaResult.records[0].get("id"),
|
|
331
|
-
},
|
|
332
|
-
],
|
|
333
|
-
createdBy: { source: "email-auto-respond" },
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
catch (err) {
|
|
337
|
-
log("error", `Account ${accountId}: failed to create admin Task for disabled auto-respond — ${err instanceof Error ? err.message : String(err)}`);
|
|
338
|
-
}
|
|
339
|
-
finally {
|
|
340
|
-
await taskSession.close();
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
/**
|
|
344
|
-
* Compute the next UTC clock-hour boundary from a given time.
|
|
345
|
-
* E.g., if now is 14:37 UTC, returns the ISO string for 15:00 UTC.
|
|
346
|
-
*/
|
|
347
|
-
function nextHourBoundary(now) {
|
|
348
|
-
const next = new Date(now);
|
|
349
|
-
next.setUTCMinutes(0, 0, 0);
|
|
350
|
-
next.setUTCHours(next.getUTCHours() + 1);
|
|
351
|
-
return next.toISOString();
|
|
352
|
-
}
|
|
353
|
-
/**
|
|
354
|
-
* Compute the next UTC midnight boundary from a given time.
|
|
355
|
-
*/
|
|
356
|
-
function nextDayBoundary(now) {
|
|
357
|
-
const next = new Date(now);
|
|
358
|
-
next.setUTCHours(0, 0, 0, 0);
|
|
359
|
-
next.setUTCDate(next.getUTCDate() + 1);
|
|
360
|
-
return next.toISOString();
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* Read rate limit counters from the EmailAccount node.
|
|
364
|
-
* Returns defaults (0 counts, null boundaries) if not yet set.
|
|
365
|
-
*/
|
|
366
|
-
async function readRateLimitState(driver, accountId) {
|
|
367
|
-
const session = driver.session();
|
|
368
|
-
try {
|
|
369
|
-
const result = await session.run(`MATCH (e:EmailAccount {accountId: $accountId})
|
|
370
|
-
RETURN e.autoRespondSentThisHour AS sentThisHour,
|
|
371
|
-
e.autoRespondSentToday AS sentToday,
|
|
372
|
-
e.autoRespondHourResetAt AS hourResetAt,
|
|
373
|
-
e.autoRespondDayResetAt AS dayResetAt`, { accountId });
|
|
374
|
-
if (result.records.length === 0) {
|
|
375
|
-
return { sentThisHour: 0, sentToday: 0, hourResetAt: null, dayResetAt: null };
|
|
376
|
-
}
|
|
377
|
-
const rec = result.records[0];
|
|
378
|
-
return {
|
|
379
|
-
sentThisHour: rec.get("sentThisHour") ? neo4j.integer.toNumber(rec.get("sentThisHour")) : 0,
|
|
380
|
-
sentToday: rec.get("sentToday") ? neo4j.integer.toNumber(rec.get("sentToday")) : 0,
|
|
381
|
-
hourResetAt: rec.get("hourResetAt") ?? null,
|
|
382
|
-
dayResetAt: rec.get("dayResetAt") ?? null,
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
finally {
|
|
386
|
-
await session.close();
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
/**
|
|
390
|
-
* Reset counters if the clock hour or day boundary has passed.
|
|
391
|
-
* Initialises boundaries on first run.
|
|
392
|
-
* Returns the updated in-memory state (does NOT re-read from Neo4j).
|
|
393
|
-
*/
|
|
394
|
-
async function resetCountersIfNeeded(driver, accountId, state, now) {
|
|
395
|
-
let hourReset = false;
|
|
396
|
-
let dayReset = false;
|
|
397
|
-
// First run — no boundaries set yet
|
|
398
|
-
if (!state.hourResetAt) {
|
|
399
|
-
state.hourResetAt = nextHourBoundary(now);
|
|
400
|
-
state.sentThisHour = 0;
|
|
401
|
-
hourReset = true;
|
|
402
|
-
}
|
|
403
|
-
else if (now >= new Date(state.hourResetAt)) {
|
|
404
|
-
state.sentThisHour = 0;
|
|
405
|
-
state.hourResetAt = nextHourBoundary(now);
|
|
406
|
-
hourReset = true;
|
|
407
|
-
}
|
|
408
|
-
if (!state.dayResetAt) {
|
|
409
|
-
state.dayResetAt = nextDayBoundary(now);
|
|
410
|
-
state.sentToday = 0;
|
|
411
|
-
dayReset = true;
|
|
412
|
-
}
|
|
413
|
-
else if (now >= new Date(state.dayResetAt)) {
|
|
414
|
-
state.sentToday = 0;
|
|
415
|
-
state.dayResetAt = nextDayBoundary(now);
|
|
416
|
-
dayReset = true;
|
|
417
|
-
}
|
|
418
|
-
if (hourReset || dayReset) {
|
|
419
|
-
const session = driver.session();
|
|
420
|
-
try {
|
|
421
|
-
await session.run(`MATCH (e:EmailAccount {accountId: $accountId})
|
|
422
|
-
SET e.autoRespondSentThisHour = $sentThisHour,
|
|
423
|
-
e.autoRespondSentToday = $sentToday,
|
|
424
|
-
e.autoRespondHourResetAt = $hourResetAt,
|
|
425
|
-
e.autoRespondDayResetAt = $dayResetAt`, {
|
|
426
|
-
accountId,
|
|
427
|
-
sentThisHour: neo4j.int(state.sentThisHour),
|
|
428
|
-
sentToday: neo4j.int(state.sentToday),
|
|
429
|
-
hourResetAt: state.hourResetAt,
|
|
430
|
-
dayResetAt: state.dayResetAt,
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
catch (err) {
|
|
434
|
-
log("error", `Account ${accountId}: failed to reset rate limit counters — ${err instanceof Error ? err.message : String(err)}`);
|
|
435
|
-
}
|
|
436
|
-
finally {
|
|
437
|
-
await session.close();
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
return state;
|
|
441
|
-
}
|
|
442
|
-
/**
|
|
443
|
-
* Increment both hourly and daily send counters atomically.
|
|
444
|
-
*/
|
|
445
|
-
async function incrementSendCounters(driver, accountId) {
|
|
446
|
-
const session = driver.session();
|
|
447
|
-
try {
|
|
448
|
-
await session.run(`MATCH (e:EmailAccount {accountId: $accountId})
|
|
449
|
-
SET e.autoRespondSentThisHour = COALESCE(e.autoRespondSentThisHour, 0) + 1,
|
|
450
|
-
e.autoRespondSentToday = COALESCE(e.autoRespondSentToday, 0) + 1`, { accountId });
|
|
451
|
-
}
|
|
452
|
-
catch (err) {
|
|
453
|
-
log("error", `Account ${accountId}: failed to increment send counters — ${err instanceof Error ? err.message : String(err)}`);
|
|
454
|
-
}
|
|
455
|
-
finally {
|
|
456
|
-
await session.close();
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
/**
|
|
460
|
-
* Create an admin Task node to notify the admin that a rate limit was hit.
|
|
461
|
-
* Dedup: skips creation if an open rate-limit Task already exists for this account.
|
|
462
|
-
*/
|
|
463
|
-
async function createRateLimitTask(driver, accountId, limitType, count, limit) {
|
|
464
|
-
const session = driver.session();
|
|
465
|
-
try {
|
|
466
|
-
// Check for an existing open rate-limit Task to avoid duplicates across poll cycles
|
|
467
|
-
const existing = await session.run(`MATCH (t:Task {accountId: $accountId, status: "open"})
|
|
468
|
-
WHERE t.title STARTS WITH "Email auto-respond"
|
|
469
|
-
AND t.title ENDS WITH "rate limit reached"
|
|
470
|
-
RETURN t LIMIT 1`, { accountId });
|
|
471
|
-
if (existing.records.length > 0) {
|
|
472
|
-
log("info", `Account ${accountId}: rate limit Task already exists — skipping duplicate creation`);
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
// Write doctrine: AFFECTS → EmailAccount. The
|
|
476
|
-
// dedup check above already ran; if EmailAccount is missing, log
|
|
477
|
-
// and bail — cron must survive.
|
|
478
|
-
const eaResult = await session.run(`MATCH (ea:EmailAccount {accountId: $accountId}) RETURN elementId(ea) AS id LIMIT 1`, { accountId });
|
|
479
|
-
if (eaResult.records.length === 0) {
|
|
480
|
-
log("warn", `Account ${accountId}: EmailAccount not found — skipping rate-limit Task creation`);
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
await writeNodeWithEdges({
|
|
484
|
-
session,
|
|
485
|
-
labels: ["Task"],
|
|
486
|
-
props: {
|
|
487
|
-
taskId: randomUUID(),
|
|
488
|
-
accountId,
|
|
489
|
-
title: `Email auto-respond ${limitType} rate limit reached`,
|
|
490
|
-
description: `Auto-respond sent ${count} replies this ${limitType === "hourly" ? "hour" : "day"}, reaching the ${limitType} limit of ${limit}. Remaining emails in this poll cycle were skipped. The limit resets automatically at the next ${limitType === "hourly" ? "clock hour" : "midnight UTC"}. Adjust limits via "configure auto-respond" if needed.`,
|
|
491
|
-
status: "open",
|
|
492
|
-
priority: "medium",
|
|
493
|
-
createdAt: new Date().toISOString(),
|
|
494
|
-
scope: "admin",
|
|
495
|
-
},
|
|
496
|
-
relationships: [
|
|
497
|
-
{
|
|
498
|
-
type: "AFFECTS",
|
|
499
|
-
direction: "outgoing",
|
|
500
|
-
targetNodeId: eaResult.records[0].get("id"),
|
|
501
|
-
},
|
|
502
|
-
],
|
|
503
|
-
createdBy: { source: "email-auto-respond" },
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
catch (err) {
|
|
507
|
-
log("error", `Account ${accountId}: failed to create rate limit Task node — ${err instanceof Error ? err.message : String(err)}`);
|
|
508
|
-
}
|
|
509
|
-
finally {
|
|
510
|
-
await session.close();
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
// ---------------------------------------------------------------------------
|
|
514
|
-
// Process a single email
|
|
515
|
-
// ---------------------------------------------------------------------------
|
|
516
|
-
async function processEmail(msg, emailConfig, emailPassword, driver, accountId) {
|
|
517
|
-
// Build the dispatch payload from the email. Subject/From/Date are
|
|
518
|
-
// prefixed so claude has the threading context inside the PTY turn.
|
|
519
|
-
const userMessage = [
|
|
520
|
-
`From: ${msg.from}`,
|
|
521
|
-
`Subject: ${msg.subject}`,
|
|
522
|
-
`Date: ${msg.date}`,
|
|
523
|
-
"",
|
|
524
|
-
msg.body,
|
|
525
|
-
].join("\n");
|
|
526
|
-
// Email dispatches through the shared PTY-bridge pattern
|
|
527
|
-
// (out-of-process variant that queries the manager's senderId index
|
|
528
|
-
// because cron runs as a separate Node process). The per-sender PTY
|
|
529
|
-
// persists across cron invocations, so a multi-email thread continues
|
|
530
|
-
// a single claude session keyed by the sender's email address.
|
|
531
|
-
const senderEmail = stripSenderAddress(msg.from);
|
|
532
|
-
const dispatchResult = await dispatchEmailToClaude({
|
|
533
|
-
accountId,
|
|
534
|
-
senderEmail,
|
|
535
|
-
text: `<incoming-email>\n${userMessage}\n</incoming-email>`,
|
|
536
|
-
timeoutMs: emailTurnTimeoutMs(),
|
|
537
|
-
});
|
|
538
|
-
if ("error" in dispatchResult) {
|
|
539
|
-
log("warn", `Auto-respond unavailable for UID ${msg.uid} from ${msg.from}: dispatch ${dispatchResult.error}`);
|
|
540
|
-
return;
|
|
541
|
-
}
|
|
542
|
-
const replyText = dispatchResult.turnText;
|
|
543
|
-
if (!replyText.trim()) {
|
|
544
|
-
log("warn", `Empty response from agent for email UID ${msg.uid} from ${msg.from}`);
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
547
|
-
// Build threading headers
|
|
548
|
-
const inReplyTo = msg.messageId || undefined;
|
|
549
|
-
const references = [msg.references, msg.messageId].filter(Boolean).join(" ") || undefined;
|
|
550
|
-
// Append AI disclosure footer (EU AI Act Article 50 — proactive identification)
|
|
551
|
-
const replyBody = replyText + "\n\n---\nThis reply was generated by an AI assistant.";
|
|
552
|
-
// Send reply
|
|
553
|
-
const subject = replySubject(msg.subject);
|
|
554
|
-
const { messageId: outboundMessageId } = await sendEmail(emailConfig, emailPassword, {
|
|
555
|
-
to: msg.from,
|
|
556
|
-
subject,
|
|
557
|
-
body: replyBody,
|
|
558
|
-
inReplyTo,
|
|
559
|
-
references,
|
|
560
|
-
headers: {
|
|
561
|
-
"Auto-Submitted": "auto-replied",
|
|
562
|
-
"X-AI-Generated": "true",
|
|
563
|
-
},
|
|
564
|
-
});
|
|
565
|
-
log("info", `Replied to UID ${msg.uid} from ${msg.from} — subject: ${msg.subject}`);
|
|
566
|
-
// Store outbound reply as an Email node for audit trail
|
|
567
|
-
if (msg.messageId) {
|
|
568
|
-
try {
|
|
569
|
-
// Look up the original email's emailId by messageId
|
|
570
|
-
const normalisedOrigMsgId = stripAngleBrackets(msg.messageId);
|
|
571
|
-
const lookupSession = driver.session();
|
|
572
|
-
let originalEmailId = null;
|
|
573
|
-
try {
|
|
574
|
-
const lookupResult = await lookupSession.run(`MATCH (e:Email {messageId: $messageId, accountId: $accountId})
|
|
575
|
-
RETURN e.emailId AS emailId LIMIT 1`, { messageId: normalisedOrigMsgId, accountId });
|
|
576
|
-
originalEmailId = lookupResult.records[0]?.get("emailId");
|
|
577
|
-
}
|
|
578
|
-
finally {
|
|
579
|
-
await lookupSession.close();
|
|
580
|
-
}
|
|
581
|
-
if (originalEmailId) {
|
|
582
|
-
const result = await storeOutboundEmail({
|
|
583
|
-
messageId: outboundMessageId,
|
|
584
|
-
inReplyTo: msg.messageId,
|
|
585
|
-
toAddresses: [msg.from],
|
|
586
|
-
fromAddress: emailConfig.agentAddress,
|
|
587
|
-
subject,
|
|
588
|
-
bodyPreview: replyBody,
|
|
589
|
-
accountId,
|
|
590
|
-
originalEmailId,
|
|
591
|
-
}, driver);
|
|
592
|
-
log("info", `Stored outbound Email node ${result.emailId} with REPLY_TO edge`);
|
|
593
|
-
}
|
|
594
|
-
else {
|
|
595
|
-
log("warn", `Original email not found in graph for messageId=${msg.messageId} — outbound reply not stored`);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
catch (err) {
|
|
599
|
-
log("error", `Failed to store outbound Email node: ${err instanceof Error ? err.message : String(err)}`);
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
// ---------------------------------------------------------------------------
|
|
604
|
-
// Process a single account
|
|
605
|
-
// ---------------------------------------------------------------------------
|
|
606
|
-
async function processAccount(account, driver) {
|
|
607
|
-
const { accountId, config: emailConfig, autoRespond: arConfig } = account;
|
|
608
|
-
// Check poll interval
|
|
609
|
-
if (arConfig.lastPollAt) {
|
|
610
|
-
const elapsed = Date.now() - new Date(arConfig.lastPollAt).getTime();
|
|
611
|
-
const intervalMs = arConfig.pollIntervalMinutes * 60_000;
|
|
612
|
-
if (elapsed < intervalMs) {
|
|
613
|
-
return; // Not time to poll yet
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
// Replies are produced by a per-sender claude PTY session
|
|
617
|
-
// spawned via the loopback claude-session-manager. The PTY-bridge
|
|
618
|
-
// (../lib/claude-bridge.ts) handles role=public + tool-surface gating
|
|
619
|
-
//, so the legacy per-account agentSlug / agentModel /
|
|
620
|
-
// assembleSystemPrompt path is obsolete: claude's binary loads its own
|
|
621
|
-
// identity. The autoRespond flag on the EmailAccount node is the sole
|
|
622
|
-
// on/off knob.
|
|
623
|
-
// Read email password
|
|
624
|
-
let emailPassword;
|
|
625
|
-
try {
|
|
626
|
-
emailPassword = readPassword();
|
|
627
|
-
}
|
|
628
|
-
catch (err) {
|
|
629
|
-
log("error", `Account ${accountId}: email password not available — ${err instanceof Error ? err.message : String(err)}`);
|
|
630
|
-
const failures = await incrementFailures(driver, accountId);
|
|
631
|
-
if (failures >= CIRCUIT_BREAKER_THRESHOLD) {
|
|
632
|
-
await disableAutoRespond(driver, accountId, "Email password file missing");
|
|
633
|
-
}
|
|
634
|
-
return;
|
|
635
|
-
}
|
|
636
|
-
// Fetch new messages via IMAP
|
|
637
|
-
let messages;
|
|
638
|
-
try {
|
|
639
|
-
messages = await fetchForAutoRespond(emailConfig, emailPassword, {
|
|
640
|
-
sinceUid: arConfig.lastRespondedUid,
|
|
641
|
-
agentAddress: emailConfig.agentAddress,
|
|
642
|
-
limit: MAX_EMAILS_PER_POLL,
|
|
643
|
-
});
|
|
644
|
-
}
|
|
645
|
-
catch (err) {
|
|
646
|
-
log("error", `Account ${accountId}: IMAP fetch failed — ${err instanceof Error ? err.message : String(err)}`);
|
|
647
|
-
const failures = await incrementFailures(driver, accountId);
|
|
648
|
-
if (failures >= CIRCUIT_BREAKER_THRESHOLD) {
|
|
649
|
-
await disableAutoRespond(driver, accountId, `IMAP fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
650
|
-
log("error", `Account ${accountId}: auto-respond DISABLED after ${failures} failures`);
|
|
651
|
-
}
|
|
652
|
-
return;
|
|
653
|
-
}
|
|
654
|
-
log("info", `Account ${accountId}: fetched ${messages.length} new messages`);
|
|
655
|
-
if (messages.length === 0) {
|
|
656
|
-
await updateLastPollAt(driver, accountId);
|
|
657
|
-
return;
|
|
658
|
-
}
|
|
659
|
-
// ── Rate limit state ──────────────────────────────────────────────
|
|
660
|
-
const hourlyLimit = arConfig.autoRespondHourlyLimit;
|
|
661
|
-
const dailyLimit = arConfig.autoRespondDailyLimit;
|
|
662
|
-
const now = new Date();
|
|
663
|
-
let rateLimitState;
|
|
664
|
-
try {
|
|
665
|
-
rateLimitState = await readRateLimitState(driver, accountId);
|
|
666
|
-
rateLimitState = await resetCountersIfNeeded(driver, accountId, rateLimitState, now);
|
|
667
|
-
}
|
|
668
|
-
catch (err) {
|
|
669
|
-
// Fail-open: if we can't read counters, proceed without rate limiting.
|
|
670
|
-
// Bounded by MAX_EMAILS_PER_POLL (10) per poll cycle.
|
|
671
|
-
log("error", `Account ${accountId}: failed to read rate limit state — ${err instanceof Error ? err.message : String(err)}. Proceeding without rate limiting.`);
|
|
672
|
-
rateLimitState = { sentThisHour: 0, sentToday: 0, hourResetAt: null, dayResetAt: null };
|
|
673
|
-
}
|
|
674
|
-
// ── Sender dedup (one reply per sender per poll cycle) ────────────
|
|
675
|
-
const repliedSenders = new Set();
|
|
676
|
-
// ── Process each message sequentially ─────────────────────────────
|
|
677
|
-
for (const msg of messages) {
|
|
678
|
-
// Skip auto-replies (loop prevention)
|
|
679
|
-
if (msg.isAutoReply) {
|
|
680
|
-
log("info", `Skipping auto-reply from ${msg.from} (UID ${msg.uid})`);
|
|
681
|
-
await updateLastRespondedUid(driver, accountId, msg.uid);
|
|
682
|
-
continue;
|
|
683
|
-
}
|
|
684
|
-
// Skip emails from the agent's own address (loop prevention)
|
|
685
|
-
if (msg.from.toLowerCase() === emailConfig.agentAddress.toLowerCase()) {
|
|
686
|
-
log("info", `Skipping own email from ${msg.from} (UID ${msg.uid})`);
|
|
687
|
-
await updateLastRespondedUid(driver, accountId, msg.uid);
|
|
688
|
-
continue;
|
|
689
|
-
}
|
|
690
|
-
// Skip emails with no extractable body
|
|
691
|
-
if (!msg.body || msg.body === "(body unavailable)") {
|
|
692
|
-
log("warn", `Skipping email with no body from ${msg.from} (UID ${msg.uid})`);
|
|
693
|
-
await updateLastRespondedUid(driver, accountId, msg.uid);
|
|
694
|
-
continue;
|
|
695
|
-
}
|
|
696
|
-
// Sender dedup — skip if we already replied to this sender in this poll cycle
|
|
697
|
-
const senderKey = msg.from.toLowerCase();
|
|
698
|
-
if (repliedSenders.has(senderKey)) {
|
|
699
|
-
log("info", `Skipping duplicate sender ${msg.from} (UID ${msg.uid}) — already replied this poll cycle`);
|
|
700
|
-
await updateLastRespondedUid(driver, accountId, msg.uid);
|
|
701
|
-
continue;
|
|
702
|
-
}
|
|
703
|
-
// Screening gate — check the Email node's classification in Neo4j
|
|
704
|
-
try {
|
|
705
|
-
const check = await lookupScreening(driver, msg.messageId, accountId);
|
|
706
|
-
if (!check.found) {
|
|
707
|
-
// Email not yet in Neo4j — email-fetch hasn't classified it.
|
|
708
|
-
// Skip without advancing UID. If later emails in this batch advance
|
|
709
|
-
// the UID past this one, this email won't be retried — but it will
|
|
710
|
-
// still be stored by email-fetch with its screening verdict. The
|
|
711
|
-
// security posture (no reply without screening) takes precedence
|
|
712
|
-
// over reply completeness.
|
|
713
|
-
log("info", `Skipping UID ${msg.uid} from ${msg.from} — Email node not yet classified (email-fetch pending)`);
|
|
714
|
-
continue;
|
|
715
|
-
}
|
|
716
|
-
if (check.promptInjectionFlag) {
|
|
717
|
-
log("warn", `Skipping UID ${msg.uid} from ${msg.from} — prompt injection flag set`);
|
|
718
|
-
await updateLastRespondedUid(driver, accountId, msg.uid);
|
|
719
|
-
continue;
|
|
720
|
-
}
|
|
721
|
-
if (check.screening !== "clean") {
|
|
722
|
-
log("info", `Skipping UID ${msg.uid} from ${msg.from} — screening=${check.screening ?? "absent"}`);
|
|
723
|
-
await updateLastRespondedUid(driver, accountId, msg.uid);
|
|
724
|
-
continue;
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
catch (err) {
|
|
728
|
-
log("error", `Account ${accountId}: screening lookup failed for UID ${msg.uid} — ${err instanceof Error ? err.message : String(err)}`);
|
|
729
|
-
const failures = await incrementFailures(driver, accountId);
|
|
730
|
-
if (failures >= CIRCUIT_BREAKER_THRESHOLD) {
|
|
731
|
-
await disableAutoRespond(driver, accountId, `Screening lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
732
|
-
log("error", `Account ${accountId}: auto-respond DISABLED after ${failures} failures (screening lookup)`);
|
|
733
|
-
return;
|
|
734
|
-
}
|
|
735
|
-
continue;
|
|
736
|
-
}
|
|
737
|
-
// Rate limit check — hourly
|
|
738
|
-
if (rateLimitState.sentThisHour >= hourlyLimit) {
|
|
739
|
-
log("warn", `Account ${accountId}: hourly rate limit reached (${rateLimitState.sentThisHour}/${hourlyLimit}). Skipping remaining emails.`);
|
|
740
|
-
await createRateLimitTask(driver, accountId, "hourly", rateLimitState.sentThisHour, hourlyLimit);
|
|
741
|
-
break;
|
|
742
|
-
}
|
|
743
|
-
// Rate limit check — daily
|
|
744
|
-
if (rateLimitState.sentToday >= dailyLimit) {
|
|
745
|
-
log("warn", `Account ${accountId}: daily rate limit reached (${rateLimitState.sentToday}/${dailyLimit}). Skipping remaining emails.`);
|
|
746
|
-
await createRateLimitTask(driver, accountId, "daily", rateLimitState.sentToday, dailyLimit);
|
|
747
|
-
break;
|
|
748
|
-
}
|
|
749
|
-
try {
|
|
750
|
-
await processEmail(msg, emailConfig, emailPassword, driver, accountId);
|
|
751
|
-
await updateLastRespondedUid(driver, accountId, msg.uid);
|
|
752
|
-
await incrementSendCounters(driver, accountId);
|
|
753
|
-
rateLimitState.sentThisHour++;
|
|
754
|
-
rateLimitState.sentToday++;
|
|
755
|
-
repliedSenders.add(senderKey);
|
|
756
|
-
// Reset failure counter on success
|
|
757
|
-
await updateLastPollAt(driver, accountId);
|
|
758
|
-
}
|
|
759
|
-
catch (err) {
|
|
760
|
-
log("error", `Account ${accountId}: failed to process email UID ${msg.uid} from ${msg.from} — ${err instanceof Error ? err.message : String(err)}`);
|
|
761
|
-
const failures = await incrementFailures(driver, accountId);
|
|
762
|
-
if (failures >= CIRCUIT_BREAKER_THRESHOLD) {
|
|
763
|
-
await disableAutoRespond(driver, accountId, `Failed to process email from ${msg.from}: ${err instanceof Error ? err.message : String(err)}`);
|
|
764
|
-
log("error", `Account ${accountId}: auto-respond DISABLED after ${failures} failures`);
|
|
765
|
-
return; // Stop processing this account
|
|
766
|
-
}
|
|
767
|
-
// Continue to next message — the UID is NOT advanced so this email will be retried
|
|
768
|
-
continue;
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
// Final poll timestamp update
|
|
772
|
-
await updateLastPollAt(driver, accountId);
|
|
773
|
-
}
|
|
774
|
-
// ---------------------------------------------------------------------------
|
|
775
|
-
// Main
|
|
776
|
-
// ---------------------------------------------------------------------------
|
|
777
|
-
async function main() {
|
|
778
|
-
if (!acquireLock()) {
|
|
779
|
-
return; // Another instance is running
|
|
780
|
-
}
|
|
781
|
-
let driver = null;
|
|
782
|
-
try {
|
|
783
|
-
driver = connectNeo4j();
|
|
784
|
-
const accounts = await getAutoRespondAccounts(driver);
|
|
785
|
-
if (accounts.length === 0) {
|
|
786
|
-
releaseLock();
|
|
787
|
-
return; // No auto-respond accounts — silent exit
|
|
788
|
-
}
|
|
789
|
-
log("info", `Processing ${accounts.length} auto-respond account(s)`);
|
|
790
|
-
for (const account of accounts) {
|
|
791
|
-
try {
|
|
792
|
-
await processAccount(account, driver);
|
|
793
|
-
}
|
|
794
|
-
catch (err) {
|
|
795
|
-
log("error", `Unexpected error processing account ${account.accountId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
catch (err) {
|
|
800
|
-
log("error", `Fatal error: ${err instanceof Error ? err.message : String(err)}`);
|
|
801
|
-
}
|
|
802
|
-
finally {
|
|
803
|
-
if (driver) {
|
|
804
|
-
try {
|
|
805
|
-
await driver.close();
|
|
806
|
-
}
|
|
807
|
-
catch { /* best effort */ }
|
|
808
|
-
}
|
|
809
|
-
releaseLock();
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
main().catch((err) => {
|
|
813
|
-
log("error", `Unhandled error: ${err instanceof Error ? err.message : String(err)}`);
|
|
814
|
-
releaseLock();
|
|
815
|
-
process.exit(1);
|
|
816
|
-
});
|
|
817
|
-
//# sourceMappingURL=email-auto-respond.js.map
|