@xopcai/xopc 0.0.159 → 0.0.161

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 (215) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/xopc.extension.json +1 -1
  3. package/dist/gateway/static/root/assets/{action-boundary-settings-panel-CaRK2vIy.js → action-boundary-settings-panel-BIVBUiNT.js} +1 -1
  4. package/dist/gateway/static/root/assets/activity-store-o7GRljfD.js +1 -0
  5. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-BeVf03hx.js → agent-avatar-dicebear-9R3szLMc.js} +1 -1
  6. package/dist/gateway/static/root/assets/agents-_qbUaYbs.js +2 -0
  7. package/dist/gateway/static/root/assets/{api-BjcBIV2O.js → api-jSTqFMq9.js} +1 -1
  8. package/dist/gateway/static/root/assets/{app-management-settings-panel-BG2ulMYw.js → app-management-settings-panel-D81cyE76.js} +1 -1
  9. package/dist/gateway/static/root/assets/{appearance-settings-0Kjenr2T.js → appearance-settings-Ct6bZQCV.js} +1 -1
  10. package/dist/gateway/static/root/assets/apps-page-0sPqD0FK.js +1 -0
  11. package/dist/gateway/static/root/assets/{archive-plugin-ChiNnH1e.js → archive-plugin-CfD7AZKn.js} +1 -1
  12. package/dist/gateway/static/root/assets/{attachment-load-pbBlQR-g.js → attachment-load-CP_Gaxt3.js} +1 -1
  13. package/dist/gateway/static/root/assets/{automation-suggestion-card-CTZDOyDy.js → automation-suggestion-card-BGKM_NDZ.js} +1 -1
  14. package/dist/gateway/static/root/assets/automations-page-BxWBuBsE.js +6 -0
  15. package/dist/gateway/static/root/assets/{binary-plugins-BeZGzpGF.js → binary-plugins-IPr2EVnS.js} +1 -1
  16. package/dist/gateway/static/root/assets/{block-editor-BnDmhN5Z.js → block-editor-B6aHEGA6.js} +3 -3
  17. package/dist/gateway/static/root/assets/browser-settings-page-C499b9AH.js +8 -0
  18. package/dist/gateway/static/root/assets/{capability-presets-api-Ii36j_7v.js → capability-presets-api-CCX9-LjU.js} +1 -1
  19. package/dist/gateway/static/root/assets/{capability-presets-settings-panel-Cl3dw4yh.js → capability-presets-settings-panel-Dva9SiPK.js} +2 -2
  20. package/dist/gateway/static/root/assets/{channel-recipient-api-CFqrSi5K.js → channel-recipient-api-MmLMLDWf.js} +1 -1
  21. package/dist/gateway/static/root/assets/channels-settings-DNAU7u9o.js +1 -0
  22. package/dist/gateway/static/root/assets/channels-status-swr-CIOH7tgn.js +1 -0
  23. package/dist/gateway/static/root/assets/check-DUBLcKHA.js +1 -0
  24. package/dist/gateway/static/root/assets/circle-alert-BEjgurr2.js +1 -0
  25. package/dist/gateway/static/root/assets/cn-JdwAhgNB.js +1 -0
  26. package/dist/gateway/static/root/assets/{connector-logo-CiLxuZ8S.js → connector-logo-BbjzIs87.js} +1 -1
  27. package/dist/gateway/static/root/assets/connectors-page-TwiBp8vY.js +2 -0
  28. package/dist/gateway/static/root/assets/createLucideIcon-DPHK1VkS.js +1 -0
  29. package/dist/gateway/static/root/assets/desktop-pet-CYH5ZSXy.js +1 -0
  30. package/dist/gateway/static/root/assets/desktop-pet-settings-DQLqGLc0.js +1 -0
  31. package/dist/gateway/static/root/assets/desktop-pet-sprite-DTqXSpqf.js +1 -0
  32. package/dist/gateway/static/root/assets/{directory-picker-path-field-CC2VL5Mc.js → directory-picker-path-field-ClsiXhmz.js} +1 -1
  33. package/dist/gateway/static/root/assets/{dreaming-settings-KY7ZfVsy.js → dreaming-settings-CjMPPdzg.js} +3 -3
  34. package/dist/gateway/static/root/assets/{extension-debug-page-C8KBeucs.js → extension-debug-page-57eizjhB.js} +1 -1
  35. package/dist/gateway/static/root/assets/{extension-page-CV-5Kcyk.js → extension-page-CsbSb3u1.js} +1 -1
  36. package/dist/gateway/static/root/assets/{extension-settings-page-DvUtDrAu.js → extension-settings-page-DKA9-lyP.js} +1 -1
  37. package/dist/gateway/static/root/assets/{gateway-config-api-DXfVeJLc.js → gateway-config-api-DIPJuzBZ.js} +1 -1
  38. package/dist/gateway/static/root/assets/gateway-settings-CDLqAQY7.js +1 -0
  39. package/dist/gateway/static/root/assets/{gateway-startup-retry-cxQ94qMm.js → gateway-startup-retry-DrfSJtvT.js} +1 -1
  40. package/dist/gateway/static/root/assets/goal-create-dialog-Q3B0E-0s.js +1 -0
  41. package/dist/gateway/static/root/assets/goal-detail-page-C5VAK04G.js +4 -0
  42. package/dist/gateway/static/root/assets/{goals-config-api-P8VhLqYF.js → goals-config-api-lGC10UkU.js} +1 -1
  43. package/dist/gateway/static/root/assets/goals-page-BJb-5IYw.js +2 -0
  44. package/dist/gateway/static/root/assets/{goals-settings-DHXyaH_O.js → goals-settings-BWq0pdrA.js} +1 -1
  45. package/dist/gateway/static/root/assets/{heartbeat-settings-CNckP_Dv.js → heartbeat-settings-D4o8BJVv.js} +1 -1
  46. package/dist/gateway/static/root/assets/index-CkUHH170.css +1 -0
  47. package/dist/gateway/static/root/assets/index-vq_cJZJC.js +199 -0
  48. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-jXmv1RVX.js → keyboard-shortcuts-settings-6T2QYehS.js} +1 -1
  49. package/dist/gateway/static/root/assets/local-app-workbench-page-euH17Kvb.js +2 -0
  50. package/dist/gateway/static/root/assets/{local-apps-page-Cs5LKjtc.js → local-apps-page-DCjg2K9f.js} +1 -1
  51. package/dist/gateway/static/root/assets/{locale-store-C_M71MK9.js → locale-store-Dar-xqQr.js} +1 -1
  52. package/dist/gateway/static/root/assets/logs-page-CyYCBqym.js +7 -0
  53. package/dist/gateway/static/root/assets/{markdown-split-BFqeSNte.js → markdown-split-rt1XO43g.js} +1 -1
  54. package/dist/gateway/static/root/assets/{media-plugins-Cc8379gq.js → media-plugins-Bwo8CJuh.js} +1 -1
  55. package/dist/gateway/static/root/assets/messages-CxySDn9d.js +5 -0
  56. package/dist/gateway/static/root/assets/models-hub-panel-DObxxjc9.js +2 -0
  57. package/dist/gateway/static/root/assets/{notes-page-DlGU4ipo.js → notes-page-Dp-pkX7d.js} +1 -1
  58. package/dist/gateway/static/root/assets/notes-workbench-CCb4RXcZ.js +4 -0
  59. package/dist/gateway/static/root/assets/{page-tabs-kFOswGlG.js → page-tabs-CzZ4iKOb.js} +1 -1
  60. package/dist/gateway/static/root/assets/{preview-open-alternatives-CvjuvABg.js → preview-open-alternatives-3E_Qa-58.js} +1 -1
  61. package/dist/gateway/static/root/assets/product-delivery-BTRT9kCS.js +1 -0
  62. package/dist/gateway/static/root/assets/product-open-page-B2T48Sbt.js +1 -0
  63. package/dist/gateway/static/root/assets/project-detail-page-BvEcriRC.js +1 -0
  64. package/dist/gateway/static/root/assets/projects-page-B_e79MRJ.js +1 -0
  65. package/dist/gateway/static/root/assets/remote-access-hub-DGcqnYfR.js +1 -0
  66. package/dist/gateway/static/root/assets/{save-bar-controls-DwrD7ODx.js → save-bar-controls-CkeKasGm.js} +1 -1
  67. package/dist/gateway/static/root/assets/{schedule-field-Ba4WzTmE.js → schedule-field-TkqUL3wm.js} +1 -1
  68. package/dist/gateway/static/root/assets/{schema-form-DvydL8eF.js → schema-form-DC-iU5IG.js} +1 -1
  69. package/dist/gateway/static/root/assets/sessions-page-DQZhrDJ8.js +1 -0
  70. package/dist/gateway/static/root/assets/settings-advanced-gate-BkdeN99Y.js +1 -0
  71. package/dist/gateway/static/root/assets/{settings-collapsible-section-LaQqGGej.js → settings-collapsible-section-Dpqth3oA.js} +1 -1
  72. package/dist/gateway/static/root/assets/{settings-form-section-DruI__qO.js → settings-form-section-CNqF6IwY.js} +1 -1
  73. package/dist/gateway/static/root/assets/{settings-loading-skeleton-UvA-eDhF.js → settings-loading-skeleton-DkvwitK_.js} +1 -1
  74. package/dist/gateway/static/root/assets/{settings-page-layout-DG-XMIPu.js → settings-page-layout-DWvNgISx.js} +1 -1
  75. package/dist/gateway/static/root/assets/{settings-page-5oCGSebm.js → settings-page-qPG7I2zd.js} +1 -1
  76. package/dist/gateway/static/root/assets/setup-status-panel-lwRAh9aw.js +1 -0
  77. package/dist/gateway/static/root/assets/{share-preview-page-FLVeTrRt.js → share-preview-page-DGpSPW-n.js} +1 -1
  78. package/dist/gateway/static/root/assets/shares-settings-BGj9IVUr.js +1 -0
  79. package/dist/gateway/static/root/assets/skill-reload-api-B2UBAs5U.js +1 -0
  80. package/dist/gateway/static/root/assets/skills-page-DdjzFZ8I.js +2 -0
  81. package/dist/gateway/static/root/assets/{system-settings-panel-CeK93xVi.js → system-settings-panel-8TxxSbUw.js} +1 -1
  82. package/dist/gateway/static/root/assets/{theme-store-C8ohIHhw.js → theme-store-B5FzMWOx.js} +1 -1
  83. package/dist/gateway/static/root/assets/{typed-models-lib-D1Lr2gG0.js → typed-models-lib-DEGtSA5h.js} +1 -1
  84. package/dist/gateway/static/root/assets/{use-channel-catalog-BWzou5ar.js → use-channel-catalog-BDU6FneG.js} +1 -1
  85. package/dist/gateway/static/root/assets/user-context-page-BTTZpAGV.js +1 -0
  86. package/dist/gateway/static/root/assets/voice-api-key-field-DuDQd-WU.js +1 -0
  87. package/dist/gateway/static/root/assets/work-discovery-page-BUVkVCsW.js +1 -0
  88. package/dist/gateway/static/root/assets/work-item-detail-page-DeEPPu4n.js +1 -0
  89. package/dist/gateway/static/root/assets/work-page-DQNUyTUq.js +1 -0
  90. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-Bynqe4kV.js → workflow-run-setup-panel-Cwi8Pz7n.js} +2 -2
  91. package/dist/gateway/static/root/assets/workflows-page-BIy4wrzO.js +11 -0
  92. package/dist/gateway/static/root/index.html +12 -10
  93. package/dist/package.js +1 -1
  94. package/dist/src/automations/service/action-executor.js +1 -1
  95. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  96. package/dist/src/config/agent-profile.js +1 -1
  97. package/dist/src/gateway/agents-admin.js +1 -1
  98. package/dist/src/gateway/chat-stream/mapper.d.ts +1 -0
  99. package/dist/src/gateway/chat-stream/mapper.js +43 -7
  100. package/dist/src/gateway/hono/routes/home.js +15 -2
  101. package/dist/src/gateway/hono/routes/work-discovery.js +451 -0
  102. package/dist/src/gateway/hono/sse.d.ts +2 -0
  103. package/dist/src/gateway/hono/sse.js +10 -3
  104. package/dist/src/gateway/run-loop.js +17 -7
  105. package/dist/src/gateway/server.d.ts +1 -0
  106. package/dist/src/gateway/server.js +9 -0
  107. package/dist/src/gateway/service.d.ts +2 -0
  108. package/dist/src/gateway/service.js +27 -2
  109. package/dist/src/local-apps/preview-runtime-bridge.d.ts +1 -1
  110. package/dist/src/local-apps/preview-runtime-bridge.js +14 -11
  111. package/dist/src/mobile/notification-service.js +1 -0
  112. package/dist/src/pets/factory.js +23 -26
  113. package/dist/src/pets/manifest.d.ts +2 -1
  114. package/dist/src/pets/manifest.js +15 -8
  115. package/dist/src/pets/validator.js +8 -3
  116. package/dist/src/proactive/calendar-signals.d.ts +4 -0
  117. package/dist/src/proactive/calendar-signals.js +120 -0
  118. package/dist/src/proactive/focus-service.d.ts +62 -0
  119. package/dist/src/proactive/focus-service.js +173 -0
  120. package/dist/src/proactive/index.d.ts +7 -0
  121. package/dist/src/proactive/index.js +7 -0
  122. package/dist/src/proactive/insight-repository.d.ts +19 -0
  123. package/dist/src/proactive/insight-repository.js +106 -0
  124. package/dist/src/proactive/run-processor.d.ts +19 -0
  125. package/dist/src/proactive/run-processor.js +125 -0
  126. package/dist/src/proactive/trial-expiry-reconciler.d.ts +5 -0
  127. package/dist/src/proactive/trial-expiry-reconciler.js +30 -0
  128. package/dist/src/proactive/types.d.ts +64 -0
  129. package/dist/src/proactive/types.js +1 -0
  130. package/dist/src/proactive/watch-repository.d.ts +28 -0
  131. package/dist/src/proactive/watch-repository.js +129 -0
  132. package/dist/src/storage/sqlite/knowledge-repository.d.ts +1 -0
  133. package/dist/src/storage/sqlite/knowledge-repository.js +4 -0
  134. package/dist/src/storage/sqlite/migrations/047_work_discovery_sources.sql +16 -0
  135. package/dist/src/storage/sqlite/migrations/048_work_understanding_investigations.sql +40 -0
  136. package/dist/src/storage/sqlite/migrations/049_work_understanding_threads.sql +52 -0
  137. package/dist/src/storage/sqlite/migrations/050_work_discovery_source_refreshes.sql +15 -0
  138. package/dist/src/storage/sqlite/migrations/051_focus_watches.sql +26 -0
  139. package/dist/src/storage/sqlite/migrations/052_proactive_insights.sql +25 -0
  140. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  141. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  142. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  143. package/dist/src/storage/sqlite/schema.js +1 -1
  144. package/dist/src/work-discovery/analyzer.d.ts +12 -1
  145. package/dist/src/work-discovery/analyzer.js +126 -7
  146. package/dist/src/work-discovery/candidate-discovery.d.ts +17 -0
  147. package/dist/src/work-discovery/candidate-discovery.js +279 -0
  148. package/dist/src/work-discovery/evaluation.d.ts +11 -0
  149. package/dist/src/work-discovery/evaluation.js +17 -0
  150. package/dist/src/work-discovery/governance.d.ts +25 -0
  151. package/dist/src/work-discovery/governance.js +96 -0
  152. package/dist/src/work-discovery/incremental-repository.d.ts +13 -0
  153. package/dist/src/work-discovery/incremental-repository.js +55 -0
  154. package/dist/src/work-discovery/incremental.d.ts +2 -0
  155. package/dist/src/work-discovery/incremental.js +7 -0
  156. package/dist/src/work-discovery/index.d.ts +10 -0
  157. package/dist/src/work-discovery/index.js +13 -3
  158. package/dist/src/work-discovery/investigation-repository.d.ts +16 -0
  159. package/dist/src/work-discovery/investigation-repository.js +122 -0
  160. package/dist/src/work-discovery/investigator.d.ts +55 -0
  161. package/dist/src/work-discovery/investigator.js +358 -0
  162. package/dist/src/work-discovery/probe.d.ts +22 -0
  163. package/dist/src/work-discovery/probe.js +88 -1
  164. package/dist/src/work-discovery/service.d.ts +110 -1
  165. package/dist/src/work-discovery/service.js +578 -8
  166. package/dist/src/work-discovery/source-repository.d.ts +13 -0
  167. package/dist/src/work-discovery/source-repository.js +72 -0
  168. package/dist/src/work-discovery/thread-repository.d.ts +34 -0
  169. package/dist/src/work-discovery/thread-repository.js +112 -0
  170. package/dist/src/work-discovery/thread-service.d.ts +8 -0
  171. package/dist/src/work-discovery/thread-service.js +104 -0
  172. package/dist/src/work-discovery/types.d.ts +145 -0
  173. package/package.json +1 -1
  174. package/skills/creative/hatch-pet/SKILL.md +2 -2
  175. package/dist/gateway/static/root/assets/activity-store-C-57rvkX.js +0 -1
  176. package/dist/gateway/static/root/assets/agents-P05C4A-3.js +0 -2
  177. package/dist/gateway/static/root/assets/apps-page-DANGrBpN.js +0 -1
  178. package/dist/gateway/static/root/assets/automations-page-BXfGL_cm.js +0 -6
  179. package/dist/gateway/static/root/assets/browser-settings-page-534luV0B.js +0 -8
  180. package/dist/gateway/static/root/assets/channels-settings-M91zFJVi.js +0 -1
  181. package/dist/gateway/static/root/assets/channels-status-swr-vooEF4lG.js +0 -1
  182. package/dist/gateway/static/root/assets/check-BODqo66o.js +0 -1
  183. package/dist/gateway/static/root/assets/circle-alert-RzVebuX2.js +0 -1
  184. package/dist/gateway/static/root/assets/cn-BQ_8x0bT.js +0 -1
  185. package/dist/gateway/static/root/assets/connectors-page-CV5UulW6.js +0 -2
  186. package/dist/gateway/static/root/assets/desktop-pet-Cil2eSpW.js +0 -1
  187. package/dist/gateway/static/root/assets/desktop-pet-settings-DwsC6l5F.js +0 -1
  188. package/dist/gateway/static/root/assets/desktop-pet-sprite-DqZ6fbIj.js +0 -1
  189. package/dist/gateway/static/root/assets/gateway-settings-BpJ0v56l.js +0 -1
  190. package/dist/gateway/static/root/assets/goal-create-dialog-lMHD-2CX.js +0 -1
  191. package/dist/gateway/static/root/assets/goal-detail-page-yPmF-G7b.js +0 -4
  192. package/dist/gateway/static/root/assets/goals-page-BHTCKobG.js +0 -2
  193. package/dist/gateway/static/root/assets/index-BgZhEOkN.css +0 -1
  194. package/dist/gateway/static/root/assets/index-BuqGh2SW.js +0 -193
  195. package/dist/gateway/static/root/assets/local-app-workbench-page-zpp_wciE.js +0 -2
  196. package/dist/gateway/static/root/assets/logs-page-B4CRYqYR.js +0 -7
  197. package/dist/gateway/static/root/assets/messages-BuT0SR1R.js +0 -5
  198. package/dist/gateway/static/root/assets/models-hub-panel-1hdoAoB_.js +0 -2
  199. package/dist/gateway/static/root/assets/notes-workbench-DQEAZTA5.js +0 -4
  200. package/dist/gateway/static/root/assets/project-detail-page-Bp9bC7A6.js +0 -1
  201. package/dist/gateway/static/root/assets/projects-page-RNorFXTw.js +0 -1
  202. package/dist/gateway/static/root/assets/remote-access-hub-BqRTcM_6.js +0 -1
  203. package/dist/gateway/static/root/assets/sessions-page-B2EfW1cm.js +0 -1
  204. package/dist/gateway/static/root/assets/settings-advanced-gate-CklL85T4.js +0 -1
  205. package/dist/gateway/static/root/assets/setup-status-panel-D3my1HgB.js +0 -1
  206. package/dist/gateway/static/root/assets/shares-settings-DVxgjrsP.js +0 -1
  207. package/dist/gateway/static/root/assets/skill-reload-api-Bcu58ZaF.js +0 -1
  208. package/dist/gateway/static/root/assets/skills-page-CDd-84FE.js +0 -2
  209. package/dist/gateway/static/root/assets/sliding-segmented-uWmrxE3P.js +0 -1
  210. package/dist/gateway/static/root/assets/user-context-page-1OyJnTyR.js +0 -1
  211. package/dist/gateway/static/root/assets/voice-api-key-field-B2ncRwHS.js +0 -1
  212. package/dist/gateway/static/root/assets/work-discovery-page-BZlY4PZL.js +0 -1
  213. package/dist/gateway/static/root/assets/work-item-detail-page-DPXMrneY.js +0 -1
  214. package/dist/gateway/static/root/assets/work-page-CTERoqyv.js +0 -1
  215. package/dist/gateway/static/root/assets/workflows-page-CLgsnqFQ.js +0 -11
@@ -48,6 +48,10 @@ import { startConnectorMemorySyncCoordinator } from "../connectors/memory-sync-c
48
48
  import { startConnectorSupervisor } from "../connectors/supervisor.js";
49
49
  import { assertGatewayAuthConfigured, extractToken, resolveGatewayAuth, validateToken } from "./auth.js";
50
50
  import { reconcileDreamingAutomations } from "./dreaming-automation-reconciler.js";
51
+ import { FocusService } from "../proactive/focus-service.js";
52
+ import { processFocusAutomationRun } from "../proactive/run-processor.js";
53
+ import { startTrialExpiryReconciler } from "../proactive/trial-expiry-reconciler.js";
54
+ import "../proactive/index.js";
51
55
  import { NotesStore } from "../notes/store.js";
52
56
  import { buildNoteAgentContext } from "../notes/agent-context.js";
53
57
  import { NotesService } from "../notes/service.js";
@@ -141,6 +145,7 @@ var GatewayService = class {
141
145
  connectedKnowledgeCoordinator = null;
142
146
  stopAutomationProductEventBridge = null;
143
147
  stopSessionTranscriptAutomationEvents = null;
148
+ stopFocusTrialExpiryReconciler = null;
144
149
  /**
145
150
  * Webchat agent invocation surface (`runAgent`, `abortAgentRun`, `steer*`,
146
151
  * `submitClarifyResponse`, clarify-bridge dispatch). Owns the
@@ -356,7 +361,7 @@ var GatewayService = class {
356
361
  getDefaultAgentId: () => getDefaultAgentId(this.config),
357
362
  getProjectWorkspaceRoot: (projectId) => this.projects.get(projectId)?.workspaceRoot,
358
363
  workflowRunService: this.createWorkflowRunService(),
359
- onRunCompleted: (run) => this.emit("automation.run.completed", { run })
364
+ onRunCompleted: (run) => this.handleAutomationRunCompleted(run)
360
365
  });
361
366
  this._agentService.persistentGoals.setWebchatContinuationScheduler((sessionKey, message) => {
362
367
  const scheduleWhenIdle = () => {
@@ -666,7 +671,7 @@ var GatewayService = class {
666
671
  agentService: this.agentService,
667
672
  getDefaultAgentId: () => getDefaultAgentId(this.config),
668
673
  workflowRunService: this.createWorkflowRunService(),
669
- onRunCompleted: (run) => this.emit("automation.run.completed", { run })
674
+ onRunCompleted: (run) => this.handleAutomationRunCompleted(run)
670
675
  });
671
676
  this.startAutomationProductEventBridge();
672
677
  await trace.measure("workflows.reconcile", () => this.reconcileInterruptedWorkflowRuns());
@@ -679,6 +684,7 @@ var GatewayService = class {
679
684
  });
680
685
  await trace.measure("automations.initialize", () => this.automationService.initialize());
681
686
  await trace.measure("dreaming.reconcile", () => this.reconcileDreamingAutomations());
687
+ this.stopFocusTrialExpiryReconciler = startTrialExpiryReconciler(new FocusService(this.automationService));
682
688
  await this.notesService.initialize();
683
689
  this.ensureHeartbeatService().start(heartbeatRunnerConfigFromConfig(this.config));
684
690
  this.connectorSupervisor = startConnectorSupervisor({
@@ -834,6 +840,8 @@ var GatewayService = class {
834
840
  this.lastChannelConnectDeferMode = "auto";
835
841
  this.lastChannelConnectDeferSource = "off";
836
842
  await this.channelManager.stop();
843
+ await this.stopFocusTrialExpiryReconciler?.();
844
+ this.stopFocusTrialExpiryReconciler = null;
837
845
  await this.automationService.stop();
838
846
  this.stopAutomationProductEventBridge?.();
839
847
  this.stopAutomationProductEventBridge = null;
@@ -1234,6 +1242,23 @@ var GatewayService = class {
1234
1242
  this.createGoalNotificationService().handleGatewayEvent(type, payload);
1235
1243
  this.createMobileNotificationService().handleGatewayEvent(type, payload);
1236
1244
  }
1245
+ handleAutomationRunCompleted(run) {
1246
+ try {
1247
+ const proactive = processFocusAutomationRun(run);
1248
+ this.emit("automation.run.completed", {
1249
+ run,
1250
+ silent: proactive.handled
1251
+ });
1252
+ if (proactive.insight) this.emit("proactive.insight.created", { insight: proactive.insight });
1253
+ } catch (err) {
1254
+ log.warn({
1255
+ err,
1256
+ automationId: run.automationId,
1257
+ runId: run.id
1258
+ }, "Proactive result processing failed");
1259
+ this.emit("automation.run.completed", { run });
1260
+ }
1261
+ }
1237
1262
  startAutomationProductEventBridge() {
1238
1263
  this.stopAutomationProductEventBridge?.();
1239
1264
  this.stopSessionTranscriptAutomationEvents?.();
@@ -1,4 +1,4 @@
1
1
  import type { LocalAppAcceptanceConfig } from './acceptance.js';
2
- export declare const LOCAL_APP_RUNTIME_BRIDGE_SCRIPT = "(() => {\n let hostPort = null;\n const pending = [];\n const send = (type, detail) => {\n const message = { source: 'xopc-local-app-preview', version: 1, type, detail };\n if (hostPort) hostPort.postMessage(message);\n else pending.push(message);\n };\n const connect = (event) => {\n const message = event.data;\n if (event.source !== parent || message?.source !== 'xopc-local-app-host' || message?.version !== 1 || message?.type !== 'connect' || !event.ports[0]) return;\n event.stopImmediatePropagation();\n removeEventListener('message', connect, true);\n hostPort = event.ports[0];\n hostPort.start();\n for (const queued of pending.splice(0)) hostPort.postMessage(queued);\n };\n addEventListener('message', connect, true);\n const text = (value) => String(value ?? '').slice(0, 500);\n let runtimeFailure = '';\n const visible = (element) => {\n const style = getComputedStyle(element);\n const rect = element.getBoundingClientRect();\n return style.display !== 'none' && style.visibility !== 'hidden' && Number(style.opacity) !== 0 && rect.width > 0 && rect.height > 0;\n };\n const accessibleName = (element) => {\n const labelledBy = text(element.getAttribute('aria-labelledby'));\n const labelledText = labelledBy.split(/\\s+/).map((id) => document.getElementById(id)?.textContent).filter(Boolean).join(' ');\n return text(element.getAttribute('aria-label') || labelledText || element.labels?.[0]?.textContent || element.textContent || element.getAttribute('alt') || element.getAttribute('title') || element.getAttribute('placeholder') || element.getAttribute('name')).trim();\n };\n const target = (id) => Array.from(document.querySelectorAll('[data-xopc-test-id]'))\n .find((element) => element.getAttribute('data-xopc-test-id') === id);\n const pause = () => new Promise((resolve) => setTimeout(resolve, 50));\n const readCriteria = () => {\n const encoded = document.querySelector('meta[name=\"xopc-local-app-acceptance\"]')?.content;\n if (!encoded) return { schemaVersion: 1, scenarios: [] };\n const base64 = encoded.replace(/-/g, '+').replace(/_/g, '/');\n const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, '=');\n const bytes = Uint8Array.from(atob(padded), (char) => char.charCodeAt(0));\n return JSON.parse(new TextDecoder().decode(bytes));\n };\n addEventListener('error', (event) => {\n runtimeFailure = text(event.message || 'A preview resource failed to load');\n send('error', {\n kind: 'script_error',\n message: runtimeFailure,\n filename: text(event.filename),\n line: Number(event.lineno) || undefined,\n column: Number(event.colno) || undefined,\n });\n }, true);\n addEventListener('unhandledrejection', (event) => {\n runtimeFailure = text(event.reason instanceof Error ? event.reason.message : event.reason);\n send('error', { kind: 'unhandled_rejection', message: runtimeFailure });\n });\n const runAcceptance = () => {\n const checks = [];\n const bodyReady = Boolean(document.body);\n checks.push({ id: 'document', status: bodyReady ? 'passed' : 'failed', message: bodyReady ? 'Preview document loaded' : 'Preview document has no body' });\n const contentNodes = bodyReady ? Array.from(document.body.querySelectorAll('*')).slice(0, 300) : [];\n const hasVisibleContent = contentNodes.some((element) => visible(element) && (text(element.textContent).trim() || /^(IMG|SVG|CANVAS|VIDEO)$/.test(element.tagName)));\n checks.push({ id: 'content', status: hasVisibleContent ? 'passed' : 'failed', message: hasVisibleContent ? 'Visible content rendered' : 'No visible content was rendered' });\n const controls = bodyReady ? Array.from(document.body.querySelectorAll('a[href], button, input, select, textarea, [role=\"button\"], [tabindex]')).filter((element) => visible(element) && !element.disabled && element.getAttribute('aria-disabled') !== 'true') : [];\n const unnamed = controls.filter((element) => !accessibleName(element));\n let interactionStatus = controls.length ? 'passed' : 'skipped';\n let interactionMessage = controls.length ? controls.length + ' interactive control(s) are discoverable' : 'No interactive controls to exercise';\n if (unnamed.length) {\n interactionStatus = 'failed';\n interactionMessage = unnamed.length + ' interactive control(s) need an accessible name';\n } else if (controls.length) {\n const previous = document.activeElement;\n controls[0].focus({ preventScroll: true });\n if (document.activeElement !== controls[0]) {\n interactionStatus = 'failed';\n interactionMessage = 'The first interactive control could not receive focus';\n }\n if (previous && typeof previous.focus === 'function') previous.focus({ preventScroll: true });\n else controls[0].blur();\n }\n checks.push({ id: 'interaction', status: interactionStatus, message: interactionMessage });\n send('acceptance', {\n status: checks.some((check) => check.status === 'failed') ? 'failed' : 'passed',\n checks,\n interactiveCount: controls.length,\n });\n };\n const runCriteria = async () => {\n const params = new URLSearchParams(location.search);\n if (params.get('xopcAcceptance') !== '1') return;\n let config;\n try {\n config = readCriteria();\n } catch (error) {\n send('criteria', { status: 'failed', scenarioCount: 0, scenarios: [{ id: 'config', name: 'Acceptance config', status: 'failed', message: text(error instanceof Error ? error.message : error) }] });\n return;\n }\n const requestedScenario = params.get('xopcScenario');\n const selectedScenarios = requestedScenario\n ? (config.scenarios || []).filter((scenario) => scenario.id === requestedScenario)\n : config.scenarios || [];\n if (requestedScenario && !selectedScenarios.length) {\n send('criteria', { status: 'failed', scenarioCount: 1, scenarios: [{ id: requestedScenario, name: requestedScenario, status: 'failed', message: 'Requested scenario was not found' }] });\n return;\n }\n const scenarios = [];\n for (const scenario of selectedScenarios) {\n let failure = '';\n for (let index = 0; index < scenario.steps.length; index += 1) {\n const step = scenario.steps[index];\n try {\n if (step.action === 'click') {\n const element = target(step.target);\n if (!(element instanceof HTMLElement)) throw new Error('Target \"' + step.target + '\" was not found');\n if ('disabled' in element && element.disabled || element.getAttribute('aria-disabled') === 'true') throw new Error('Target \"' + step.target + '\" is disabled');\n element.click();\n await pause();\n } else if (step.action === 'fill') {\n const element = target(step.target);\n if (!(element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement)) throw new Error('Target \"' + step.target + '\" cannot be filled');\n const prototype = element instanceof HTMLInputElement ? HTMLInputElement.prototype : element instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLSelectElement.prototype;\n const setter = Object.getOwnPropertyDescriptor(prototype, 'value')?.set;\n if (setter) setter.call(element, step.value);\n else element.value = step.value;\n element.dispatchEvent(new Event('input', { bubbles: true }));\n element.dispatchEvent(new Event('change', { bubbles: true }));\n await pause();\n } else if (step.assert === 'text_visible') {\n const found = Array.from(document.body.querySelectorAll('*')).some((element) => visible(element) && text(element.textContent).includes(step.text));\n if (!found) throw new Error('Visible text was not found: \"' + step.text + '\"');\n } else if (step.assert === 'element_exists') {\n if (!target(step.target)) throw new Error('Target \"' + step.target + '\" was not found');\n } else if (step.assert === 'value_equals') {\n const element = target(step.target);\n if (!element || !('value' in element) || element.value !== step.value) throw new Error('Target \"' + step.target + '\" does not have the expected value');\n }\n if (runtimeFailure) throw new Error(runtimeFailure);\n } catch (error) {\n failure = 'Step ' + (index + 1) + ': ' + text(error instanceof Error ? error.message : error);\n break;\n }\n }\n scenarios.push({ id: scenario.id, name: scenario.name, status: failure ? 'failed' : 'passed', message: failure || 'Scenario passed' });\n }\n send('criteria', {\n status: scenarios.some((scenario) => scenario.status === 'failed') ? 'failed' : 'passed',\n scenarioCount: scenarios.length,\n scenarios,\n });\n };\n const loaded = () => {\n send('ready', { readyState: document.readyState });\n const run = () => { runAcceptance(); void runCriteria(); };\n if (typeof requestAnimationFrame === 'function') requestAnimationFrame(() => requestAnimationFrame(run));\n else setTimeout(run, 0);\n };\n if (document.readyState === 'complete') queueMicrotask(loaded);\n else addEventListener('load', loaded, { once: true });\n})();";
2
+ export declare const LOCAL_APP_RUNTIME_BRIDGE_SCRIPT = "(() => {\n let hostPort = null;\n const latest = new Map();\n const send = (type, detail) => {\n const message = { source: 'xopc-local-app-preview', version: 1, type, detail };\n latest.set(type, message);\n hostPort?.postMessage(message);\n };\n const connect = (event) => {\n const message = event.data;\n if (event.source !== parent || message?.source !== 'xopc-local-app-host' || message?.version !== 1 || message?.type !== 'connect' || !event.ports[0]) return;\n event.stopImmediatePropagation();\n hostPort?.close();\n hostPort = event.ports[0];\n hostPort.start();\n for (const message of latest.values()) hostPort.postMessage(message);\n };\n addEventListener('message', connect, true);\n const text = (value) => String(value ?? '').slice(0, 500);\n let runtimeFailure = '';\n const visible = (element) => {\n const style = getComputedStyle(element);\n const rect = element.getBoundingClientRect();\n return style.display !== 'none' && style.visibility !== 'hidden' && Number(style.opacity) !== 0 && rect.width > 0 && rect.height > 0;\n };\n const accessibleName = (element) => {\n const labelledBy = text(element.getAttribute('aria-labelledby'));\n const labelledText = labelledBy.split(/\\s+/).map((id) => document.getElementById(id)?.textContent).filter(Boolean).join(' ');\n return text(element.getAttribute('aria-label') || labelledText || element.labels?.[0]?.textContent || element.textContent || element.getAttribute('alt') || element.getAttribute('title') || element.getAttribute('placeholder') || element.getAttribute('name')).trim();\n };\n const target = (id) => Array.from(document.querySelectorAll('[data-xopc-test-id]'))\n .find((element) => element.getAttribute('data-xopc-test-id') === id);\n const settle = async () => {\n await Promise.resolve();\n await Promise.resolve();\n };\n const readCriteria = () => {\n const encoded = document.querySelector('meta[name=\"xopc-local-app-acceptance\"]')?.content;\n if (!encoded) return { schemaVersion: 1, scenarios: [] };\n const base64 = encoded.replace(/-/g, '+').replace(/_/g, '/');\n const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, '=');\n const bytes = Uint8Array.from(atob(padded), (char) => char.charCodeAt(0));\n return JSON.parse(new TextDecoder().decode(bytes));\n };\n addEventListener('error', (event) => {\n runtimeFailure = text(event.message || 'A preview resource failed to load');\n send('error', {\n kind: 'script_error',\n message: runtimeFailure,\n filename: text(event.filename),\n line: Number(event.lineno) || undefined,\n column: Number(event.colno) || undefined,\n });\n }, true);\n addEventListener('unhandledrejection', (event) => {\n runtimeFailure = text(event.reason instanceof Error ? event.reason.message : event.reason);\n send('error', { kind: 'unhandled_rejection', message: runtimeFailure });\n });\n const runAcceptance = () => {\n const checks = [];\n const bodyReady = Boolean(document.body);\n checks.push({ id: 'document', status: bodyReady ? 'passed' : 'failed', message: bodyReady ? 'Preview document loaded' : 'Preview document has no body' });\n const contentNodes = bodyReady ? Array.from(document.body.querySelectorAll('*')).slice(0, 300) : [];\n const hasVisibleContent = contentNodes.some((element) => visible(element) && (text(element.textContent).trim() || /^(IMG|SVG|CANVAS|VIDEO)$/.test(element.tagName)));\n checks.push({ id: 'content', status: hasVisibleContent ? 'passed' : 'failed', message: hasVisibleContent ? 'Visible content rendered' : 'No visible content was rendered' });\n const controls = bodyReady ? Array.from(document.body.querySelectorAll('a[href], button, input, select, textarea, [role=\"button\"], [tabindex]')).filter((element) => visible(element) && !element.disabled && element.getAttribute('aria-disabled') !== 'true') : [];\n const unnamed = controls.filter((element) => !accessibleName(element));\n let interactionStatus = controls.length ? 'passed' : 'skipped';\n let interactionMessage = controls.length ? controls.length + ' interactive control(s) are discoverable' : 'No interactive controls to exercise';\n if (unnamed.length) {\n interactionStatus = 'failed';\n interactionMessage = unnamed.length + ' interactive control(s) need an accessible name';\n } else if (controls.length) {\n const previous = document.activeElement;\n controls[0].focus({ preventScroll: true });\n if (document.activeElement !== controls[0]) {\n interactionStatus = 'failed';\n interactionMessage = 'The first interactive control could not receive focus';\n }\n if (previous && typeof previous.focus === 'function') previous.focus({ preventScroll: true });\n else controls[0].blur();\n }\n checks.push({ id: 'interaction', status: interactionStatus, message: interactionMessage });\n send('acceptance', {\n status: checks.some((check) => check.status === 'failed') ? 'failed' : 'passed',\n checks,\n interactiveCount: controls.length,\n });\n };\n const runCriteria = async () => {\n const params = new URLSearchParams(location.search);\n if (params.get('xopcAcceptance') !== '1') return;\n let config;\n try {\n config = readCriteria();\n } catch (error) {\n send('criteria', { status: 'failed', scenarioCount: 0, scenarios: [{ id: 'config', name: 'Acceptance config', status: 'failed', message: text(error instanceof Error ? error.message : error) }] });\n return;\n }\n const requestedScenario = params.get('xopcScenario');\n const selectedScenarios = requestedScenario\n ? (config.scenarios || []).filter((scenario) => scenario.id === requestedScenario)\n : config.scenarios || [];\n if (requestedScenario && !selectedScenarios.length) {\n send('criteria', { status: 'failed', scenarioCount: 1, scenarios: [{ id: requestedScenario, name: requestedScenario, status: 'failed', message: 'Requested scenario was not found' }] });\n return;\n }\n const scenarios = [];\n for (const scenario of selectedScenarios) {\n let failure = '';\n for (let index = 0; index < scenario.steps.length; index += 1) {\n const step = scenario.steps[index];\n try {\n if (step.action === 'click') {\n const element = target(step.target);\n if (!(element instanceof HTMLElement)) throw new Error('Target \"' + step.target + '\" was not found');\n if ('disabled' in element && element.disabled || element.getAttribute('aria-disabled') === 'true') throw new Error('Target \"' + step.target + '\" is disabled');\n element.click();\n await settle();\n } else if (step.action === 'fill') {\n const element = target(step.target);\n if (!(element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement)) throw new Error('Target \"' + step.target + '\" cannot be filled');\n const prototype = element instanceof HTMLInputElement ? HTMLInputElement.prototype : element instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLSelectElement.prototype;\n const setter = Object.getOwnPropertyDescriptor(prototype, 'value')?.set;\n if (setter) setter.call(element, step.value);\n else element.value = step.value;\n element.dispatchEvent(new Event('input', { bubbles: true }));\n element.dispatchEvent(new Event('change', { bubbles: true }));\n await settle();\n } else if (step.assert === 'text_visible') {\n const found = Array.from(document.body.querySelectorAll('*')).some((element) => visible(element) && text(element.textContent).includes(step.text));\n if (!found) throw new Error('Visible text was not found: \"' + step.text + '\"');\n } else if (step.assert === 'element_exists') {\n if (!target(step.target)) throw new Error('Target \"' + step.target + '\" was not found');\n } else if (step.assert === 'value_equals') {\n const element = target(step.target);\n if (!element || !('value' in element) || element.value !== step.value) throw new Error('Target \"' + step.target + '\" does not have the expected value');\n }\n if (runtimeFailure) throw new Error(runtimeFailure);\n } catch (error) {\n failure = 'Step ' + (index + 1) + ': ' + text(error instanceof Error ? error.message : error);\n break;\n }\n }\n scenarios.push({ id: scenario.id, name: scenario.name, status: failure ? 'failed' : 'passed', message: failure || 'Scenario passed' });\n }\n send('criteria', {\n status: scenarios.some((scenario) => scenario.status === 'failed') ? 'failed' : 'passed',\n scenarioCount: scenarios.length,\n scenarios,\n });\n };\n const loaded = () => {\n send('ready', { readyState: document.readyState });\n void runCriteria();\n if (typeof requestAnimationFrame === 'function') requestAnimationFrame(() => requestAnimationFrame(runAcceptance));\n else setTimeout(runAcceptance, 0);\n };\n if (document.readyState === 'complete') queueMicrotask(loaded);\n else addEventListener('load', loaded, { once: true });\n})();";
3
3
  export declare const LOCAL_APP_RUNTIME_BRIDGE_HASH: string;
4
4
  export declare function injectLocalAppRuntimeBridge(html: string, acceptance?: LocalAppAcceptanceConfig): string;
@@ -3,20 +3,20 @@ import { createHash } from "node:crypto";
3
3
  //#region src/local-apps/preview-runtime-bridge.ts
4
4
  const LOCAL_APP_RUNTIME_BRIDGE_SCRIPT = `(() => {
5
5
  let hostPort = null;
6
- const pending = [];
6
+ const latest = new Map();
7
7
  const send = (type, detail) => {
8
8
  const message = { source: 'xopc-local-app-preview', version: 1, type, detail };
9
- if (hostPort) hostPort.postMessage(message);
10
- else pending.push(message);
9
+ latest.set(type, message);
10
+ hostPort?.postMessage(message);
11
11
  };
12
12
  const connect = (event) => {
13
13
  const message = event.data;
14
14
  if (event.source !== parent || message?.source !== 'xopc-local-app-host' || message?.version !== 1 || message?.type !== 'connect' || !event.ports[0]) return;
15
15
  event.stopImmediatePropagation();
16
- removeEventListener('message', connect, true);
16
+ hostPort?.close();
17
17
  hostPort = event.ports[0];
18
18
  hostPort.start();
19
- for (const queued of pending.splice(0)) hostPort.postMessage(queued);
19
+ for (const message of latest.values()) hostPort.postMessage(message);
20
20
  };
21
21
  addEventListener('message', connect, true);
22
22
  const text = (value) => String(value ?? '').slice(0, 500);
@@ -33,7 +33,10 @@ const LOCAL_APP_RUNTIME_BRIDGE_SCRIPT = `(() => {
33
33
  };
34
34
  const target = (id) => Array.from(document.querySelectorAll('[data-xopc-test-id]'))
35
35
  .find((element) => element.getAttribute('data-xopc-test-id') === id);
36
- const pause = () => new Promise((resolve) => setTimeout(resolve, 50));
36
+ const settle = async () => {
37
+ await Promise.resolve();
38
+ await Promise.resolve();
39
+ };
37
40
  const readCriteria = () => {
38
41
  const encoded = document.querySelector('meta[name="xopc-local-app-acceptance"]')?.content;
39
42
  if (!encoded) return { schemaVersion: 1, scenarios: [] };
@@ -116,7 +119,7 @@ const LOCAL_APP_RUNTIME_BRIDGE_SCRIPT = `(() => {
116
119
  if (!(element instanceof HTMLElement)) throw new Error('Target "' + step.target + '" was not found');
117
120
  if ('disabled' in element && element.disabled || element.getAttribute('aria-disabled') === 'true') throw new Error('Target "' + step.target + '" is disabled');
118
121
  element.click();
119
- await pause();
122
+ await settle();
120
123
  } else if (step.action === 'fill') {
121
124
  const element = target(step.target);
122
125
  if (!(element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement)) throw new Error('Target "' + step.target + '" cannot be filled');
@@ -126,7 +129,7 @@ const LOCAL_APP_RUNTIME_BRIDGE_SCRIPT = `(() => {
126
129
  else element.value = step.value;
127
130
  element.dispatchEvent(new Event('input', { bubbles: true }));
128
131
  element.dispatchEvent(new Event('change', { bubbles: true }));
129
- await pause();
132
+ await settle();
130
133
  } else if (step.assert === 'text_visible') {
131
134
  const found = Array.from(document.body.querySelectorAll('*')).some((element) => visible(element) && text(element.textContent).includes(step.text));
132
135
  if (!found) throw new Error('Visible text was not found: "' + step.text + '"');
@@ -152,9 +155,9 @@ const LOCAL_APP_RUNTIME_BRIDGE_SCRIPT = `(() => {
152
155
  };
153
156
  const loaded = () => {
154
157
  send('ready', { readyState: document.readyState });
155
- const run = () => { runAcceptance(); void runCriteria(); };
156
- if (typeof requestAnimationFrame === 'function') requestAnimationFrame(() => requestAnimationFrame(run));
157
- else setTimeout(run, 0);
158
+ void runCriteria();
159
+ if (typeof requestAnimationFrame === 'function') requestAnimationFrame(() => requestAnimationFrame(runAcceptance));
160
+ else setTimeout(runAcceptance, 0);
158
161
  };
159
162
  if (document.readyState === 'complete') queueMicrotask(loaded);
160
163
  else addEventListener('load', loaded, { once: true });
@@ -44,6 +44,7 @@ function goalEvent(payload) {
44
44
  }
45
45
  function automationEvent(payload) {
46
46
  if (!payload || typeof payload !== "object") return null;
47
+ if (payload.silent === true) return null;
47
48
  const run = payload.run;
48
49
  if (!run || run.status !== "failed" && run.status !== "timeout" && run.status !== "succeeded") return null;
49
50
  const type = run.status === "succeeded" ? "automation.completed" : "automation.failed";
@@ -75,23 +75,26 @@ function esc(value) {
75
75
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
76
76
  }
77
77
  function desktopPetActionFrameCount(action) {
78
- if (action === "idle") return 6;
79
- if (action === "success" || action === "error") return 8;
78
+ if (action === "idle" || action === "sleep") return 6;
79
+ if (action === "success" || action === "concern") return 8;
80
80
  return 10;
81
81
  }
82
82
  function desktopPetActionFps(action) {
83
- if (action === "idle") return 6;
84
- if (action === "success" || action === "error") return 12;
83
+ if (action === "idle" || action === "sleep") return 6;
84
+ if (action === "success" || action === "concern") return 12;
85
85
  return 10;
86
86
  }
87
87
  function desktopPetActionLoops(action) {
88
- return action !== "success" && action !== "error";
88
+ return action === "idle" || action === "sleep" || action === "research" || action === "read" || action === "create" || action === "execute" || action === "wait";
89
89
  }
90
90
  function renderAccessory(action, frame, style) {
91
91
  const t = frame / Math.max(1, desktopPetActionFrameCount(action) - 1);
92
92
  const bob = Math.sin(t * Math.PI * 2) * 2;
93
93
  const { palette } = style;
94
- if (action === "typing") {
94
+ if (action === "sleep") return `<g fill="${palette.screen}" font-family="Arial, sans-serif" font-weight="800"><text x="70" y="28" font-size="13" opacity="${frame % 2 ? .45 : .9}">z</text><text x="80" y="17" font-size="10" opacity="${frame % 2 ? .9 : .45}">z</text></g>`;
95
+ if (action === "greet" || action === "wake" || action === "pet") return `<path d="M76 23c-7-9-19 1-7 13l7 7 7-7c12-12 0-22-7-13z" fill="${palette.cheek}" stroke="#101827" stroke-width="2"/>`;
96
+ if (action === "wait") return `<g fill="${palette.screen}" transform="translate(62 18)"><circle cx="0" cy="0" r="3" opacity="${frame % 3 === 0 ? 1 : .35}"/><circle cx="10" cy="0" r="3" opacity="${frame % 3 === 1 ? 1 : .35}"/><circle cx="20" cy="0" r="3" opacity="${frame % 3 === 2 ? 1 : .35}"/></g>`;
97
+ if (action === "create") {
95
98
  const glow = frame % 2 === 0 ? 1 : .45;
96
99
  return `
97
100
  <g transform="translate(55 ${59 + bob})">
@@ -103,7 +106,7 @@ function renderAccessory(action, frame, style) {
103
106
  <rect x="29" y="28" width="5" height="2" rx="1" fill="#e0f2fe" opacity="${glow}"/>
104
107
  </g>`;
105
108
  }
106
- if (action === "toolbox") {
109
+ if (action === "prepare") {
107
110
  const open = frame % 2 === 0 ? -11 : -3;
108
111
  return `
109
112
  <g transform="translate(6 ${61 + bob})">
@@ -114,38 +117,30 @@ function renderAccessory(action, frame, style) {
114
117
  <rect x="29" y="${8 + open / 2}" width="5" height="17" rx="2" fill="#e5e7eb" stroke="#101827" stroke-width="2" transform="rotate(25 31 24)"/>
115
118
  </g>`;
116
119
  }
117
- if (action === "search") return `
120
+ if (action === "research") return `
118
121
  <g transform="translate(58 ${58 + bob}) rotate(${frame % 2 === 0 ? -7 : 4} 18 18)">
119
122
  <circle cx="15" cy="14" r="11" fill="#e0f2fe" stroke="${palette.accent}" stroke-width="5"/>
120
123
  <rect x="24" y="24" width="17" height="7" rx="3" fill="${palette.accent}" stroke="#101827" stroke-width="3" transform="rotate(43 24 24)"/>
121
124
  </g>`;
122
- if (action === "file") return `
125
+ if (action === "read") return `
123
126
  <g transform="translate(59 ${55 + bob}) rotate(${frame % 2 === 0 ? -5 : 5} 16 20)">
124
127
  <path d="M5 1h22l9 9v31H5z" fill="#eff6ff" stroke="#101827" stroke-width="3" stroke-linejoin="round"/>
125
128
  <path d="M27 1v10h9" fill="${palette.accent}" stroke="#101827" stroke-width="3" stroke-linejoin="round"/>
126
129
  <rect x="12" y="20" width="16" height="3" rx="1.5" fill="${palette.body}"/>
127
130
  <rect x="12" y="28" width="12" height="3" rx="1.5" fill="${palette.body}"/>
128
131
  </g>`;
129
- if (action === "terminal") return `
132
+ if (action === "execute") return `
130
133
  <g transform="translate(55 ${61 + bob})">
131
134
  <rect x="0" y="0" width="39" height="29" rx="7" fill="#111827" stroke="#101827" stroke-width="3"/>
132
135
  <path d="M10 10l6 5-6 5" fill="none" stroke="#86efac" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" opacity="${frame % 2 === 0 ? 1 : .4}"/>
133
136
  <path d="M20 20h9" stroke="#86efac" stroke-width="3" stroke-linecap="round"/>
134
- </g>`;
135
- if (action === "browser") return `
136
- <g transform="translate(55 ${58 + bob})">
137
- <rect x="0" y="0" width="40" height="31" rx="7" fill="#e0f2fe" stroke="#101827" stroke-width="3"/>
138
- <path d="M1 11h38" stroke="${palette.accent}" stroke-width="8"/>
139
- <circle cx="10" cy="10" r="2" fill="#101827"/>
140
- <circle cx="17" cy="10" r="2" fill="#101827"/>
141
- <rect x="11" y="21" width="20" height="4" rx="2" fill="${palette.body}"/>
142
137
  </g>`;
143
138
  if (action === "success") return `
144
139
  <g fill="#facc15" transform="translate(61 ${12 + bob})">
145
140
  <path d="M12 0l3 8 8 3-8 3-3 8-3-8-8-3 8-3z"/>
146
141
  <path d="M-12 18l2 5 5 2-5 2-2 5-2-5-5-2 5-2z" opacity="0.78"/>
147
142
  </g>`;
148
- if (action === "error") return `
143
+ if (action === "concern") return `
149
144
  <g transform="translate(61 ${56 + bob})">
150
145
  <path d="M18 0l18 32H0z" fill="#facc15" stroke="#101827" stroke-width="3" stroke-linejoin="round"/>
151
146
  <text x="18" y="26" text-anchor="middle" font-family="Arial, sans-serif" font-size="22" font-weight="900" fill="#101827">!</text>
@@ -170,14 +165,14 @@ function renderHead(style) {
170
165
  }
171
166
  function renderFrame(style, action, frame) {
172
167
  const t = frame / Math.max(1, desktopPetActionFrameCount(action) - 1);
173
- const bob = Math.sin(t * Math.PI * 2) * (action === "idle" ? 2.5 : 1.5);
174
- const tilt = action === "toolbox" ? Math.sin(t * Math.PI * 2) * 2.5 : 0;
168
+ const bob = action === "sleep" ? 7 : action === "pickedUp" ? -6 : action === "released" ? 4 : Math.sin(t * Math.PI * 2) * (action === "idle" ? 2.5 : 1.5);
169
+ const tilt = action === "prepare" ? Math.sin(t * Math.PI * 2) * 2.5 : action === "pet" ? Math.sin(t * Math.PI) * 5 : 0;
175
170
  const { palette } = style;
176
- const face = action === "error" ? "#3f0f18" : palette.face;
177
- const eye = action === "error" ? "#fca5a5" : palette.screen;
178
- const mouth = action === "success" ? "M39 45q5 5 10 0" : "M42 46h9";
179
- const leftArm = action === "typing" ? 23 + frame % 2 * 13 : action === "toolbox" ? 39 + frame % 2 * 10 : 9;
180
- const rightArm = action === "typing" ? -23 - frame % 2 * 13 : action === "toolbox" ? -39 - frame % 2 * 10 : -9;
171
+ const face = action === "concern" ? "#3f0f18" : palette.face;
172
+ const eye = action === "concern" ? "#fca5a5" : palette.screen;
173
+ const mouth = action === "success" || action === "greet" || action === "pet" ? "M39 45q5 5 10 0" : "M42 46h9";
174
+ const leftArm = action === "create" || action === "execute" ? 23 + frame % 2 * 13 : action === "prepare" || action === "read" ? 39 + frame % 2 * 10 : action === "pickedUp" ? 58 : 9;
175
+ const rightArm = action === "create" || action === "execute" ? -23 - frame % 2 * 13 : action === "prepare" || action === "read" ? -39 - frame % 2 * 10 : action === "greet" || action === "wake" ? -48 - frame % 2 * 12 : action === "pickedUp" ? -58 : -9;
181
176
  return `
182
177
  <g transform="translate(0 ${bob}) rotate(${tilt} 48 76)">
183
178
  ${renderHead(style)}
@@ -287,6 +282,7 @@ async function createDesktopPetPackage(input) {
287
282
  await mkdir(targetRoot, { recursive: true });
288
283
  const { id, dir } = await resolvePackageDir(targetRoot, (input.id?.trim() ? normalizePackageId(input.id) : null) || slugify(input.name?.trim() || titleFromPrompt(prompt), `pet-${hashString(prompt).toString(36)}`), input.overwrite === true);
289
284
  const existingManifest = input.overwrite === true ? await readExistingManifest(dir) : null;
285
+ if (existingManifest && existingManifest.schemaVersion !== 2) throw new Error(`Existing pet package must use schemaVersion 2`);
290
286
  const existingName = typeof existingManifest?.name === "string" ? existingManifest.name.trim() : "";
291
287
  const persona = normalizePersona(input.persona ?? existingManifest?.persona);
292
288
  const baseName = clampText(input.name?.trim() || existingName || titleFromPrompt(prompt), 36);
@@ -296,6 +292,7 @@ async function createDesktopPetPackage(input) {
296
292
  const thumbnailPath = join(tempDir, "thumbnail.svg");
297
293
  const manifestPath = join(tempDir, "manifest.json");
298
294
  const manifest = {
295
+ schemaVersion: 2,
299
296
  id,
300
297
  name: baseName,
301
298
  description,
@@ -1,2 +1,3 @@
1
- export declare const DESKTOP_PET_ACTIONS: readonly ["idle", "typing", "toolbox", "search", "file", "terminal", "browser", "success", "error"];
1
+ export declare const DESKTOP_PET_SCHEMA_VERSION: 2;
2
+ export declare const DESKTOP_PET_ACTIONS: readonly ["idle", "sleep", "wake", "greet", "prepare", "research", "read", "create", "execute", "wait", "success", "concern", "pet", "pickedUp", "released"];
2
3
  export type DesktopPetPackageAction = (typeof DESKTOP_PET_ACTIONS)[number];
@@ -1,14 +1,21 @@
1
1
  //#region src/pets/manifest.ts
2
+ const DESKTOP_PET_SCHEMA_VERSION = 2;
2
3
  const DESKTOP_PET_ACTIONS = [
3
4
  "idle",
4
- "typing",
5
- "toolbox",
6
- "search",
7
- "file",
8
- "terminal",
9
- "browser",
5
+ "sleep",
6
+ "wake",
7
+ "greet",
8
+ "prepare",
9
+ "research",
10
+ "read",
11
+ "create",
12
+ "execute",
13
+ "wait",
10
14
  "success",
11
- "error"
15
+ "concern",
16
+ "pet",
17
+ "pickedUp",
18
+ "released"
12
19
  ];
13
20
  //#endregion
14
- export { DESKTOP_PET_ACTIONS };
21
+ export { DESKTOP_PET_ACTIONS, DESKTOP_PET_SCHEMA_VERSION };
@@ -135,6 +135,7 @@ async function validateDesktopPetPackage(dir) {
135
135
  };
136
136
  }
137
137
  if (typeof manifest.id !== "string" || !manifest.id.trim()) details.push("id is required");
138
+ if (manifest.schemaVersion !== 2) details.push(`schemaVersion must be 2`);
138
139
  if (typeof manifest.name !== "string" || !manifest.name.trim()) details.push("name is required");
139
140
  if (typeof manifest.description !== "string" || !manifest.description.trim()) details.push("description is required");
140
141
  if (positiveNumber(manifest.canvasWidth) === null || positiveNumber(manifest.canvasHeight) === null) details.push("canvasWidth and canvasHeight must be positive numbers");
@@ -143,9 +144,13 @@ async function validateDesktopPetPackage(dir) {
143
144
  const rawAnimations = manifest.animations && typeof manifest.animations === "object" ? manifest.animations : null;
144
145
  if (!rawAnimations) details.push("animations is required");
145
146
  const animations = [];
146
- if (rawAnimations) for (const action of DESKTOP_PET_ACTIONS) {
147
- const animation = parseAnimation(action, rawAnimations[action], details);
148
- if (animation) animations.push(animation);
147
+ if (rawAnimations) {
148
+ const supportedActions = new Set(DESKTOP_PET_ACTIONS);
149
+ for (const action of Object.keys(rawAnimations)) if (!supportedActions.has(action)) details.push(`${action}: unsupported animation action`);
150
+ for (const action of DESKTOP_PET_ACTIONS) {
151
+ const animation = parseAnimation(action, rawAnimations[action], details);
152
+ if (animation) animations.push(animation);
153
+ }
149
154
  }
150
155
  for (const animation of animations) {
151
156
  const sourcePath = safeResolve(dir, animation.src);
@@ -0,0 +1,4 @@
1
+ import type { KnowledgeSourceItem } from '../knowledge/types.js';
2
+ import type { FocusCalendarSignal, FocusView } from './types.js';
3
+ export declare function buildFocusCalendarSignals(items: KnowledgeSourceItem[], focuses: FocusView[], nowMs?: number): FocusCalendarSignal[];
4
+ export declare function listFocusCalendarSignals(focuses: FocusView[], nowMs?: number): FocusCalendarSignal[];
@@ -0,0 +1,120 @@
1
+ import { listKnowledgeSourceItems } from "../storage/sqlite/knowledge-repository.js";
2
+ //#region src/proactive/calendar-signals.ts
3
+ const LOOKAHEAD_MS = 7 * 864e5;
4
+ const FIELD_LIMIT = 8;
5
+ const TITLE_KEYS = new Set([
6
+ "summary",
7
+ "title",
8
+ "name",
9
+ "subject"
10
+ ]);
11
+ const DESCRIPTION_KEYS = new Set([
12
+ "description",
13
+ "notes",
14
+ "body",
15
+ "agenda"
16
+ ]);
17
+ const START_KEYS = new Set([
18
+ "start",
19
+ "starttime",
20
+ "start_time",
21
+ "startat",
22
+ "start_at",
23
+ "datetime"
24
+ ]);
25
+ const END_KEYS = new Set([
26
+ "end",
27
+ "endtime",
28
+ "end_time",
29
+ "endat",
30
+ "end_at"
31
+ ]);
32
+ const STOP_WORDS = new Set([
33
+ "about",
34
+ "after",
35
+ "before",
36
+ "from",
37
+ "have",
38
+ "meeting",
39
+ "project",
40
+ "review",
41
+ "the",
42
+ "this",
43
+ "with"
44
+ ]);
45
+ function parsePayload(item) {
46
+ if (!item.normalizedText) return null;
47
+ try {
48
+ return JSON.parse(item.normalizedText);
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+ function findStrings(value, keys, depth = 0) {
54
+ if (depth > 6 || value == null) return [];
55
+ if (Array.isArray(value)) return value.flatMap((item) => findStrings(item, keys, depth + 1)).slice(0, FIELD_LIMIT);
56
+ if (typeof value !== "object") return [];
57
+ const record = value;
58
+ const direct = Object.entries(record).flatMap(([key, nested]) => {
59
+ if (!keys.has(key.toLowerCase().replaceAll("-", "_"))) return [];
60
+ if (typeof nested === "string") return [nested];
61
+ return findStrings(nested, new Set([
62
+ "datetime",
63
+ "date",
64
+ "value"
65
+ ]), depth + 1);
66
+ });
67
+ if (direct.length > 0) return direct.slice(0, FIELD_LIMIT);
68
+ return Object.values(record).flatMap((nested) => findStrings(nested, keys, depth + 1)).slice(0, FIELD_LIMIT);
69
+ }
70
+ function firstDate(payload, keys) {
71
+ for (const value of findStrings(payload, keys)) {
72
+ const parsed = Date.parse(value);
73
+ if (Number.isFinite(parsed)) return parsed;
74
+ }
75
+ }
76
+ function terms(value) {
77
+ const normalized = value.toLocaleLowerCase();
78
+ const result = new Set(normalized.match(/[a-z0-9][a-z0-9_-]{2,}/g)?.filter((word) => !STOP_WORDS.has(word)) ?? []);
79
+ for (const sequence of normalized.match(/[\p{Script=Han}]{2,}/gu) ?? []) for (let index = 0; index < sequence.length - 1; index += 1) result.add(sequence.slice(index, index + 2));
80
+ return result;
81
+ }
82
+ function relatedFocus(focuses, text) {
83
+ const eventTerms = terms(text);
84
+ if (eventTerms.size === 0) return void 0;
85
+ return focuses.map((focus) => ({
86
+ focus,
87
+ score: [...terms(`${focus.title} ${focus.summary}`)].filter((term) => eventTerms.has(term)).length
88
+ })).filter((candidate) => candidate.score >= 2).toSorted((left, right) => right.score - left.score || right.focus.focusScore - left.focus.focusScore)[0]?.focus;
89
+ }
90
+ function buildFocusCalendarSignals(items, focuses, nowMs = Date.now()) {
91
+ const end = nowMs + LOOKAHEAD_MS;
92
+ const signals = items.flatMap((item) => {
93
+ if (item.itemType !== "calendar_event") return [];
94
+ const payload = parsePayload(item);
95
+ const title = findStrings(payload, TITLE_KEYS)[0]?.trim();
96
+ const startsAt = firstDate(payload, START_KEYS);
97
+ if (!title || startsAt == null || startsAt < nowMs || startsAt > end) return [];
98
+ const focus = relatedFocus(focuses, `${title} ${findStrings(payload, DESCRIPTION_KEYS).join(" ")}`);
99
+ if (!focus) return [];
100
+ const endsAt = firstDate(payload, END_KEYS);
101
+ return [{
102
+ id: item.id,
103
+ focusId: focus.id,
104
+ focusTitle: focus.title,
105
+ title,
106
+ startsAt,
107
+ ...endsAt != null ? { endsAt } : {},
108
+ sourceInstanceId: item.sourceInstanceId
109
+ }];
110
+ }).toSorted((left, right) => left.startsAt - right.startsAt);
111
+ return signals.filter((signal, index) => signals.findIndex((candidate) => candidate.focusId === signal.focusId && candidate.startsAt === signal.startsAt && candidate.title.toLocaleLowerCase() === signal.title.toLocaleLowerCase()) === index).slice(0, 20);
112
+ }
113
+ function listFocusCalendarSignals(focuses, nowMs = Date.now()) {
114
+ return buildFocusCalendarSignals(listKnowledgeSourceItems({
115
+ itemType: "calendar_event",
116
+ limit: 500
117
+ }), focuses, nowMs);
118
+ }
119
+ //#endregion
120
+ export { buildFocusCalendarSignals, listFocusCalendarSignals };
@@ -0,0 +1,62 @@
1
+ import type { Automation, AutomationRun } from '../automations/index.js';
2
+ import type { FocusView, FocusWatch, FocusWatchKind } from './types.js';
3
+ interface AutomationPort {
4
+ create(input: {
5
+ name: string;
6
+ description: string;
7
+ projectId?: string;
8
+ trigger: {
9
+ kind: 'schedule';
10
+ schedule: {
11
+ kind: 'interval';
12
+ everyMs: number;
13
+ };
14
+ };
15
+ action: {
16
+ kind: 'agent';
17
+ instruction: string;
18
+ timeoutSeconds: number;
19
+ };
20
+ safety: {
21
+ mode: 'suggest_only';
22
+ };
23
+ afterRun: {
24
+ kind: 'none';
25
+ };
26
+ reliability: {
27
+ timeoutSeconds: number;
28
+ disableAfterConsecutiveFailures: number;
29
+ };
30
+ }): Promise<Automation>;
31
+ pause(id: string): Promise<Automation | null>;
32
+ resume(id: string): Promise<Automation | null>;
33
+ update(id: string, patch: {
34
+ action: {
35
+ kind: 'agent';
36
+ instruction: string;
37
+ timeoutSeconds: number;
38
+ };
39
+ }): Promise<Automation | null>;
40
+ runNow(id: string): Promise<AutomationRun>;
41
+ }
42
+ export declare class FocusService {
43
+ private readonly automations;
44
+ private readonly goals;
45
+ constructor(automations: AutomationPort);
46
+ list(options?: {
47
+ includeUnreviewed?: boolean;
48
+ }): FocusView[];
49
+ confirm(id: string): FocusView | null;
50
+ activateTrial(input: {
51
+ threadId: string;
52
+ kind?: FocusWatchKind;
53
+ eventContext?: string;
54
+ }): Promise<{
55
+ focus: FocusView;
56
+ watch: FocusWatch;
57
+ }>;
58
+ pauseWatch(id: string): Promise<FocusWatch | null>;
59
+ reconcileExpiredTrials(nowMs?: number): Promise<number>;
60
+ recordInsightFeedback(watchId: string, useful: boolean): Promise<FocusWatch | null>;
61
+ }
62
+ export {};