@elizaos/agent 2.0.0-alpha.144 → 2.0.0-alpha.151

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 (598) hide show
  1. package/apps/app-lifeops/src/actions/inbox-digest.d.ts +2 -0
  2. package/apps/app-lifeops/src/actions/inbox-digest.d.ts.map +1 -0
  3. package/apps/app-lifeops/src/actions/inbox-digest.js +1 -0
  4. package/apps/app-lifeops/src/actions/inbox-respond.d.ts +2 -0
  5. package/apps/app-lifeops/src/actions/inbox-respond.d.ts.map +1 -0
  6. package/apps/app-lifeops/src/actions/inbox-respond.js +1 -0
  7. package/apps/app-lifeops/src/actions/inbox-triage.d.ts +2 -0
  8. package/apps/app-lifeops/src/actions/inbox-triage.d.ts.map +1 -0
  9. package/apps/app-lifeops/src/actions/inbox-triage.js +1 -0
  10. package/apps/app-lifeops/src/actions/inbox.d.ts +3 -0
  11. package/apps/app-lifeops/src/actions/inbox.d.ts.map +1 -0
  12. package/apps/app-lifeops/src/actions/inbox.js +856 -0
  13. package/apps/app-lifeops/src/actions/update-owner-profile.d.ts +3 -0
  14. package/apps/app-lifeops/src/actions/update-owner-profile.d.ts.map +1 -0
  15. package/apps/app-lifeops/src/actions/update-owner-profile.js +131 -0
  16. package/apps/app-lifeops/src/inbox/channel-deep-links.d.ts.map +1 -0
  17. package/apps/app-lifeops/src/inbox/config.d.ts.map +1 -0
  18. package/{packages/agent → apps/app-lifeops}/src/inbox/config.js +1 -1
  19. package/apps/app-lifeops/src/inbox/message-fetcher.d.ts.map +1 -0
  20. package/apps/app-lifeops/src/inbox/reflection.d.ts.map +1 -0
  21. package/apps/app-lifeops/src/inbox/repository.d.ts.map +1 -0
  22. package/apps/app-lifeops/src/inbox/triage-classifier.d.ts.map +1 -0
  23. package/apps/app-lifeops/src/inbox/types.d.ts.map +1 -0
  24. package/apps/app-lifeops/src/lifeops/index.d.ts +25 -0
  25. package/apps/app-lifeops/src/lifeops/index.d.ts.map +1 -0
  26. package/apps/app-lifeops/src/lifeops/index.js +24 -0
  27. package/apps/app-lifeops/src/lifeops/screen-context.d.ts +52 -0
  28. package/apps/app-lifeops/src/lifeops/screen-context.d.ts.map +1 -0
  29. package/apps/app-lifeops/src/lifeops/screen-context.js +332 -0
  30. package/apps/app-lifeops/src/plugin.d.ts +3 -0
  31. package/apps/app-lifeops/src/plugin.d.ts.map +1 -1
  32. package/apps/app-lifeops/src/plugin.js +16 -3
  33. package/apps/app-lifeops/src/providers/inbox-triage.d.ts +3 -0
  34. package/apps/app-lifeops/src/providers/inbox-triage.d.ts.map +1 -0
  35. package/apps/app-lifeops/src/providers/inbox-triage.js +89 -0
  36. package/package.json +6 -4
  37. package/packages/agent/src/actions/calendar.d.ts +1 -18
  38. package/packages/agent/src/actions/calendar.d.ts.map +1 -1
  39. package/packages/agent/src/actions/calendar.js +1 -3143
  40. package/packages/agent/src/actions/check-balance.d.ts +17 -0
  41. package/packages/agent/src/actions/check-balance.d.ts.map +1 -0
  42. package/packages/agent/src/actions/check-balance.js +167 -0
  43. package/packages/agent/src/actions/connector-resolver.d.ts +75 -0
  44. package/packages/agent/src/actions/connector-resolver.d.ts.map +1 -0
  45. package/packages/agent/src/actions/connector-resolver.js +245 -0
  46. package/packages/agent/src/actions/context-signal-lexicon.d.ts +1 -1
  47. package/packages/agent/src/actions/context-signal-lexicon.d.ts.map +1 -1
  48. package/packages/agent/src/actions/context-signal-lexicon.js +6 -0
  49. package/packages/agent/src/actions/eject-plugin.d.ts +3 -0
  50. package/packages/agent/src/actions/eject-plugin.d.ts.map +1 -0
  51. package/packages/agent/src/actions/eject-plugin.js +48 -0
  52. package/packages/agent/src/actions/execute-trade.d.ts +17 -0
  53. package/packages/agent/src/actions/execute-trade.d.ts.map +1 -0
  54. package/packages/agent/src/actions/execute-trade.js +299 -0
  55. package/packages/agent/src/actions/get-self-status.d.ts +13 -0
  56. package/packages/agent/src/actions/get-self-status.d.ts.map +1 -0
  57. package/packages/agent/src/actions/get-self-status.js +66 -0
  58. package/packages/agent/src/actions/gmail.d.ts +1 -32
  59. package/packages/agent/src/actions/gmail.d.ts.map +1 -1
  60. package/packages/agent/src/actions/gmail.js +1 -1734
  61. package/packages/agent/src/actions/inbox-digest.d.ts +1 -1
  62. package/packages/agent/src/actions/inbox-digest.d.ts.map +1 -1
  63. package/packages/agent/src/actions/inbox-digest.js +1 -1
  64. package/packages/agent/src/actions/inbox-respond.d.ts +1 -1
  65. package/packages/agent/src/actions/inbox-respond.d.ts.map +1 -1
  66. package/packages/agent/src/actions/inbox-respond.js +1 -1
  67. package/packages/agent/src/actions/inbox-triage.d.ts +1 -1
  68. package/packages/agent/src/actions/inbox-triage.d.ts.map +1 -1
  69. package/packages/agent/src/actions/inbox-triage.js +1 -1
  70. package/packages/agent/src/actions/inbox.d.ts +1 -2
  71. package/packages/agent/src/actions/inbox.d.ts.map +1 -1
  72. package/packages/agent/src/actions/inbox.js +1 -856
  73. package/packages/agent/src/actions/index.d.ts +13 -0
  74. package/packages/agent/src/actions/index.d.ts.map +1 -1
  75. package/packages/agent/src/actions/index.js +13 -0
  76. package/packages/agent/src/actions/install-plugin.d.ts +3 -0
  77. package/packages/agent/src/actions/install-plugin.d.ts.map +1 -0
  78. package/packages/agent/src/actions/install-plugin.js +65 -0
  79. package/packages/agent/src/actions/life-goal-extractor.d.ts +1 -68
  80. package/packages/agent/src/actions/life-goal-extractor.d.ts.map +1 -1
  81. package/packages/agent/src/actions/life-goal-extractor.js +1 -354
  82. package/packages/agent/src/actions/life-param-extractor.d.ts +1 -77
  83. package/packages/agent/src/actions/life-param-extractor.d.ts.map +1 -1
  84. package/packages/agent/src/actions/life-param-extractor.js +1 -423
  85. package/packages/agent/src/actions/life-recent-context.d.ts +1 -8
  86. package/packages/agent/src/actions/life-recent-context.d.ts.map +1 -1
  87. package/packages/agent/src/actions/life-recent-context.js +1 -84
  88. package/packages/agent/src/actions/life-update-extractor.d.ts +1 -26
  89. package/packages/agent/src/actions/life-update-extractor.d.ts.map +1 -1
  90. package/packages/agent/src/actions/life-update-extractor.js +1 -195
  91. package/packages/agent/src/actions/life.d.ts +1 -8
  92. package/packages/agent/src/actions/life.d.ts.map +1 -1
  93. package/packages/agent/src/actions/life.extractor.d.ts +1 -17
  94. package/packages/agent/src/actions/life.extractor.d.ts.map +1 -1
  95. package/packages/agent/src/actions/life.extractor.js +1 -264
  96. package/packages/agent/src/actions/life.js +1 -3379
  97. package/packages/agent/src/actions/lifeops-extraction-config.d.ts +1 -15
  98. package/packages/agent/src/actions/lifeops-extraction-config.d.ts.map +1 -1
  99. package/packages/agent/src/actions/lifeops-extraction-config.js +1 -25
  100. package/packages/agent/src/actions/lifeops-google-helpers.d.ts +1 -61
  101. package/packages/agent/src/actions/lifeops-google-helpers.d.ts.map +1 -1
  102. package/packages/agent/src/actions/lifeops-google-helpers.js +1 -607
  103. package/packages/agent/src/actions/list-ejected.d.ts +3 -0
  104. package/packages/agent/src/actions/list-ejected.d.ts.map +1 -0
  105. package/packages/agent/src/actions/list-ejected.js +35 -0
  106. package/packages/agent/src/actions/log-level.d.ts +3 -0
  107. package/packages/agent/src/actions/log-level.d.ts.map +1 -0
  108. package/packages/agent/src/actions/log-level.js +125 -0
  109. package/packages/agent/src/actions/manage-tasks.d.ts.map +1 -1
  110. package/packages/agent/src/actions/manage-tasks.js +51 -15
  111. package/packages/agent/src/actions/media.d.ts +21 -0
  112. package/packages/agent/src/actions/media.d.ts.map +1 -0
  113. package/packages/agent/src/actions/media.js +384 -0
  114. package/packages/agent/src/actions/read-messages.d.ts +14 -0
  115. package/packages/agent/src/actions/read-messages.d.ts.map +1 -0
  116. package/packages/agent/src/actions/read-messages.js +228 -0
  117. package/packages/agent/src/actions/reinject-plugin.d.ts +3 -0
  118. package/packages/agent/src/actions/reinject-plugin.d.ts.map +1 -0
  119. package/packages/agent/src/actions/reinject-plugin.js +47 -0
  120. package/packages/agent/src/actions/send-message.d.ts +0 -7
  121. package/packages/agent/src/actions/send-message.d.ts.map +1 -1
  122. package/packages/agent/src/actions/send-message.js +170 -49
  123. package/packages/agent/src/actions/sync-plugin.d.ts +3 -0
  124. package/packages/agent/src/actions/sync-plugin.d.ts.map +1 -0
  125. package/packages/agent/src/actions/sync-plugin.js +47 -0
  126. package/packages/agent/src/actions/timezone-normalization.d.ts +1 -2
  127. package/packages/agent/src/actions/timezone-normalization.d.ts.map +1 -1
  128. package/packages/agent/src/actions/timezone-normalization.js +1 -107
  129. package/packages/agent/src/actions/transfer-token.d.ts +17 -0
  130. package/packages/agent/src/actions/transfer-token.d.ts.map +1 -0
  131. package/packages/agent/src/actions/transfer-token.js +470 -0
  132. package/packages/agent/src/actions/update-owner-profile.d.ts +1 -2
  133. package/packages/agent/src/actions/update-owner-profile.d.ts.map +1 -1
  134. package/packages/agent/src/actions/update-owner-profile.js +1 -131
  135. package/packages/agent/src/actions/wallet-action-shared.d.ts +15 -0
  136. package/packages/agent/src/actions/wallet-action-shared.d.ts.map +1 -0
  137. package/packages/agent/src/actions/wallet-action-shared.js +24 -0
  138. package/packages/agent/src/api/agent-admin-routes.d.ts.map +1 -1
  139. package/packages/agent/src/api/agent-admin-routes.js +1 -1
  140. package/packages/agent/src/api/binance-skill-helpers.d.ts.map +1 -1
  141. package/packages/agent/src/api/binance-skill-helpers.js +8 -3
  142. package/packages/agent/src/api/chat-routes.d.ts.map +1 -1
  143. package/packages/agent/src/api/chat-routes.js +20 -5
  144. package/packages/agent/src/api/coding-agents-auth-sanitize.d.ts +1 -22
  145. package/packages/agent/src/api/coding-agents-auth-sanitize.d.ts.map +1 -1
  146. package/packages/agent/src/api/coding-agents-auth-sanitize.js +1 -39
  147. package/packages/agent/src/api/coding-agents-preflight-normalize.d.ts +1 -28
  148. package/packages/agent/src/api/coding-agents-preflight-normalize.d.ts.map +1 -1
  149. package/packages/agent/src/api/coding-agents-preflight-normalize.js +1 -45
  150. package/packages/agent/src/api/coordinator-types.d.ts +1 -46
  151. package/packages/agent/src/api/coordinator-types.d.ts.map +1 -1
  152. package/packages/agent/src/api/coordinator-types.js +1 -1
  153. package/packages/agent/src/api/coordinator-wiring.d.ts +1 -45
  154. package/packages/agent/src/api/coordinator-wiring.d.ts.map +1 -1
  155. package/packages/agent/src/api/coordinator-wiring.js +1 -108
  156. package/packages/agent/src/api/index.d.ts +1 -1
  157. package/packages/agent/src/api/index.d.ts.map +1 -1
  158. package/packages/agent/src/api/index.js +1 -1
  159. package/packages/agent/src/api/lifeops-browser-packaging.d.ts +1 -15
  160. package/packages/agent/src/api/lifeops-browser-packaging.d.ts.map +1 -1
  161. package/packages/agent/src/api/lifeops-browser-packaging.js +1 -305
  162. package/packages/agent/src/api/lifeops-routes.d.ts +1 -19
  163. package/packages/agent/src/api/lifeops-routes.d.ts.map +1 -1
  164. package/packages/agent/src/api/lifeops-routes.js +1 -1173
  165. package/packages/agent/src/api/server.d.ts.map +1 -1
  166. package/packages/agent/src/api/server.js +6 -6
  167. package/packages/agent/src/api/task-agent-message-routing.d.ts +1 -9
  168. package/packages/agent/src/api/task-agent-message-routing.d.ts.map +1 -1
  169. package/packages/agent/src/api/task-agent-message-routing.js +1 -62
  170. package/packages/agent/src/api/website-blocker-routes.d.ts +1 -6
  171. package/packages/agent/src/api/website-blocker-routes.d.ts.map +1 -1
  172. package/packages/agent/src/api/website-blocker-routes.js +1 -174
  173. package/packages/agent/src/config/types.agent-defaults.d.ts +1 -1
  174. package/packages/agent/src/config/types.agent-defaults.d.ts.map +1 -1
  175. package/packages/agent/src/evals/coordinator-eval-client.d.ts +1 -38
  176. package/packages/agent/src/evals/coordinator-eval-client.d.ts.map +1 -1
  177. package/packages/agent/src/evals/coordinator-eval-client.js +1 -138
  178. package/packages/agent/src/evals/coordinator-live-runner.d.ts +1 -56
  179. package/packages/agent/src/evals/coordinator-live-runner.d.ts.map +1 -1
  180. package/packages/agent/src/evals/coordinator-live-runner.js +1 -546
  181. package/packages/agent/src/evals/coordinator-preflight.d.ts +1 -31
  182. package/packages/agent/src/evals/coordinator-preflight.d.ts.map +1 -1
  183. package/packages/agent/src/evals/coordinator-preflight.js +1 -296
  184. package/packages/agent/src/evals/coordinator-scenarios.d.ts +1 -23
  185. package/packages/agent/src/evals/coordinator-scenarios.d.ts.map +1 -1
  186. package/packages/agent/src/evals/coordinator-scenarios.js +1 -1141
  187. package/packages/agent/src/lifeops/app-state.d.ts +1 -10
  188. package/packages/agent/src/lifeops/app-state.d.ts.map +1 -1
  189. package/packages/agent/src/lifeops/app-state.js +1 -32
  190. package/packages/agent/src/lifeops/apple-reminders.d.ts +1 -57
  191. package/packages/agent/src/lifeops/apple-reminders.d.ts.map +1 -1
  192. package/packages/agent/src/lifeops/apple-reminders.js +1 -325
  193. package/packages/agent/src/lifeops/defaults.d.ts +1 -23
  194. package/packages/agent/src/lifeops/defaults.d.ts.map +1 -1
  195. package/packages/agent/src/lifeops/defaults.js +1 -205
  196. package/packages/agent/src/lifeops/engine.d.ts +1 -7
  197. package/packages/agent/src/lifeops/engine.d.ts.map +1 -1
  198. package/packages/agent/src/lifeops/engine.js +1 -389
  199. package/packages/agent/src/lifeops/goal-grounding.d.ts +1 -53
  200. package/packages/agent/src/lifeops/goal-grounding.d.ts.map +1 -1
  201. package/packages/agent/src/lifeops/goal-grounding.js +1 -147
  202. package/packages/agent/src/lifeops/goal-semantic-evaluator.d.ts +1 -11
  203. package/packages/agent/src/lifeops/goal-semantic-evaluator.d.ts.map +1 -1
  204. package/packages/agent/src/lifeops/goal-semantic-evaluator.js +1 -154
  205. package/packages/agent/src/lifeops/google-api-error.d.ts +1 -6
  206. package/packages/agent/src/lifeops/google-api-error.d.ts.map +1 -1
  207. package/packages/agent/src/lifeops/google-api-error.js +1 -35
  208. package/packages/agent/src/lifeops/google-calendar.d.ts +1 -52
  209. package/packages/agent/src/lifeops/google-calendar.d.ts.map +1 -1
  210. package/packages/agent/src/lifeops/google-calendar.js +1 -268
  211. package/packages/agent/src/lifeops/google-connector-gateway.d.ts +1 -18
  212. package/packages/agent/src/lifeops/google-connector-gateway.d.ts.map +1 -1
  213. package/packages/agent/src/lifeops/google-connector-gateway.js +1 -65
  214. package/packages/agent/src/lifeops/google-fetch.d.ts +1 -10
  215. package/packages/agent/src/lifeops/google-fetch.d.ts.map +1 -1
  216. package/packages/agent/src/lifeops/google-fetch.js +1 -85
  217. package/packages/agent/src/lifeops/google-gmail.d.ts +1 -53
  218. package/packages/agent/src/lifeops/google-gmail.d.ts.map +1 -1
  219. package/packages/agent/src/lifeops/google-gmail.js +1 -471
  220. package/packages/agent/src/lifeops/google-managed-client.d.ts +1 -126
  221. package/packages/agent/src/lifeops/google-managed-client.d.ts.map +1 -1
  222. package/packages/agent/src/lifeops/google-managed-client.js +1 -294
  223. package/packages/agent/src/lifeops/google-oauth.d.ts +1 -60
  224. package/packages/agent/src/lifeops/google-oauth.d.ts.map +1 -1
  225. package/packages/agent/src/lifeops/google-oauth.js +1 -494
  226. package/packages/agent/src/lifeops/google-scopes.d.ts +1 -12
  227. package/packages/agent/src/lifeops/google-scopes.d.ts.map +1 -1
  228. package/packages/agent/src/lifeops/google-scopes.js +1 -96
  229. package/packages/agent/src/lifeops/index.d.ts +1 -2
  230. package/packages/agent/src/lifeops/index.d.ts.map +1 -1
  231. package/packages/agent/src/lifeops/index.js +1 -2
  232. package/packages/agent/src/lifeops/owner-profile.d.ts +1 -14
  233. package/packages/agent/src/lifeops/owner-profile.d.ts.map +1 -1
  234. package/packages/agent/src/lifeops/owner-profile.js +1 -194
  235. package/packages/agent/src/lifeops/repository.d.ts +1 -208
  236. package/packages/agent/src/lifeops/repository.d.ts.map +1 -1
  237. package/packages/agent/src/lifeops/repository.js +1 -3187
  238. package/packages/agent/src/lifeops/runtime.d.ts +1 -13
  239. package/packages/agent/src/lifeops/runtime.d.ts.map +1 -1
  240. package/packages/agent/src/lifeops/runtime.js +1 -120
  241. package/packages/agent/src/lifeops/screen-context.d.ts +1 -51
  242. package/packages/agent/src/lifeops/screen-context.d.ts.map +1 -1
  243. package/packages/agent/src/lifeops/screen-context.js +1 -332
  244. package/packages/agent/src/lifeops/seed-routines.d.ts +1 -19
  245. package/packages/agent/src/lifeops/seed-routines.d.ts.map +1 -1
  246. package/packages/agent/src/lifeops/seed-routines.js +1 -111
  247. package/packages/agent/src/lifeops/service.d.ts +1 -274
  248. package/packages/agent/src/lifeops/service.d.ts.map +1 -1
  249. package/packages/agent/src/lifeops/service.js +1 -9260
  250. package/packages/agent/src/lifeops/sql.d.ts +1 -30
  251. package/packages/agent/src/lifeops/sql.d.ts.map +1 -1
  252. package/packages/agent/src/lifeops/sql.js +1 -247
  253. package/packages/agent/src/lifeops/time.d.ts +1 -16
  254. package/packages/agent/src/lifeops/time.d.ts.map +1 -1
  255. package/packages/agent/src/lifeops/time.js +1 -132
  256. package/packages/agent/src/lifeops/twilio.d.ts +1 -24
  257. package/packages/agent/src/lifeops/twilio.d.ts.map +1 -1
  258. package/packages/agent/src/lifeops/twilio.js +1 -157
  259. package/packages/agent/src/lifeops/x-poster.d.ts +1 -18
  260. package/packages/agent/src/lifeops/x-poster.d.ts.map +1 -1
  261. package/packages/agent/src/lifeops/x-poster.js +1 -148
  262. package/packages/agent/src/providers/inbox-triage.d.ts +1 -2
  263. package/packages/agent/src/providers/inbox-triage.d.ts.map +1 -1
  264. package/packages/agent/src/providers/inbox-triage.js +1 -89
  265. package/packages/agent/src/providers/index.d.ts +4 -1
  266. package/packages/agent/src/providers/index.d.ts.map +1 -1
  267. package/packages/agent/src/providers/index.js +4 -1
  268. package/packages/agent/src/providers/lifeops.d.ts +1 -2
  269. package/packages/agent/src/providers/lifeops.d.ts.map +1 -1
  270. package/packages/agent/src/providers/lifeops.js +1 -157
  271. package/packages/agent/src/providers/local-models.d.ts +118 -0
  272. package/packages/agent/src/providers/local-models.d.ts.map +1 -0
  273. package/packages/agent/src/providers/local-models.js +427 -0
  274. package/packages/agent/src/providers/media-provider.d.ts +192 -0
  275. package/packages/agent/src/providers/media-provider.d.ts.map +1 -0
  276. package/packages/agent/src/providers/media-provider.js +1088 -0
  277. package/packages/agent/src/providers/self-status.d.ts +4 -0
  278. package/packages/agent/src/providers/self-status.d.ts.map +1 -0
  279. package/packages/agent/src/providers/self-status.js +12 -0
  280. package/packages/agent/src/providers/tasks.d.ts.map +1 -1
  281. package/packages/agent/src/providers/tasks.js +7 -7
  282. package/packages/agent/src/runtime/core-plugins.js +1 -1
  283. package/packages/agent/src/runtime/eliza-plugin.d.ts.map +1 -1
  284. package/packages/agent/src/runtime/eliza-plugin.js +1 -7
  285. package/packages/agent/src/runtime/eliza.js +2 -2
  286. package/packages/agent/src/runtime/plugin-collector.js +3 -3
  287. package/packages/agent/src/runtime/plugin-lifecycle.d.ts.map +1 -1
  288. package/packages/agent/src/runtime/plugin-lifecycle.js +3 -13
  289. package/packages/agent/src/runtime/trajectory-internals.d.ts.map +1 -1
  290. package/packages/agent/src/runtime/trajectory-internals.js +1 -3
  291. package/packages/agent/src/services/built-in-app-routes/hyperscape.d.ts.map +1 -1
  292. package/packages/agent/src/services/coding-task-executor.d.ts +3 -3
  293. package/packages/agent/src/services/coding-task-executor.js +3 -3
  294. package/packages/shared/src/awareness/index.d.ts +2 -0
  295. package/packages/shared/src/awareness/index.d.ts.map +1 -0
  296. package/packages/shared/src/awareness/index.js +1 -0
  297. package/packages/shared/src/awareness/registry.d.ts +27 -0
  298. package/packages/shared/src/awareness/registry.d.ts.map +1 -0
  299. package/packages/shared/src/awareness/registry.js +161 -0
  300. package/packages/shared/src/i18n/generated/validation-keyword-data.d.ts +24 -0
  301. package/packages/shared/src/i18n/generated/validation-keyword-data.d.ts.map +1 -1
  302. package/packages/shared/src/i18n/generated/validation-keyword-data.js +24 -0
  303. package/packages/shared/src/runtime-env.d.ts.map +1 -1
  304. package/packages/shared/src/runtime-env.js +5 -1
  305. package/packages/typescript/src/generated/action-docs.d.ts +135 -0
  306. package/packages/typescript/src/generated/action-docs.d.ts.map +1 -1
  307. package/packages/typescript/src/generated/action-docs.js +237 -0
  308. package/packages/typescript/src/i18n/generated/validation-keyword-data.d.ts +24 -0
  309. package/packages/typescript/src/i18n/generated/validation-keyword-data.d.ts.map +1 -1
  310. package/packages/typescript/src/i18n/generated/validation-keyword-data.js +24 -0
  311. package/packages/typescript/src/index.node.d.ts +2 -2
  312. package/packages/typescript/src/index.node.d.ts.map +1 -1
  313. package/packages/typescript/src/index.node.js +4 -3
  314. package/packages/typescript/src/plugin-lifecycle.d.ts.map +1 -1
  315. package/packages/typescript/src/plugin-lifecycle.js +42 -3
  316. package/packages/typescript/src/services/message.d.ts.map +1 -1
  317. package/packages/typescript/src/services/message.js +32 -0
  318. package/apps/app-training/src/core/cli.d.ts +0 -11
  319. package/apps/app-training/src/core/cli.d.ts.map +0 -1
  320. package/apps/app-training/src/core/cli.js +0 -302
  321. package/apps/app-training/src/core/context-audit.d.ts +0 -51
  322. package/apps/app-training/src/core/context-audit.d.ts.map +0 -1
  323. package/apps/app-training/src/core/context-audit.js +0 -141
  324. package/apps/app-training/src/core/context-catalog.d.ts +0 -47
  325. package/apps/app-training/src/core/context-catalog.d.ts.map +0 -1
  326. package/apps/app-training/src/core/context-catalog.js +0 -259
  327. package/apps/app-training/src/core/context-types.d.ts +0 -3
  328. package/apps/app-training/src/core/context-types.d.ts.map +0 -1
  329. package/apps/app-training/src/core/context-types.js +0 -11
  330. package/apps/app-training/src/core/dataset-generator.d.ts +0 -135
  331. package/apps/app-training/src/core/dataset-generator.d.ts.map +0 -1
  332. package/apps/app-training/src/core/dataset-generator.js +0 -703
  333. package/apps/app-training/src/core/replay-validator.d.ts +0 -96
  334. package/apps/app-training/src/core/replay-validator.d.ts.map +0 -1
  335. package/apps/app-training/src/core/replay-validator.js +0 -265
  336. package/apps/app-training/src/core/roleplay-executor.d.ts +0 -123
  337. package/apps/app-training/src/core/roleplay-executor.d.ts.map +0 -1
  338. package/apps/app-training/src/core/roleplay-executor.js +0 -645
  339. package/apps/app-training/src/core/roleplay-trajectories.d.ts +0 -54
  340. package/apps/app-training/src/core/roleplay-trajectories.d.ts.map +0 -1
  341. package/apps/app-training/src/core/roleplay-trajectories.js +0 -73
  342. package/apps/app-training/src/core/scenario-blueprints.d.ts +0 -62
  343. package/apps/app-training/src/core/scenario-blueprints.d.ts.map +0 -1
  344. package/apps/app-training/src/core/scenario-blueprints.js +0 -790
  345. package/apps/app-training/src/core/trajectory-task-datasets.d.ts +0 -38
  346. package/apps/app-training/src/core/trajectory-task-datasets.d.ts.map +0 -1
  347. package/apps/app-training/src/core/trajectory-task-datasets.js +0 -281
  348. package/apps/app-training/src/core/vertex-tuning.d.ts +0 -139
  349. package/apps/app-training/src/core/vertex-tuning.d.ts.map +0 -1
  350. package/apps/app-training/src/core/vertex-tuning.js +0 -234
  351. package/packages/agent/src/inbox/channel-deep-links.d.ts.map +0 -1
  352. package/packages/agent/src/inbox/config.d.ts.map +0 -1
  353. package/packages/agent/src/inbox/message-fetcher.d.ts.map +0 -1
  354. package/packages/agent/src/inbox/reflection.d.ts.map +0 -1
  355. package/packages/agent/src/inbox/repository.d.ts.map +0 -1
  356. package/packages/agent/src/inbox/triage-classifier.d.ts.map +0 -1
  357. package/packages/agent/src/inbox/types.d.ts.map +0 -1
  358. package/packages/agent/src/training/cli.d.ts +0 -2
  359. package/packages/agent/src/training/cli.d.ts.map +0 -1
  360. package/packages/agent/src/training/cli.js +0 -2
  361. package/packages/agent/src/training/context-audit.d.ts +0 -2
  362. package/packages/agent/src/training/context-audit.d.ts.map +0 -1
  363. package/packages/agent/src/training/context-audit.js +0 -2
  364. package/packages/agent/src/training/context-catalog.d.ts +0 -2
  365. package/packages/agent/src/training/context-catalog.d.ts.map +0 -1
  366. package/packages/agent/src/training/context-catalog.js +0 -2
  367. package/packages/agent/src/training/context-types.d.ts +0 -2
  368. package/packages/agent/src/training/context-types.d.ts.map +0 -1
  369. package/packages/agent/src/training/context-types.js +0 -2
  370. package/packages/agent/src/training/dataset-generator.d.ts +0 -2
  371. package/packages/agent/src/training/dataset-generator.d.ts.map +0 -1
  372. package/packages/agent/src/training/dataset-generator.js +0 -2
  373. package/packages/agent/src/training/replay-validator.d.ts +0 -2
  374. package/packages/agent/src/training/replay-validator.d.ts.map +0 -1
  375. package/packages/agent/src/training/replay-validator.js +0 -2
  376. package/packages/agent/src/training/roleplay-executor.d.ts +0 -2
  377. package/packages/agent/src/training/roleplay-executor.d.ts.map +0 -1
  378. package/packages/agent/src/training/roleplay-executor.js +0 -2
  379. package/packages/agent/src/training/roleplay-trajectories.d.ts +0 -2
  380. package/packages/agent/src/training/roleplay-trajectories.d.ts.map +0 -1
  381. package/packages/agent/src/training/roleplay-trajectories.js +0 -2
  382. package/packages/agent/src/training/scenario-blueprints.d.ts +0 -2
  383. package/packages/agent/src/training/scenario-blueprints.d.ts.map +0 -1
  384. package/packages/agent/src/training/scenario-blueprints.js +0 -2
  385. package/packages/agent/src/training/trajectory-task-datasets.d.ts +0 -2
  386. package/packages/agent/src/training/trajectory-task-datasets.d.ts.map +0 -1
  387. package/packages/agent/src/training/trajectory-task-datasets.js +0 -2
  388. package/packages/agent/src/training/vertex-tuning.d.ts +0 -2
  389. package/packages/agent/src/training/vertex-tuning.d.ts.map +0 -1
  390. package/packages/agent/src/training/vertex-tuning.js +0 -2
  391. package/packages/typescript/src/features/orchestrator/actions/coding-task-handlers.d.ts +0 -41
  392. package/packages/typescript/src/features/orchestrator/actions/coding-task-handlers.d.ts.map +0 -1
  393. package/packages/typescript/src/features/orchestrator/actions/coding-task-handlers.js +0 -443
  394. package/packages/typescript/src/features/orchestrator/actions/coding-task-helpers.d.ts +0 -34
  395. package/packages/typescript/src/features/orchestrator/actions/coding-task-helpers.d.ts.map +0 -1
  396. package/packages/typescript/src/features/orchestrator/actions/coding-task-helpers.js +0 -171
  397. package/packages/typescript/src/features/orchestrator/actions/eval-metadata.d.ts +0 -11
  398. package/packages/typescript/src/features/orchestrator/actions/eval-metadata.d.ts.map +0 -1
  399. package/packages/typescript/src/features/orchestrator/actions/eval-metadata.js +0 -55
  400. package/packages/typescript/src/features/orchestrator/actions/finalize-workspace.d.ts +0 -11
  401. package/packages/typescript/src/features/orchestrator/actions/finalize-workspace.d.ts.map +0 -1
  402. package/packages/typescript/src/features/orchestrator/actions/finalize-workspace.js +0 -214
  403. package/packages/typescript/src/features/orchestrator/actions/list-agents.d.ts +0 -13
  404. package/packages/typescript/src/features/orchestrator/actions/list-agents.d.ts.map +0 -1
  405. package/packages/typescript/src/features/orchestrator/actions/list-agents.js +0 -174
  406. package/packages/typescript/src/features/orchestrator/actions/manage-issues.d.ts +0 -11
  407. package/packages/typescript/src/features/orchestrator/actions/manage-issues.d.ts.map +0 -1
  408. package/packages/typescript/src/features/orchestrator/actions/manage-issues.js +0 -428
  409. package/packages/typescript/src/features/orchestrator/actions/provision-workspace.d.ts +0 -11
  410. package/packages/typescript/src/features/orchestrator/actions/provision-workspace.d.ts.map +0 -1
  411. package/packages/typescript/src/features/orchestrator/actions/provision-workspace.js +0 -189
  412. package/packages/typescript/src/features/orchestrator/actions/send-to-agent.d.ts +0 -12
  413. package/packages/typescript/src/features/orchestrator/actions/send-to-agent.d.ts.map +0 -1
  414. package/packages/typescript/src/features/orchestrator/actions/send-to-agent.js +0 -265
  415. package/packages/typescript/src/features/orchestrator/actions/spawn-agent.d.ts +0 -12
  416. package/packages/typescript/src/features/orchestrator/actions/spawn-agent.d.ts.map +0 -1
  417. package/packages/typescript/src/features/orchestrator/actions/spawn-agent.js +0 -356
  418. package/packages/typescript/src/features/orchestrator/actions/start-coding-task.d.ts +0 -22
  419. package/packages/typescript/src/features/orchestrator/actions/start-coding-task.d.ts.map +0 -1
  420. package/packages/typescript/src/features/orchestrator/actions/start-coding-task.js +0 -270
  421. package/packages/typescript/src/features/orchestrator/actions/stop-agent.d.ts +0 -12
  422. package/packages/typescript/src/features/orchestrator/actions/stop-agent.d.ts.map +0 -1
  423. package/packages/typescript/src/features/orchestrator/actions/stop-agent.js +0 -192
  424. package/packages/typescript/src/features/orchestrator/actions/task-control.d.ts +0 -3
  425. package/packages/typescript/src/features/orchestrator/actions/task-control.d.ts.map +0 -1
  426. package/packages/typescript/src/features/orchestrator/actions/task-control.js +0 -217
  427. package/packages/typescript/src/features/orchestrator/actions/task-history.d.ts +0 -3
  428. package/packages/typescript/src/features/orchestrator/actions/task-history.d.ts.map +0 -1
  429. package/packages/typescript/src/features/orchestrator/actions/task-history.js +0 -323
  430. package/packages/typescript/src/features/orchestrator/actions/task-share.d.ts +0 -3
  431. package/packages/typescript/src/features/orchestrator/actions/task-share.d.ts.map +0 -1
  432. package/packages/typescript/src/features/orchestrator/actions/task-share.js +0 -168
  433. package/packages/typescript/src/features/orchestrator/actions/task-thread-target.d.ts +0 -11
  434. package/packages/typescript/src/features/orchestrator/actions/task-thread-target.d.ts.map +0 -1
  435. package/packages/typescript/src/features/orchestrator/actions/task-thread-target.js +0 -68
  436. package/packages/typescript/src/features/orchestrator/api/agent-routes.d.ts +0 -18
  437. package/packages/typescript/src/features/orchestrator/api/agent-routes.d.ts.map +0 -1
  438. package/packages/typescript/src/features/orchestrator/api/agent-routes.js +0 -654
  439. package/packages/typescript/src/features/orchestrator/api/coordinator-routes.d.ts +0 -22
  440. package/packages/typescript/src/features/orchestrator/api/coordinator-routes.d.ts.map +0 -1
  441. package/packages/typescript/src/features/orchestrator/api/coordinator-routes.js +0 -403
  442. package/packages/typescript/src/features/orchestrator/api/hook-routes.d.ts +0 -18
  443. package/packages/typescript/src/features/orchestrator/api/hook-routes.d.ts.map +0 -1
  444. package/packages/typescript/src/features/orchestrator/api/hook-routes.js +0 -164
  445. package/packages/typescript/src/features/orchestrator/api/issue-routes.d.ts +0 -17
  446. package/packages/typescript/src/features/orchestrator/api/issue-routes.d.ts.map +0 -1
  447. package/packages/typescript/src/features/orchestrator/api/issue-routes.js +0 -132
  448. package/packages/typescript/src/features/orchestrator/api/routes.d.ts +0 -37
  449. package/packages/typescript/src/features/orchestrator/api/routes.d.ts.map +0 -1
  450. package/packages/typescript/src/features/orchestrator/api/routes.js +0 -96
  451. package/packages/typescript/src/features/orchestrator/api/workspace-routes.d.ts +0 -17
  452. package/packages/typescript/src/features/orchestrator/api/workspace-routes.d.ts.map +0 -1
  453. package/packages/typescript/src/features/orchestrator/api/workspace-routes.js +0 -149
  454. package/packages/typescript/src/features/orchestrator/base-plugin.d.ts +0 -19
  455. package/packages/typescript/src/features/orchestrator/base-plugin.d.ts.map +0 -1
  456. package/packages/typescript/src/features/orchestrator/base-plugin.js +0 -75
  457. package/packages/typescript/src/features/orchestrator/claude-jsonl-completion-watcher.d.ts +0 -101
  458. package/packages/typescript/src/features/orchestrator/claude-jsonl-completion-watcher.d.ts.map +0 -1
  459. package/packages/typescript/src/features/orchestrator/claude-jsonl-completion-watcher.js +0 -310
  460. package/packages/typescript/src/features/orchestrator/index.d.ts +0 -33
  461. package/packages/typescript/src/features/orchestrator/index.d.ts.map +0 -1
  462. package/packages/typescript/src/features/orchestrator/index.js +0 -30
  463. package/packages/typescript/src/features/orchestrator/patch-agent-orchestrator-plugin.d.ts +0 -15
  464. package/packages/typescript/src/features/orchestrator/patch-agent-orchestrator-plugin.d.ts.map +0 -1
  465. package/packages/typescript/src/features/orchestrator/patch-agent-orchestrator-plugin.js +0 -1449
  466. package/packages/typescript/src/features/orchestrator/providers/action-examples.d.ts +0 -14
  467. package/packages/typescript/src/features/orchestrator/providers/action-examples.d.ts.map +0 -1
  468. package/packages/typescript/src/features/orchestrator/providers/action-examples.js +0 -151
  469. package/packages/typescript/src/features/orchestrator/providers/active-workspace-context.d.ts +0 -13
  470. package/packages/typescript/src/features/orchestrator/providers/active-workspace-context.d.ts.map +0 -1
  471. package/packages/typescript/src/features/orchestrator/providers/active-workspace-context.js +0 -142
  472. package/packages/typescript/src/features/orchestrator/services/agent-credentials.d.ts +0 -6
  473. package/packages/typescript/src/features/orchestrator/services/agent-credentials.d.ts.map +0 -1
  474. package/packages/typescript/src/features/orchestrator/services/agent-credentials.js +0 -91
  475. package/packages/typescript/src/features/orchestrator/services/agent-metrics.d.ts +0 -30
  476. package/packages/typescript/src/features/orchestrator/services/agent-metrics.d.ts.map +0 -1
  477. package/packages/typescript/src/features/orchestrator/services/agent-metrics.js +0 -54
  478. package/packages/typescript/src/features/orchestrator/services/agent-selection.d.ts +0 -53
  479. package/packages/typescript/src/features/orchestrator/services/agent-selection.d.ts.map +0 -1
  480. package/packages/typescript/src/features/orchestrator/services/agent-selection.js +0 -70
  481. package/packages/typescript/src/features/orchestrator/services/ansi-utils.d.ts +0 -61
  482. package/packages/typescript/src/features/orchestrator/services/ansi-utils.d.ts.map +0 -1
  483. package/packages/typescript/src/features/orchestrator/services/ansi-utils.js +0 -252
  484. package/packages/typescript/src/features/orchestrator/services/config-env.d.ts +0 -13
  485. package/packages/typescript/src/features/orchestrator/services/config-env.d.ts.map +0 -1
  486. package/packages/typescript/src/features/orchestrator/services/config-env.js +0 -37
  487. package/packages/typescript/src/features/orchestrator/services/coordinator-event-normalizer.d.ts +0 -50
  488. package/packages/typescript/src/features/orchestrator/services/coordinator-event-normalizer.d.ts.map +0 -1
  489. package/packages/typescript/src/features/orchestrator/services/coordinator-event-normalizer.js +0 -184
  490. package/packages/typescript/src/features/orchestrator/services/debug-capture.d.ts +0 -38
  491. package/packages/typescript/src/features/orchestrator/services/debug-capture.d.ts.map +0 -1
  492. package/packages/typescript/src/features/orchestrator/services/debug-capture.js +0 -113
  493. package/packages/typescript/src/features/orchestrator/services/pty-auto-response.d.ts +0 -30
  494. package/packages/typescript/src/features/orchestrator/services/pty-auto-response.d.ts.map +0 -1
  495. package/packages/typescript/src/features/orchestrator/services/pty-auto-response.js +0 -146
  496. package/packages/typescript/src/features/orchestrator/services/pty-init.d.ts +0 -54
  497. package/packages/typescript/src/features/orchestrator/services/pty-init.d.ts.map +0 -1
  498. package/packages/typescript/src/features/orchestrator/services/pty-init.js +0 -315
  499. package/packages/typescript/src/features/orchestrator/services/pty-service.d.ts +0 -175
  500. package/packages/typescript/src/features/orchestrator/services/pty-service.d.ts.map +0 -1
  501. package/packages/typescript/src/features/orchestrator/services/pty-service.js +0 -1469
  502. package/packages/typescript/src/features/orchestrator/services/pty-session-io.d.ts +0 -49
  503. package/packages/typescript/src/features/orchestrator/services/pty-session-io.d.ts.map +0 -1
  504. package/packages/typescript/src/features/orchestrator/services/pty-session-io.js +0 -180
  505. package/packages/typescript/src/features/orchestrator/services/pty-spawn.d.ts +0 -53
  506. package/packages/typescript/src/features/orchestrator/services/pty-spawn.d.ts.map +0 -1
  507. package/packages/typescript/src/features/orchestrator/services/pty-spawn.js +0 -280
  508. package/packages/typescript/src/features/orchestrator/services/pty-types.d.ts +0 -80
  509. package/packages/typescript/src/features/orchestrator/services/pty-types.d.ts.map +0 -1
  510. package/packages/typescript/src/features/orchestrator/services/pty-types.js +0 -51
  511. package/packages/typescript/src/features/orchestrator/services/repo-input.d.ts +0 -16
  512. package/packages/typescript/src/features/orchestrator/services/repo-input.d.ts.map +0 -1
  513. package/packages/typescript/src/features/orchestrator/services/repo-input.js +0 -88
  514. package/packages/typescript/src/features/orchestrator/services/stall-classifier.d.ts +0 -69
  515. package/packages/typescript/src/features/orchestrator/services/stall-classifier.d.ts.map +0 -1
  516. package/packages/typescript/src/features/orchestrator/services/stall-classifier.js +0 -446
  517. package/packages/typescript/src/features/orchestrator/services/swarm-coordinator-prompts.d.ts +0 -97
  518. package/packages/typescript/src/features/orchestrator/services/swarm-coordinator-prompts.d.ts.map +0 -1
  519. package/packages/typescript/src/features/orchestrator/services/swarm-coordinator-prompts.js +0 -342
  520. package/packages/typescript/src/features/orchestrator/services/swarm-coordinator.d.ts +0 -421
  521. package/packages/typescript/src/features/orchestrator/services/swarm-coordinator.d.ts.map +0 -1
  522. package/packages/typescript/src/features/orchestrator/services/swarm-coordinator.js +0 -2356
  523. package/packages/typescript/src/features/orchestrator/services/swarm-decision-loop.d.ts +0 -52
  524. package/packages/typescript/src/features/orchestrator/services/swarm-decision-loop.d.ts.map +0 -1
  525. package/packages/typescript/src/features/orchestrator/services/swarm-decision-loop.js +0 -1538
  526. package/packages/typescript/src/features/orchestrator/services/swarm-event-triage.d.ts +0 -49
  527. package/packages/typescript/src/features/orchestrator/services/swarm-event-triage.d.ts.map +0 -1
  528. package/packages/typescript/src/features/orchestrator/services/swarm-event-triage.js +0 -171
  529. package/packages/typescript/src/features/orchestrator/services/swarm-history.d.ts +0 -27
  530. package/packages/typescript/src/features/orchestrator/services/swarm-history.d.ts.map +0 -1
  531. package/packages/typescript/src/features/orchestrator/services/swarm-history.js +0 -148
  532. package/packages/typescript/src/features/orchestrator/services/swarm-idle-watchdog.d.ts +0 -22
  533. package/packages/typescript/src/features/orchestrator/services/swarm-idle-watchdog.d.ts.map +0 -1
  534. package/packages/typescript/src/features/orchestrator/services/swarm-idle-watchdog.js +0 -265
  535. package/packages/typescript/src/features/orchestrator/services/task-acceptance.d.ts +0 -8
  536. package/packages/typescript/src/features/orchestrator/services/task-acceptance.d.ts.map +0 -1
  537. package/packages/typescript/src/features/orchestrator/services/task-acceptance.js +0 -114
  538. package/packages/typescript/src/features/orchestrator/services/task-agent-auth.d.ts +0 -68
  539. package/packages/typescript/src/features/orchestrator/services/task-agent-auth.d.ts.map +0 -1
  540. package/packages/typescript/src/features/orchestrator/services/task-agent-auth.js +0 -559
  541. package/packages/typescript/src/features/orchestrator/services/task-agent-frameworks.d.ts +0 -82
  542. package/packages/typescript/src/features/orchestrator/services/task-agent-frameworks.d.ts.map +0 -1
  543. package/packages/typescript/src/features/orchestrator/services/task-agent-frameworks.js +0 -738
  544. package/packages/typescript/src/features/orchestrator/services/task-kind.d.ts +0 -3
  545. package/packages/typescript/src/features/orchestrator/services/task-kind.d.ts.map +0 -1
  546. package/packages/typescript/src/features/orchestrator/services/task-kind.js +0 -40
  547. package/packages/typescript/src/features/orchestrator/services/task-policy.d.ts +0 -17
  548. package/packages/typescript/src/features/orchestrator/services/task-policy.d.ts.map +0 -1
  549. package/packages/typescript/src/features/orchestrator/services/task-policy.js +0 -226
  550. package/packages/typescript/src/features/orchestrator/services/task-registry.d.ts +0 -550
  551. package/packages/typescript/src/features/orchestrator/services/task-registry.d.ts.map +0 -1
  552. package/packages/typescript/src/features/orchestrator/services/task-registry.js +0 -2182
  553. package/packages/typescript/src/features/orchestrator/services/task-share.d.ts +0 -18
  554. package/packages/typescript/src/features/orchestrator/services/task-share.d.ts.map +0 -1
  555. package/packages/typescript/src/features/orchestrator/services/task-share.js +0 -159
  556. package/packages/typescript/src/features/orchestrator/services/task-validation.d.ts +0 -69
  557. package/packages/typescript/src/features/orchestrator/services/task-validation.d.ts.map +0 -1
  558. package/packages/typescript/src/features/orchestrator/services/task-validation.js +0 -587
  559. package/packages/typescript/src/features/orchestrator/services/task-verifier-runner.d.ts +0 -5
  560. package/packages/typescript/src/features/orchestrator/services/task-verifier-runner.d.ts.map +0 -1
  561. package/packages/typescript/src/features/orchestrator/services/task-verifier-runner.js +0 -372
  562. package/packages/typescript/src/features/orchestrator/services/trajectory-context.d.ts +0 -73
  563. package/packages/typescript/src/features/orchestrator/services/trajectory-context.d.ts.map +0 -1
  564. package/packages/typescript/src/features/orchestrator/services/trajectory-context.js +0 -64
  565. package/packages/typescript/src/features/orchestrator/services/trajectory-feedback.d.ts +0 -53
  566. package/packages/typescript/src/features/orchestrator/services/trajectory-feedback.d.ts.map +0 -1
  567. package/packages/typescript/src/features/orchestrator/services/trajectory-feedback.js +0 -260
  568. package/packages/typescript/src/features/orchestrator/services/workspace-git-ops.d.ts +0 -28
  569. package/packages/typescript/src/features/orchestrator/services/workspace-git-ops.d.ts.map +0 -1
  570. package/packages/typescript/src/features/orchestrator/services/workspace-git-ops.js +0 -105
  571. package/packages/typescript/src/features/orchestrator/services/workspace-github.d.ts +0 -58
  572. package/packages/typescript/src/features/orchestrator/services/workspace-github.d.ts.map +0 -1
  573. package/packages/typescript/src/features/orchestrator/services/workspace-github.js +0 -139
  574. package/packages/typescript/src/features/orchestrator/services/workspace-lifecycle.d.ts +0 -18
  575. package/packages/typescript/src/features/orchestrator/services/workspace-lifecycle.d.ts.map +0 -1
  576. package/packages/typescript/src/features/orchestrator/services/workspace-lifecycle.js +0 -86
  577. package/packages/typescript/src/features/orchestrator/services/workspace-service.d.ts +0 -118
  578. package/packages/typescript/src/features/orchestrator/services/workspace-service.d.ts.map +0 -1
  579. package/packages/typescript/src/features/orchestrator/services/workspace-service.js +0 -533
  580. package/packages/typescript/src/features/orchestrator/services/workspace-types.d.ts +0 -81
  581. package/packages/typescript/src/features/orchestrator/services/workspace-types.d.ts.map +0 -1
  582. package/packages/typescript/src/features/orchestrator/services/workspace-types.js +0 -8
  583. package/packages/typescript/src/features/orchestrator/task-progress-streamer.d.ts +0 -38
  584. package/packages/typescript/src/features/orchestrator/task-progress-streamer.d.ts.map +0 -1
  585. package/packages/typescript/src/features/orchestrator/task-progress-streamer.js +0 -293
  586. /package/{packages/agent → apps/app-lifeops}/src/inbox/channel-deep-links.d.ts +0 -0
  587. /package/{packages/agent → apps/app-lifeops}/src/inbox/channel-deep-links.js +0 -0
  588. /package/{packages/agent → apps/app-lifeops}/src/inbox/config.d.ts +0 -0
  589. /package/{packages/agent → apps/app-lifeops}/src/inbox/message-fetcher.d.ts +0 -0
  590. /package/{packages/agent → apps/app-lifeops}/src/inbox/message-fetcher.js +0 -0
  591. /package/{packages/agent → apps/app-lifeops}/src/inbox/reflection.d.ts +0 -0
  592. /package/{packages/agent → apps/app-lifeops}/src/inbox/reflection.js +0 -0
  593. /package/{packages/agent → apps/app-lifeops}/src/inbox/repository.d.ts +0 -0
  594. /package/{packages/agent → apps/app-lifeops}/src/inbox/repository.js +0 -0
  595. /package/{packages/agent → apps/app-lifeops}/src/inbox/triage-classifier.d.ts +0 -0
  596. /package/{packages/agent → apps/app-lifeops}/src/inbox/triage-classifier.js +0 -0
  597. /package/{packages/agent → apps/app-lifeops}/src/inbox/types.d.ts +0 -0
  598. /package/{packages/agent → apps/app-lifeops}/src/inbox/types.js +0 -0
@@ -1,3143 +1 @@
1
- import { ModelType, parseJSONObjectFromText, parseKeyValueXml, } from "@elizaos/core";
2
- import { getValidationKeywordTerms, textIncludesKeywordTerm, } from "@elizaos/shared/validation-keywords";
3
- import { resolveDefaultTimeZone } from "../lifeops/defaults.js";
4
- import { LifeOpsService, LifeOpsServiceError } from "../lifeops/service.js";
5
- import { addDaysToLocalDate, buildUtcDateFromLocalParts, getWeekdayForLocalDate, getZonedDateParts, } from "../lifeops/time.js";
6
- import { collectKeywordTermMatchesForKey, hasContextSignalSyncForKey, } from "./context-signal.js";
7
- import { renderGroundedActionReply } from "./grounded-action-reply.js";
8
- import { calendarReadUnavailableMessage, calendarWriteUnavailableMessage, detailArray, detailBoolean, detailNumber, detailString, formatCalendarEventDateTime, formatCalendarFeed, formatNextEventContext, getGoogleCapabilityStatus, hasLifeOpsAccess, INTERNAL_URL, messageText, toActionData, } from "./lifeops-google-helpers.js";
9
- const MIN_CREATE_EVENT_DURATION_MINUTES = 15;
10
- const CALENDAR_VALIDATION_CONTEXT_LIMIT = 12;
11
- const PARAMETER_DOC_NOISE_PATTERN = /\b(?:actions?|params?|parameters?|query\?:string|subaction\?:string|details\?:object|required parameter|supported keys include|may include:|match against titles|structured calendar arguments|structured data when needed|boolean when)\b|\b\w+\?:\w+\b/i;
12
- const CAL_I18N_OPTS = { includeAllLocales: true };
13
- const CAL_AFFIRMATIVE_TERMS = getValidationKeywordTerms("contextSignal.affirmative.strong", CAL_I18N_OPTS);
14
- const CAL_TEMPORAL_FOLLOWUP_TERMS = getValidationKeywordTerms("contextSignal.temporal_followup.strong", CAL_I18N_OPTS);
15
- const CAL_LIFEOPS_STRONG_TERMS = getValidationKeywordTerms("contextSignal.lifeops.strong", CAL_I18N_OPTS);
16
- const CAL_CALENDAR_STRONG_TERMS = getValidationKeywordTerms("contextSignal.calendar.strong", CAL_I18N_OPTS);
17
- const CAL_CALENDAR_WEAK_TERMS = getValidationKeywordTerms("contextSignal.calendar.weak", CAL_I18N_OPTS);
18
- function textMatchesAnyCal(text, terms) {
19
- return terms.some((term) => textIncludesKeywordTerm(text, term));
20
- }
21
- const I18N_LOCALES = ["en", "zh-CN", "ko", "es", "pt", "vi", "tl"];
22
- function buildIntlMonthMap() {
23
- const map = {};
24
- for (const locale of I18N_LOCALES) {
25
- for (let month = 0; month < 12; month++) {
26
- const date = new Date(2024, month, 15);
27
- for (const style of ["long", "short"]) {
28
- const name = new Intl.DateTimeFormat(locale, { month: style })
29
- .format(date)
30
- .toLowerCase()
31
- .replace(/\.$/, "");
32
- if (name.length > 0)
33
- map[name] = month + 1;
34
- }
35
- }
36
- }
37
- return map;
38
- }
39
- function buildIntlWeekdayMap() {
40
- const map = {};
41
- for (const locale of I18N_LOCALES) {
42
- for (let dow = 0; dow < 7; dow++) {
43
- const date = new Date(2024, 0, 7 + dow);
44
- for (const style of ["long", "short"]) {
45
- const name = new Intl.DateTimeFormat(locale, { weekday: style })
46
- .format(date)
47
- .toLowerCase()
48
- .replace(/\.$/, "");
49
- if (name.length > 0)
50
- map[name] = dow;
51
- }
52
- }
53
- }
54
- return map;
55
- }
56
- const MONTH_MAP = buildIntlMonthMap();
57
- const WEEKDAY_MAP = buildIntlWeekdayMap();
58
- const MONTH_NAMES_SORTED = Object.keys(MONTH_MAP).sort((a, b) => b.length - a.length);
59
- const MONTH_NAME_PATTERN = new RegExp(`\\b(${MONTH_NAMES_SORTED.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\.?\\s+(\\d{1,2})(?:st|nd|rd|th)?(?:,?\\s+(\\d{4}))?\\b`, "i");
60
- const WEEKDAY_NAMES_SORTED = Object.keys(WEEKDAY_MAP).sort((a, b) => b.length - a.length);
61
- const WEEKDAY_NAME_PATTERN = new RegExp(`\\b(?:(this|next)\\s+)?(${WEEKDAY_NAMES_SORTED.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b`, "i");
62
- const CALENDAR_DETAIL_ALIASES = {
63
- calendarId: ["calendarid", "calendar_id"],
64
- timeMin: ["timemin", "time_min"],
65
- timeMax: ["timemax", "time_max"],
66
- timeZone: ["timezone", "time_zone"],
67
- forceSync: ["forcesync", "force_sync"],
68
- windowDays: ["windowdays", "window_days"],
69
- startAt: ["startat", "start_at"],
70
- endAt: ["endat", "end_at"],
71
- durationMinutes: ["durationminutes", "duration_minutes"],
72
- windowPreset: ["windowpreset", "window_preset"],
73
- eventId: [
74
- "eventid",
75
- "event_id",
76
- "externaleventid",
77
- "external_event_id",
78
- "googleeventid",
79
- "google_event_id",
80
- ],
81
- newTitle: ["newtitle", "new_title", "renameto", "rename_to"],
82
- description: ["desc", "summary", "body"],
83
- location: ["place", "venue"],
84
- };
85
- function normalizeCalendarSubaction(value) {
86
- if (typeof value !== "string") {
87
- return null;
88
- }
89
- const normalized = value.trim().toLowerCase();
90
- switch (normalized) {
91
- case "feed":
92
- case "next_event":
93
- case "search_events":
94
- case "create_event":
95
- case "update_event":
96
- case "delete_event":
97
- case "trip_window":
98
- return normalized;
99
- default:
100
- return null;
101
- }
102
- }
103
- function normalizeShouldAct(value) {
104
- if (typeof value === "boolean") {
105
- return value;
106
- }
107
- if (typeof value === "string") {
108
- const normalized = value.trim().toLowerCase();
109
- if (normalized === "true") {
110
- return true;
111
- }
112
- if (normalized === "false") {
113
- return false;
114
- }
115
- }
116
- return null;
117
- }
118
- function normalizePlannerResponse(value) {
119
- if (typeof value !== "string") {
120
- return undefined;
121
- }
122
- const trimmed = value.trim();
123
- return trimmed.length > 0 ? trimmed : undefined;
124
- }
125
- function buildCalendarReplyOnlyFallback(subaction) {
126
- switch (subaction) {
127
- case "create_event":
128
- return "What event do you want to add, and when should it happen?";
129
- case "search_events":
130
- case "trip_window":
131
- return "What calendar event or trip do you want me to look up?";
132
- case "next_event":
133
- case "feed":
134
- return "Do you want today's schedule, your next event, or a specific event?";
135
- case "update_event":
136
- return "Which calendar event do you want to change, and what should change?";
137
- case "delete_event":
138
- return "Which calendar event do you want to delete?";
139
- default:
140
- return "What do you want to do on your calendar — check your schedule, find an event, or create one?";
141
- }
142
- }
143
- function looksLikeLifeReminderRequestForCalendarAction(text) {
144
- const normalized = normalizeText(text);
145
- if (!normalized) {
146
- return false;
147
- }
148
- if (collectKeywordTermMatchesForKey([normalized], "calendar", {
149
- includeAllLocales: true,
150
- }).size > 0) {
151
- return false;
152
- }
153
- return textMatchesAnyCal(normalized, CAL_LIFEOPS_STRONG_TERMS);
154
- }
155
- function buildCalendarServiceErrorFallback(error, intent) {
156
- const normalized = normalizeText(error.message);
157
- if (normalized.includes("utc 'z' suffix") ||
158
- normalized.includes("local datetime without 'z'")) {
159
- return `I couldn't pin down the event time from "${intent}". Tell me the date and time again in plain language, like "Friday at 8 pm Pacific."`;
160
- }
161
- if (normalized.includes("startat is required") ||
162
- normalized.includes("windowpreset is not provided")) {
163
- return "I still need the time for that event. Tell me when it should happen.";
164
- }
165
- if (normalized.includes("endat must be later than startat")) {
166
- return "That end time lands before the start. Give me the date and time again and I'll fix it.";
167
- }
168
- if (error.status === 429 || normalized.includes("rate limit")) {
169
- return "Calendar is rate-limited right now. Try again in a bit.";
170
- }
171
- return "I couldn't finish that calendar change yet. Tell me the event and timing again, and I'll try it a different way.";
172
- }
173
- function buildCalendarEventDisambiguationFallback(args) {
174
- const previewLines = args.candidates.slice(0, 3).map((candidate) => {
175
- const when = formatCalendarEventDateTime(candidate, {
176
- includeTimeZoneName: true,
177
- });
178
- return `- ${candidate.title} (${when})`;
179
- });
180
- const intro = args.titleHint
181
- ? `I found multiple events matching "${args.titleHint}".`
182
- : "I found multiple matching calendar events.";
183
- const suffix = args.candidates.length > 3
184
- ? ` There are ${args.candidates.length} matches total.`
185
- : "";
186
- return [
187
- intro,
188
- ...previewLines,
189
- `Tell me which one to ${args.action} by giving the title and date/time.${suffix}`,
190
- ].join("\n");
191
- }
192
- function shouldDeleteAllMatchingCalendarEvents(args) {
193
- const normalizedIntent = normalizeText(args.intent);
194
- const normalizedTitleHint = normalizeText(args.titleHint ?? "");
195
- const titleStartsWithQuantifier = /^(all|both|every|each)\b/.test(normalizedTitleHint);
196
- if (/\b(?:remove|delete|cancel|kill|drop)\b\s+(?:the\s+)?(?:duplicates?|copies)\b/.test(normalizedIntent)) {
197
- return true;
198
- }
199
- const quantifierMatch = normalizedIntent.match(/\b(?:remove|delete|cancel|kill|drop)\b\s+(both|all|every|each)\b/);
200
- if (!quantifierMatch) {
201
- return false;
202
- }
203
- if (titleStartsWithQuantifier) {
204
- return false;
205
- }
206
- const quantifier = quantifierMatch[1];
207
- if (quantifier === "both" && args.candidateCount !== 2) {
208
- return false;
209
- }
210
- const trailingIntent = normalizedIntent.slice((quantifierMatch.index ?? 0) + quantifierMatch[0].length);
211
- return /\b(?:matching|events?|meetings?|appointments?|invites?|entries|duplicates?|copies)\b/.test(trailingIntent);
212
- }
213
- async function renderCalendarActionReply(args) {
214
- const { runtime, message, state, intent, scenario, fallback, context } = args;
215
- return renderGroundedActionReply({
216
- runtime,
217
- message,
218
- state,
219
- intent,
220
- domain: "calendar",
221
- scenario,
222
- fallback,
223
- context,
224
- preferCharacterVoice: true,
225
- additionalRules: [
226
- "Mirror the user's phrasing for dates, times, ranges, and scheduling language when possible.",
227
- "Prefer phrases like tomorrow morning, next week, later, earlier, free, busy, or the user's own wording over robotic calendar language.",
228
- "Never surface raw ISO timestamps unless the user used raw ISO timestamps.",
229
- "Preserve all concrete event facts from the context and canonical fallback.",
230
- "If this is reply-only or a clarification, do not pretend you already changed the calendar.",
231
- ],
232
- });
233
- }
234
- function normalizeText(value) {
235
- return value.trim().toLowerCase().replace(/\s+/g, " ");
236
- }
237
- function normalizeLookupKey(value) {
238
- return value.toLowerCase().replace(/[^a-z0-9]/g, "");
239
- }
240
- function hasCalendarTextSignal(text) {
241
- if (!text.trim()) {
242
- return false;
243
- }
244
- if (collectKeywordTermMatchesForKey([text], "calendar", {
245
- includeAllLocales: true,
246
- strength: "strong",
247
- }).size > 0) {
248
- return true;
249
- }
250
- return (collectKeywordTermMatchesForKey([text], "calendar", {
251
- includeAllLocales: true,
252
- strength: "weak",
253
- }).size >= 2);
254
- }
255
- function wordCount(value) {
256
- const normalized = normalizeText(value);
257
- if (!normalized) {
258
- return 0;
259
- }
260
- return normalized.split(" ").filter(Boolean).length;
261
- }
262
- function tokenize(value) {
263
- return normalizeText(value)
264
- .split(" ")
265
- .filter((token) => token.length >= 2);
266
- }
267
- function tokenVariants(token) {
268
- const normalized = token.trim().toLowerCase();
269
- if (!normalized) {
270
- return [];
271
- }
272
- const variants = new Set([normalized]);
273
- if (normalized.endsWith("ies") && normalized.length > 3) {
274
- variants.add(`${normalized.slice(0, -3)}y`);
275
- }
276
- if (normalized.endsWith("es") && normalized.length > 4) {
277
- variants.add(normalized.slice(0, -2));
278
- }
279
- if (normalized.endsWith("s") &&
280
- !normalized.endsWith("ss") &&
281
- normalized.length > 3) {
282
- variants.add(normalized.slice(0, -1));
283
- }
284
- return [...variants];
285
- }
286
- function tokenizeForSearch(value) {
287
- return [...new Set(tokenize(value).flatMap((token) => tokenVariants(token)))];
288
- }
289
- function normalizeCalendarSearchQueryValue(value) {
290
- if (!value) {
291
- return undefined;
292
- }
293
- if (PARAMETER_DOC_NOISE_PATTERN.test(value)) {
294
- return undefined;
295
- }
296
- const cleaned = normalizeText(value)
297
- .replace(/\b(?:actions?|params?|parameters?)\b[:;]*/g, "")
298
- .replace(/\b\w+\?:\w+(?:\s+\[[^\]]+\])?\s*-\s*/g, " ")
299
- .replace(/\b(?:search|find|look(?:ing)? for|show me|check)\s+(?:my\s+)?(?:calendar|schedule)\s+for\b/g, "")
300
- .replace(/\b(?:search|find|look(?:ing)? for|show me|check)\b/g, "")
301
- .replace(/\b(?:on|in) my calendar\b/g, "")
302
- .replace(/\b(?:today|tomorrow|tonight|this week(?:end)?|next week(?:end)?|week after(?: next)?|this month|next month|this year|next year)\b/g, "")
303
- .replace(/\b(?:scheduled|coming up|happening|for me)\b/g, "")
304
- .replace(/\b(?:events?|appointments?|meetings?)\b$/g, "")
305
- .replace(/\bsupported keys include\b.*$/g, "")
306
- .replace(/\bmatch against titles\b.*$/g, "")
307
- .replace(/\bstructured calendar arguments\b.*$/g, "")
308
- .replace(/[;:,]+/g, " ")
309
- .replace(/^[^a-z0-9]+|[^a-z0-9]+$/g, "")
310
- .trim();
311
- if (!cleaned ||
312
- ["calendar", "schedule", "event", "events"].includes(cleaned) ||
313
- textMatchesAnyCal(cleaned, CAL_TEMPORAL_FOLLOWUP_TERMS) ||
314
- PARAMETER_DOC_NOISE_PATTERN.test(cleaned)) {
315
- return undefined;
316
- }
317
- return cleaned;
318
- }
319
- function dedupeCalendarQueries(queries) {
320
- const normalized = queries
321
- .map((query) => normalizeCalendarSearchQueryValue(query))
322
- .filter((query) => Boolean(query));
323
- return [...new Set(normalized)];
324
- }
325
- function normalizeCalendarDetails(details) {
326
- if (!details) {
327
- return undefined;
328
- }
329
- const normalized = { ...details };
330
- const aliasMap = new Map();
331
- for (const [canonical, aliases] of Object.entries(CALENDAR_DETAIL_ALIASES)) {
332
- aliasMap.set(normalizeLookupKey(canonical), canonical);
333
- for (const alias of aliases) {
334
- aliasMap.set(normalizeLookupKey(alias), canonical);
335
- }
336
- }
337
- for (const [key, value] of Object.entries(details)) {
338
- const canonical = aliasMap.get(normalizeLookupKey(key));
339
- if (!canonical) {
340
- continue;
341
- }
342
- if (normalized[canonical] === undefined) {
343
- normalized[canonical] = value;
344
- }
345
- }
346
- return normalized;
347
- }
348
- function parseStateLine(line) {
349
- const trimmed = line.trim();
350
- const timestampedMatch = trimmed.match(/^\d{1,2}:\d{2}\s+\([^)]+\)\s+\[[^\]]+\]\s+(\S+)\s*:\s*(.*)/);
351
- if (timestampedMatch) {
352
- return {
353
- role: timestampedMatch[1].toLowerCase(),
354
- text: timestampedMatch[2].trim(),
355
- };
356
- }
357
- const simpleMatch = trimmed.match(/^(user|assistant|system|owner|admin|\S+)\s*:\s*(.*)/i);
358
- if (simpleMatch) {
359
- return {
360
- role: simpleMatch[1].toLowerCase(),
361
- text: simpleMatch[2].trim(),
362
- };
363
- }
364
- return { role: "", text: trimmed };
365
- }
366
- const SYSTEM_ROLE_NAMES = new Set(["assistant", "system"]);
367
- function splitStateTextCandidates(value) {
368
- return value
369
- .split(/\n+/)
370
- .map((line) => parseStateLine(line).text)
371
- .filter((text) => text.length > 0);
372
- }
373
- function userIntentsFromState(state) {
374
- if (!state || typeof state !== "object") {
375
- return [];
376
- }
377
- const stateRecord = state;
378
- const values = stateRecord.values && typeof stateRecord.values === "object"
379
- ? stateRecord.values
380
- : undefined;
381
- const raw = typeof values?.recentMessages === "string"
382
- ? values.recentMessages
383
- : typeof stateRecord.text === "string"
384
- ? stateRecord.text
385
- : "";
386
- if (!raw) {
387
- return [];
388
- }
389
- const agentName = typeof values?.agentName === "string" ? values.agentName.toLowerCase() : "";
390
- const excludedRoles = new Set(SYSTEM_ROLE_NAMES);
391
- if (agentName) {
392
- excludedRoles.add(agentName);
393
- }
394
- return raw
395
- .split(/\n+/)
396
- .filter((line) => {
397
- const { role } = parseStateLine(line);
398
- return role.length > 0 && !excludedRoles.has(role);
399
- })
400
- .map((line) => parseStateLine(line).text)
401
- .filter((text) => text.length > 0);
402
- }
403
- function planningConversationLines(state) {
404
- if (!state || typeof state !== "object") {
405
- return [];
406
- }
407
- const stateRecord = state;
408
- const values = stateRecord.values && typeof stateRecord.values === "object"
409
- ? stateRecord.values
410
- : undefined;
411
- const raw = typeof values?.recentMessages === "string"
412
- ? values.recentMessages
413
- : typeof stateRecord.text === "string"
414
- ? stateRecord.text
415
- : "";
416
- if (!raw) {
417
- return [];
418
- }
419
- return raw
420
- .split(/\n+/)
421
- .map((line) => parseStateLine(line))
422
- .filter((line) => line.role.length > 0 && line.text.length > 0)
423
- .map((line) => `${line.role}: ${line.text}`);
424
- }
425
- function hasCalendarContextSignal(message, state) {
426
- return hasContextSignalSyncForKey(message, state, "calendar", {
427
- contextLimit: CALENDAR_VALIDATION_CONTEXT_LIMIT,
428
- });
429
- }
430
- function stateTextCandidates(state) {
431
- if (!state || typeof state !== "object") {
432
- return [];
433
- }
434
- const stateRecord = state;
435
- const values = stateRecord.values && typeof stateRecord.values === "object"
436
- ? stateRecord.values
437
- : undefined;
438
- const candidates = [];
439
- const pushText = (value) => {
440
- if (typeof value === "string" && value.trim().length > 0) {
441
- candidates.push(...splitStateTextCandidates(value));
442
- }
443
- };
444
- pushText(values?.recentMessages);
445
- pushText(stateRecord.text);
446
- const recentMessagesData = stateRecord.recentMessagesData ?? stateRecord.recentMessages;
447
- if (Array.isArray(recentMessagesData)) {
448
- for (const item of recentMessagesData) {
449
- if (!item || typeof item !== "object") {
450
- continue;
451
- }
452
- const content = item.content;
453
- if (!content || typeof content !== "object") {
454
- continue;
455
- }
456
- pushText(content.text);
457
- }
458
- }
459
- return [...new Set(candidates)];
460
- }
461
- function scoreIntentCandidate(value) {
462
- const normalized = normalizeText(value);
463
- if (!normalized) {
464
- return Number.NEGATIVE_INFINITY;
465
- }
466
- let score = Math.min(normalized.length, 160) / 16;
467
- if (textMatchesAnyCal(normalized, CAL_AFFIRMATIVE_TERMS)) {
468
- score -= 200;
469
- }
470
- if (PARAMETER_DOC_NOISE_PATTERN.test(normalized)) {
471
- score -= 500;
472
- }
473
- if (hasCalendarTextSignal(value)) {
474
- score += 10;
475
- }
476
- if (textMatchesAnyCal(normalized, CAL_CALENDAR_STRONG_TERMS)) {
477
- score += 14;
478
- }
479
- if (textMatchesAnyCal(normalized, CAL_CALENDAR_WEAK_TERMS)) {
480
- score += 10;
481
- }
482
- if (textMatchesAnyCal(normalized, CAL_CALENDAR_STRONG_TERMS)) {
483
- score += 8;
484
- }
485
- return score;
486
- }
487
- function looksLikeNarrativeCalendarQuery(value) {
488
- const normalized = normalizeText(value);
489
- if (!normalized) {
490
- return false;
491
- }
492
- return (/\b(?:tell me if|let me know if|whether|can you|could you|would you|do i have|are there|what(?:'s| is) on|what(?: event| events)? do i have|when do i|try to find)\b/.test(normalized) &&
493
- /\b(?:calendar|schedule|event|events|flight|flights|travel|trip|appointment|meeting|hotel|stay|return)\b/.test(normalized));
494
- }
495
- function looksLikeLiteralRequestEcho(query, intent) {
496
- const normalizedQuery = normalizeText(query);
497
- const normalizedIntent = normalizeText(intent);
498
- const questionLike = /[?¿]/.test(query);
499
- if (!normalizedQuery || !normalizedIntent) {
500
- return false;
501
- }
502
- if (normalizedQuery === normalizedIntent) {
503
- return (questionLike ||
504
- wordCount(normalizedQuery) >= 10 ||
505
- normalizedQuery.length >= 80);
506
- }
507
- return ((normalizedQuery.includes(normalizedIntent) ||
508
- normalizedIntent.includes(normalizedQuery)) &&
509
- (questionLike || normalizedQuery.length >= 96));
510
- }
511
- function resolveCalendarIntent(paramsIntent, message, state) {
512
- const normalizeFollowUpConstraint = (value) => {
513
- const cleaned = value
514
- .trim()
515
- .replace(/^(?:yes|yeah|yep|yup|ok|okay|sure|please|please do|do it|go ahead|sounds good)\b[\s,.-]*/i, "")
516
- .replace(/^(?:and\s+|also\s+)/i, "")
517
- .replace(/^(?:what about|how about|and the|also the|or the|only the|just the)\s+/i, "")
518
- .trim();
519
- if (/^(?:try\s+(?:it|again|that)|retry|do\s+(?:it\s+)?again|one\s+more\s+time|proceed|go for it)$/i.test(cleaned)) {
520
- return "";
521
- }
522
- return cleaned;
523
- };
524
- const currentMessageText = messageText(message).trim();
525
- const normalizedCurrentMessage = normalizeText(currentMessageText);
526
- const currentMessageHasCalendarSignal = hasCalendarTextSignal(currentMessageText);
527
- const isRefinement = /^(?:what about|how about|and the|also the|or the|only the|just the)\b/i.test(normalizedCurrentMessage);
528
- if (currentMessageText && currentMessageHasCalendarSignal && !isRefinement) {
529
- return currentMessageText;
530
- }
531
- if (currentMessageText &&
532
- (textMatchesAnyCal(normalizedCurrentMessage, CAL_AFFIRMATIVE_TERMS) ||
533
- textMatchesAnyCal(normalizedCurrentMessage, CAL_TEMPORAL_FOLLOWUP_TERMS) ||
534
- isRefinement ||
535
- hasCalendarContextSignal(message, state))) {
536
- const followUpCandidates = userIntentsFromState(state).filter((candidate) => hasCalendarTextSignal(candidate) &&
537
- normalizeText(candidate) !== normalizedCurrentMessage);
538
- const recentRelevantIntent = followUpCandidates.length > 0
539
- ? followUpCandidates.reduce((best, current) => scoreIntentCandidate(current) >= scoreIntentCandidate(best)
540
- ? current
541
- : best)
542
- : undefined;
543
- if (recentRelevantIntent) {
544
- const followUpConstraint = normalizeFollowUpConstraint(currentMessageText);
545
- return followUpConstraint
546
- ? `${recentRelevantIntent} ${followUpConstraint}`.trim()
547
- : recentRelevantIntent;
548
- }
549
- }
550
- const candidates = [
551
- {
552
- text: paramsIntent?.trim(),
553
- source: "params",
554
- },
555
- {
556
- text: messageText(message).trim(),
557
- source: "message",
558
- },
559
- ...stateTextCandidates(state).map((text) => ({
560
- text,
561
- source: "state",
562
- })),
563
- ].filter((candidate) => Boolean(candidate.text && candidate.text.trim().length > 0));
564
- if (candidates.length === 0) {
565
- return "";
566
- }
567
- return [...candidates]
568
- .sort((left, right) => {
569
- const leftBonus = left.source === "message" && hasCalendarTextSignal(left.text) ? 20 : 0;
570
- const rightBonus = right.source === "message" && hasCalendarTextSignal(right.text)
571
- ? 20
572
- : 0;
573
- return (scoreIntentCandidate(right.text) +
574
- rightBonus -
575
- (scoreIntentCandidate(left.text) + leftBonus));
576
- })
577
- .map((candidate) => candidate.text)[0];
578
- }
579
- function inferCalendarSubaction(intent, details, query) {
580
- // Delete intent is checked first because phrases like "delete the duplicate
581
- // event" otherwise get swept up by the search_events branch via "duplicate"
582
- // → "look for". Only the verb decides the subaction here.
583
- if (/\b(delete|remove|cancel|drop|get rid of|trash|kill)\b.*\b(event|meeting|appointment|calendar|reminder|invite)\b/.test(intent) ||
584
- /\b(uncancel|unbook|unschedule)\b/.test(intent)) {
585
- return "delete_event";
586
- }
587
- // Update intent — same eager-match treatment so "rename", "move", "reschedule"
588
- // don't get pulled into search_events.
589
- if (/\b(rename|reschedule|move|push|change|update|edit|modify)\b.*\b(event|meeting|appointment|calendar|invite)\b/.test(intent)) {
590
- return "update_event";
591
- }
592
- if (query ||
593
- detailString(details, "query") ||
594
- /\b(find|search|look for|matching|related to|flight|flights|fly|travel|trip|return)\b/.test(intent)) {
595
- return "search_events";
596
- }
597
- if (detailString(details, "startAt") ||
598
- detailNumber(details, "durationMinutes") ||
599
- /\b(create|add|book)\b/.test(intent) ||
600
- /\bschedule\s+(?:a|an|this|that|the)?\s*(?:calendar\s+)?(?:meeting|event|appointment|call)\b/.test(intent) ||
601
- /\bput\b.*\b(calendar|meeting|event)\b/.test(intent) ||
602
- /\bmake an event\b/.test(intent)) {
603
- return "create_event";
604
- }
605
- if (/\b(next|upcoming|soon|about to|coming up)\b/.test(intent) &&
606
- /\b(event|meeting|appointment|call|calendar item|thing)\b/.test(intent)) {
607
- return "next_event";
608
- }
609
- return "feed";
610
- }
611
- function shouldTrustExplicitCalendarSubaction(subaction, params, details) {
612
- if (!subaction) {
613
- return false;
614
- }
615
- switch (subaction) {
616
- case "create_event":
617
- return Boolean(params.title ||
618
- detailString(details, "title") ||
619
- detailString(details, "startAt") ||
620
- detailString(details, "windowPreset") ||
621
- detailNumber(details, "durationMinutes"));
622
- case "update_event":
623
- return Boolean(detailString(details, "eventId") ||
624
- detailString(details, "title") ||
625
- detailString(details, "newTitle") ||
626
- detailString(details, "startAt") ||
627
- detailString(details, "endAt"));
628
- case "delete_event":
629
- return Boolean(detailString(details, "eventId") ||
630
- params.title ||
631
- detailString(details, "title"));
632
- case "search_events":
633
- return Boolean(params.query ||
634
- detailString(details, "query") ||
635
- (params.queries?.length ?? 0) > 0 ||
636
- (detailArray(details, "queries")?.length ?? 0) > 0);
637
- default:
638
- return false;
639
- }
640
- }
641
- function cleanTripLocation(value) {
642
- const cleaned = value
643
- .trim()
644
- .replace(/\b(?:today|tomorrow|tonight|this week(?:end)?|next week(?:end)?|this month|next month|for me|coming up|upcoming|on my calendar|on the calendar|on my schedule|on the schedule)\b.*$/i, "")
645
- .replace(/^[^a-z0-9]+|[^a-z0-9]+$/gi, "")
646
- .trim();
647
- return cleaned.length > 0 ? cleaned : undefined;
648
- }
649
- function inferTripWindowIntent(intent) {
650
- const patterns = [
651
- /\bwhile\s+(?:i(?:'m| am)|im)\s+in\s+(.+?)(?=$|[?.!,])/i,
652
- /\bwhen\s+(?:i(?:'m| am)|im)\s+in\s+(.+?)(?=$|[?.!,])/i,
653
- /\bduring\s+(?:my\s+)?(?:trip|stay|visit)\s+(?:to|in)\s+(.+?)(?=$|[?.!,])/i,
654
- /\bonce\s+(?:i(?:'m| am)|im)\s+in\s+(.+?)(?=$|[?.!,])/i,
655
- ];
656
- for (const pattern of patterns) {
657
- const match = intent.match(pattern);
658
- const location = cleanTripLocation(match?.[1] ?? "");
659
- if (location) {
660
- return { location };
661
- }
662
- }
663
- return null;
664
- }
665
- function parseExplicitLocalDate(value, timeZone) {
666
- const normalized = normalizeText(value);
667
- const localToday = getZonedDateParts(new Date(), timeZone);
668
- const isoMatch = normalized.match(/\b(\d{4})-(\d{1,2})-(\d{1,2})\b/);
669
- if (isoMatch) {
670
- return {
671
- year: Number(isoMatch[1]),
672
- month: Number(isoMatch[2]),
673
- day: Number(isoMatch[3]),
674
- };
675
- }
676
- const monthNameMatch = normalized.match(MONTH_NAME_PATTERN);
677
- if (monthNameMatch) {
678
- return {
679
- year: monthNameMatch[3] ? Number(monthNameMatch[3]) : localToday.year,
680
- month: MONTH_MAP[normalizeLookupKey(monthNameMatch[1])],
681
- day: Number(monthNameMatch[2]),
682
- };
683
- }
684
- const numericMatch = normalized.match(/\b(\d{1,2})[/-](\d{1,2})(?:[/-](\d{2,4}))?\b/);
685
- if (numericMatch) {
686
- const yearRaw = numericMatch[3];
687
- const parsedYear = yearRaw === undefined
688
- ? localToday.year
689
- : yearRaw.length === 2
690
- ? 2000 + Number(yearRaw)
691
- : Number(yearRaw);
692
- return {
693
- year: parsedYear,
694
- month: Number(numericMatch[1]),
695
- day: Number(numericMatch[2]),
696
- };
697
- }
698
- const weekdayMatch = normalized.match(WEEKDAY_NAME_PATTERN);
699
- if (weekdayMatch) {
700
- const qualifier = normalizeLookupKey(weekdayMatch[1] ?? "");
701
- const weekdayKey = normalizeLookupKey(weekdayMatch[2] ?? "");
702
- const targetWeekday = WEEKDAY_MAP[weekdayKey];
703
- if (targetWeekday !== undefined) {
704
- const currentWeekday = new Date(Date.UTC(localToday.year, Math.max(0, localToday.month - 1), localToday.day, 12, 0, 0)).getUTCDay();
705
- let delta = (targetWeekday - currentWeekday + 7) % 7;
706
- if (qualifier === "next") {
707
- delta = delta === 0 ? 7 : delta + 7;
708
- }
709
- return addDaysToLocalDate({
710
- year: localToday.year,
711
- month: localToday.month,
712
- day: localToday.day,
713
- }, delta);
714
- }
715
- }
716
- return null;
717
- }
718
- function resolveCalendarTimeZone(details) {
719
- return detailString(details, "timeZone") ?? resolveDefaultTimeZone();
720
- }
721
- function getLocalTodayDate(timeZone) {
722
- const localNow = getZonedDateParts(new Date(), timeZone);
723
- return {
724
- year: localNow.year,
725
- month: localNow.month,
726
- day: localNow.day,
727
- };
728
- }
729
- function addMonthsToLocalDate(dateOnly, monthDelta) {
730
- const utcDate = new Date(Date.UTC(dateOnly.year, dateOnly.month - 1 + monthDelta, dateOnly.day, 12, 0, 0));
731
- return {
732
- year: utcDate.getUTCFullYear(),
733
- month: utcDate.getUTCMonth() + 1,
734
- day: utcDate.getUTCDate(),
735
- };
736
- }
737
- function buildLocalDateRange(timeZone, startDate, endDateExclusive, options) {
738
- return {
739
- timeMin: buildUtcDateFromLocalParts(timeZone, {
740
- year: startDate.year,
741
- month: startDate.month,
742
- day: startDate.day,
743
- hour: options?.startHour ?? 0,
744
- minute: options?.startMinute ?? 0,
745
- second: 0,
746
- }).toISOString(),
747
- timeMax: buildUtcDateFromLocalParts(timeZone, {
748
- year: endDateExclusive.year,
749
- month: endDateExclusive.month,
750
- day: endDateExclusive.day,
751
- hour: options?.endHour ?? 0,
752
- minute: options?.endMinute ?? 0,
753
- second: 0,
754
- }).toISOString(),
755
- };
756
- }
757
- function buildLocalDayRange(timeZone, startOffsetDays, endOffsetDaysExclusive) {
758
- const localToday = getLocalTodayDate(timeZone);
759
- return buildLocalDateRange(timeZone, addDaysToLocalDate(localToday, startOffsetDays), addDaysToLocalDate(localToday, endOffsetDaysExclusive));
760
- }
761
- function compareLocalDates(left, right) {
762
- if (left.year !== right.year) {
763
- return left.year - right.year;
764
- }
765
- if (left.month !== right.month) {
766
- return left.month - right.month;
767
- }
768
- return left.day - right.day;
769
- }
770
- function resolveCreateEventCalendarTimeZone(details, feed, fallbackTimeZone) {
771
- const explicitTimeZone = detailString(details, "timeZone");
772
- if (explicitTimeZone) {
773
- return explicitTimeZone;
774
- }
775
- const counts = new Map();
776
- for (const event of feed?.events ?? []) {
777
- const eventTimeZone = typeof event.timezone === "string" ? event.timezone.trim() : "";
778
- if (!eventTimeZone) {
779
- continue;
780
- }
781
- counts.set(eventTimeZone, (counts.get(eventTimeZone) ?? 0) + 1);
782
- }
783
- let winner = fallbackTimeZone;
784
- let winnerCount = 0;
785
- for (const [timeZone, count] of counts.entries()) {
786
- if (count > winnerCount) {
787
- winner = timeZone;
788
- winnerCount = count;
789
- }
790
- }
791
- return winner;
792
- }
793
- function formatCreateEventCalendarContext(context) {
794
- if (!context) {
795
- return "(calendar context unavailable)";
796
- }
797
- const lines = [
798
- `Calendar timezone: ${context.calendarTimeZone}`,
799
- `Context window: ${context.feed.timeMin} to ${context.feed.timeMax}`,
800
- ];
801
- if (context.feed.events.length === 0) {
802
- lines.push("(no upcoming events in the next 2 weeks)");
803
- return lines.join("\n");
804
- }
805
- const visibleEvents = context.feed.events.slice(0, 40);
806
- for (const event of visibleEvents) {
807
- const when = event.isAllDay
808
- ? formatCalendarMoment(event)
809
- : formatCalendarEventDateTime(event, {
810
- includeTimeZoneName: true,
811
- includeYear: true,
812
- });
813
- lines.push(`- ${when} — ${event.title}${event.location ? ` @ ${event.location}` : ""}`);
814
- }
815
- if (context.feed.events.length > visibleEvents.length) {
816
- lines.push(`... ${context.feed.events.length - visibleEvents.length} more upcoming events omitted`);
817
- }
818
- return lines.join("\n");
819
- }
820
- function isPersonalCreateEvent(intent, title) {
821
- return /\b(hug|wife|husband|partner|girlfriend|boyfriend|family|mom|dad|date|dinner|lunch|coffee|check in|check-in|call|text|birthday|anniversary|pick up|pickup|drop off|drop-off)\b/i.test(`${intent} ${title}`);
822
- }
823
- function resolveSuggestedCreateEventDurationMinutes(intent, title) {
824
- if (isShortPreparationEvent(intent, title)) {
825
- return MIN_CREATE_EVENT_DURATION_MINUTES;
826
- }
827
- return isPersonalCreateEvent(intent, title) ? 15 : 60;
828
- }
829
- function roundUpToStep(value, step) {
830
- return Math.ceil(value / step) * step;
831
- }
832
- function overlapsBusyWindow(startMinute, durationMinutes, busyWindows) {
833
- const endMinute = startMinute + durationMinutes;
834
- return busyWindows.some((window) => startMinute < window.endMinute && endMinute > window.startMinute);
835
- }
836
- function busyWindowsForLocalDate(events, targetDate, timeZone) {
837
- const windows = [];
838
- for (const event of events) {
839
- if (event.isAllDay) {
840
- continue;
841
- }
842
- const start = getZonedDateParts(new Date(event.startAt), timeZone);
843
- const end = getZonedDateParts(new Date(event.endAt), timeZone);
844
- const startDate = { year: start.year, month: start.month, day: start.day };
845
- const endDate = { year: end.year, month: end.month, day: end.day };
846
- if (compareLocalDates(endDate, targetDate) < 0 ||
847
- compareLocalDates(startDate, targetDate) > 0) {
848
- continue;
849
- }
850
- const startMinute = compareLocalDates(startDate, targetDate) < 0
851
- ? 0
852
- : start.hour * 60 + start.minute;
853
- const endMinute = compareLocalDates(endDate, targetDate) > 0
854
- ? 24 * 60
855
- : Math.max(startMinute + 1, end.hour * 60 + end.minute);
856
- windows.push({ startMinute, endMinute });
857
- }
858
- return windows.sort((left, right) => left.startMinute - right.startMinute);
859
- }
860
- function resolvePreferredCreateEventMinutes(intent, title, targetDate) {
861
- const weekday = getWeekdayForLocalDate(targetDate);
862
- if (isPersonalCreateEvent(intent, title)) {
863
- return [19 * 60, 20 * 60, 18 * 60 + 30, 17 * 60 + 30];
864
- }
865
- if (/\b(dentist|doctor|therapy|appointment|meeting|interview|review|sync)\b/i.test(`${intent} ${title}`)) {
866
- return [9 * 60, 10 * 60, 11 * 60, 14 * 60, 15 * 60];
867
- }
868
- return weekday === 0 || weekday === 6
869
- ? [10 * 60, 13 * 60, 18 * 60]
870
- : [9 * 60, 11 * 60, 14 * 60, 16 * 60, 19 * 60];
871
- }
872
- function chooseSuggestedCreateEventMinute(args) {
873
- for (const minute of args.preferredMinutes) {
874
- if (!overlapsBusyWindow(minute, args.durationMinutes, args.busyWindows)) {
875
- return minute;
876
- }
877
- }
878
- const latestEnd = Math.max(0, ...args.busyWindows.map((window) => window.endMinute));
879
- const afterLastEvent = roundUpToStep(latestEnd + 15, 15);
880
- if (afterLastEvent + args.durationMinutes <= 22 * 60 &&
881
- !overlapsBusyWindow(afterLastEvent, args.durationMinutes, args.busyWindows)) {
882
- return afterLastEvent;
883
- }
884
- for (let minute = 8 * 60; minute <= 21 * 60; minute += 30) {
885
- if (!overlapsBusyWindow(minute, args.durationMinutes, args.busyWindows)) {
886
- return minute;
887
- }
888
- }
889
- return null;
890
- }
891
- function suggestCreateEventStartAt(args) {
892
- if (!args.calendarContext) {
893
- return null;
894
- }
895
- const targetDate = parseExplicitLocalDate(args.currentMessage, args.calendarContext.calendarTimeZone) ??
896
- parseExplicitLocalDate(args.intent, args.calendarContext.calendarTimeZone);
897
- if (!targetDate) {
898
- return null;
899
- }
900
- const durationMinutes = resolveSuggestedCreateEventDurationMinutes(args.intent, args.title);
901
- const busyWindows = busyWindowsForLocalDate(args.calendarContext.feed.events, targetDate, args.calendarContext.calendarTimeZone);
902
- const startMinute = chooseSuggestedCreateEventMinute({
903
- busyWindows,
904
- preferredMinutes: resolvePreferredCreateEventMinutes(args.intent, args.title, targetDate),
905
- durationMinutes,
906
- });
907
- if (startMinute === null) {
908
- return null;
909
- }
910
- return {
911
- startAt: buildUtcDateFromLocalParts(args.calendarContext.calendarTimeZone, {
912
- year: targetDate.year,
913
- month: targetDate.month,
914
- day: targetDate.day,
915
- hour: Math.floor(startMinute / 60),
916
- minute: startMinute % 60,
917
- second: 0,
918
- }).toISOString(),
919
- timeZone: args.calendarContext.calendarTimeZone,
920
- };
921
- }
922
- async function loadCreateEventCalendarContext(service, details, hasCalendarRead) {
923
- if (!hasCalendarRead) {
924
- return null;
925
- }
926
- const requestTimeZone = resolveCalendarTimeZone(details);
927
- const feed = await service.getCalendarFeed(INTERNAL_URL, {
928
- mode: detailString(details, "mode"),
929
- side: detailString(details, "side"),
930
- calendarId: detailString(details, "calendarId"),
931
- timeZone: requestTimeZone,
932
- forceSync: detailBoolean(details, "forceSync"),
933
- ...buildLocalDayRange(requestTimeZone, 0, 14),
934
- });
935
- if (!feed || !Array.isArray(feed.events)) {
936
- return null;
937
- }
938
- return {
939
- calendarTimeZone: resolveCreateEventCalendarTimeZone(details, feed, requestTimeZone),
940
- feed,
941
- };
942
- }
943
- function normalizeIsoDateTime(value) {
944
- if (typeof value !== "string" || value.trim().length === 0) {
945
- return undefined;
946
- }
947
- const parsed = Date.parse(value.trim());
948
- return Number.isFinite(parsed) ? new Date(parsed).toISOString() : undefined;
949
- }
950
- function normalizeWindowLabel(value) {
951
- if (typeof value !== "string") {
952
- return undefined;
953
- }
954
- const cleaned = value.trim();
955
- return cleaned.length > 0 && cleaned.length <= 80 ? cleaned : undefined;
956
- }
957
- function resolveCalendarLlmWindow(llmPlan) {
958
- const timeMin = normalizeIsoDateTime(llmPlan?.timeMin);
959
- const timeMax = normalizeIsoDateTime(llmPlan?.timeMax);
960
- if (!timeMin || !timeMax) {
961
- return null;
962
- }
963
- const minMs = Date.parse(timeMin);
964
- const maxMs = Date.parse(timeMax);
965
- const spanMs = maxMs - minMs;
966
- if (!Number.isFinite(spanMs) ||
967
- spanMs <= 0 ||
968
- spanMs > 370 * 24 * 60 * 60 * 1000) {
969
- return null;
970
- }
971
- return {
972
- timeMin,
973
- timeMax,
974
- label: normalizeWindowLabel(llmPlan?.windowLabel) ?? "for the requested window",
975
- };
976
- }
977
- function resolveWeekendWindow(timeZone, modifier) {
978
- const localToday = getLocalTodayDate(timeZone);
979
- const currentWeekday = getWeekdayForLocalDate(localToday);
980
- let startOffsetDays = (6 - currentWeekday + 7) % 7;
981
- let endOffsetFromToday = startOffsetDays + 2;
982
- if (modifier === "this" && (currentWeekday === 6 || currentWeekday === 0)) {
983
- startOffsetDays = 0;
984
- endOffsetFromToday = currentWeekday === 6 ? 2 : 1;
985
- }
986
- if (modifier === "next") {
987
- startOffsetDays += 7;
988
- endOffsetFromToday += 7;
989
- }
990
- const startDay = addDaysToLocalDate(localToday, startOffsetDays);
991
- const endDay = addDaysToLocalDate(localToday, endOffsetFromToday);
992
- return buildLocalDateRange(timeZone, startDay, endDay);
993
- }
994
- function resolveMonthWindow(timeZone, modifier) {
995
- const localToday = getLocalTodayDate(timeZone);
996
- if (modifier === "this") {
997
- const endOfWindow = addMonthsToLocalDate({
998
- year: localToday.year,
999
- month: localToday.month,
1000
- day: 1,
1001
- }, 1);
1002
- return buildLocalDateRange(timeZone, localToday, endOfWindow);
1003
- }
1004
- const startOfNextMonth = addMonthsToLocalDate({
1005
- year: localToday.year,
1006
- month: localToday.month,
1007
- day: 1,
1008
- }, 1);
1009
- const startOfFollowingMonth = addMonthsToLocalDate(startOfNextMonth, 1);
1010
- return buildLocalDateRange(timeZone, startOfNextMonth, startOfFollowingMonth);
1011
- }
1012
- function resolveTonightWindow(timeZone) {
1013
- const localNow = getZonedDateParts(new Date(), timeZone);
1014
- const startHour = Math.max(localNow.hour, 17);
1015
- const startMinute = localNow.hour >= 17 ? localNow.minute : 0;
1016
- const startDay = {
1017
- year: localNow.year,
1018
- month: localNow.month,
1019
- day: localNow.day,
1020
- };
1021
- const endDay = addDaysToLocalDate(startDay, 1);
1022
- return buildLocalDateRange(timeZone, startDay, endDay, {
1023
- startHour,
1024
- startMinute,
1025
- });
1026
- }
1027
- // Wide window used by update_event / delete_event lookups when the user
1028
- // gave no time hint. Reaches 1 year back and 5 years forward — far enough
1029
- // to find a future birthday or a recent past meeting without scanning the
1030
- // entire account.
1031
- function buildWideLookupRange(timeZone) {
1032
- return buildLocalDayRange(timeZone, -365, 365 * 5);
1033
- }
1034
- function resolveCalendarWindow(intent, details, forSearch, llmPlan) {
1035
- const timeMin = detailString(details, "timeMin");
1036
- const timeMax = detailString(details, "timeMax");
1037
- const calendarId = detailString(details, "calendarId");
1038
- const timeZone = resolveCalendarTimeZone(details);
1039
- const forceSync = detailBoolean(details, "forceSync");
1040
- if (timeMin || timeMax) {
1041
- return {
1042
- request: {
1043
- calendarId,
1044
- timeMin: timeMin ?? undefined,
1045
- timeMax: timeMax ?? undefined,
1046
- timeZone,
1047
- forceSync,
1048
- },
1049
- label: detailString(details, "label") ?? "for the requested window",
1050
- };
1051
- }
1052
- const llmWindow = resolveCalendarLlmWindow(llmPlan);
1053
- if (llmWindow) {
1054
- return {
1055
- request: {
1056
- calendarId,
1057
- timeZone,
1058
- forceSync,
1059
- timeMin: llmWindow.timeMin,
1060
- timeMax: llmWindow.timeMax,
1061
- },
1062
- label: llmWindow.label,
1063
- };
1064
- }
1065
- const normalizedIntent = normalizeText(intent);
1066
- const explicitDate = parseExplicitLocalDate(normalizedIntent, timeZone);
1067
- if (explicitDate) {
1068
- const nextDate = addDaysToLocalDate(explicitDate, 1);
1069
- const explicitDateLabel = (normalizedIntent.match(/(?:on|for)\s+(.+)$/i)?.[1] ?? normalizedIntent)
1070
- .replace(/^(?:on|for)\s+/i, "")
1071
- .trim();
1072
- return {
1073
- request: {
1074
- calendarId,
1075
- timeZone,
1076
- forceSync,
1077
- timeMin: buildUtcDateFromLocalParts(timeZone, {
1078
- year: explicitDate.year,
1079
- month: explicitDate.month,
1080
- day: explicitDate.day,
1081
- hour: 0,
1082
- minute: 0,
1083
- second: 0,
1084
- }).toISOString(),
1085
- timeMax: buildUtcDateFromLocalParts(timeZone, {
1086
- year: nextDate.year,
1087
- month: nextDate.month,
1088
- day: nextDate.day,
1089
- hour: 0,
1090
- minute: 0,
1091
- second: 0,
1092
- }).toISOString(),
1093
- },
1094
- label: `on ${explicitDateLabel}`,
1095
- };
1096
- }
1097
- if (/\btonight\b/.test(normalizedIntent)) {
1098
- return {
1099
- request: {
1100
- calendarId,
1101
- timeZone,
1102
- forceSync,
1103
- ...resolveTonightWindow(timeZone),
1104
- },
1105
- label: "tonight",
1106
- };
1107
- }
1108
- if (/\bnext week\b/.test(normalizedIntent) &&
1109
- /\b(?:week after next|the week after)\b/.test(normalizedIntent)) {
1110
- return {
1111
- request: {
1112
- calendarId,
1113
- timeZone,
1114
- forceSync,
1115
- ...buildLocalDayRange(timeZone, 7, 21),
1116
- },
1117
- label: "next week or the week after",
1118
- };
1119
- }
1120
- if (/\btomorrow\b/.test(normalizedIntent)) {
1121
- return {
1122
- request: {
1123
- calendarId,
1124
- timeZone,
1125
- forceSync,
1126
- ...buildLocalDayRange(timeZone, 1, 2),
1127
- },
1128
- label: "tomorrow",
1129
- };
1130
- }
1131
- if (/\bnext weekend\b/.test(normalizedIntent)) {
1132
- return {
1133
- request: {
1134
- calendarId,
1135
- timeZone,
1136
- forceSync,
1137
- ...resolveWeekendWindow(timeZone, "next"),
1138
- },
1139
- label: "next weekend",
1140
- };
1141
- }
1142
- if (/\b(?:this weekend|weekend)\b/.test(normalizedIntent)) {
1143
- return {
1144
- request: {
1145
- calendarId,
1146
- timeZone,
1147
- forceSync,
1148
- ...resolveWeekendWindow(timeZone, "this"),
1149
- },
1150
- label: "this weekend",
1151
- };
1152
- }
1153
- if (/\b(?:week after next|the week after)\b/.test(normalizedIntent)) {
1154
- return {
1155
- request: {
1156
- calendarId,
1157
- timeZone,
1158
- forceSync,
1159
- ...buildLocalDayRange(timeZone, 14, 21),
1160
- },
1161
- label: "the week after next",
1162
- };
1163
- }
1164
- if (/\bnext week\b/.test(normalizedIntent)) {
1165
- return {
1166
- request: {
1167
- calendarId,
1168
- timeZone,
1169
- forceSync,
1170
- ...buildLocalDayRange(timeZone, 7, 14),
1171
- },
1172
- label: "next week",
1173
- };
1174
- }
1175
- if (/\b(this week|week)\b/.test(normalizedIntent)) {
1176
- return {
1177
- request: {
1178
- calendarId,
1179
- timeZone,
1180
- forceSync,
1181
- ...buildLocalDayRange(timeZone, 0, 7),
1182
- },
1183
- label: "this week",
1184
- };
1185
- }
1186
- if (/\bnext month\b/.test(normalizedIntent)) {
1187
- return {
1188
- request: {
1189
- calendarId,
1190
- timeZone,
1191
- forceSync,
1192
- ...resolveMonthWindow(timeZone, "next"),
1193
- },
1194
- label: "next month",
1195
- };
1196
- }
1197
- if (/\bthis month\b/.test(normalizedIntent)) {
1198
- return {
1199
- request: {
1200
- calendarId,
1201
- timeZone,
1202
- forceSync,
1203
- ...resolveMonthWindow(timeZone, "this"),
1204
- },
1205
- label: "this month",
1206
- };
1207
- }
1208
- const windowDays = detailNumber(details, "windowDays");
1209
- if (forSearch) {
1210
- const days = windowDays && windowDays > 0 ? Math.min(windowDays, 90) : 30;
1211
- return {
1212
- request: {
1213
- calendarId,
1214
- timeZone,
1215
- forceSync,
1216
- ...buildLocalDayRange(timeZone, 0, days),
1217
- },
1218
- label: `across the next ${days} days`,
1219
- };
1220
- }
1221
- return {
1222
- request: {
1223
- calendarId,
1224
- timeZone,
1225
- forceSync,
1226
- ...buildLocalDayRange(timeZone, 0, 1),
1227
- },
1228
- label: "today",
1229
- };
1230
- }
1231
- function resolveTripWindowRequest(details, llmPlan) {
1232
- const timeMin = detailString(details, "timeMin");
1233
- const timeMax = detailString(details, "timeMax");
1234
- const calendarId = detailString(details, "calendarId");
1235
- const timeZone = resolveCalendarTimeZone(details);
1236
- const forceSync = detailBoolean(details, "forceSync");
1237
- if (timeMin || timeMax) {
1238
- return {
1239
- calendarId,
1240
- timeMin: timeMin ?? undefined,
1241
- timeMax: timeMax ?? undefined,
1242
- timeZone,
1243
- forceSync,
1244
- };
1245
- }
1246
- const llmWindow = resolveCalendarLlmWindow(llmPlan);
1247
- if (llmWindow) {
1248
- return {
1249
- calendarId,
1250
- timeZone,
1251
- forceSync,
1252
- timeMin: llmWindow.timeMin,
1253
- timeMax: llmWindow.timeMax,
1254
- };
1255
- }
1256
- const windowDays = detailNumber(details, "windowDays");
1257
- const days = windowDays && windowDays > 0 ? Math.min(windowDays, 120) : 60;
1258
- return {
1259
- calendarId,
1260
- timeZone,
1261
- forceSync,
1262
- ...buildLocalDayRange(timeZone, 0, days),
1263
- };
1264
- }
1265
- function inferCalendarSearchQuery(intent) {
1266
- const normalizedIntent = normalizeText(intent);
1267
- if (/\b(flight|flights|fly|flying|travel|trip)\b/.test(normalizedIntent)) {
1268
- const locationMatch = normalizedIntent.match(/\b(?:from|to)\s+(.+?)(?=\b(?:today|tomorrow|tonight|this week(?:end)?|next week(?:end)?|week after(?: next)?|this month|next month|this year|next year|or|and|please|idk|i dk|i don't know)\b|[?.!,]|$)/i);
1269
- const parts = ["flight"];
1270
- if (/\b(return|back|home)\b/.test(normalizedIntent)) {
1271
- parts.push("return");
1272
- }
1273
- const location = normalizeCalendarSearchQueryValue(locationMatch?.[1] ?? "");
1274
- if (location) {
1275
- parts.push(location);
1276
- }
1277
- return normalizeCalendarSearchQueryValue(parts.join(" ")) ?? "flight";
1278
- }
1279
- const dateMatch = normalizedIntent.match(/\b(?:on|for)\s+((?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:t(?:ember)?)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)\.?\s+\d{1,2}(?:st|nd|rd|th)?(?:,?\s+\d{4})?|\d{1,2}[/-]\d{1,2}(?:[/-]\d{2,4})?|\d{4}-\d{1,2}-\d{1,2})\b/i);
1280
- const normalizedDate = normalizeCalendarSearchQueryValue(dateMatch?.[1]);
1281
- if (normalizedDate) {
1282
- return normalizedDate;
1283
- }
1284
- const patterns = [
1285
- /^(?:please\s+)?(?:find|search(?: for)?|look(?:ing)? for|show me)\s+(.+)$/i,
1286
- /^(?:please\s+)?(?:do i have|are there)\s+(?:any\s+)?(.+?)(?:\?|$)/i,
1287
- /^(?:please\s+)?(?:check|look|see)\s+(?:my\s+)?calendar\s+for\s+(.+?)(?:\?|$)/i,
1288
- /^what\s+(?:event|events)\s+do\s+i\s+have\s+(?:on|for)\s+(.+?)(?:\?|$)/i,
1289
- /^(?:please\s+)?any\s+(.+?)(?:\?|$)/i,
1290
- ];
1291
- for (const pattern of patterns) {
1292
- const match = normalizedIntent.match(pattern);
1293
- const value = normalizeCalendarSearchQueryValue(match?.[1] ?? "");
1294
- if (value) {
1295
- return value;
1296
- }
1297
- }
1298
- return undefined;
1299
- }
1300
- function inferCalendarSearchQueries(intent) {
1301
- const normalizedIntent = normalizeText(intent);
1302
- const queries = new Set();
1303
- const push = (value) => {
1304
- const normalized = normalizeCalendarSearchQueryValue(value);
1305
- if (normalized) {
1306
- queries.add(normalized);
1307
- }
1308
- };
1309
- push(inferCalendarSearchQuery(intent));
1310
- if (/\b(return|back|home)\b/.test(normalizedIntent)) {
1311
- const locationMatch = normalizedIntent.match(/\b(?:from|to)\s+(.+?)(?=\b(?:today|tomorrow|tonight|this week(?:end)?|next week(?:end)?|week after(?: next)?|this month|next month|this year|next year|or|and|please|idk|i dk|i don't know)\b|[?.!,]|$)/i);
1312
- const location = normalizeCalendarSearchQueryValue(locationMatch?.[1] ?? "");
1313
- push(`return flight${location ? ` ${location}` : ""}`);
1314
- if (location) {
1315
- push(`flight back ${location}`);
1316
- push(`${location} return flight`);
1317
- }
1318
- }
1319
- return [...queries];
1320
- }
1321
- function sanitizeCalendarQuery(query, intent) {
1322
- if (!query) {
1323
- return undefined;
1324
- }
1325
- const raw = normalizeText(query);
1326
- if (PARAMETER_DOC_NOISE_PATTERN.test(raw) ||
1327
- raw.includes("supported keys include") ||
1328
- raw.includes("match against titles") ||
1329
- raw.includes("structured calendar arguments")) {
1330
- return undefined;
1331
- }
1332
- const cleaned = normalizeCalendarSearchQueryValue(query);
1333
- if (!cleaned ||
1334
- PARAMETER_DOC_NOISE_PATTERN.test(cleaned) ||
1335
- textMatchesAnyCal(cleaned, CAL_TEMPORAL_FOLLOWUP_TERMS) ||
1336
- looksLikeLiteralRequestEcho(cleaned, intent) ||
1337
- cleaned.length > 160) {
1338
- return undefined;
1339
- }
1340
- const inferred = inferCalendarSearchQuery(intent);
1341
- if (inferred &&
1342
- looksLikeNarrativeCalendarQuery(cleaned) &&
1343
- normalizeText(inferred) !== normalizeText(cleaned)) {
1344
- return undefined;
1345
- }
1346
- return cleaned;
1347
- }
1348
- function scoreCalendarQueryCandidate(query, intent) {
1349
- const normalized = normalizeText(query);
1350
- if (!normalized) {
1351
- return Number.NEGATIVE_INFINITY;
1352
- }
1353
- let score = 0;
1354
- if (PARAMETER_DOC_NOISE_PATTERN.test(normalized)) {
1355
- score -= 500;
1356
- }
1357
- if (looksLikeNarrativeCalendarQuery(normalized)) {
1358
- score -= 120;
1359
- }
1360
- if (looksLikeLiteralRequestEcho(query, intent)) {
1361
- score -= 120;
1362
- }
1363
- if (textMatchesAnyCal(normalized, CAL_TEMPORAL_FOLLOWUP_TERMS)) {
1364
- score -= 120;
1365
- }
1366
- const tokens = tokenizeForSearch(normalized);
1367
- if (tokens.length <= 4) {
1368
- score += 12;
1369
- }
1370
- else if (tokens.length >= 8) {
1371
- score -= 15;
1372
- }
1373
- const inferredQueries = inferCalendarSearchQueries(intent).map((value) => normalizeText(value));
1374
- if (inferredQueries.includes(normalized)) {
1375
- score += 60;
1376
- }
1377
- for (const inferredQuery of inferredQueries) {
1378
- if (!inferredQuery) {
1379
- continue;
1380
- }
1381
- if (normalized.includes(inferredQuery) ||
1382
- inferredQuery.includes(normalized)) {
1383
- score += 18;
1384
- }
1385
- const inferredTokens = new Set(tokenizeForSearch(inferredQuery));
1386
- score += tokens.filter((token) => inferredTokens.has(token)).length * 8;
1387
- }
1388
- if (/\b(flight|flights|travel|trip|return|back|home)\b/.test(normalizeText(intent)) &&
1389
- /\b(flight|flights|travel|trip|return|back|home)\b/.test(normalized)) {
1390
- score += 12;
1391
- }
1392
- return score;
1393
- }
1394
- function eventDateSearchTerms(event) {
1395
- const formatter = (options) => new Intl.DateTimeFormat("en-US", {
1396
- timeZone: event.timezone || undefined,
1397
- ...options,
1398
- }).format(new Date(event.startAt));
1399
- const monthLong = normalizeText(formatter({ month: "long" }).replace(/\./g, ""));
1400
- const monthShort = normalizeText(formatter({ month: "short" }).replace(/\./g, ""));
1401
- const weekdayLong = normalizeText(formatter({ weekday: "long" }));
1402
- const weekdayShort = normalizeText(formatter({ weekday: "short" }));
1403
- const day = formatter({ day: "numeric" });
1404
- const dayPadded = day.padStart(2, "0");
1405
- const monthNumeric = formatter({ month: "numeric" });
1406
- const monthPadded = monthNumeric.padStart(2, "0");
1407
- const year = formatter({ year: "numeric" });
1408
- return new Set([
1409
- `${monthLong} ${day}`,
1410
- `${monthLong} ${day} ${year}`,
1411
- `${monthShort} ${day}`,
1412
- `${monthShort} ${day} ${year}`,
1413
- `${weekdayLong} ${monthLong} ${day}`,
1414
- `${weekdayShort} ${monthShort} ${day}`,
1415
- `${monthNumeric}/${day}`,
1416
- `${monthNumeric}/${dayPadded}`,
1417
- `${monthPadded}/${day}`,
1418
- `${monthPadded}/${dayPadded}`,
1419
- `${year}-${monthPadded}-${dayPadded}`,
1420
- weekdayLong,
1421
- weekdayShort,
1422
- ].map((term) => normalizeText(term)));
1423
- }
1424
- async function extractCalendarSearchQueriesWithLlm(runtime, message, state, intent, timeZone) {
1425
- return (await extractCalendarPlanWithLlm(runtime, message, state, intent, timeZone)).queries;
1426
- }
1427
- export async function extractCalendarPlanWithLlm(runtime, message, state, intent, timeZone = resolveDefaultTimeZone()) {
1428
- const recentConversation = formatCreateEventRecentConversation(state);
1429
- const currentMessage = messageText(message).trim();
1430
- const now = new Date();
1431
- const nowIso = now.toISOString();
1432
- const localNow = new Intl.DateTimeFormat("en-US", {
1433
- timeZone,
1434
- dateStyle: "full",
1435
- timeStyle: "long",
1436
- }).format(now);
1437
- const prompt = [
1438
- "Plan the calendar action for this request.",
1439
- "The user may speak in any language.",
1440
- "Use the current request plus recent conversation context.",
1441
- "If the current request is vague or a follow-up, recover the subject from recent conversation and apply the new constraint from the current request.",
1442
- "You are allowed to decide that the assistant should reply naturally without acting yet.",
1443
- "Set shouldAct=false when the user is vague, only acknowledging, brainstorming, or asking for calendar help without enough specifics to safely act.",
1444
- "When shouldAct=false, provide a short natural response that asks only for what is missing.",
1445
- "",
1446
- "Return a JSON object with exactly these fields:",
1447
- " subaction: one of the allowed subactions below, or null when this should be reply-only/no-op",
1448
- " shouldAct: boolean",
1449
- " response: short natural-language reply when shouldAct is false, otherwise empty or null",
1450
- " queries: array or ||-delimited string of up to 3 search queries",
1451
- " title: optional event title",
1452
- " tripLocation: optional trip location",
1453
- " timeMin: optional ISO 8601 datetime",
1454
- " timeMax: optional ISO 8601 datetime",
1455
- " windowLabel: optional natural-language window label",
1456
- "",
1457
- "Subactions and when to use each:",
1458
- " feed — view today's, tomorrow's, or this week's schedule (e.g. 'what's on my calendar', 'what do I have today', 'this week's agenda')",
1459
- " next_event — check the next upcoming event only (e.g. 'what's my next meeting', 'when is my next appointment')",
1460
- " search_events — find events by title, attendee, location, or date range (e.g. 'find my flight', 'when is the dentist', 'meetings with John')",
1461
- " create_event — schedule a new event (e.g. 'schedule a meeting tomorrow at 3pm', 'add lunch with Sarah on Friday')",
1462
- " update_event — rename, reschedule, move, or edit an existing event (e.g. 'rename my meeting to standup', 'reschedule the dentist to Friday', 'move the call to 3pm')",
1463
- " delete_event — remove or cancel an existing event (e.g. 'delete the team meeting', 'cancel my appointment', 'remove the duplicate event')",
1464
- " trip_window — query what's happening during a trip or stay in a specific place (e.g. 'what's happening while I'm in Denver', 'my Tokyo itinerary')",
1465
- "",
1466
- "For feed, search_events, or trip_window, infer an exact timeMin/timeMax window when the request names or implies a date or date range.",
1467
- "timeMin and timeMax must be ISO 8601 datetimes that the API can use directly.",
1468
- "windowLabel should be a short natural-language label like on monday, this weekend, next month, or tonight.",
1469
- "For search_events or trip_window, extract up to 3 short search queries.",
1470
- "Preserve names, places, and keywords in their original language or script when useful.",
1471
- "Convert time constraints into concise searchable dates or windows even if the user phrases them in another language.",
1472
- "Focus on people, places, flights, itinerary, appointments, and explicit dates.",
1473
- "If the request is about a date, include a date query like april 12 or 2026-04-12.",
1474
- "If the request asks what is happening while the user is in a place, use trip_window and include tripLocation.",
1475
- "",
1476
- "Examples:",
1477
- ' "what\'s on my calendar tomorrow" → {"subaction":"feed","shouldAct":true,"response":null}',
1478
- ' "schedule a meeting with Alex at 3pm" → {"subaction":"create_event","shouldAct":true,"response":null,"title":"Meeting with Alex"}',
1479
- ' "find my return flight" → {"subaction":"search_events","shouldAct":true,"response":null,"queries":["return flight"]}',
1480
- ' "what do I have while I\'m in Tokyo" → {"subaction":"trip_window","shouldAct":true,"response":null,"queries":["tokyo"],"tripLocation":"Tokyo"}',
1481
- ' "rename my meeting to standup" → {"subaction":"update_event","shouldAct":true,"response":null,"queries":["meeting"],"title":"standup"}',
1482
- ' "delete the team meeting tomorrow" → {"subaction":"delete_event","shouldAct":true,"response":null,"queries":["team meeting"]}',
1483
- ' "can you help me with my calendar?" → {"subaction":null,"shouldAct":false,"response":"What do you want to do on your calendar — check your schedule, find an event, or create one?","queries":[]}',
1484
- "",
1485
- "The user may speak any language. Detect the calendar intent regardless of language.",
1486
- "When the user asks about what is happening in a specific location or during a trip, detect this as trip_window and extract the location, regardless of language.",
1487
- "",
1488
- "Return ONLY valid JSON. No prose. No markdown. No XML. No <think>.",
1489
- "",
1490
- `Current timezone: ${timeZone}`,
1491
- `Current local datetime: ${localNow}`,
1492
- `Current ISO datetime: ${nowIso}`,
1493
- "",
1494
- "<current_request>",
1495
- currentMessage,
1496
- "</current_request>",
1497
- "<resolved_intent>",
1498
- intent,
1499
- "</resolved_intent>",
1500
- "<recent_conversation>",
1501
- recentConversation,
1502
- "</recent_conversation>",
1503
- ].join("\n");
1504
- let rawResponse = "";
1505
- try {
1506
- const result = await runtime.useModel(ModelType.TEXT_LARGE, {
1507
- prompt,
1508
- });
1509
- rawResponse = typeof result === "string" ? result : "";
1510
- }
1511
- catch (error) {
1512
- runtime.logger?.warn?.({
1513
- src: "action:calendar",
1514
- error: error instanceof Error ? error.message : String(error),
1515
- }, "Calendar action planning model call failed");
1516
- return {
1517
- subaction: null,
1518
- queries: [],
1519
- shouldAct: null,
1520
- };
1521
- }
1522
- const parsed = parseKeyValueXml(rawResponse) ??
1523
- parseJSONObjectFromText(rawResponse);
1524
- if (!parsed) {
1525
- return {
1526
- subaction: null,
1527
- queries: [],
1528
- shouldAct: null,
1529
- };
1530
- }
1531
- const tripLocation = typeof parsed.tripLocation === "string" &&
1532
- parsed.tripLocation.trim().length > 0
1533
- ? parsed.tripLocation.trim()
1534
- : undefined;
1535
- // Extract queries from multiple possible shapes:
1536
- // - TOON string: "flight || dentist" (split on ||)
1537
- // - TOON single: "return flight" (no delimiter)
1538
- // - JSON array: ["flight", "dentist"]
1539
- // - Numbered fallbacks: query1, query2, query3
1540
- const rawQueries = [];
1541
- if (typeof parsed.queries === "string" && parsed.queries.trim().length > 0) {
1542
- for (const q of parsed.queries.split(/\s*\|\|\s*/)) {
1543
- if (q.trim().length > 0)
1544
- rawQueries.push(q.trim());
1545
- }
1546
- }
1547
- else if (Array.isArray(parsed.queries)) {
1548
- for (const value of parsed.queries) {
1549
- if (typeof value === "string")
1550
- rawQueries.push(value);
1551
- }
1552
- }
1553
- if (typeof parsed.query === "string")
1554
- rawQueries.push(parsed.query);
1555
- if (typeof parsed.query1 === "string")
1556
- rawQueries.push(parsed.query1);
1557
- if (typeof parsed.query2 === "string")
1558
- rawQueries.push(parsed.query2);
1559
- if (typeof parsed.query3 === "string")
1560
- rawQueries.push(parsed.query3);
1561
- if (tripLocation)
1562
- rawQueries.push(tripLocation);
1563
- return {
1564
- subaction: normalizeCalendarSubaction(parsed.subaction),
1565
- queries: dedupeCalendarQueries(rawQueries),
1566
- response: normalizePlannerResponse(parsed.response),
1567
- shouldAct: normalizeShouldAct(parsed.shouldAct),
1568
- title: typeof parsed.title === "string" && parsed.title.trim().length > 0
1569
- ? parsed.title.trim()
1570
- : undefined,
1571
- tripLocation,
1572
- timeMin: normalizeIsoDateTime(parsed.timeMin),
1573
- timeMax: normalizeIsoDateTime(parsed.timeMax),
1574
- windowLabel: normalizeWindowLabel(parsed.windowLabel ?? parsed.label),
1575
- };
1576
- }
1577
- async function resolveCalendarSearchQueries(runtime, message, state, explicitQueries, intent, llmPlan, timeZone) {
1578
- const providedQueries = dedupeCalendarQueries(explicitQueries.map((query) => sanitizeCalendarQuery(query, intent)));
1579
- const heuristicQueries = inferCalendarSearchQueries(intent);
1580
- const llmQueries = llmPlan && llmPlan.queries.length > 0
1581
- ? llmPlan.queries
1582
- : await extractCalendarSearchQueriesWithLlm(runtime, message, state, intent, timeZone);
1583
- const stateQueries = userIntentsFromState(state)
1584
- .reverse()
1585
- .flatMap((candidate) => inferCalendarSearchQueries(candidate));
1586
- const candidates = dedupeCalendarQueries([
1587
- ...providedQueries,
1588
- ...llmQueries,
1589
- ...heuristicQueries,
1590
- ...stateQueries,
1591
- ].map((query) => sanitizeCalendarQuery(query, intent)));
1592
- return [...candidates].sort((left, right) => scoreCalendarQueryCandidate(right, intent) -
1593
- scoreCalendarQueryCandidate(left, intent));
1594
- }
1595
- function inferCreateEventTitle(intent) {
1596
- const patterns = [
1597
- /\b(?:create|add|schedule|book|put)\s+(?:a|an|the)?\s*(.+?)(?=\b(?:for|on|at|tomorrow|today|tonight|next|this|from)\b|[?.!,]|$)/i,
1598
- /\b(?:meeting|appointment|call|event)\s+(?:with|for)\s+(.+?)(?=\b(?:for|on|at|tomorrow|today|tonight|next|this|from)\b|[?.!,]|$)/i,
1599
- ];
1600
- for (const pattern of patterns) {
1601
- const match = intent.match(pattern);
1602
- const value = match?.[1]?.trim();
1603
- if (value && !/^(calendar|event|meeting|appointment|call)$/i.test(value)) {
1604
- return value.replace(/\s+/g, " ").trim();
1605
- }
1606
- }
1607
- return undefined;
1608
- }
1609
- function isShortPreparationEvent(intent, title) {
1610
- return /\b(get ready|ready for|prep|prepare|packing|pack|leave for|head to|airport|flight|reminder|remind me)\b/i.test(`${intent} ${title}`);
1611
- }
1612
- function resolveCreateEventDurationMinutes(args) {
1613
- const { explicitDuration, extractedDuration, intent, title, hasExplicitEndAt, hasExplicitWindowPreset, hasExplicitStartAt, } = args;
1614
- if (typeof explicitDuration === "number" &&
1615
- Number.isFinite(explicitDuration)) {
1616
- return explicitDuration > 0 ? explicitDuration : undefined;
1617
- }
1618
- if (typeof extractedDuration === "number" &&
1619
- Number.isFinite(extractedDuration)) {
1620
- if (extractedDuration > 0) {
1621
- return extractedDuration;
1622
- }
1623
- if (isShortPreparationEvent(intent, title) &&
1624
- (hasExplicitStartAt || hasExplicitWindowPreset)) {
1625
- return MIN_CREATE_EVENT_DURATION_MINUTES;
1626
- }
1627
- return undefined;
1628
- }
1629
- if (!hasExplicitEndAt &&
1630
- isShortPreparationEvent(intent, title) &&
1631
- (hasExplicitStartAt || hasExplicitWindowPreset)) {
1632
- return MIN_CREATE_EVENT_DURATION_MINUTES;
1633
- }
1634
- return undefined;
1635
- }
1636
- function parseCreateEventDurationValue(value) {
1637
- if (typeof value === "number" && Number.isFinite(value)) {
1638
- return value;
1639
- }
1640
- if (typeof value === "string") {
1641
- const parsed = Number(value);
1642
- return Number.isFinite(parsed) ? parsed : undefined;
1643
- }
1644
- return undefined;
1645
- }
1646
- function pickCreateEventStringField(args, key) {
1647
- const explicit = detailString(args.details, key);
1648
- const extracted = detailString(args.extractedDetails, key);
1649
- const fallback = args.fallbackRequest &&
1650
- typeof args.fallbackRequest[key] === "string"
1651
- ? args.fallbackRequest[key]
1652
- : undefined;
1653
- return args.preferExtractedDetails
1654
- ? (extracted ?? explicit ?? fallback)
1655
- : (explicit ?? extracted ?? fallback);
1656
- }
1657
- function buildCreateEventRequest(args) {
1658
- const extractedTitle = detailString(args.extractedDetails, "title");
1659
- const title = args.preferExtractedDetails
1660
- ? (extractedTitle ??
1661
- args.explicitTitle ??
1662
- args.fallbackRequest?.title ??
1663
- args.inferredTitle)
1664
- : (args.explicitTitle ??
1665
- extractedTitle ??
1666
- args.fallbackRequest?.title ??
1667
- args.inferredTitle);
1668
- const explicitStartAt = detailString(args.details, "startAt");
1669
- const explicitEndAt = detailString(args.details, "endAt");
1670
- const explicitWindowPreset = detailString(args.details, "windowPreset");
1671
- const extractedStartAt = detailString(args.extractedDetails, "startAt");
1672
- const extractedEndAt = detailString(args.extractedDetails, "endAt");
1673
- const extractedWindowPreset = detailString(args.extractedDetails, "windowPreset");
1674
- let resolvedStartAt;
1675
- let resolvedWindowPreset;
1676
- if (args.preferExtractedDetails && extractedStartAt) {
1677
- resolvedStartAt = extractedStartAt;
1678
- resolvedWindowPreset = undefined;
1679
- }
1680
- else if (args.preferExtractedDetails && extractedWindowPreset) {
1681
- resolvedStartAt = undefined;
1682
- resolvedWindowPreset = extractedWindowPreset;
1683
- }
1684
- else {
1685
- resolvedStartAt =
1686
- explicitStartAt ?? extractedStartAt ?? args.fallbackRequest?.startAt;
1687
- resolvedWindowPreset = resolvedStartAt
1688
- ? undefined
1689
- : (explicitWindowPreset ??
1690
- extractedWindowPreset ??
1691
- args.fallbackRequest?.windowPreset);
1692
- }
1693
- const rawEndAt = args.preferExtractedDetails &&
1694
- (extractedStartAt || extractedWindowPreset) &&
1695
- !extractedEndAt
1696
- ? undefined
1697
- : args.preferExtractedDetails
1698
- ? (extractedEndAt ?? explicitEndAt ?? args.fallbackRequest?.endAt)
1699
- : (explicitEndAt ?? extractedEndAt ?? args.fallbackRequest?.endAt);
1700
- const explicitDuration = detailNumber(args.details, "durationMinutes");
1701
- const extractedDuration = parseCreateEventDurationValue(args.extractedDetails.durationMinutes);
1702
- const fallbackDuration = args.fallbackRequest?.durationMinutes;
1703
- const durationMinutes = resolveCreateEventDurationMinutes({
1704
- explicitDuration: explicitDuration,
1705
- extractedDuration,
1706
- intent: args.intent,
1707
- title: title ?? args.fallbackRequest?.title ?? "",
1708
- hasExplicitEndAt: Boolean(rawEndAt),
1709
- hasExplicitWindowPreset: Boolean(resolvedWindowPreset),
1710
- hasExplicitStartAt: Boolean(resolvedStartAt),
1711
- });
1712
- const resolvedDurationMinutes = explicitDuration !== undefined || extractedDuration !== undefined
1713
- ? durationMinutes
1714
- : fallbackDuration;
1715
- return {
1716
- title,
1717
- resolvedStartAt,
1718
- resolvedWindowPreset,
1719
- request: {
1720
- mode: detailString(args.details, "mode") ?? args.fallbackRequest?.mode,
1721
- side: (detailString(args.details, "side") ?? args.fallbackRequest?.side),
1722
- calendarId: detailString(args.details, "calendarId") ??
1723
- args.fallbackRequest?.calendarId,
1724
- title: title ?? "",
1725
- description: pickCreateEventStringField(args, "description") ??
1726
- args.fallbackRequest?.description,
1727
- location: pickCreateEventStringField(args, "location") ??
1728
- args.fallbackRequest?.location,
1729
- startAt: resolvedStartAt,
1730
- endAt: rawEndAt ?? args.fallbackRequest?.endAt,
1731
- timeZone: pickCreateEventStringField(args, "timeZone") ??
1732
- args.fallbackRequest?.timeZone,
1733
- durationMinutes: resolvedDurationMinutes,
1734
- windowPreset: resolvedWindowPreset,
1735
- attendees: normalizeCalendarAttendees(args.details) ??
1736
- args.fallbackRequest?.attendees,
1737
- },
1738
- };
1739
- }
1740
- function createEventRequestFingerprint(request) {
1741
- return JSON.stringify({
1742
- title: request.title,
1743
- description: request.description ?? null,
1744
- location: request.location ?? null,
1745
- startAt: request.startAt ?? null,
1746
- endAt: request.endAt ?? null,
1747
- timeZone: request.timeZone ?? null,
1748
- durationMinutes: request.durationMinutes ?? null,
1749
- windowPreset: request.windowPreset ?? null,
1750
- calendarId: request.calendarId ?? null,
1751
- side: request.side ?? null,
1752
- mode: request.mode ?? null,
1753
- });
1754
- }
1755
- function formatCreateEventRecentConversation(state) {
1756
- const conversation = planningConversationLines(state).join("\n").trim();
1757
- return conversation.length > 0 ? conversation : "(none)";
1758
- }
1759
- function parseCreateEventExtractionResponse(rawResponse) {
1760
- const parsed = parseKeyValueXml(rawResponse);
1761
- return parsed && typeof parsed === "object" ? parsed : {};
1762
- }
1763
- function formatUpdateEventTargetContext(event) {
1764
- if (!event) {
1765
- return "(unknown)";
1766
- }
1767
- const attendees = event.attendees
1768
- .map((attendee) => attendee.displayName ?? attendee.email ?? "")
1769
- .filter((value) => value.length > 0)
1770
- .join(", ");
1771
- return [
1772
- `title: ${event.title}`,
1773
- `startAt: ${event.startAt}`,
1774
- `endAt: ${event.endAt}`,
1775
- `timeZone: ${event.timezone ?? ""}`,
1776
- `formattedStart: ${formatCalendarEventDateTime(event, {
1777
- includeTimeZoneName: true,
1778
- })}`,
1779
- `location: ${event.location ?? ""}`,
1780
- `description: ${event.description ?? ""}`,
1781
- `attendees: ${attendees}`,
1782
- ].join("\n");
1783
- }
1784
- function shouldRetryCreateEventExtraction(error) {
1785
- const normalized = normalizeText(error.message);
1786
- if (error.status === 401 || error.status === 403) {
1787
- return false;
1788
- }
1789
- if (/\b(?:not connected|needs re-authentication|unauthorized|forbidden|permission|scope|grant)\b/.test(normalized)) {
1790
- return false;
1791
- }
1792
- return (error.status === 400 ||
1793
- error.status === 409 ||
1794
- /\b(?:startat|endat|duration|windowpreset|date|time|timezone|datetime|later than|invalid|bad request|parse|format)\b/.test(normalized));
1795
- }
1796
- async function inferCreateEventDetails(runtime, message, state, intent, calendarContext, fallbackTimeZone = resolveDefaultTimeZone()) {
1797
- const recentConversation = formatCreateEventRecentConversation(state);
1798
- const currentMessage = messageText(message).trim();
1799
- // Anchor the LLM in the present so relative phrases ("tomorrow", "next
1800
- // friday", "april 15") and explicit-but-yearless dates resolve to the
1801
- // correct ISO datetime instead of guessing or returning empty.
1802
- const now = new Date();
1803
- const nowIso = now.toISOString();
1804
- const timeZone = fallbackTimeZone;
1805
- const calendarTimeZone = calendarContext?.calendarTimeZone ?? fallbackTimeZone;
1806
- const nowReadable = new Intl.DateTimeFormat("en-US", {
1807
- timeZone,
1808
- dateStyle: "full",
1809
- timeStyle: "long",
1810
- }).format(now);
1811
- const prompt = [
1812
- "Extract calendar event creation fields from the request.",
1813
- "The user may speak in any language.",
1814
- "Use the full recent conversation below, not just the latest message.",
1815
- "Treat the latest user request as authoritative, but recover missing event subject, date, or location from earlier turns when needed.",
1816
- "If the current request is a follow-up, recover the event subject from recent conversation and apply new timing or location constraints from the current request.",
1817
- "Use the calendar context below to ground any timing guess.",
1818
- "Preserve names and places in their original language or script when useful.",
1819
- "Return XML only. No prose. Leave fields empty when unknown.",
1820
- "If a start time or window is implied but duration is not explicit, infer a reasonable positive duration.",
1821
- "For short prep or reminder blocks, use at least 15 minutes instead of 0.",
1822
- "When the user gives a concrete day or date without an exact time-of-day, use the calendar context to infer a plausible open startAt in the calendar timezone. Avoid obvious overlaps with nearby events. If the calendar context is unavailable or the timing is ambiguous, leave startAt empty.",
1823
- "Only use windowPreset for explicit 'tomorrow morning|afternoon|evening' phrasing — never as a fallback for arbitrary dates.",
1824
- "",
1825
- "<response>",
1826
- " <title>event title</title>",
1827
- " <description>optional description</description>",
1828
- " <location>optional location</location>",
1829
- " <startAt>ISO datetime if explicit or resolvable from a date phrase</startAt>",
1830
- " <endAt>ISO datetime if explicit</endAt>",
1831
- " <durationMinutes>number if implied</durationMinutes>",
1832
- " <windowPreset>tomorrow_morning|tomorrow_afternoon|tomorrow_evening</windowPreset>",
1833
- " <timeZone>IANA timezone if stated</timeZone>",
1834
- "</response>",
1835
- "",
1836
- `Current timezone: ${timeZone}`,
1837
- `Calendar timezone for scheduling: ${calendarTimeZone}`,
1838
- `Current local datetime: ${nowReadable}`,
1839
- `Current ISO datetime: ${nowIso}`,
1840
- "",
1841
- "<current_request>",
1842
- currentMessage,
1843
- "</current_request>",
1844
- "<resolved_intent>",
1845
- intent,
1846
- "</resolved_intent>",
1847
- "<recent_conversation>",
1848
- recentConversation,
1849
- "</recent_conversation>",
1850
- "<calendar_context>",
1851
- formatCreateEventCalendarContext(calendarContext),
1852
- "</calendar_context>",
1853
- ].join("\n");
1854
- try {
1855
- const result = await runtime.useModel(ModelType.TEXT_LARGE, { prompt });
1856
- const rawResponse = typeof result === "string" ? result : "";
1857
- return parseCreateEventExtractionResponse(rawResponse);
1858
- }
1859
- catch (error) {
1860
- runtime.logger?.warn?.({
1861
- src: "action:calendar",
1862
- error: error instanceof Error ? error.message : String(error),
1863
- }, "Calendar create-event extraction model call failed");
1864
- return {};
1865
- }
1866
- }
1867
- async function inferUpdateEventDetails(runtime, message, state, intent, targetEvent, fallbackTimeZone = targetEvent?.timezone ?? resolveDefaultTimeZone()) {
1868
- const recentConversation = formatCreateEventRecentConversation(state);
1869
- const currentMessage = messageText(message).trim();
1870
- const now = new Date();
1871
- const nowIso = now.toISOString();
1872
- const timeZone = fallbackTimeZone;
1873
- const nowReadable = new Intl.DateTimeFormat("en-US", {
1874
- timeZone,
1875
- dateStyle: "full",
1876
- timeStyle: "long",
1877
- }).format(now);
1878
- const prompt = [
1879
- "Extract calendar event update fields from the request.",
1880
- "The user may speak in any language.",
1881
- "Use the full recent conversation below, not just the latest message.",
1882
- "The current event below is the source of truth for unchanged fields.",
1883
- "Only return fields the user is actually changing. Leave fields empty when unchanged or unknown.",
1884
- "If the user asks to move or reschedule the event, compute absolute ISO datetimes for the updated startAt and endAt using the current event as context.",
1885
- "If the user gives a relative shift like later, earlier, push back, or move forward, apply it to the current event timing.",
1886
- "Unless the user explicitly changes the timezone, preserve the current event timezone.",
1887
- "If the user only renames the event, leave startAt, endAt, location, description, and timeZone empty.",
1888
- "Return XML only. No prose.",
1889
- "",
1890
- "<response>",
1891
- " <title>new event title if changed</title>",
1892
- " <description>updated description if changed</description>",
1893
- " <location>updated location if changed</location>",
1894
- " <startAt>updated ISO datetime if changed</startAt>",
1895
- " <endAt>updated ISO datetime if changed</endAt>",
1896
- " <timeZone>IANA timezone if changed or needed to interpret the update</timeZone>",
1897
- "</response>",
1898
- "",
1899
- `Current timezone: ${timeZone}`,
1900
- `Current local datetime: ${nowReadable}`,
1901
- `Current ISO datetime: ${nowIso}`,
1902
- "",
1903
- "<current_request>",
1904
- currentMessage,
1905
- "</current_request>",
1906
- "<resolved_intent>",
1907
- intent,
1908
- "</resolved_intent>",
1909
- "<recent_conversation>",
1910
- recentConversation,
1911
- "</recent_conversation>",
1912
- "<current_event>",
1913
- formatUpdateEventTargetContext(targetEvent),
1914
- "</current_event>",
1915
- ].join("\n");
1916
- try {
1917
- const result = await runtime.useModel(ModelType.TEXT_LARGE, { prompt });
1918
- const rawResponse = typeof result === "string" ? result : "";
1919
- return parseCreateEventExtractionResponse(rawResponse);
1920
- }
1921
- catch (error) {
1922
- runtime.logger?.warn?.({
1923
- src: "action:calendar",
1924
- error: error instanceof Error ? error.message : String(error),
1925
- }, "Calendar update-event extraction model call failed");
1926
- return {};
1927
- }
1928
- }
1929
- async function repairCreateEventDetails(runtime, message, state, intent, calendarContext, failedRequest, previousExtraction, error, fallbackTimeZone = resolveDefaultTimeZone()) {
1930
- const recentConversation = formatCreateEventRecentConversation(state);
1931
- const currentMessage = messageText(message).trim();
1932
- const now = new Date();
1933
- const timeZone = fallbackTimeZone;
1934
- const calendarTimeZone = calendarContext?.calendarTimeZone ?? fallbackTimeZone;
1935
- const nowIso = now.toISOString();
1936
- const nowReadable = new Intl.DateTimeFormat("en-US", {
1937
- timeZone,
1938
- dateStyle: "full",
1939
- timeStyle: "long",
1940
- }).format(now);
1941
- const prompt = [
1942
- "Extract calendar event creation fields from the request.",
1943
- "The previous create attempt failed. Repair the extraction so the next create attempt succeeds.",
1944
- "Use the full recent conversation below, not just the latest message.",
1945
- "The latest user request is authoritative, but preserve the existing event subject, people, and places unless the user changed them.",
1946
- "Use the calendar context below to ground any timing repair.",
1947
- "Use the exact failure reason to correct only the broken fields.",
1948
- "Return XML only. No prose. Leave fields empty when unchanged or unknown.",
1949
- "",
1950
- "<response>",
1951
- " <title>event title</title>",
1952
- " <description>optional description</description>",
1953
- " <location>optional location</location>",
1954
- " <startAt>ISO datetime if explicit or resolvable from a date phrase</startAt>",
1955
- " <endAt>ISO datetime if explicit</endAt>",
1956
- " <durationMinutes>number if implied</durationMinutes>",
1957
- " <windowPreset>tomorrow_morning|tomorrow_afternoon|tomorrow_evening</windowPreset>",
1958
- " <timeZone>IANA timezone if stated</timeZone>",
1959
- "</response>",
1960
- "",
1961
- `Current timezone: ${timeZone}`,
1962
- `Calendar timezone for scheduling: ${calendarTimeZone}`,
1963
- `Current local datetime: ${nowReadable}`,
1964
- `Current ISO datetime: ${nowIso}`,
1965
- `Create failure: ${error.message}`,
1966
- `Previous extraction: ${JSON.stringify(previousExtraction)}`,
1967
- `Previous create request: ${JSON.stringify(failedRequest)}`,
1968
- "",
1969
- "<current_request>",
1970
- currentMessage,
1971
- "</current_request>",
1972
- "<resolved_intent>",
1973
- intent,
1974
- "</resolved_intent>",
1975
- "<recent_conversation>",
1976
- recentConversation,
1977
- "</recent_conversation>",
1978
- "<calendar_context>",
1979
- formatCreateEventCalendarContext(calendarContext),
1980
- "</calendar_context>",
1981
- ].join("\n");
1982
- try {
1983
- const result = await runtime.useModel(ModelType.TEXT_LARGE, { prompt });
1984
- const rawResponse = typeof result === "string" ? result : "";
1985
- return parseCreateEventExtractionResponse(rawResponse);
1986
- }
1987
- catch (repairError) {
1988
- runtime.logger?.warn?.({
1989
- src: "action:calendar",
1990
- error: repairError instanceof Error
1991
- ? repairError.message
1992
- : String(repairError),
1993
- }, "Calendar create-event repair model call failed");
1994
- return {};
1995
- }
1996
- }
1997
- function scoreCalendarEvent(event, query) {
1998
- const normalizedQuery = normalizeText(query);
1999
- const title = normalizeText(event.title);
2000
- const description = normalizeText(event.description);
2001
- const location = normalizeText(event.location);
2002
- const attendees = event.attendees
2003
- .flatMap((attendee) => [attendee.displayName ?? "", attendee.email ?? ""])
2004
- .map((value) => normalizeText(value))
2005
- .filter((value) => value.length > 0);
2006
- let score = 0;
2007
- const queryVariants = [
2008
- ...new Set([normalizedQuery, ...tokenVariants(normalizedQuery)]),
2009
- ];
2010
- if (queryVariants.some((variant) => title === variant)) {
2011
- score += 100;
2012
- }
2013
- else if (queryVariants.some((variant) => variant.length > 0 && title.includes(variant))) {
2014
- score += 75;
2015
- }
2016
- if (queryVariants.some((variant) => variant.length > 0 && description.includes(variant))) {
2017
- score += 35;
2018
- }
2019
- if (queryVariants.some((variant) => variant.length > 0 && location.includes(variant))) {
2020
- score += 30;
2021
- }
2022
- if (attendees.some((value) => queryVariants.some((variant) => variant.length > 0 && value.includes(variant)))) {
2023
- score += 25;
2024
- }
2025
- const queryTokens = tokenizeForSearch(normalizedQuery);
2026
- if (queryTokens.length > 0) {
2027
- const titleTokens = new Set(tokenizeForSearch(title));
2028
- const descriptionTokens = new Set(tokenizeForSearch(description));
2029
- const locationTokens = new Set(tokenizeForSearch(location));
2030
- const attendeeTokens = attendees.flatMap((value) => tokenizeForSearch(value));
2031
- const attendeeTokenSet = new Set(attendeeTokens);
2032
- score += queryTokens.filter((token) => titleTokens.has(token)).length * 12;
2033
- score +=
2034
- queryTokens.filter((token) => descriptionTokens.has(token)).length * 8;
2035
- score +=
2036
- queryTokens.filter((token) => locationTokens.has(token)).length * 14;
2037
- score +=
2038
- queryTokens.filter((token) => attendeeTokenSet.has(token)).length * 8;
2039
- }
2040
- if (/\b(return|back|home)\b/.test(normalizedQuery)) {
2041
- if (/\b(return|back|home)\b/.test(`${title} ${description}`)) {
2042
- score += 24;
2043
- }
2044
- else if (/\b(flight|travel|trip)\b/.test(`${title} ${description} ${location}`)) {
2045
- score -= 36;
2046
- }
2047
- }
2048
- const dateTerms = eventDateSearchTerms(event);
2049
- if ([...dateTerms].some((term) => term === normalizedQuery ||
2050
- normalizedQuery.includes(term) ||
2051
- term.includes(normalizedQuery))) {
2052
- score += 90;
2053
- }
2054
- const dateTokens = new Set([...dateTerms].flatMap((term) => tokenizeForSearch(term)));
2055
- score += queryTokens.filter((token) => dateTokens.has(token)).length * 10;
2056
- return score;
2057
- }
2058
- function shouldGroundCalendarSearchWithLlm(query, rankedEvents) {
2059
- const strongestScore = rankedEvents[0]?.score ?? 0;
2060
- if (strongestScore <= 0) {
2061
- return false;
2062
- }
2063
- if (strongestScore >= 72) {
2064
- return false;
2065
- }
2066
- return wordCount(query) >= 2 || rankedEvents.length > 1;
2067
- }
2068
- function normalizeCalendarMatchIdsFromValue(value, allowedIds) {
2069
- const rawIds = [];
2070
- if (typeof value === "string") {
2071
- for (const token of value.split(/\s*\|\|\s*|\s*,\s*|\s+/)) {
2072
- if (token.trim().length > 0) {
2073
- rawIds.push(token.trim());
2074
- }
2075
- }
2076
- }
2077
- else if (Array.isArray(value)) {
2078
- for (const item of value) {
2079
- if (typeof item === "string" && item.trim().length > 0) {
2080
- rawIds.push(item.trim());
2081
- }
2082
- }
2083
- }
2084
- return [...new Set(rawIds.filter((id) => allowedIds.has(id)))];
2085
- }
2086
- function extractCalendarGroundedMatchIds(rawResponse, allowedIds) {
2087
- const parsed = parseKeyValueXml(rawResponse) ??
2088
- parseJSONObjectFromText(rawResponse);
2089
- if (!parsed) {
2090
- return null;
2091
- }
2092
- const possibleKeys = [
2093
- "matchIds",
2094
- "matches",
2095
- "ids",
2096
- "matchId",
2097
- "matchId1",
2098
- "matchId2",
2099
- "matchId3",
2100
- ];
2101
- const sawExplicitMatchField = possibleKeys.some((key) => key in parsed);
2102
- if (!sawExplicitMatchField) {
2103
- return null;
2104
- }
2105
- const ids = possibleKeys.flatMap((key) => normalizeCalendarMatchIdsFromValue(parsed[key], allowedIds));
2106
- return [...new Set(ids)];
2107
- }
2108
- function formatCalendarCandidateForGrounding(candidate) {
2109
- const attendees = candidate.event.attendees
2110
- .map((attendee) => attendee.displayName ?? attendee.email ?? "")
2111
- .filter((value) => value.length > 0)
2112
- .join(", ");
2113
- return [
2114
- `id: ${candidate.event.id}`,
2115
- `score: ${candidate.score}`,
2116
- `title: ${candidate.event.title}`,
2117
- `startAt: ${candidate.event.startAt}`,
2118
- `location: ${candidate.event.location ?? ""}`,
2119
- `description: ${(candidate.event.description ?? "").slice(0, 240)}`,
2120
- `attendees: ${attendees}`,
2121
- ].join("\n");
2122
- }
2123
- async function groundCalendarSearchMatchesWithLlm(runtime, state, intent, queries, candidates) {
2124
- if (candidates.length === 0) {
2125
- return [];
2126
- }
2127
- const recentConversation = formatCreateEventRecentConversation(state);
2128
- const allowedIds = new Set(candidates.map((candidate) => candidate.event.id));
2129
- const prompt = [
2130
- "Decide which candidate calendar events directly match the user's request.",
2131
- "Be strict.",
2132
- "Return NO matches when the candidate only shares a generic time window or vague travel context.",
2133
- "If the request names a person, company, topic, or event name, only match candidates that explicitly mention that subject in the title, description, location, or attendees.",
2134
- "Flights only count when the request is actually about flights/travel, or the flight text explicitly mentions the named subject.",
2135
- "Return TOON only. No prose. No <think>.",
2136
- "Use || to separate multiple ids.",
2137
- "",
2138
- "Example:",
2139
- "matchIds: evt_1 || evt_2",
2140
- "reason:",
2141
- "",
2142
- "<resolved_intent>",
2143
- intent,
2144
- "</resolved_intent>",
2145
- "<search_queries>",
2146
- queries.join(" || "),
2147
- "</search_queries>",
2148
- "<recent_conversation>",
2149
- recentConversation,
2150
- "</recent_conversation>",
2151
- "",
2152
- "Candidates:",
2153
- ...candidates.map((candidate, index) => `candidate ${index + 1}\n${formatCalendarCandidateForGrounding(candidate)}`),
2154
- ].join("\n");
2155
- try {
2156
- const result = await runtime.useModel(ModelType.TEXT_LARGE, { prompt });
2157
- const rawResponse = typeof result === "string" ? result : "";
2158
- return extractCalendarGroundedMatchIds(rawResponse, allowedIds);
2159
- }
2160
- catch (error) {
2161
- runtime.logger?.warn?.({
2162
- src: "action:calendar",
2163
- error: error instanceof Error ? error.message : String(error),
2164
- }, "Calendar search grounding model call failed");
2165
- return null;
2166
- }
2167
- }
2168
- function isTravelEvent(event) {
2169
- return /\b(flight|fly|travel|trip|hotel|stay|lodging|airbnb|check[- ]?in|check[- ]?out|return|home)\b/i.test(`${event.title} ${event.description} ${event.location}`);
2170
- }
2171
- function eventStartMs(event) {
2172
- return Date.parse(event.startAt);
2173
- }
2174
- function eventEndMs(event) {
2175
- const parsed = Date.parse(event.endAt);
2176
- if (Number.isFinite(parsed)) {
2177
- return parsed;
2178
- }
2179
- return eventStartMs(event);
2180
- }
2181
- function resolveTripWindowEvents(events, location) {
2182
- const anchors = events
2183
- .map((event) => ({
2184
- event,
2185
- score: scoreCalendarEvent(event, location) + (isTravelEvent(event) ? 12 : 0),
2186
- }))
2187
- .filter((candidate) => candidate.score > 0)
2188
- .sort((left, right) => eventStartMs(left.event) - eventStartMs(right.event));
2189
- if (anchors.length === 0) {
2190
- return null;
2191
- }
2192
- const windowStart = Math.min(...anchors.map((candidate) => eventStartMs(candidate.event)));
2193
- const windowEnd = Math.max(...anchors.map((candidate) => eventEndMs(candidate.event)));
2194
- return events
2195
- .filter((event) => eventEndMs(event) >= windowStart && eventStartMs(event) <= windowEnd)
2196
- .sort((left, right) => eventStartMs(left) - eventStartMs(right));
2197
- }
2198
- function formatCalendarMoment(event) {
2199
- if (event.isAllDay) {
2200
- return new Intl.DateTimeFormat("en-US", {
2201
- timeZone: event.timezone || undefined,
2202
- month: "short",
2203
- day: "numeric",
2204
- }).format(new Date(event.startAt));
2205
- }
2206
- return formatCalendarEventDateTime(event);
2207
- }
2208
- function formatTripWindowResults(events, location) {
2209
- if (events.length === 0) {
2210
- return `I couldn't find any upcoming calendar events while you're in ${location}.`;
2211
- }
2212
- const lines = [`Here's what's on your calendar while you're in ${location}:`];
2213
- for (const event of events.slice(0, 12)) {
2214
- lines.push(`- ${formatCalendarMoment(event)}: **${event.title}**`);
2215
- }
2216
- return lines.join("\n");
2217
- }
2218
- function formatCalendarSearchResults(events, query, label, intent) {
2219
- if (events.length === 0) {
2220
- return `No calendar events matched "${query}" ${label}.`;
2221
- }
2222
- if (events.length === 1) {
2223
- const event = events[0];
2224
- const normalizedIntent = normalizeText(intent);
2225
- const matchingSubject = /\b(flight|flights|fly|travel|trip|return|back|home)\b/.test(`${normalizedIntent} ${query}`)
2226
- ? "flight"
2227
- : "calendar event";
2228
- return `Your matching ${matchingSubject} is **${event.title}** (${formatCalendarMoment(event)}).`;
2229
- }
2230
- const lines = [
2231
- `Found ${events.length} calendar event${events.length === 1 ? "" : "s"} for "${query}" ${label}:`,
2232
- ];
2233
- for (const event of events.slice(0, 8)) {
2234
- const when = event.isAllDay
2235
- ? "all day"
2236
- : formatCalendarEventDateTime(event);
2237
- lines.push(`- **${event.title}** (${when})`);
2238
- if (event.location) {
2239
- lines.push(` Location: ${event.location}`);
2240
- }
2241
- if (event.description) {
2242
- lines.push(` ${event.description.slice(0, 120)}`);
2243
- }
2244
- }
2245
- return lines.join("\n");
2246
- }
2247
- function normalizeCalendarAttendees(details) {
2248
- const attendees = detailArray(details, "attendees");
2249
- if (!attendees) {
2250
- return undefined;
2251
- }
2252
- const mapped = attendees.map((attendee) => {
2253
- if (typeof attendee === "string" && attendee.trim().length > 0) {
2254
- return {
2255
- email: attendee.trim(),
2256
- };
2257
- }
2258
- if (!attendee ||
2259
- typeof attendee !== "object" ||
2260
- Array.isArray(attendee)) {
2261
- return null;
2262
- }
2263
- const record = attendee;
2264
- const email = typeof record.email === "string" && record.email.trim().length > 0
2265
- ? record.email.trim()
2266
- : null;
2267
- if (!email) {
2268
- return null;
2269
- }
2270
- return {
2271
- email,
2272
- displayName: typeof record.displayName === "string" &&
2273
- record.displayName.trim().length > 0
2274
- ? record.displayName.trim()
2275
- : undefined,
2276
- optional: typeof record.optional === "boolean" ? record.optional : undefined,
2277
- };
2278
- });
2279
- const normalized = mapped.filter((attendee) => attendee !== null);
2280
- return normalized.length > 0 ? normalized : undefined;
2281
- }
2282
- export const calendarAction = {
2283
- name: "CALENDAR_ACTION",
2284
- similes: [
2285
- "CALENDAR",
2286
- "CHECK_CALENDAR",
2287
- "SCHEDULE_EVENT",
2288
- "CREATE_CALENDAR_EVENT",
2289
- "SEARCH_CALENDAR",
2290
- "NEXT_MEETING",
2291
- "ITINERARY",
2292
- "TRAVEL_SCHEDULE",
2293
- "CHECK_SCHEDULE",
2294
- ],
2295
- description: "Interact with Google Calendar through LifeOps. " +
2296
- "USE this action for: viewing today's or this week's schedule; checking the next upcoming event; " +
2297
- "searching events by title, attendee, location, or date range; creating new calendar events; " +
2298
- "querying travel itineraries, flights, hotel stays, and trip windows. " +
2299
- "DO NOT use this action for email inbox work, drafting or sending emails — use GMAIL_ACTION instead. " +
2300
- "DO NOT use this action for personal habits, goals, routines, or reminders — use LIFE instead. " +
2301
- "This action provides the final grounded reply; do not pair it with a speculative REPLY action.",
2302
- suppressPostActionContinuation: true,
2303
- validate: async (runtime, message, state) => {
2304
- if (!(await hasLifeOpsAccess(runtime, message))) {
2305
- return false;
2306
- }
2307
- return hasCalendarContextSignal(message, state);
2308
- },
2309
- handler: async (runtime, message, state, options, callback) => {
2310
- if (!(await hasLifeOpsAccess(runtime, message))) {
2311
- const text = "Calendar actions are restricted to the owner, explicitly granted users, and the agent.";
2312
- await callback?.({ text });
2313
- return {
2314
- success: false,
2315
- text,
2316
- };
2317
- }
2318
- const rawParams = options?.parameters;
2319
- const params = rawParams ?? {};
2320
- const intent = resolveCalendarIntent(params.intent, message, state);
2321
- const details = normalizeCalendarDetails(params.details);
2322
- const planningTimeZone = resolveCalendarTimeZone(details);
2323
- const llmPlan = await extractCalendarPlanWithLlm(runtime, message, state, intent, planningTimeZone);
2324
- const heuristicQuery = inferCalendarSearchQuery(intent);
2325
- const inferredQuery = sanitizeCalendarQuery(params.query ?? detailString(details, "query"), intent);
2326
- const inferredQueries = dedupeCalendarQueries([
2327
- inferredQuery,
2328
- ...llmPlan.queries,
2329
- ...(params.queries ?? []),
2330
- ...(detailArray(details, "queries")?.map((value) => typeof value === "string" ? value : undefined) ?? []),
2331
- ]);
2332
- const explicitTitle = (typeof params.title === "string" && params.title.trim().length > 0
2333
- ? params.title.trim()
2334
- : undefined) ??
2335
- detailString(details, "title") ??
2336
- llmPlan.title;
2337
- const inferredTitle = explicitTitle ?? llmPlan.title ?? inferCreateEventTitle(intent);
2338
- const tripWindowIntent = llmPlan.tripLocation && llmPlan.tripLocation.trim().length > 0
2339
- ? { location: llmPlan.tripLocation.trim() }
2340
- : inferTripWindowIntent(intent);
2341
- const explicitSubaction = params.subaction;
2342
- const preferExplicitSubaction = shouldTrustExplicitCalendarSubaction(explicitSubaction, params, details);
2343
- const hasExplicitCalendarExecutionInput = Boolean(params.subaction ||
2344
- params.title ||
2345
- params.query ||
2346
- (params.queries?.length ?? 0) > 0 ||
2347
- detailString(details, "query") ||
2348
- (detailArray(details, "queries")?.length ?? 0) > 0 ||
2349
- detailString(details, "eventId") ||
2350
- detailString(details, "startAt") ||
2351
- detailString(details, "endAt") ||
2352
- detailString(details, "location") ||
2353
- detailString(details, "windowPreset") ||
2354
- detailNumber(details, "windowDays"));
2355
- const forcedSubaction = (() => {
2356
- const text = normalizeText(messageText(message));
2357
- if (/\b(?:rename|change|move|reschedule|push back)\b[^.?!]+\bto\b/.test(text) ||
2358
- /\b(rename|reschedule|update|edit|modify|change|move)\b.*\b(event|meeting|appointment|calendar|invite)\b/.test(text)) {
2359
- return "update_event";
2360
- }
2361
- if (/\b(delete|remove|cancel|drop|get rid of|trash|kill)\b.*\b(event|meeting|appointment|calendar|invite)\b/.test(text) ||
2362
- /\b(delete|remove|cancel)\b.+\b(today|tomorrow|tonight|this week|next week)\b/.test(text)) {
2363
- return "delete_event";
2364
- }
2365
- if (/\b(create|add|book|schedule|make|put)\b[^.?!]*\b(event|meeting|appointment|invite|calendar)\b/.test(text)) {
2366
- return "create_event";
2367
- }
2368
- if (/\b(show|list|tell|give|read)\b[^.?!]*\b(all|every|everything|entire|full|whole)\b[^.?!]*\b(event|events|calendar|schedule|meeting|meetings|appointment|appointments|entry|entries|agenda)\b/.test(text) ||
2369
- /\b(all|every|everything)\b\s+(?:my\s+)?(?:calendar|events?|meetings?|appointments?)\b/.test(text)) {
2370
- return "feed";
2371
- }
2372
- return null;
2373
- })();
2374
- let subaction;
2375
- if (tripWindowIntent) {
2376
- subaction = "trip_window";
2377
- }
2378
- else if (forcedSubaction) {
2379
- subaction = forcedSubaction;
2380
- }
2381
- else if (llmPlan.subaction && !preferExplicitSubaction) {
2382
- subaction = llmPlan.subaction;
2383
- }
2384
- else if (params.subaction) {
2385
- subaction = params.subaction;
2386
- }
2387
- else {
2388
- runtime.logger?.warn?.({ src: "action:calendar", intent }, "Calendar LLM plan returned no subaction; falling back to regex inference");
2389
- subaction = inferCalendarSubaction(normalizeText(intent), details, inferredQuery ?? heuristicQuery);
2390
- }
2391
- runtime.logger?.debug?.({
2392
- src: "action:calendar",
2393
- subaction,
2394
- forcedSubaction,
2395
- rawMessage: messageText(message).slice(0, 200),
2396
- resolvedIntent: intent.slice(0, 200),
2397
- params: {
2398
- subaction: params.subaction,
2399
- title: params.title,
2400
- intent: params.intent?.slice(0, 200),
2401
- },
2402
- detailKeys: details ? Object.keys(details) : [],
2403
- }, "calendar action dispatch");
2404
- const service = new LifeOpsService(runtime);
2405
- const respond = async (payload) => {
2406
- await callback?.({
2407
- text: payload.text,
2408
- source: "action",
2409
- action: "CALENDAR_ACTION",
2410
- });
2411
- return payload;
2412
- };
2413
- const renderReply = (scenario, fallback, context) => renderCalendarActionReply({
2414
- runtime,
2415
- message,
2416
- state,
2417
- intent,
2418
- scenario,
2419
- fallback,
2420
- context,
2421
- });
2422
- if (!hasExplicitCalendarExecutionInput &&
2423
- !forcedSubaction &&
2424
- !tripWindowIntent &&
2425
- looksLikeLifeReminderRequestForCalendarAction(messageText(message))) {
2426
- const fallback = "That sounds like a reminder or todo rather than a calendar event. Tell me the reminder and when it should happen.";
2427
- return respond({
2428
- success: true,
2429
- text: await renderReply("out_of_domain", fallback, {
2430
- requestedDomain: "lifeops",
2431
- }),
2432
- data: {
2433
- noop: true,
2434
- suggestedSubaction: null,
2435
- },
2436
- });
2437
- }
2438
- if (llmPlan.shouldAct === false &&
2439
- !hasExplicitCalendarExecutionInput &&
2440
- !forcedSubaction &&
2441
- !tripWindowIntent) {
2442
- const fallback = llmPlan.response ?? buildCalendarReplyOnlyFallback(llmPlan.subaction);
2443
- return respond({
2444
- success: true,
2445
- text: await renderReply("reply_only", fallback, {
2446
- llmPlan,
2447
- suggestedSubaction: llmPlan.subaction,
2448
- }),
2449
- data: {
2450
- noop: true,
2451
- ...(llmPlan.subaction
2452
- ? { suggestedSubaction: llmPlan.subaction }
2453
- : {}),
2454
- },
2455
- });
2456
- }
2457
- try {
2458
- const google = await getGoogleCapabilityStatus(service);
2459
- if (subaction === "next_event") {
2460
- if (!google.hasCalendarRead) {
2461
- return respond({
2462
- success: false,
2463
- text: calendarReadUnavailableMessage(google),
2464
- });
2465
- }
2466
- const context = await service.getNextCalendarEventContext(INTERNAL_URL, {
2467
- calendarId: detailString(details, "calendarId"),
2468
- timeZone: resolveCalendarTimeZone(details),
2469
- });
2470
- const fallback = formatNextEventContext(context);
2471
- return respond({
2472
- success: true,
2473
- text: await renderReply("next_event", fallback, {
2474
- event: context,
2475
- }),
2476
- data: toActionData(context),
2477
- });
2478
- }
2479
- if (subaction === "create_event") {
2480
- if (!google.hasCalendarWrite) {
2481
- return respond({
2482
- success: false,
2483
- text: calendarWriteUnavailableMessage(google),
2484
- });
2485
- }
2486
- let calendarContext = null;
2487
- try {
2488
- calendarContext = await loadCreateEventCalendarContext(service, details, google.hasCalendarRead);
2489
- }
2490
- catch (error) {
2491
- runtime.logger?.warn?.({
2492
- src: "action:calendar",
2493
- error: error instanceof Error ? error.message : String(error),
2494
- }, "Calendar create-event context fetch failed");
2495
- }
2496
- const extractedDetails = await inferCreateEventDetails(runtime, message, state, intent, calendarContext, planningTimeZone);
2497
- const { title, resolvedStartAt, resolvedWindowPreset, request } = buildCreateEventRequest({
2498
- details,
2499
- extractedDetails,
2500
- explicitTitle,
2501
- inferredTitle,
2502
- intent,
2503
- });
2504
- if (!title) {
2505
- return respond({
2506
- success: false,
2507
- text: await renderReply("clarify_create_event_title", "What event do you want to add?", {
2508
- missing: ["title"],
2509
- }),
2510
- });
2511
- }
2512
- // The LifeOps service throws a raw 400 when neither startAt nor a
2513
- // window preset is supplied. Catch that case here so the user gets a
2514
- // useful prompt instead of "startAt is required when windowPreset is
2515
- // not provided" — and so the failure path doesn't re-trigger the
2516
- // action via post-action continuation.
2517
- if (!resolvedStartAt && !resolvedWindowPreset) {
2518
- const suggestedStartAt = title
2519
- ? suggestCreateEventStartAt({
2520
- currentMessage: messageText(message).trim(),
2521
- intent,
2522
- title,
2523
- calendarContext,
2524
- })
2525
- : null;
2526
- const fallback = suggestedStartAt
2527
- ? `i can tentatively put "${title}" on ${formatCalendarEventDateTime({
2528
- startAt: suggestedStartAt.startAt,
2529
- timezone: suggestedStartAt.timeZone,
2530
- }, { includeTimeZoneName: true })}. if you want a different time, tell me what works better.`
2531
- : `i need a time for "${title}" in ${calendarContext?.calendarTimeZone ??
2532
- resolveCalendarTimeZone(details)}. try "tomorrow morning", "tomorrow afternoon", "tomorrow evening", or give me a specific date and time.`;
2533
- return respond({
2534
- success: false,
2535
- text: await renderReply("clarify_create_event_time", fallback, {
2536
- title,
2537
- suggestedStartAt,
2538
- calendarTimeZone: calendarContext?.calendarTimeZone ??
2539
- resolveCalendarTimeZone(details),
2540
- }),
2541
- });
2542
- }
2543
- let requestToCreate = request;
2544
- let event;
2545
- try {
2546
- event = await service.createCalendarEvent(INTERNAL_URL, requestToCreate);
2547
- }
2548
- catch (error) {
2549
- if (error instanceof LifeOpsServiceError &&
2550
- shouldRetryCreateEventExtraction(error)) {
2551
- const repairedDetails = await repairCreateEventDetails(runtime, message, state, intent, calendarContext, requestToCreate, extractedDetails, error, planningTimeZone);
2552
- const repaired = buildCreateEventRequest({
2553
- details,
2554
- extractedDetails: repairedDetails,
2555
- explicitTitle,
2556
- inferredTitle,
2557
- intent,
2558
- fallbackRequest: requestToCreate,
2559
- preferExtractedDetails: true,
2560
- });
2561
- if (repaired.title &&
2562
- (repaired.resolvedStartAt || repaired.resolvedWindowPreset) &&
2563
- createEventRequestFingerprint(repaired.request) !==
2564
- createEventRequestFingerprint(requestToCreate)) {
2565
- runtime.logger?.info?.({
2566
- src: "action:calendar",
2567
- error: error.message,
2568
- priorRequest: requestToCreate,
2569
- repairedRequest: repaired.request,
2570
- }, "Retrying calendar create-event after repair extraction");
2571
- requestToCreate = repaired.request;
2572
- event = await service.createCalendarEvent(INTERNAL_URL, requestToCreate);
2573
- }
2574
- else {
2575
- throw error;
2576
- }
2577
- }
2578
- else {
2579
- throw error;
2580
- }
2581
- }
2582
- const fallback = `Created calendar event "${event.title}" for ${formatCalendarEventDateTime(event, {
2583
- includeTimeZoneName: true,
2584
- })}.`;
2585
- return respond({
2586
- success: true,
2587
- text: await renderReply("created_event", fallback, {
2588
- event,
2589
- request: requestToCreate,
2590
- }),
2591
- data: toActionData(event),
2592
- });
2593
- }
2594
- if (subaction === "update_event") {
2595
- if (!google.hasCalendarWrite) {
2596
- return respond({
2597
- success: false,
2598
- text: calendarWriteUnavailableMessage(google),
2599
- });
2600
- }
2601
- // Parse "rename X to Y" / "change X to Y" patterns directly from
2602
- // the user message. The chat LLM tends to put only the NEW title in
2603
- // params.title, but we need the OLD title to find the event we're
2604
- // patching. Pull both halves from the literal phrase if it's there.
2605
- // We try the raw message first because resolveCalendarIntent may
2606
- // have replaced the user's text with the LLM's rewritten version,
2607
- // which often drops "rename" entirely.
2608
- const rawMessageText = messageText(message);
2609
- const renamePattern = /\b(?:rename|change|update|edit)\b\s+["“]?([^"”]+?)["”]?\s+(?:to|into|as)\s+["“]?([^"”]+?)["”]?(?:[.!?]|$)/i;
2610
- const renameMatch = rawMessageText.match(renamePattern) ?? intent.match(renamePattern);
2611
- const oldTitleFromIntent = renameMatch?.[1]?.trim();
2612
- const newTitleFromIntent = renameMatch?.[2]?.trim();
2613
- const explicitEventId = detailString(details, "eventId");
2614
- let resolvedEventId = explicitEventId;
2615
- let resolvedCalendarId = detailString(details, "calendarId");
2616
- let targetEvent = null;
2617
- // Same lookup-by-title fallback as delete_event so the user can say
2618
- // "rename my dentist appointment to dentist follow-up" without first
2619
- // copying an opaque google id.
2620
- if (!resolvedEventId) {
2621
- // Use a wide lookup window — events can be far in the future
2622
- // (e.g. a birthday in 2027). The default narrow window would
2623
- // miss anything beyond the current day.
2624
- // forceSync: true is critical here — without it the feed query
2625
- // returns the local cache (life_calendar_events), which may not
2626
- // contain far-future events that have never been synced before
2627
- // (or any events at all if the cache was wiped). Forcing the
2628
- // sync makes the bot pull a fresh window from Google so the
2629
- // title-based lookup actually has events to filter against.
2630
- const wideLookup = buildWideLookupRange(resolveCalendarTimeZone(details));
2631
- const feed = await service.getCalendarFeed(INTERNAL_URL, {
2632
- mode: detailString(details, "mode"),
2633
- side: detailString(details, "side"),
2634
- calendarId: detailString(details, "calendarId"),
2635
- timeZone: resolveCalendarTimeZone(details),
2636
- forceSync: true,
2637
- ...wideLookup,
2638
- });
2639
- // Prefer the OLD title parsed from "rename X to Y" — explicit
2640
- // title from the chat LLM almost always carries the NEW name.
2641
- const titleHint = oldTitleFromIntent ?? explicitTitle ?? inferredTitle;
2642
- const candidates = titleHint
2643
- ? feed.events.filter((e) => normalizeText(e.title).includes(normalizeText(titleHint)))
2644
- : feed.events;
2645
- if (candidates.length === 0) {
2646
- const fallback = titleHint
2647
- ? `i couldn't find an event matching "${titleHint}" in that window.`
2648
- : "i couldn't find any events to update in that window. give me a title or a date.";
2649
- return respond({
2650
- success: false,
2651
- text: await renderReply("update_event_not_found", fallback, {
2652
- titleHint,
2653
- }),
2654
- });
2655
- }
2656
- if (candidates.length > 1) {
2657
- const fallback = buildCalendarEventDisambiguationFallback({
2658
- action: "update",
2659
- candidates,
2660
- titleHint,
2661
- });
2662
- return respond({
2663
- success: false,
2664
- text: await renderReply("clarify_update_event_target", fallback, {
2665
- candidateCount: candidates.length,
2666
- titleHint,
2667
- candidates,
2668
- }),
2669
- });
2670
- }
2671
- targetEvent = candidates[0];
2672
- resolvedEventId = targetEvent.externalId;
2673
- resolvedCalendarId = targetEvent.calendarId;
2674
- }
2675
- const newTitle = newTitleFromIntent ??
2676
- detailString(details, "newTitle") ??
2677
- explicitTitle;
2678
- // Reuse the same LLM extractor that create_event uses to pull
2679
- // startAt / endAt / location / description out of the user's intent
2680
- // text. The chat LLM rarely populates `details.startAt` directly for
2681
- // an update — it just rewrites the intent and lets the action figure
2682
- // out the time. Without this we'd PATCH with no fields and the
2683
- // event wouldn't actually move.
2684
- //
2685
- // CRITICAL: only run the extractor when the user actually mentioned
2686
- // a time. For pure rename intents like "rename X to Y" the LLM will
2687
- // happily hallucinate a startAt from the year in the new title
2688
- // ("rename my party 2027 to ..." → startAt 2027-01-01), and a
2689
- // PATCH with start.dateTime but no matching end.dateTime triggers
2690
- // Google's "Bad Request" rejection. Detect time keywords in the
2691
- // raw message before invoking the extractor.
2692
- const explicitStartAtForUpdate = detailString(details, "startAt");
2693
- const explicitEndAtForUpdate = detailString(details, "endAt");
2694
- const rawForUpdate = normalizeText(messageText(message));
2695
- const hasTimeAnchor = /\b(at|on|by|from|until)\s+\d/.test(rawForUpdate) ||
2696
- /\b(today|tomorrow|tonight|monday|tuesday|wednesday|thursday|friday|saturday|sunday|january|february|march|april|may|june|july|august|september|october|november|december)\b/.test(rawForUpdate) ||
2697
- /\b\d{1,2}(?::\d{2})?\s*(?:am|pm)\b/.test(rawForUpdate) ||
2698
- /\b\d{1,2}\/\d{1,2}(?:\/\d{2,4})?\b/.test(rawForUpdate);
2699
- const hasRelativeShiftCue = /\b(?:later|earlier|push back|push it back|bring forward|move forward|move back|delay|postpone|advance)\b/.test(rawForUpdate);
2700
- const needsTimeExtraction = (hasTimeAnchor || hasRelativeShiftCue) &&
2701
- !(explicitStartAtForUpdate ||
2702
- explicitEndAtForUpdate ||
2703
- detailNumber(details, "durationMinutes"));
2704
- const shouldInferUpdateDetails = Boolean(targetEvent) &&
2705
- (needsTimeExtraction ||
2706
- /\b(?:rename|change|move|reschedule|update|edit|location|description|notes)\b/.test(rawForUpdate));
2707
- const extractedForUpdate = shouldInferUpdateDetails
2708
- ? await inferUpdateEventDetails(runtime, message, state, intent, targetEvent, targetEvent?.timezone ?? planningTimeZone)
2709
- : needsTimeExtraction
2710
- ? await inferCreateEventDetails(runtime, message, state, intent, null, targetEvent?.timezone ?? planningTimeZone)
2711
- : {};
2712
- const extractedStartAt = typeof extractedForUpdate.startAt === "string"
2713
- ? extractedForUpdate.startAt.trim()
2714
- : undefined;
2715
- const extractedEndAt = typeof extractedForUpdate.endAt === "string"
2716
- ? extractedForUpdate.endAt.trim()
2717
- : undefined;
2718
- const extractedLocation = typeof extractedForUpdate.location === "string"
2719
- ? extractedForUpdate.location.trim()
2720
- : undefined;
2721
- const extractedDescription = typeof extractedForUpdate.description === "string"
2722
- ? extractedForUpdate.description.trim()
2723
- : undefined;
2724
- const extractedTimeZoneForUpdate = typeof extractedForUpdate.timeZone === "string"
2725
- ? extractedForUpdate.timeZone.trim()
2726
- : undefined;
2727
- const event = await service.updateCalendarEvent(INTERNAL_URL, {
2728
- mode: detailString(details, "mode"),
2729
- side: detailString(details, "side"),
2730
- calendarId: resolvedCalendarId,
2731
- eventId: resolvedEventId ?? "",
2732
- title: newTitle,
2733
- description: detailString(details, "description") ?? extractedDescription,
2734
- location: detailString(details, "location") ?? extractedLocation,
2735
- startAt: explicitStartAtForUpdate ?? extractedStartAt,
2736
- endAt: explicitEndAtForUpdate ?? extractedEndAt,
2737
- timeZone: detailString(details, "timeZone") ??
2738
- extractedTimeZoneForUpdate ??
2739
- targetEvent?.timezone ??
2740
- undefined,
2741
- });
2742
- const fallback = `updated "${event.title}" — ${formatCalendarEventDateTime(event, {
2743
- includeTimeZoneName: true,
2744
- })}.`;
2745
- return respond({
2746
- success: true,
2747
- text: await renderReply("updated_event", fallback, {
2748
- event,
2749
- targetEvent,
2750
- }),
2751
- data: toActionData(event),
2752
- });
2753
- }
2754
- if (subaction === "delete_event") {
2755
- if (!google.hasCalendarWrite) {
2756
- return respond({
2757
- success: false,
2758
- text: calendarWriteUnavailableMessage(google),
2759
- });
2760
- }
2761
- const explicitEventId = detailString(details, "eventId");
2762
- const calendarIdForDelete = detailString(details, "calendarId");
2763
- // The LLM may not know the event id directly. Fall back to a feed
2764
- // lookup so phrases like "delete the duplicate test event tomorrow"
2765
- // can resolve to a concrete event without forcing the user to copy
2766
- // an opaque google id from the bot's previous reply.
2767
- const resolvedEventId = explicitEventId;
2768
- let resolvedEventTitle;
2769
- const resolvedCalendarId = calendarIdForDelete;
2770
- if (!resolvedEventId) {
2771
- // For delete-by-title we honor an explicit time window if the
2772
- // user gave one ("delete the test event tomorrow"); otherwise we
2773
- // search wide so far-future events are still findable.
2774
- // forceSync: true ensures the lookup actually queries Google
2775
- // instead of returning a stale (or empty) local cache.
2776
- const hasExplicitWindow = /\b(today|tomorrow|tonight|this week|next week|the week after|this weekend|next weekend|weekend|this month|next month|monday|tuesday|wednesday|thursday|friday|saturday|sunday)\b/i.test(intent);
2777
- const feedRequest = hasExplicitWindow
2778
- ? resolveCalendarWindow(intent, details, false, llmPlan).request
2779
- : {
2780
- calendarId: detailString(details, "calendarId"),
2781
- timeZone: resolveCalendarTimeZone(details),
2782
- ...buildWideLookupRange(resolveCalendarTimeZone(details)),
2783
- };
2784
- const feed = await service.getCalendarFeed(INTERNAL_URL, {
2785
- mode: detailString(details, "mode"),
2786
- side: detailString(details, "side"),
2787
- forceSync: true,
2788
- ...feedRequest,
2789
- });
2790
- const titleHint = explicitTitle ?? inferredTitle;
2791
- const candidates = titleHint
2792
- ? feed.events.filter((e) => normalizeText(e.title).includes(normalizeText(titleHint)))
2793
- : feed.events;
2794
- if (candidates.length === 0) {
2795
- const fallback = titleHint
2796
- ? `i couldn't find an event matching "${titleHint}" in that window.`
2797
- : "i couldn't find any events to delete in that window. give me a title or a date.";
2798
- return respond({
2799
- success: false,
2800
- text: await renderReply("delete_event_not_found", fallback, {
2801
- titleHint,
2802
- }),
2803
- });
2804
- }
2805
- // Detect "delete all / delete both / delete N" phrasing — when the
2806
- // user explicitly opts in to multi-delete, sweep every match.
2807
- const deleteAllMatch = shouldDeleteAllMatchingCalendarEvents({
2808
- intent,
2809
- titleHint,
2810
- candidateCount: candidates.length,
2811
- });
2812
- if (candidates.length > 1 && !deleteAllMatch) {
2813
- const fallback = buildCalendarEventDisambiguationFallback({
2814
- action: "delete",
2815
- candidates,
2816
- titleHint,
2817
- });
2818
- return respond({
2819
- success: false,
2820
- text: await renderReply("clarify_delete_event_target", fallback, {
2821
- candidateCount: candidates.length,
2822
- titleHint,
2823
- candidates,
2824
- }),
2825
- });
2826
- }
2827
- const targets = deleteAllMatch ? candidates : [candidates[0]];
2828
- const deleteResults = [];
2829
- for (const target of targets) {
2830
- try {
2831
- await service.deleteCalendarEvent(INTERNAL_URL, {
2832
- mode: detailString(details, "mode"),
2833
- side: detailString(details, "side"),
2834
- calendarId: target.calendarId,
2835
- eventId: target.externalId,
2836
- });
2837
- deleteResults.push({ title: target.title, ok: true });
2838
- }
2839
- catch (err) {
2840
- deleteResults.push({
2841
- title: target.title,
2842
- ok: false,
2843
- error: err instanceof Error ? err.message : String(err),
2844
- });
2845
- }
2846
- }
2847
- const okCount = deleteResults.filter((r) => r.ok).length;
2848
- const failCount = deleteResults.length - okCount;
2849
- const publicDeleteResults = deleteResults.map((result) => ({
2850
- title: result.title,
2851
- ok: result.ok,
2852
- }));
2853
- const summary = failCount === 0
2854
- ? targets.length === 1
2855
- ? `deleted "${deleteResults[0].title}".`
2856
- : `deleted ${okCount} matching events.`
2857
- : okCount === 0
2858
- ? `I couldn't delete those ${deleteResults.length} matching events. Try again in a bit or tell me which one to remove.`
2859
- : `Deleted ${okCount} matching event${okCount === 1 ? "" : "s"}, but ${failCount} failed. Tell me which one to remove if you want me to retry individually.`;
2860
- return respond({
2861
- success: failCount === 0,
2862
- text: await renderReply("deleted_event", summary, {
2863
- deleteResults: publicDeleteResults,
2864
- okCount,
2865
- failCount,
2866
- }),
2867
- });
2868
- }
2869
- // Path: explicit eventId was given, no feed lookup needed
2870
- if (!resolvedEventId) {
2871
- return respond({
2872
- success: false,
2873
- text: await renderReply("clarify_delete_event_target", "i need an event id or a title + date to delete an event.", {
2874
- missing: ["event target"],
2875
- }),
2876
- });
2877
- }
2878
- await service.deleteCalendarEvent(INTERNAL_URL, {
2879
- mode: detailString(details, "mode"),
2880
- side: detailString(details, "side"),
2881
- calendarId: resolvedCalendarId,
2882
- eventId: resolvedEventId,
2883
- });
2884
- const fallback = resolvedEventTitle
2885
- ? `deleted "${resolvedEventTitle}".`
2886
- : "deleted that calendar event.";
2887
- return respond({
2888
- success: true,
2889
- text: await renderReply("deleted_event", fallback, {
2890
- eventTitle: resolvedEventTitle,
2891
- }),
2892
- });
2893
- }
2894
- if (!google.hasCalendarRead) {
2895
- return respond({
2896
- success: false,
2897
- text: calendarReadUnavailableMessage(google),
2898
- });
2899
- }
2900
- if (subaction === "trip_window" && tripWindowIntent) {
2901
- const feed = await service.getCalendarFeed(INTERNAL_URL, {
2902
- mode: detailString(details, "mode"),
2903
- side: detailString(details, "side"),
2904
- ...resolveTripWindowRequest(details, llmPlan),
2905
- });
2906
- const itineraryEvents = resolveTripWindowEvents(feed.events, tripWindowIntent.location);
2907
- if (!itineraryEvents || itineraryEvents.length === 0) {
2908
- const fallback = `I couldn't find a clear trip window for ${tripWindowIntent.location} in your upcoming calendar.`;
2909
- return respond({
2910
- success: true,
2911
- text: await renderReply("trip_window_not_found", fallback, {
2912
- location: tripWindowIntent.location,
2913
- }),
2914
- data: toActionData({
2915
- ...feed,
2916
- location: tripWindowIntent.location,
2917
- events: [],
2918
- }),
2919
- });
2920
- }
2921
- const fallback = formatTripWindowResults(itineraryEvents, tripWindowIntent.location);
2922
- return respond({
2923
- success: true,
2924
- text: await renderReply("trip_window_results", fallback, {
2925
- location: tripWindowIntent.location,
2926
- events: itineraryEvents,
2927
- }),
2928
- data: toActionData({
2929
- ...feed,
2930
- location: tripWindowIntent.location,
2931
- events: itineraryEvents,
2932
- }),
2933
- });
2934
- }
2935
- // When the user explicitly asks for "all events" / "everything" / a
2936
- // multi-year span, broaden the lookup window past the default
2937
- // "today only" feed window. resolveCalendarWindow's default is too
2938
- // narrow for these queries — without this branch, "show all my
2939
- // events" returns "no events today" even when the calendar has
2940
- // dozens of upcoming items. We apply this regardless of whether the
2941
- // chat LLM picked feed or search_events because both subactions go
2942
- // through this code path.
2943
- const rawMessageNorm = normalizeText(messageText(message));
2944
- const wantsWideWindow = /\b(all|every|everything|entire|full|whole)\b[^.?!]*\b(event|events|calendar|schedule|meeting|meetings|appointment|appointments|entry|entries|agenda)\b/.test(rawMessageNorm) ||
2945
- /\b(next|past|last)\s+\d+\s*(year|years|month|months|weeks?)\b/.test(rawMessageNorm) ||
2946
- /\b(today\s+(?:until|through|to)\s+next\s+(?:year|month))\b/.test(rawMessageNorm) ||
2947
- /\bevery\s+calendar\s+entry\b/.test(rawMessageNorm);
2948
- const baseResolved = resolveCalendarWindow(intent, details, subaction === "search_events" || wantsWideWindow, llmPlan);
2949
- const request = wantsWideWindow
2950
- ? {
2951
- ...baseResolved.request,
2952
- ...buildWideLookupRange(resolveCalendarTimeZone(details)),
2953
- }
2954
- : baseResolved.request;
2955
- const label = wantsWideWindow
2956
- ? "across the full window"
2957
- : baseResolved.label;
2958
- const feed = await service.getCalendarFeed(INTERNAL_URL, {
2959
- mode: detailString(details, "mode"),
2960
- side: detailString(details, "side"),
2961
- forceSync: wantsWideWindow,
2962
- ...request,
2963
- });
2964
- if (subaction === "search_events") {
2965
- const searchQueries = await resolveCalendarSearchQueries(runtime, message, state, [...inferredQueries], intent, llmPlan, planningTimeZone);
2966
- const query = searchQueries[0];
2967
- if (!query || searchQueries.length === 0) {
2968
- return respond({
2969
- success: false,
2970
- text: await renderReply("clarify_calendar_search", "I couldn't infer what to look for in your calendar yet. Try naming a person, place, trip, or date.", {
2971
- missing: ["search target"],
2972
- }),
2973
- });
2974
- }
2975
- const rankedEvents = feed.events
2976
- .map((event) => {
2977
- const matchedQueries = [];
2978
- let score = 0;
2979
- for (const candidateQuery of searchQueries) {
2980
- const queryScore = scoreCalendarEvent(event, candidateQuery);
2981
- if (queryScore > 0) {
2982
- matchedQueries.push(candidateQuery);
2983
- score += queryScore;
2984
- }
2985
- }
2986
- if (matchedQueries.length > 1) {
2987
- score += (matchedQueries.length - 1) * 12;
2988
- }
2989
- return { event, score, matchedQueries };
2990
- })
2991
- .filter((candidate) => candidate.score > 0 && candidate.matchedQueries.length > 0)
2992
- .sort((left, right) => {
2993
- if (right.score !== left.score) {
2994
- return right.score - left.score;
2995
- }
2996
- return (Date.parse(left.event.startAt) - Date.parse(right.event.startAt));
2997
- });
2998
- const strongestScore = rankedEvents[0]?.score ?? 0;
2999
- const strongestThreshold = strongestScore >= 30 ? Math.max(16, strongestScore - 12) : 1;
3000
- let filteredEvents = rankedEvents
3001
- .filter((candidate) => candidate.score >= strongestThreshold)
3002
- .map((candidate) => candidate.event);
3003
- if (shouldGroundCalendarSearchWithLlm(query, rankedEvents)) {
3004
- const groundedIds = await groundCalendarSearchMatchesWithLlm(runtime, state, intent, searchQueries, rankedEvents.slice(0, 6));
3005
- if (groundedIds) {
3006
- const groundedIdSet = new Set(groundedIds);
3007
- filteredEvents = rankedEvents
3008
- .filter((candidate) => groundedIdSet.has(candidate.event.id))
3009
- .map((candidate) => candidate.event);
3010
- }
3011
- }
3012
- const fallback = formatCalendarSearchResults(filteredEvents, query, label, intent);
3013
- return respond({
3014
- success: true,
3015
- text: await renderReply("search_results", fallback, {
3016
- query,
3017
- queries: searchQueries,
3018
- events: filteredEvents,
3019
- label,
3020
- }),
3021
- data: toActionData({
3022
- ...feed,
3023
- query,
3024
- queries: searchQueries,
3025
- events: filteredEvents,
3026
- }),
3027
- });
3028
- }
3029
- const fallback = formatCalendarFeed(feed, label);
3030
- return respond({
3031
- success: true,
3032
- text: await renderReply("feed_results", fallback, {
3033
- label,
3034
- events: feed.events,
3035
- }),
3036
- data: toActionData(feed),
3037
- });
3038
- }
3039
- catch (error) {
3040
- if (error instanceof LifeOpsServiceError) {
3041
- const fallback = buildCalendarServiceErrorFallback(error, intent);
3042
- return respond({
3043
- success: false,
3044
- text: await renderReply("service_error", fallback, {
3045
- status: error.status,
3046
- subaction,
3047
- }),
3048
- });
3049
- }
3050
- throw error;
3051
- }
3052
- },
3053
- parameters: [
3054
- {
3055
- name: "subaction",
3056
- description: "Calendar operation. Use search_events for flights, itinerary, travel, appointments, or keyword lookup; feed for agenda/schedule reads; next_event for the next upcoming event; create_event only when creating a new event.",
3057
- required: false,
3058
- schema: {
3059
- type: "string",
3060
- enum: [
3061
- "feed",
3062
- "next_event",
3063
- "search_events",
3064
- "create_event",
3065
- "update_event",
3066
- "delete_event",
3067
- "trip_window",
3068
- ],
3069
- },
3070
- },
3071
- {
3072
- name: "intent",
3073
- description: 'Natural language calendar request, especially schedule or itinerary questions. Examples: "what is on my calendar today", "do i have any flights this week", "when do i fly back from denver", "create a meeting tomorrow at 3pm".',
3074
- required: false,
3075
- schema: { type: "string" },
3076
- },
3077
- {
3078
- name: "title",
3079
- description: "Event title when creating an event. Optional for read/search actions.",
3080
- required: false,
3081
- schema: { type: "string" },
3082
- },
3083
- {
3084
- name: "query",
3085
- description: "Short search phrase for search_events, such as flight, dentist, Denver, or return flight.",
3086
- required: false,
3087
- schema: { type: "string" },
3088
- },
3089
- {
3090
- name: "queries",
3091
- description: "Optional array of search phrases for search_events. The action will combine and dedupe them.",
3092
- required: false,
3093
- schema: {
3094
- type: "array",
3095
- items: { type: "string" },
3096
- },
3097
- },
3098
- {
3099
- name: "details",
3100
- description: "Optional structured calendar fields such as time bounds, timezone, calendar id, create-event timing, location, and attendees.",
3101
- required: false,
3102
- schema: { type: "object" },
3103
- },
3104
- ],
3105
- examples: [
3106
- [
3107
- {
3108
- name: "{{name1}}",
3109
- content: { text: "What's on my calendar today?" },
3110
- },
3111
- {
3112
- name: "{{agentName}}",
3113
- content: {
3114
- text: "Events today:\n- **Team sync** (10:00 AM – 10:30 AM)",
3115
- },
3116
- },
3117
- ],
3118
- [
3119
- {
3120
- name: "{{name1}}",
3121
- content: { text: "What is my next meeting?" },
3122
- },
3123
- {
3124
- name: "{{agentName}}",
3125
- content: {
3126
- text: "**Next event: Product review** (2:00 PM – 3:00 PM) — in 45 min",
3127
- },
3128
- },
3129
- ],
3130
- [
3131
- {
3132
- name: "{{name1}}",
3133
- content: { text: "Create a dentist appointment for tomorrow at 3pm." },
3134
- },
3135
- {
3136
- name: "{{agentName}}",
3137
- content: {
3138
- text: 'Created calendar event "Dentist appointment" for tomorrow at 3:00 PM.',
3139
- },
3140
- },
3141
- ],
3142
- ],
3143
- };
1
+ export * from "@elizaos/app-lifeops/actions/calendar";