crewly 1.16.0 → 1.17.1

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.
Files changed (200) hide show
  1. package/README.md +33 -1
  2. package/config/roles/orchestrator/prompt.md +20 -5
  3. package/config/skills/agent/core/calendar-create/SKILL.md +62 -0
  4. package/config/skills/agent/core/calendar-create/execute.sh +91 -0
  5. package/config/skills/agent/core/calendar-create/execute.test.sh +80 -0
  6. package/config/skills/agent/core/calendar-list/SKILL.md +58 -0
  7. package/config/skills/agent/core/calendar-list/execute.sh +88 -0
  8. package/config/skills/agent/core/calendar-list/execute.test.sh +71 -0
  9. package/config/skills/agent/core/gmail-read/SKILL.md +61 -0
  10. package/config/skills/agent/core/gmail-read/execute.sh +62 -0
  11. package/config/skills/agent/core/gmail-read/execute.test.sh +68 -0
  12. package/config/skills/agent/core/gmail-search/SKILL.md +64 -0
  13. package/config/skills/agent/core/gmail-search/execute.sh +70 -0
  14. package/config/skills/agent/core/gmail-search/execute.test.sh +67 -0
  15. package/config/skills/agent/core/gmail-send/SKILL.md +69 -0
  16. package/config/skills/agent/core/gmail-send/execute.sh +114 -0
  17. package/config/skills/agent/core/gmail-send/execute.test.sh +83 -0
  18. package/config/skills/orchestrator/list-missions/SKILL.md +47 -0
  19. package/config/skills/orchestrator/list-missions/execute.sh +34 -0
  20. package/config/skills/orchestrator/list-missions/execute.test.sh +33 -0
  21. package/config/slack-master-app-manifest.json +64 -0
  22. package/dist/backend/backend/src/constants.d.ts +125 -0
  23. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  24. package/dist/backend/backend/src/constants.js +125 -0
  25. package/dist/backend/backend/src/constants.js.map +1 -1
  26. package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts.map +1 -1
  27. package/dist/backend/backend/src/controllers/cloud/cloud.controller.js +3 -1
  28. package/dist/backend/backend/src/controllers/cloud/cloud.controller.js.map +1 -1
  29. package/dist/backend/backend/src/controllers/google/google.controller.d.ts +100 -0
  30. package/dist/backend/backend/src/controllers/google/google.controller.d.ts.map +1 -0
  31. package/dist/backend/backend/src/controllers/google/google.controller.js +293 -0
  32. package/dist/backend/backend/src/controllers/google/google.controller.js.map +1 -0
  33. package/dist/backend/backend/src/controllers/google/google.routes.d.ts +26 -0
  34. package/dist/backend/backend/src/controllers/google/google.routes.d.ts.map +1 -0
  35. package/dist/backend/backend/src/controllers/google/google.routes.js +38 -0
  36. package/dist/backend/backend/src/controllers/google/google.routes.js.map +1 -0
  37. package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
  38. package/dist/backend/backend/src/controllers/slack/slack.controller.js +209 -2
  39. package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
  40. package/dist/backend/backend/src/index.d.ts.map +1 -1
  41. package/dist/backend/backend/src/index.js +58 -8
  42. package/dist/backend/backend/src/index.js.map +1 -1
  43. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  44. package/dist/backend/backend/src/routes/api.routes.js +4 -0
  45. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  46. package/dist/backend/backend/src/services/cloud/cloud-initializer.d.ts +8 -0
  47. package/dist/backend/backend/src/services/cloud/cloud-initializer.d.ts.map +1 -1
  48. package/dist/backend/backend/src/services/cloud/cloud-initializer.js +25 -0
  49. package/dist/backend/backend/src/services/cloud/cloud-initializer.js.map +1 -1
  50. package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
  51. package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
  52. package/dist/backend/backend/src/services/cloud/cloud-sync.types.js +1 -0
  53. package/dist/backend/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
  54. package/dist/backend/backend/src/services/core/crewly-home.utils.d.ts +49 -0
  55. package/dist/backend/backend/src/services/core/crewly-home.utils.d.ts.map +1 -1
  56. package/dist/backend/backend/src/services/core/crewly-home.utils.js +86 -0
  57. package/dist/backend/backend/src/services/core/crewly-home.utils.js.map +1 -1
  58. package/dist/backend/backend/src/services/google/calendar.service.d.ts +98 -0
  59. package/dist/backend/backend/src/services/google/calendar.service.d.ts.map +1 -0
  60. package/dist/backend/backend/src/services/google/calendar.service.js +125 -0
  61. package/dist/backend/backend/src/services/google/calendar.service.js.map +1 -0
  62. package/dist/backend/backend/src/services/google/gmail.service.d.ts +165 -0
  63. package/dist/backend/backend/src/services/google/gmail.service.d.ts.map +1 -0
  64. package/dist/backend/backend/src/services/google/gmail.service.js +296 -0
  65. package/dist/backend/backend/src/services/google/gmail.service.js.map +1 -0
  66. package/dist/backend/backend/src/services/google/google-api.client.d.ts +44 -0
  67. package/dist/backend/backend/src/services/google/google-api.client.d.ts.map +1 -0
  68. package/dist/backend/backend/src/services/google/google-api.client.js +104 -0
  69. package/dist/backend/backend/src/services/google/google-api.client.js.map +1 -0
  70. package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts +159 -0
  71. package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts.map +1 -0
  72. package/dist/backend/backend/src/services/google/google-workspace-token.service.js +274 -0
  73. package/dist/backend/backend/src/services/google/google-workspace-token.service.js.map +1 -0
  74. package/dist/backend/backend/src/services/memory/mission-context.service.d.ts.map +1 -1
  75. package/dist/backend/backend/src/services/memory/mission-context.service.js +2 -1
  76. package/dist/backend/backend/src/services/memory/mission-context.service.js.map +1 -1
  77. package/dist/backend/backend/src/services/memory/project-memory.service.d.ts.map +1 -1
  78. package/dist/backend/backend/src/services/memory/project-memory.service.js +3 -2
  79. package/dist/backend/backend/src/services/memory/project-memory.service.js.map +1 -1
  80. package/dist/backend/backend/src/services/memory/session-memory.service.d.ts.map +1 -1
  81. package/dist/backend/backend/src/services/memory/session-memory.service.js +2 -1
  82. package/dist/backend/backend/src/services/memory/session-memory.service.js.map +1 -1
  83. package/dist/backend/backend/src/services/project/task.service.d.ts.map +1 -1
  84. package/dist/backend/backend/src/services/project/task.service.js +2 -3
  85. package/dist/backend/backend/src/services/project/task.service.js.map +1 -1
  86. package/dist/backend/backend/src/services/project/ticket-editor.service.d.ts.map +1 -1
  87. package/dist/backend/backend/src/services/project/ticket-editor.service.js +2 -3
  88. package/dist/backend/backend/src/services/project/ticket-editor.service.js.map +1 -1
  89. package/dist/backend/backend/src/services/slack/slack-agent-identity.service.d.ts +12 -1
  90. package/dist/backend/backend/src/services/slack/slack-agent-identity.service.d.ts.map +1 -1
  91. package/dist/backend/backend/src/services/slack/slack-agent-identity.service.js +30 -0
  92. package/dist/backend/backend/src/services/slack/slack-agent-identity.service.js.map +1 -1
  93. package/dist/backend/backend/src/services/slack/slack-cloud-config.service.d.ts +186 -0
  94. package/dist/backend/backend/src/services/slack/slack-cloud-config.service.d.ts.map +1 -0
  95. package/dist/backend/backend/src/services/slack/slack-cloud-config.service.js +409 -0
  96. package/dist/backend/backend/src/services/slack/slack-cloud-config.service.js.map +1 -0
  97. package/dist/backend/backend/src/services/slack/slack-initializer.d.ts +95 -1
  98. package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
  99. package/dist/backend/backend/src/services/slack/slack-initializer.js +284 -4
  100. package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
  101. package/dist/backend/backend/src/services/slack/slack-instance-registry.service.d.ts +178 -0
  102. package/dist/backend/backend/src/services/slack/slack-instance-registry.service.d.ts.map +1 -0
  103. package/dist/backend/backend/src/services/slack/slack-instance-registry.service.js +392 -0
  104. package/dist/backend/backend/src/services/slack/slack-instance-registry.service.js.map +1 -0
  105. package/dist/backend/backend/src/services/slack/slack.service.d.ts +76 -1
  106. package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
  107. package/dist/backend/backend/src/services/slack/slack.service.js +234 -47
  108. package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
  109. package/dist/backend/backend/src/services/v3/escalation-router.service.d.ts.map +1 -1
  110. package/dist/backend/backend/src/services/v3/escalation-router.service.js +2 -1
  111. package/dist/backend/backend/src/services/v3/escalation-router.service.js.map +1 -1
  112. package/dist/backend/backend/src/services/v3/mission-paths.d.ts +17 -0
  113. package/dist/backend/backend/src/services/v3/mission-paths.d.ts.map +1 -1
  114. package/dist/backend/backend/src/services/v3/mission-paths.js +21 -2
  115. package/dist/backend/backend/src/services/v3/mission-paths.js.map +1 -1
  116. package/dist/backend/backend/src/services/v3/mission-reminder.service.d.ts +4 -0
  117. package/dist/backend/backend/src/services/v3/mission-reminder.service.d.ts.map +1 -1
  118. package/dist/backend/backend/src/services/v3/mission-reminder.service.js +17 -0
  119. package/dist/backend/backend/src/services/v3/mission-reminder.service.js.map +1 -1
  120. package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.d.ts +116 -0
  121. package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.d.ts.map +1 -0
  122. package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.js +254 -0
  123. package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.js.map +1 -0
  124. package/dist/backend/backend/src/services/v3/request.service.d.ts.map +1 -1
  125. package/dist/backend/backend/src/services/v3/request.service.js +2 -1
  126. package/dist/backend/backend/src/services/v3/request.service.js.map +1 -1
  127. package/dist/backend/backend/src/services/v3/task-projection.service.d.ts.map +1 -1
  128. package/dist/backend/backend/src/services/v3/task-projection.service.js +2 -1
  129. package/dist/backend/backend/src/services/v3/task-projection.service.js.map +1 -1
  130. package/dist/backend/backend/src/services/v3/trigger-engine.service.d.ts.map +1 -1
  131. package/dist/backend/backend/src/services/v3/trigger-engine.service.js +2 -1
  132. package/dist/backend/backend/src/services/v3/trigger-engine.service.js.map +1 -1
  133. package/dist/backend/backend/src/services/v3/v3-data.service.d.ts.map +1 -1
  134. package/dist/backend/backend/src/services/v3/v3-data.service.js +2 -1
  135. package/dist/backend/backend/src/services/v3/v3-data.service.js.map +1 -1
  136. package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.d.ts.map +1 -1
  137. package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.js +6 -1
  138. package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.js.map +1 -1
  139. package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.d.ts.map +1 -1
  140. package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.js +3 -1
  141. package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.js.map +1 -1
  142. package/dist/backend/backend/src/types/slack.types.d.ts +141 -2
  143. package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
  144. package/dist/backend/backend/src/types/slack.types.js.map +1 -1
  145. package/dist/backend/backend/src/types/v2/mission.types.d.ts +2 -0
  146. package/dist/backend/backend/src/types/v2/mission.types.d.ts.map +1 -1
  147. package/dist/backend/backend/src/types/v2/mission.types.js.map +1 -1
  148. package/dist/backend/build-info.json +2 -2
  149. package/dist/cli/backend/src/constants.d.ts +125 -0
  150. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  151. package/dist/cli/backend/src/constants.js +125 -0
  152. package/dist/cli/backend/src/constants.js.map +1 -1
  153. package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
  154. package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
  155. package/dist/cli/backend/src/services/cloud/cloud-sync.types.js +1 -0
  156. package/dist/cli/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
  157. package/dist/cli/backend/src/services/core/crewly-home.utils.d.ts +49 -0
  158. package/dist/cli/backend/src/services/core/crewly-home.utils.d.ts.map +1 -1
  159. package/dist/cli/backend/src/services/core/crewly-home.utils.js +86 -0
  160. package/dist/cli/backend/src/services/core/crewly-home.utils.js.map +1 -1
  161. package/dist/cli/backend/src/services/memory/mission-context.service.d.ts.map +1 -1
  162. package/dist/cli/backend/src/services/memory/mission-context.service.js +2 -1
  163. package/dist/cli/backend/src/services/memory/mission-context.service.js.map +1 -1
  164. package/dist/cli/backend/src/services/memory/project-memory.service.d.ts.map +1 -1
  165. package/dist/cli/backend/src/services/memory/project-memory.service.js +3 -2
  166. package/dist/cli/backend/src/services/memory/project-memory.service.js.map +1 -1
  167. package/dist/cli/backend/src/services/memory/session-memory.service.d.ts.map +1 -1
  168. package/dist/cli/backend/src/services/memory/session-memory.service.js +2 -1
  169. package/dist/cli/backend/src/services/memory/session-memory.service.js.map +1 -1
  170. package/dist/cli/backend/src/services/slack/slack-agent-identity.service.d.ts +12 -1
  171. package/dist/cli/backend/src/services/slack/slack-agent-identity.service.d.ts.map +1 -1
  172. package/dist/cli/backend/src/services/slack/slack-agent-identity.service.js +30 -0
  173. package/dist/cli/backend/src/services/slack/slack-agent-identity.service.js.map +1 -1
  174. package/dist/cli/backend/src/services/slack/slack.service.d.ts +76 -1
  175. package/dist/cli/backend/src/services/slack/slack.service.d.ts.map +1 -1
  176. package/dist/cli/backend/src/services/slack/slack.service.js +234 -47
  177. package/dist/cli/backend/src/services/slack/slack.service.js.map +1 -1
  178. package/dist/cli/backend/src/services/v3/escalation-router.service.d.ts.map +1 -1
  179. package/dist/cli/backend/src/services/v3/escalation-router.service.js +2 -1
  180. package/dist/cli/backend/src/services/v3/escalation-router.service.js.map +1 -1
  181. package/dist/cli/backend/src/services/v3/mission-paths.d.ts +17 -0
  182. package/dist/cli/backend/src/services/v3/mission-paths.d.ts.map +1 -1
  183. package/dist/cli/backend/src/services/v3/mission-paths.js +21 -2
  184. package/dist/cli/backend/src/services/v3/mission-paths.js.map +1 -1
  185. package/dist/cli/backend/src/services/v3/request.service.d.ts.map +1 -1
  186. package/dist/cli/backend/src/services/v3/request.service.js +2 -1
  187. package/dist/cli/backend/src/services/v3/request.service.js.map +1 -1
  188. package/dist/cli/backend/src/services/v3/v3-data.service.d.ts.map +1 -1
  189. package/dist/cli/backend/src/services/v3/v3-data.service.js +2 -1
  190. package/dist/cli/backend/src/services/v3/v3-data.service.js.map +1 -1
  191. package/dist/cli/backend/src/types/slack.types.d.ts +141 -2
  192. package/dist/cli/backend/src/types/slack.types.d.ts.map +1 -1
  193. package/dist/cli/backend/src/types/slack.types.js.map +1 -1
  194. package/dist/cli/backend/src/types/v2/mission.types.d.ts +2 -0
  195. package/dist/cli/backend/src/types/v2/mission.types.d.ts.map +1 -1
  196. package/dist/cli/backend/src/types/v2/mission.types.js.map +1 -1
  197. package/frontend/dist/assets/{index-019817e2.js → index-c87332bf.js} +273 -273
  198. package/frontend/dist/assets/{index-a56613e7.css → index-f6b4230e.css} +1 -1
  199. package/frontend/dist/index.html +2 -2
  200. package/package.json +1 -1
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Gmail Service
3
+ *
4
+ * Search, read and send on the owner's mailbox through the Gmail REST API
5
+ * with a Cloud-minted access token. Messages are built locally as RFC 822
6
+ * and sent base64url-encoded; bodies are decoded here; attachments are
7
+ * listed but never downloaded.
8
+ *
9
+ * @module services/google/gmail.service
10
+ */
11
+ import { type GoogleApiDeps } from './google-api.client.js';
12
+ /** One search hit (metadata only). */
13
+ export interface GmailSearchHit {
14
+ id: string;
15
+ threadId: string;
16
+ from: string;
17
+ to: string;
18
+ subject: string;
19
+ date: string;
20
+ snippet: string;
21
+ labelIds: string[];
22
+ }
23
+ /** An attachment reference — name/type/size only, never the bytes. */
24
+ export interface GmailAttachment {
25
+ filename: string;
26
+ mimeType: string;
27
+ size: number;
28
+ attachmentId: string;
29
+ }
30
+ /** A fully read message. */
31
+ export interface GmailMessage {
32
+ id: string;
33
+ threadId: string;
34
+ from: string;
35
+ to: string;
36
+ cc: string;
37
+ subject: string;
38
+ date: string;
39
+ /** RFC 822 Message-ID header (for replies) */
40
+ messageId: string;
41
+ snippet: string;
42
+ /** Decoded body text */
43
+ body: string;
44
+ /** Which part the body came from */
45
+ bodyType: 'text' | 'html' | 'none';
46
+ attachments: GmailAttachment[];
47
+ labelIds: string[];
48
+ }
49
+ /** Input to {@link GmailService.search}. */
50
+ export interface GmailSearchInput {
51
+ query: string;
52
+ max?: number;
53
+ }
54
+ /** Input to {@link GmailService.send}. */
55
+ export interface GmailSendInput {
56
+ to: string;
57
+ cc?: string;
58
+ subject: string;
59
+ text: string;
60
+ /** Gmail thread to append to */
61
+ threadId?: string;
62
+ /** RFC 822 Message-ID of the message being answered */
63
+ inReplyTo?: string;
64
+ }
65
+ /** Result of {@link GmailService.send}. */
66
+ export interface GmailSendResult {
67
+ id: string;
68
+ threadId: string;
69
+ labelIds: string[];
70
+ }
71
+ /**
72
+ * Standard base64url without padding — what `messages.send` expects in `raw`.
73
+ *
74
+ * @param input - Bytes or UTF-8 string
75
+ * @returns base64url string
76
+ */
77
+ export declare function base64UrlEncode(input: string | Buffer): string;
78
+ /**
79
+ * Decode Gmail's base64url body data (tolerates standard base64 too).
80
+ *
81
+ * @param data - base64url string
82
+ * @returns UTF-8 text
83
+ */
84
+ export declare function base64UrlDecode(data: string): string;
85
+ /**
86
+ * RFC 2047 encoded-word for a header value that is not plain printable
87
+ * ASCII (a Chinese subject, an emoji); ASCII values are left untouched.
88
+ *
89
+ * @param value - Header value
90
+ * @returns The value as it should appear on the wire
91
+ */
92
+ export declare function encodeHeaderValue(value: string): string;
93
+ /**
94
+ * Build the RFC 822 message for {@link GmailService.send}. The From header is
95
+ * left to Gmail (it fills in the authenticated user); the body is UTF-8
96
+ * text/plain, base64 transfer-encoded so any script is safe on the wire.
97
+ *
98
+ * @param input - Recipients, subject, body, optional reply threading
99
+ * @returns The raw message with CRLF line endings
100
+ * @throws GoogleWorkspaceError(400, validation) when `to` or `subject` is empty
101
+ */
102
+ export declare function buildRfc822(input: GmailSendInput): string;
103
+ /**
104
+ * Very small HTML → text: drop scripts/styles, turn block boundaries into
105
+ * newlines, strip tags, unescape the common entities, collapse blank runs.
106
+ * Only used when a message has no text/plain part.
107
+ *
108
+ * @param html - HTML source
109
+ * @returns Plain text
110
+ */
111
+ export declare function stripHtml(html: string): string;
112
+ /**
113
+ * Gmail operations on the signed-in owner's mailbox.
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * const gmail = new GmailService({ tokens: GoogleWorkspaceTokenService.getInstance() });
118
+ * const hits = await gmail.search({ query: 'is:unread newer_than:1d' });
119
+ * ```
120
+ */
121
+ export declare class GmailService {
122
+ private readonly deps;
123
+ private readonly base;
124
+ /**
125
+ * @param deps - Token provider and optional fetch override
126
+ */
127
+ constructor(deps: GoogleApiDeps);
128
+ /**
129
+ * Gmail search (`messages.list`) followed by one metadata `messages.get`
130
+ * per hit for From / To / Subject / Date and the snippet.
131
+ *
132
+ * @param input - Gmail query string and result cap
133
+ * @returns Hits in Gmail's order (newest first)
134
+ * @throws GoogleWorkspaceError on auth / Google failures
135
+ */
136
+ search(input: GmailSearchInput): Promise<GmailSearchHit[]>;
137
+ /**
138
+ * Full `messages.get`; body decoded (text/plain preferred, stripped
139
+ * text/html as fallback), attachments listed only.
140
+ *
141
+ * @param id - Gmail message id
142
+ * @returns The message
143
+ * @throws GoogleWorkspaceError — 404 when Gmail has no such message
144
+ */
145
+ read(id: string): Promise<GmailMessage>;
146
+ /**
147
+ * `messages.send` with a locally built RFC 822 message.
148
+ *
149
+ * @param input - Recipients, subject, text and optional reply threading
150
+ * @returns The sent message's id / threadId / labels
151
+ * @throws GoogleWorkspaceError(400, validation) for missing to/subject; auth / Google failures
152
+ */
153
+ send(input: GmailSendInput): Promise<GmailSendResult>;
154
+ }
155
+ /**
156
+ * Coerce a caller-supplied result cap into [1, ceiling], defaulting when
157
+ * absent or not a number.
158
+ *
159
+ * @param value - Requested cap
160
+ * @param fallback - Default
161
+ * @param ceiling - Hard maximum
162
+ * @returns The cap to send to Google
163
+ */
164
+ export declare function clampMax(value: number | undefined, fallback: number, ceiling: number): number;
165
+ //# sourceMappingURL=gmail.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gmail.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/google/gmail.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,296 @@
1
+ /**
2
+ * Gmail Service
3
+ *
4
+ * Search, read and send on the owner's mailbox through the Gmail REST API
5
+ * with a Cloud-minted access token. Messages are built locally as RFC 822
6
+ * and sent base64url-encoded; bodies are decoded here; attachments are
7
+ * listed but never downloaded.
8
+ *
9
+ * @module services/google/gmail.service
10
+ */
11
+ import { GOOGLE_WORKSPACE_CONSTANTS } from '../../constants.js';
12
+ import { GoogleWorkspaceError } from './google-workspace-token.service.js';
13
+ import { buildGoogleUrl, googleRequest } from './google-api.client.js';
14
+ // ---------------------------------------------------------------------------
15
+ // Encoding helpers (exported for the tests and the controller's dry-run)
16
+ // ---------------------------------------------------------------------------
17
+ /**
18
+ * Standard base64url without padding — what `messages.send` expects in `raw`.
19
+ *
20
+ * @param input - Bytes or UTF-8 string
21
+ * @returns base64url string
22
+ */
23
+ export function base64UrlEncode(input) {
24
+ return Buffer.from(input).toString('base64url');
25
+ }
26
+ /**
27
+ * Decode Gmail's base64url body data (tolerates standard base64 too).
28
+ *
29
+ * @param data - base64url string
30
+ * @returns UTF-8 text
31
+ */
32
+ export function base64UrlDecode(data) {
33
+ return Buffer.from(data, 'base64url').toString('utf8');
34
+ }
35
+ /**
36
+ * RFC 2047 encoded-word for a header value that is not plain printable
37
+ * ASCII (a Chinese subject, an emoji); ASCII values are left untouched.
38
+ *
39
+ * @param value - Header value
40
+ * @returns The value as it should appear on the wire
41
+ */
42
+ export function encodeHeaderValue(value) {
43
+ // eslint-disable-next-line no-control-regex
44
+ if (/^[\x20-\x7e]*$/.test(value))
45
+ return value;
46
+ return `=?UTF-8?B?${Buffer.from(value, 'utf8').toString('base64')}?=`;
47
+ }
48
+ /**
49
+ * Wrap a base64 body at the RFC 2045 line width.
50
+ *
51
+ * @param base64 - Unwrapped base64
52
+ * @returns CRLF-wrapped base64
53
+ */
54
+ function wrapBase64(base64) {
55
+ const width = GOOGLE_WORKSPACE_CONSTANTS.MIME_LINE_WIDTH;
56
+ const lines = [];
57
+ for (let i = 0; i < base64.length; i += width)
58
+ lines.push(base64.slice(i, i + width));
59
+ return lines.join('\r\n');
60
+ }
61
+ /**
62
+ * Build the RFC 822 message for {@link GmailService.send}. The From header is
63
+ * left to Gmail (it fills in the authenticated user); the body is UTF-8
64
+ * text/plain, base64 transfer-encoded so any script is safe on the wire.
65
+ *
66
+ * @param input - Recipients, subject, body, optional reply threading
67
+ * @returns The raw message with CRLF line endings
68
+ * @throws GoogleWorkspaceError(400, validation) when `to` or `subject` is empty
69
+ */
70
+ export function buildRfc822(input) {
71
+ const CODES = GOOGLE_WORKSPACE_CONSTANTS.ERROR_CODES;
72
+ if (!input.to?.trim())
73
+ throw new GoogleWorkspaceError(400, CODES.VALIDATION, '"to" is required');
74
+ if (!input.subject?.trim())
75
+ throw new GoogleWorkspaceError(400, CODES.VALIDATION, '"subject" is required');
76
+ const headers = [`To: ${input.to.trim()}`];
77
+ if (input.cc?.trim())
78
+ headers.push(`Cc: ${input.cc.trim()}`);
79
+ headers.push(`Subject: ${encodeHeaderValue(input.subject.trim())}`);
80
+ if (input.inReplyTo?.trim()) {
81
+ const ref = input.inReplyTo.trim();
82
+ headers.push(`In-Reply-To: ${ref}`, `References: ${ref}`);
83
+ }
84
+ headers.push('MIME-Version: 1.0', 'Content-Type: text/plain; charset="UTF-8"', 'Content-Transfer-Encoding: base64');
85
+ const body = wrapBase64(Buffer.from(input.text ?? '', 'utf8').toString('base64'));
86
+ return `${headers.join('\r\n')}\r\n\r\n${body}`;
87
+ }
88
+ /**
89
+ * Very small HTML → text: drop scripts/styles, turn block boundaries into
90
+ * newlines, strip tags, unescape the common entities, collapse blank runs.
91
+ * Only used when a message has no text/plain part.
92
+ *
93
+ * @param html - HTML source
94
+ * @returns Plain text
95
+ */
96
+ export function stripHtml(html) {
97
+ return html
98
+ .replace(/<(script|style)[\s\S]*?<\/\1>/gi, '')
99
+ .replace(/<br\s*\/?>/gi, '\n')
100
+ .replace(/<\/(p|div|tr|li|h[1-6]|blockquote)>/gi, '\n')
101
+ .replace(/<[^>]+>/g, '')
102
+ .replace(/&nbsp;/gi, ' ')
103
+ .replace(/&amp;/gi, '&')
104
+ .replace(/&lt;/gi, '<')
105
+ .replace(/&gt;/gi, '>')
106
+ .replace(/&quot;/gi, '"')
107
+ .replace(/&#39;|&apos;/gi, "'")
108
+ .replace(/[ \t]+\n/g, '\n')
109
+ .replace(/\n{3,}/g, '\n\n')
110
+ .trim();
111
+ }
112
+ /**
113
+ * Case-insensitive header lookup.
114
+ *
115
+ * @param headers - Gmail header list
116
+ * @param name - Header name
117
+ * @returns Value or empty string
118
+ */
119
+ function header(headers, name) {
120
+ const lower = name.toLowerCase();
121
+ return headers?.find((h) => h.name.toLowerCase() === lower)?.value ?? '';
122
+ }
123
+ /**
124
+ * Walk a MIME tree depth-first.
125
+ *
126
+ * @param part - Root part
127
+ * @param visit - Callback per part
128
+ */
129
+ function walkParts(part, visit) {
130
+ if (!part)
131
+ return;
132
+ visit(part);
133
+ for (const child of part.parts ?? [])
134
+ walkParts(child, visit);
135
+ }
136
+ /**
137
+ * Pick the body: first text/plain part with inline data, else first text/html
138
+ * (stripped), else nothing.
139
+ *
140
+ * @param payload - Message payload
141
+ * @returns Body text and which kind it was
142
+ */
143
+ function extractBody(payload) {
144
+ let plain;
145
+ let html;
146
+ walkParts(payload, (p) => {
147
+ if (p.filename || !p.body?.data)
148
+ return;
149
+ const mime = (p.mimeType ?? '').toLowerCase();
150
+ if (mime === 'text/plain' && plain === undefined)
151
+ plain = base64UrlDecode(p.body.data);
152
+ else if (mime === 'text/html' && html === undefined)
153
+ html = base64UrlDecode(p.body.data);
154
+ });
155
+ if (plain !== undefined)
156
+ return { body: plain, bodyType: 'text' };
157
+ if (html !== undefined)
158
+ return { body: stripHtml(html), bodyType: 'html' };
159
+ return { body: '', bodyType: 'none' };
160
+ }
161
+ /**
162
+ * Every part that carries a filename and an attachmentId.
163
+ *
164
+ * @param payload - Message payload
165
+ * @returns Attachment references
166
+ */
167
+ function extractAttachments(payload) {
168
+ const out = [];
169
+ walkParts(payload, (p) => {
170
+ if (p.filename && p.body?.attachmentId) {
171
+ out.push({
172
+ filename: p.filename,
173
+ mimeType: p.mimeType ?? 'application/octet-stream',
174
+ size: p.body.size ?? 0,
175
+ attachmentId: p.body.attachmentId,
176
+ });
177
+ }
178
+ });
179
+ return out;
180
+ }
181
+ // ---------------------------------------------------------------------------
182
+ // Service
183
+ // ---------------------------------------------------------------------------
184
+ /**
185
+ * Gmail operations on the signed-in owner's mailbox.
186
+ *
187
+ * @example
188
+ * ```ts
189
+ * const gmail = new GmailService({ tokens: GoogleWorkspaceTokenService.getInstance() });
190
+ * const hits = await gmail.search({ query: 'is:unread newer_than:1d' });
191
+ * ```
192
+ */
193
+ export class GmailService {
194
+ deps;
195
+ base = GOOGLE_WORKSPACE_CONSTANTS.GMAIL_API_BASE;
196
+ /**
197
+ * @param deps - Token provider and optional fetch override
198
+ */
199
+ constructor(deps) {
200
+ this.deps = deps;
201
+ }
202
+ /**
203
+ * Gmail search (`messages.list`) followed by one metadata `messages.get`
204
+ * per hit for From / To / Subject / Date and the snippet.
205
+ *
206
+ * @param input - Gmail query string and result cap
207
+ * @returns Hits in Gmail's order (newest first)
208
+ * @throws GoogleWorkspaceError on auth / Google failures
209
+ */
210
+ async search(input) {
211
+ const query = (input.query ?? '').trim();
212
+ const max = clampMax(input.max, GOOGLE_WORKSPACE_CONSTANTS.GMAIL_DEFAULT_MAX_RESULTS, GOOGLE_WORKSPACE_CONSTANTS.GMAIL_MAX_RESULTS_CEILING);
213
+ const list = await googleRequest(this.deps, buildGoogleUrl(`${this.base}/messages`, { q: query, maxResults: max }));
214
+ const refs = list.messages ?? [];
215
+ if (refs.length === 0)
216
+ return [];
217
+ const messages = await Promise.all(refs.map((ref) => googleRequest(this.deps, buildGoogleUrl(`${this.base}/messages/${encodeURIComponent(ref.id)}`, {
218
+ format: 'metadata',
219
+ metadataHeaders: [...GOOGLE_WORKSPACE_CONSTANTS.GMAIL_SEARCH_HEADERS],
220
+ }))));
221
+ return messages.map((m) => {
222
+ const h = m.payload?.headers;
223
+ return {
224
+ id: m.id,
225
+ threadId: m.threadId,
226
+ from: header(h, 'From'),
227
+ to: header(h, 'To'),
228
+ subject: header(h, 'Subject'),
229
+ date: header(h, 'Date'),
230
+ snippet: m.snippet ?? '',
231
+ labelIds: m.labelIds ?? [],
232
+ };
233
+ });
234
+ }
235
+ /**
236
+ * Full `messages.get`; body decoded (text/plain preferred, stripped
237
+ * text/html as fallback), attachments listed only.
238
+ *
239
+ * @param id - Gmail message id
240
+ * @returns The message
241
+ * @throws GoogleWorkspaceError — 404 when Gmail has no such message
242
+ */
243
+ async read(id) {
244
+ if (!id?.trim()) {
245
+ throw new GoogleWorkspaceError(400, GOOGLE_WORKSPACE_CONSTANTS.ERROR_CODES.VALIDATION, 'message id is required');
246
+ }
247
+ const m = await googleRequest(this.deps, buildGoogleUrl(`${this.base}/messages/${encodeURIComponent(id.trim())}`, { format: 'full' }));
248
+ const h = m.payload?.headers;
249
+ const { body, bodyType } = extractBody(m.payload);
250
+ return {
251
+ id: m.id,
252
+ threadId: m.threadId,
253
+ from: header(h, 'From'),
254
+ to: header(h, 'To'),
255
+ cc: header(h, 'Cc'),
256
+ subject: header(h, 'Subject'),
257
+ date: header(h, 'Date'),
258
+ messageId: header(h, 'Message-ID'),
259
+ snippet: m.snippet ?? '',
260
+ body,
261
+ bodyType,
262
+ attachments: extractAttachments(m.payload),
263
+ labelIds: m.labelIds ?? [],
264
+ };
265
+ }
266
+ /**
267
+ * `messages.send` with a locally built RFC 822 message.
268
+ *
269
+ * @param input - Recipients, subject, text and optional reply threading
270
+ * @returns The sent message's id / threadId / labels
271
+ * @throws GoogleWorkspaceError(400, validation) for missing to/subject; auth / Google failures
272
+ */
273
+ async send(input) {
274
+ const raw = base64UrlEncode(buildRfc822(input));
275
+ const body = { raw };
276
+ if (input.threadId?.trim())
277
+ body.threadId = input.threadId.trim();
278
+ const sent = await googleRequest(this.deps, `${this.base}/messages/send`, { method: 'POST', body });
279
+ return { id: sent.id, threadId: sent.threadId, labelIds: sent.labelIds ?? [] };
280
+ }
281
+ }
282
+ /**
283
+ * Coerce a caller-supplied result cap into [1, ceiling], defaulting when
284
+ * absent or not a number.
285
+ *
286
+ * @param value - Requested cap
287
+ * @param fallback - Default
288
+ * @param ceiling - Hard maximum
289
+ * @returns The cap to send to Google
290
+ */
291
+ export function clampMax(value, fallback, ceiling) {
292
+ if (value === undefined || !Number.isFinite(value))
293
+ return fallback;
294
+ return Math.min(Math.max(Math.floor(value), 1), ceiling);
295
+ }
296
+ //# sourceMappingURL=gmail.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gmail.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/google/gmail.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Google API client — the one place Gmail and Calendar calls go through.
3
+ *
4
+ * Attaches the Cloud-minted access token, maps Google's HTTP failures to
5
+ * {@link GoogleWorkspaceError}, and drops the cached token on a 401 so the
6
+ * next call refreshes instead of failing the same way again.
7
+ *
8
+ * @module services/google/google-api.client
9
+ */
10
+ /** The slice of GoogleWorkspaceTokenService the Google services need. */
11
+ export interface GoogleTokenProvider {
12
+ getAccessToken(): Promise<string>;
13
+ clearCache(): void;
14
+ }
15
+ /** Shared constructor dependencies for Gmail / Calendar services. */
16
+ export interface GoogleApiDeps {
17
+ tokens: GoogleTokenProvider;
18
+ fetchImpl?: typeof fetch;
19
+ }
20
+ /**
21
+ * Build a URL with query parameters; `undefined`/empty values are skipped and
22
+ * array values are repeated (Gmail's `metadataHeaders=A&metadataHeaders=B`).
23
+ *
24
+ * @param base - Absolute URL without query
25
+ * @param params - Query parameters
26
+ * @returns The full URL string
27
+ */
28
+ export declare function buildGoogleUrl(base: string, params?: Record<string, string | number | string[] | undefined>): string;
29
+ /**
30
+ * Authenticated JSON call to a Google API.
31
+ *
32
+ * @param deps - Token provider + fetch
33
+ * @param url - Absolute URL (use {@link buildGoogleUrl})
34
+ * @param init - Method and optional JSON body
35
+ * @returns The parsed JSON response
36
+ * @throws GoogleWorkspaceError — token failures pass through; Google 401 →
37
+ * 401 google_error (cache dropped), 403/404/429 keep their status, other
38
+ * failures → 502 google_error, unreachable → 502 network
39
+ */
40
+ export declare function googleRequest<T>(deps: GoogleApiDeps, url: string, init?: {
41
+ method?: 'GET' | 'POST';
42
+ body?: unknown;
43
+ }): Promise<T>;
44
+ //# sourceMappingURL=google-api.client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google-api.client.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/google/google-api.client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,yEAAyE;AACzE,MAAM,WAAW,mBAAmB;IAClC,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,UAAU,IAAI,IAAI,CAAC;CACpB;AAED,qEAAqE;AACrE,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,mBAAmB,CAAC;IAC5B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAwBD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAM,GAAG,MAAM,CAWxH;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,IAAI,EAAE,aAAa,EACnB,GAAG,EAAE,MAAM,EACX,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GACrD,OAAO,CAAC,CAAC,CAAC,CAsCZ"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Google API client — the one place Gmail and Calendar calls go through.
3
+ *
4
+ * Attaches the Cloud-minted access token, maps Google's HTTP failures to
5
+ * {@link GoogleWorkspaceError}, and drops the cached token on a 401 so the
6
+ * next call refreshes instead of failing the same way again.
7
+ *
8
+ * @module services/google/google-api.client
9
+ */
10
+ import { GOOGLE_WORKSPACE_CONSTANTS } from '../../constants.js';
11
+ import { GoogleWorkspaceError } from './google-workspace-token.service.js';
12
+ /**
13
+ * Extract the human message from a Google error body, tolerating both the
14
+ * object and the plain-string shapes Google uses.
15
+ *
16
+ * @param text - Raw response body
17
+ * @returns Message or undefined
18
+ */
19
+ function googleErrorMessage(text) {
20
+ try {
21
+ const parsed = JSON.parse(text);
22
+ if (typeof parsed.error === 'string')
23
+ return parsed.error;
24
+ return parsed.error?.message;
25
+ }
26
+ catch {
27
+ return text ? text.slice(0, 200) : undefined;
28
+ }
29
+ }
30
+ /**
31
+ * Build a URL with query parameters; `undefined`/empty values are skipped and
32
+ * array values are repeated (Gmail's `metadataHeaders=A&metadataHeaders=B`).
33
+ *
34
+ * @param base - Absolute URL without query
35
+ * @param params - Query parameters
36
+ * @returns The full URL string
37
+ */
38
+ export function buildGoogleUrl(base, params = {}) {
39
+ const url = new URL(base);
40
+ for (const [key, value] of Object.entries(params)) {
41
+ if (value === undefined || value === '')
42
+ continue;
43
+ if (Array.isArray(value)) {
44
+ for (const v of value)
45
+ url.searchParams.append(key, v);
46
+ }
47
+ else {
48
+ url.searchParams.set(key, String(value));
49
+ }
50
+ }
51
+ return url.toString();
52
+ }
53
+ /**
54
+ * Authenticated JSON call to a Google API.
55
+ *
56
+ * @param deps - Token provider + fetch
57
+ * @param url - Absolute URL (use {@link buildGoogleUrl})
58
+ * @param init - Method and optional JSON body
59
+ * @returns The parsed JSON response
60
+ * @throws GoogleWorkspaceError — token failures pass through; Google 401 →
61
+ * 401 google_error (cache dropped), 403/404/429 keep their status, other
62
+ * failures → 502 google_error, unreachable → 502 network
63
+ */
64
+ export async function googleRequest(deps, url, init = {}) {
65
+ const token = await deps.tokens.getAccessToken();
66
+ const fetchImpl = deps.fetchImpl ?? fetch;
67
+ const CODES = GOOGLE_WORKSPACE_CONSTANTS.ERROR_CODES;
68
+ let res;
69
+ try {
70
+ res = await fetchImpl(url, {
71
+ method: init.method ?? 'GET',
72
+ headers: {
73
+ Authorization: `Bearer ${token}`,
74
+ Accept: 'application/json',
75
+ ...(init.body !== undefined ? { 'Content-Type': 'application/json' } : {}),
76
+ },
77
+ body: init.body !== undefined ? JSON.stringify(init.body) : undefined,
78
+ signal: AbortSignal.timeout(GOOGLE_WORKSPACE_CONSTANTS.REQUEST_TIMEOUT_MS),
79
+ });
80
+ }
81
+ catch (err) {
82
+ const message = err instanceof Error ? err.message : String(err);
83
+ throw new GoogleWorkspaceError(502, CODES.NETWORK, `Google unreachable: ${message}`);
84
+ }
85
+ const text = await res.text();
86
+ if (!res.ok) {
87
+ const message = googleErrorMessage(text) ?? `Google request failed (${res.status})`;
88
+ if (res.status === 401) {
89
+ deps.tokens.clearCache();
90
+ throw new GoogleWorkspaceError(401, CODES.GOOGLE_ERROR, `Google rejected the access token: ${message}`);
91
+ }
92
+ const passthrough = res.status === 403 || res.status === 404 || res.status === 429;
93
+ throw new GoogleWorkspaceError(passthrough ? res.status : 502, CODES.GOOGLE_ERROR, message);
94
+ }
95
+ if (!text)
96
+ return {};
97
+ try {
98
+ return JSON.parse(text);
99
+ }
100
+ catch {
101
+ throw new GoogleWorkspaceError(502, CODES.GOOGLE_ERROR, 'Google returned a non-JSON response.');
102
+ }
103
+ }
104
+ //# sourceMappingURL=google-api.client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google-api.client.js","sourceRoot":"","sources":["../../../../../../backend/src/services/google/google-api.client.ts"],"names":[],"mappings":"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"}