@xopcai/xopc 0.0.284 → 0.0.285

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 (224) 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/{activity-store-CwbIMGLY.js → activity-store-B0BTIk9j.js} +1 -1
  4. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-DjiOld-S.js → agent-avatar-dicebear-Dc-0QZTQ.js} +1 -1
  5. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-CcmqK1Sc.js +1 -0
  6. package/dist/gateway/static/root/assets/agents-B_7PVt7S.js +1 -0
  7. package/dist/gateway/static/root/assets/api-COG_Tzpv.js +1 -0
  8. package/dist/gateway/static/root/assets/{api-3lphBVuY.js → api-Coly9ab6.js} +1 -1
  9. package/dist/gateway/static/root/assets/{app-management-settings-panel-B2nwCXxE.js → app-management-settings-panel-TWBO-msg.js} +1 -1
  10. package/dist/gateway/static/root/assets/{appearance-settings-BXO4Ej88.js → appearance-settings-CpTZ2KuT.js} +1 -1
  11. package/dist/gateway/static/root/assets/apps-page-6qxxKFsK.js +1 -0
  12. package/dist/gateway/static/root/assets/{archive-plugin-6ZRqNjla.js → archive-plugin-z_u6alob.js} +1 -1
  13. package/dist/gateway/static/root/assets/{attachment-load-CmPZalcW.js → attachment-load-C2nS04TC.js} +1 -1
  14. package/dist/gateway/static/root/assets/{automations-page-Bc3WbK5n.js → automations-page-aIBsw9zR.js} +1 -1
  15. package/dist/gateway/static/root/assets/automations-workspace-BRQlU4qk.js +6 -0
  16. package/dist/gateway/static/root/assets/{binary-plugins-OWR6cxpc.js → binary-plugins-CGmXoZnl.js} +1 -1
  17. package/dist/gateway/static/root/assets/{block-editor-Ca-tDQXm.js → block-editor-CtvzCZhC.js} +3 -3
  18. package/dist/gateway/static/root/assets/{browser-automation-inputs-Dxy2_p3U.js → browser-automation-inputs-BOTU1isH.js} +1 -1
  19. package/dist/gateway/static/root/assets/{browser-automations-page-CibQIxpi.js → browser-automations-page-BwU5V8J0.js} +2 -2
  20. package/dist/gateway/static/root/assets/browser-settings-page-DDjbDeHQ.js +8 -0
  21. package/dist/gateway/static/root/assets/capabilities-settings-panel-DM1Bcf1x.js +8 -0
  22. package/dist/gateway/static/root/assets/channels-settings-Bq36CUxL.js +1 -0
  23. package/dist/gateway/static/root/assets/{channels-status-swr-Bh-G9mdZ.js → channels-status-swr-Dfi0Desh.js} +1 -1
  24. package/dist/gateway/static/root/assets/{chat-terminal-dock-lbzbSpV3.js → chat-terminal-dock-B0MvcO6j.js} +2 -2
  25. package/dist/gateway/static/root/assets/connectors-page-L8JehrxF.js +2 -0
  26. package/dist/gateway/static/root/assets/{date-picker-tYvbTfZT.js → date-picker-DXOvbHCr.js} +1 -1
  27. package/dist/gateway/static/root/assets/{dependency-picker-B8yo7-7V.js → dependency-picker-BEnC-eS0.js} +1 -1
  28. package/dist/gateway/static/root/assets/{desktop-pet-BUGkGB4-.js → desktop-pet-DtPNvznL.js} +1 -1
  29. package/dist/gateway/static/root/assets/{desktop-pet-settings-DNeVGZNp.js → desktop-pet-settings-CYyjcSn6.js} +1 -1
  30. package/dist/gateway/static/root/assets/device-pairing-wizard-BPMGvAKE.js +1 -0
  31. package/dist/gateway/static/root/assets/{extension-debug-page-CiK0QI2p.js → extension-debug-page-H_aPMGXW.js} +1 -1
  32. package/dist/gateway/static/root/assets/{extension-page-CirPNHJd.js → extension-page-C1JnpeMt.js} +1 -1
  33. package/dist/gateway/static/root/assets/{extension-settings-page-Baua1_Le.js → extension-settings-page-C75kCOqe.js} +1 -1
  34. package/dist/gateway/static/root/assets/{gateway-config-swr-Co4hHe0i.js → gateway-config-swr-G2lQt7Ak.js} +1 -1
  35. package/dist/gateway/static/root/assets/gateway-settings-BCF732TU.js +1 -0
  36. package/dist/gateway/static/root/assets/{gateway-startup-retry-DBPNJG-e.js → gateway-startup-retry-GP31PUrG.js} +1 -1
  37. package/dist/gateway/static/root/assets/{heartbeat-settings-DTM_kYpE.js → heartbeat-settings-C7fjiowt.js} +1 -1
  38. package/dist/gateway/static/root/assets/home-page-DZXeXdUL.js +1 -0
  39. package/dist/gateway/static/root/assets/{image-generation-api-D0rMTumD.js → image-generation-api-CKFhfHSw.js} +1 -1
  40. package/dist/gateway/static/root/assets/imports-page-GROumhj8.js +1 -0
  41. package/dist/gateway/static/root/assets/{index-C9aQA4Dr.js → index-ChIrvv3t.js} +47 -47
  42. package/dist/gateway/static/root/assets/index-R1VNlHjj.css +2 -0
  43. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-CMUaF61L.js → keyboard-shortcuts-settings-zcI822m_.js} +1 -1
  44. package/dist/gateway/static/root/assets/local-app-workbench-page-CAEKGTAg.js +2 -0
  45. package/dist/gateway/static/root/assets/{local-apps-page-CHxyT09t.js → local-apps-page-BtgDJ92Q.js} +1 -1
  46. package/dist/gateway/static/root/assets/{locale-store-DO6JJ1l2.js → locale-store-DlOv7896.js} +1 -1
  47. package/dist/gateway/static/root/assets/logs-page-DHZ4BEkG.js +2 -0
  48. package/dist/gateway/static/root/assets/{management-settings-jXbA2S54.js → management-settings-DasZxCyD.js} +1 -1
  49. package/dist/gateway/static/root/assets/{markdown-view-CKInRsDh.js → markdown-view-6oYdBahT.js} +1 -1
  50. package/dist/gateway/static/root/assets/{media-plugins-BaPkSvW7.js → media-plugins-CxthbBvk.js} +1 -1
  51. package/dist/gateway/static/root/assets/{messages-BgZRsCkG.js → messages-Ls1Aaiqd.js} +1 -1
  52. package/dist/gateway/static/root/assets/note-detail-panel-BcWq3ddw.js +5 -0
  53. package/dist/gateway/static/root/assets/notes-page-DfZ8QWyv.js +8 -0
  54. package/dist/gateway/static/root/assets/{page-tabs-CdYemUOu.js → page-tabs-BG39KidE.js} +1 -1
  55. package/dist/gateway/static/root/assets/{preview-open-alternatives-DzgF1yYO.js → preview-open-alternatives-Bevpbq9f.js} +1 -1
  56. package/dist/gateway/static/root/assets/proactive-page-D2U_xQaS.js +1 -0
  57. package/dist/gateway/static/root/assets/{product-open-page-iQ-eW6Aj.js → product-open-page-Bi1seZpM.js} +1 -1
  58. package/dist/gateway/static/root/assets/project-detail-page-CzlGTW4F.js +1 -0
  59. package/dist/gateway/static/root/assets/projects-page-mQsM-TsP.js +1 -0
  60. package/dist/gateway/static/root/assets/{registry-api-DMNudT60.js → registry-api-D0ddwBFj.js} +1 -1
  61. package/dist/gateway/static/root/assets/remote-access-hub-Cb56JcO1.js +1 -0
  62. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-u_wVepf7.js → runtime-tools-settings-panel-Wkru8_My.js} +2 -2
  63. package/dist/gateway/static/root/assets/{schema-form-YdZj2xdI.js → schema-form-CSMRzj9Y.js} +1 -1
  64. package/dist/gateway/static/root/assets/sessions-page-Hj9uFcIO.js +1 -0
  65. package/dist/gateway/static/root/assets/settings-advanced-gate-CV4-_EVA.js +1 -0
  66. package/dist/gateway/static/root/assets/{settings-form-section-Bw6xKOX5.js → settings-form-section-wcAdCkK8.js} +1 -1
  67. package/dist/gateway/static/root/assets/{settings-page-CV8kVdnE.js → settings-page-BcQGAnCs.js} +1 -1
  68. package/dist/gateway/static/root/assets/setup-status-panel-DzxmsaQG.js +1 -0
  69. package/dist/gateway/static/root/assets/{share-preview-page-BHwjHlrV.js → share-preview-page-DQocUl-1.js} +1 -1
  70. package/dist/gateway/static/root/assets/shares-settings-xlJh1ccG.js +1 -0
  71. package/dist/gateway/static/root/assets/skill-reload-api-wSXJRSxX.js +1 -0
  72. package/dist/gateway/static/root/assets/{skills-page-DRKdb6O5.js → skills-page-AmqtBJo7.js} +1 -1
  73. package/dist/gateway/static/root/assets/{skills-page.utils-DYfaLtts.js → skills-page.utils-DtlfCgJp.js} +1 -1
  74. package/dist/gateway/static/root/assets/{system-settings-panel-CWrWpKV-.js → system-settings-panel-Ct698Nf-.js} +1 -1
  75. package/dist/gateway/static/root/assets/task-detail-page-sQpwq5-c.js +1 -0
  76. package/dist/gateway/static/root/assets/{theme-store-CDc7Wqy5.js → theme-store-ng8KJsAT.js} +1 -1
  77. package/dist/gateway/static/root/assets/{tunnel-api-Bmsy-5Fs.js → tunnel-api-Boajol0M.js} +1 -1
  78. package/dist/gateway/static/root/assets/{use-autosave-D-FRib85.js → use-autosave-BSyxRHt2.js} +1 -1
  79. package/dist/gateway/static/root/assets/user-model-page-DIEHnAER.js +2 -0
  80. package/dist/gateway/static/root/assets/{voice-api-key-field-iQ7EBzII.js → voice-api-key-field-SE3OQI1s.js} +1 -1
  81. package/dist/gateway/static/root/assets/{voice-config-api-DNMWA2tx.js → voice-config-api-VNuCmdFe.js} +1 -1
  82. package/dist/gateway/static/root/assets/{work-discovery-overlay-BesuhDlH.js → work-discovery-overlay-Dn1vy2oq.js} +1 -1
  83. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-Du81FoCy.js → workflow-run-setup-panel-BJEfGY-k.js} +2 -2
  84. package/dist/gateway/static/root/assets/workflows-page-DgRNIyr7.js +6 -0
  85. package/dist/gateway/static/root/index.html +10 -10
  86. package/dist/package.js +1 -1
  87. package/dist/packages/gateway-contract/src/proactive.js +13 -4
  88. package/dist/src/agent/coding/repository-instructions.js +1 -1
  89. package/dist/src/agent/coding/workspace-revision.js +1 -1
  90. package/dist/src/agent/commands/command-isolation.js +1 -1
  91. package/dist/src/agent/tools/factory.js +7 -3
  92. package/dist/src/agent/tools/review-workspace.js +1 -1
  93. package/dist/src/browser/providers/browser-ext-install.js +10 -2
  94. package/dist/src/cli/command-manifest.js +2 -2
  95. package/dist/src/cli/commands/agent.js +2 -2
  96. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  97. package/dist/src/cli/commands/gateway/lifecycle.js +2 -2
  98. package/dist/src/cli/commands/gateway/run-foreground.js +3 -0
  99. package/dist/src/cli/commands/import.js +54 -6
  100. package/dist/src/cli/commands/models.js +1 -1
  101. package/dist/src/cli/commands/onboard.js +1 -1
  102. package/dist/src/cli/commands/platform.js +1 -1
  103. package/dist/src/cli/commands/profile.js +1 -1
  104. package/dist/src/cli/commands/tunnel.js +1 -1
  105. package/dist/src/cli/commands/update.js +1 -1
  106. package/dist/src/cli/extension-cli-register.js +1 -1
  107. package/dist/src/config/index.js +1 -1
  108. package/dist/src/discussions/action-tasks.js +1 -1
  109. package/dist/src/discussions/edits.js +1 -1
  110. package/dist/src/discussions/index.js +1 -1
  111. package/dist/src/discussions/organizer.js +1 -1
  112. package/dist/src/discussions/recording.js +1 -1
  113. package/dist/src/discussions/sealer.js +1 -1
  114. package/dist/src/discussions/service.js +2 -2
  115. package/dist/src/endpoint-tools/invocation-service.d.ts +2 -0
  116. package/dist/src/endpoint-tools/invocation-service.js +48 -19
  117. package/dist/src/gateway/heartbeat/check-store.d.ts +18 -0
  118. package/dist/src/gateway/heartbeat/check-store.js +73 -0
  119. package/dist/src/gateway/heartbeat/service.d.ts +12 -3
  120. package/dist/src/gateway/heartbeat/service.js +62 -45
  121. package/dist/src/gateway/hono/routes/discussions.js +2 -2
  122. package/dist/src/gateway/hono/routes/imports.js +18 -6
  123. package/dist/src/gateway/hono/routes/proactive-control.js +2 -1
  124. package/dist/src/gateway/run-loop.js +1 -1
  125. package/dist/src/gateway/service.d.ts +7 -0
  126. package/dist/src/gateway/service.js +24 -9
  127. package/dist/src/heartbeat/index.js +1 -1
  128. package/dist/src/imports/importService.d.ts +17 -12
  129. package/dist/src/imports/importService.js +67 -70
  130. package/dist/src/imports/inventory.d.ts +11 -0
  131. package/dist/src/imports/inventory.js +202 -0
  132. package/dist/src/imports/productImport.d.ts +4 -4
  133. package/dist/src/imports/productImport.js +201 -108
  134. package/dist/src/imports/runtime.js +7 -2
  135. package/dist/src/imports/scanner.d.ts +1 -1
  136. package/dist/src/imports/scanner.js +4 -4
  137. package/dist/src/imports/types.d.ts +53 -0
  138. package/dist/src/infra/background-task.d.ts +2 -0
  139. package/dist/src/infra/background-task.js +25 -0
  140. package/dist/src/infra/process-diagnostics.d.ts +5 -0
  141. package/dist/src/infra/process-diagnostics.js +97 -0
  142. package/dist/src/infra/unhandled-rejections.js +6 -3
  143. package/dist/src/infra/update-restart.js +1 -1
  144. package/dist/src/knowledge/ingestion-service.js +1 -0
  145. package/dist/src/media-shared/http/ssrf-guard.js +1 -1
  146. package/dist/src/notifications/proactive-policy.js +7 -5
  147. package/dist/src/proactive/execution/authorization.d.ts +2 -0
  148. package/dist/src/proactive/execution/authorization.js +10 -1
  149. package/dist/src/proactive/execution/context.js +1 -0
  150. package/dist/src/proactive/execution/repository.d.ts +2 -0
  151. package/dist/src/proactive/execution/repository.js +19 -2
  152. package/dist/src/proactive/execution/worker.js +14 -4
  153. package/dist/src/proactive/experience.d.ts +4 -1
  154. package/dist/src/proactive/experience.js +3 -0
  155. package/dist/src/proactive/follow-ups.d.ts +5 -0
  156. package/dist/src/proactive/follow-ups.js +18 -7
  157. package/dist/src/proactive/inbox/cards.js +2 -1
  158. package/dist/src/proactive/inbox/delivery.js +6 -7
  159. package/dist/src/proactive/inbox/digest.d.ts +1 -1
  160. package/dist/src/proactive/inbox/digest.js +23 -13
  161. package/dist/src/proactive/inbox/notification-policy.d.ts +1 -1
  162. package/dist/src/proactive/inbox/notification-policy.js +9 -6
  163. package/dist/src/proactive/inbox/repository.js +3 -2
  164. package/dist/src/proactive/inbox/service.d.ts +6 -0
  165. package/dist/src/proactive/inbox/service.js +18 -0
  166. package/dist/src/proactive/inbox/types.d.ts +1 -0
  167. package/dist/src/proactive/policy/presence.d.ts +1 -1
  168. package/dist/src/proactive/policy/presence.js +7 -5
  169. package/dist/src/proactive/policy/service.d.ts +8 -5
  170. package/dist/src/proactive/policy/service.js +12 -12
  171. package/dist/src/proactive/scenarios/control.d.ts +3 -3
  172. package/dist/src/proactive/scenarios/control.js +12 -2
  173. package/dist/src/proactive/source-freshness.d.ts +8 -0
  174. package/dist/src/proactive/source-freshness.js +27 -0
  175. package/dist/src/proactive/status.d.ts +2 -0
  176. package/dist/src/proactive/status.js +21 -0
  177. package/dist/src/proactive/temporal/worker.js +2 -1
  178. package/dist/src/realtime/runtime.js +31 -5
  179. package/dist/src/realtime/writer.js +16 -9
  180. package/dist/src/storage/sqlite/import-repository.d.ts +2 -3
  181. package/dist/src/storage/sqlite/import-repository.js +3 -3
  182. package/dist/src/storage/sqlite/import-selection-repository.d.ts +15 -0
  183. package/dist/src/storage/sqlite/import-selection-repository.js +28 -0
  184. package/dist/src/storage/sqlite/knowledge-repository.d.ts +1 -0
  185. package/dist/src/storage/sqlite/knowledge-repository.js +2 -2
  186. package/dist/src/storage/sqlite/migrations/175_import_selections.sql +8 -0
  187. package/dist/src/storage/sqlite/migrations/176_proactive_quality.sql +5 -0
  188. package/dist/src/storage/sqlite/migrations/177_proactive_check_controls.sql +25 -0
  189. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  190. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  191. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  192. package/dist/src/storage/sqlite/schema.js +1 -1
  193. package/dist/src/work-discovery/candidate-discovery.js +1 -1
  194. package/dist/src/work-discovery/probe.js +1 -1
  195. package/package.json +2 -2
  196. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-D9qP10BK.js +0 -1
  197. package/dist/gateway/static/root/assets/agents-D-bkOuAl.js +0 -1
  198. package/dist/gateway/static/root/assets/apps-page-BKwBBwJN.js +0 -1
  199. package/dist/gateway/static/root/assets/automations-workspace-B2-f9qZu.js +0 -6
  200. package/dist/gateway/static/root/assets/browser-settings-page-Dtn4rMeq.js +0 -8
  201. package/dist/gateway/static/root/assets/capabilities-settings-panel-DiwtR9MD.js +0 -8
  202. package/dist/gateway/static/root/assets/channels-settings-pTdR1q1K.js +0 -1
  203. package/dist/gateway/static/root/assets/connectors-page-BSQ7Hs2w.js +0 -2
  204. package/dist/gateway/static/root/assets/device-pairing-wizard-CYkT7ecp.js +0 -1
  205. package/dist/gateway/static/root/assets/gateway-settings-CzpSYWex.js +0 -1
  206. package/dist/gateway/static/root/assets/home-page-CSSJvdd6.js +0 -1
  207. package/dist/gateway/static/root/assets/imports-page-XqVVYbAp.js +0 -1
  208. package/dist/gateway/static/root/assets/index-B_Sg5BKz.css +0 -2
  209. package/dist/gateway/static/root/assets/local-app-workbench-page-CZSgTYuI.js +0 -2
  210. package/dist/gateway/static/root/assets/logs-page-z5ktnEzX.js +0 -2
  211. package/dist/gateway/static/root/assets/note-detail-panel-DSaZfkna.js +0 -5
  212. package/dist/gateway/static/root/assets/notes-page-CePEyv_C.js +0 -8
  213. package/dist/gateway/static/root/assets/proactive-page-Cy2Gm2f8.js +0 -1
  214. package/dist/gateway/static/root/assets/project-detail-page-BliwYpns.js +0 -1
  215. package/dist/gateway/static/root/assets/projects-page-Dm-BqnQn.js +0 -1
  216. package/dist/gateway/static/root/assets/remote-access-hub-C_XuQ9XR.js +0 -1
  217. package/dist/gateway/static/root/assets/sessions-page-Drt1aS2G.js +0 -1
  218. package/dist/gateway/static/root/assets/settings-advanced-gate-BAUd06_9.js +0 -1
  219. package/dist/gateway/static/root/assets/setup-status-panel-BHI9e5Tw.js +0 -1
  220. package/dist/gateway/static/root/assets/shares-settings-DOAOjZBQ.js +0 -1
  221. package/dist/gateway/static/root/assets/skill-reload-api-DU0VQpHj.js +0 -1
  222. package/dist/gateway/static/root/assets/task-detail-page-DiPgMmBM.js +0 -1
  223. package/dist/gateway/static/root/assets/user-model-page-DxRaRdA0.js +0 -2
  224. package/dist/gateway/static/root/assets/workflows-page-CZikdgNw.js +0 -6
@@ -1,9 +1,11 @@
1
1
  import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
3
  import { shouldSilence, stripHeartbeatToken } from "../../heartbeat/tokens.js";
4
+ import { proactiveChecksAllowed, proactivePreferences } from "../../proactive/policy/service.js";
5
+ import { beginHeartbeatCheck, completeHeartbeatCheck, deliverHeartbeatChecks, recentHeartbeatChecks, recoverHeartbeatChecks } from "./check-store.js";
6
+ import { appendCronEventLines } from "../../heartbeat/event-prompt.js";
4
7
  import { isWithinActiveHours } from "../../heartbeat/active-hours.js";
5
8
  import { isHeartbeatContentEmpty } from "../../heartbeat/content-check.js";
6
- import { appendCronEventLines } from "../../heartbeat/event-prompt.js";
7
9
  import { createHeartbeatWake } from "../../heartbeat/wake.js";
8
10
  import { resolveHeartbeatMdPath } from "../workspace-heartbeat-path.js";
9
11
  import { readFile } from "fs/promises";
@@ -31,22 +33,41 @@ function heartbeatRunnerConfigFromConfig(cfg) {
31
33
  var HeartbeatService = class {
32
34
  intervalId = null;
33
35
  wake;
34
- lastHeartbeatText = "";
35
- lastHeartbeatAt = 0;
36
+ nextCheckAt = null;
37
+ deliveryTimer = null;
38
+ delivering = false;
39
+ recovered = false;
36
40
  runnerConfig = null;
37
41
  constructor(deps) {
38
42
  this.deps = deps;
39
- this.wake = createHeartbeatWake((reasons) => this.runHeartbeatOnce(reasons));
43
+ this.wake = createHeartbeatWake(async (reasons) => {
44
+ try {
45
+ await this.runHeartbeatOnce(reasons);
46
+ } catch (err) {
47
+ log.error({ err }, "Heartbeat check failed unexpectedly");
48
+ }
49
+ });
40
50
  }
41
51
  start(config) {
52
+ if (this.runnerConfig) this.stop();
42
53
  if (!config.enabled) {
43
54
  log.info("Heartbeat disabled");
44
55
  this.runnerConfig = null;
45
56
  return;
46
57
  }
47
58
  this.runnerConfig = config;
59
+ if (!this.recovered) {
60
+ recoverHeartbeatChecks(this.deps.getWorkspace());
61
+ this.recovered = true;
62
+ }
63
+ this.deliveryTimer = setInterval(() => {
64
+ this.drainDeliveries().catch((err) => log.warn({ err }, "Heartbeat delivery check failed"));
65
+ }, 1e4);
66
+ this.deliveryTimer.unref?.();
67
+ this.nextCheckAt = new Date(Date.now() + config.intervalMs).toISOString();
48
68
  log.info({ intervalMs: config.intervalMs }, "Heartbeat timer started (interval wake)");
49
69
  this.intervalId = setInterval(() => {
70
+ this.nextCheckAt = new Date(Date.now() + config.intervalMs).toISOString();
50
71
  this.wake.request({ reason: "interval" });
51
72
  }, config.intervalMs);
52
73
  this.intervalId.unref?.();
@@ -56,6 +77,9 @@ var HeartbeatService = class {
56
77
  this.wake.request({ reason: opts?.reason ?? "manual" });
57
78
  }
58
79
  stop() {
80
+ if (this.deliveryTimer) clearInterval(this.deliveryTimer);
81
+ this.deliveryTimer = null;
82
+ this.nextCheckAt = null;
59
83
  if (this.intervalId) {
60
84
  clearInterval(this.intervalId);
61
85
  this.intervalId = null;
@@ -73,6 +97,24 @@ var HeartbeatService = class {
73
97
  isRunning() {
74
98
  return this.intervalId !== null;
75
99
  }
100
+ status() {
101
+ const preferences = proactivePreferences(this.deps.getWorkspace());
102
+ return {
103
+ enabled: Boolean(this.runnerConfig?.enabled),
104
+ checksAllowed: proactiveChecksAllowed(preferences),
105
+ nextCheckAt: proactiveChecksAllowed(preferences) ? this.nextCheckAt : null,
106
+ recent: recentHeartbeatChecks(this.deps.getWorkspace())
107
+ };
108
+ }
109
+ async drainDeliveries() {
110
+ if (this.delivering || !this.runnerConfig?.enabled) return;
111
+ this.delivering = true;
112
+ try {
113
+ await deliverHeartbeatChecks(this.deps.getWorkspace(), this.deps.messageBus, this.runnerConfig.target?.trim(), this.runnerConfig.targetChatId?.trim());
114
+ } finally {
115
+ this.delivering = false;
116
+ }
117
+ }
76
118
  async runHeartbeatOnce(reasons) {
77
119
  const reasonSummary = [...new Set(reasons)].join(", ") || "unknown";
78
120
  const cfg = this.runnerConfig;
@@ -80,36 +122,34 @@ var HeartbeatService = class {
80
122
  log.debug({ reasons: reasonSummary }, "Heartbeat: skip (disabled)");
81
123
  return;
82
124
  }
125
+ if (!proactiveChecksAllowed(proactivePreferences(this.deps.getWorkspace()))) return;
83
126
  if (cfg.activeHours && !isWithinActiveHours(cfg.activeHours)) {
84
127
  log.debug({ reasons: reasonSummary }, "Heartbeat: skip (outside active hours)");
85
128
  return;
86
129
  }
130
+ const checkId = beginHeartbeatCheck(this.deps.getWorkspace());
87
131
  const heartbeatPath = resolveHeartbeatMdPath(this.deps.getConfig());
88
132
  if (!heartbeatPath) {
89
- log.debug({ reasons: reasonSummary }, "Heartbeat: skip (no HEARTBEAT path)");
133
+ completeHeartbeatCheck(checkId, "blocked", "No workspace checklist path");
90
134
  return;
91
135
  }
92
136
  let heartbeatContent;
93
137
  try {
94
138
  const raw = await readFile(heartbeatPath, "utf-8");
95
139
  if (isHeartbeatContentEmpty(raw)) {
96
- log.debug({
97
- path: heartbeatPath,
98
- reasons: reasonSummary
99
- }, "Heartbeat: skip (HEARTBEAT.md empty)");
140
+ completeHeartbeatCheck(checkId, "empty_checklist");
100
141
  return;
101
142
  }
102
143
  heartbeatContent = raw.trim();
103
144
  } catch {
104
- log.debug({
105
- path: heartbeatPath,
106
- reasons: reasonSummary
107
- }, "Heartbeat: HEARTBEAT.md missing; continuing");
145
+ completeHeartbeatCheck(checkId, "blocked", "Checklist could not be read");
146
+ return;
108
147
  }
109
148
  const sessionKey = cfg.isolatedSession ? `heartbeat:isolated:${Date.now()}` : "heartbeat:main";
110
149
  let basePrompt = (cfg.prompt?.trim() || DEFAULT_PROMPT).trim();
111
150
  if (heartbeatContent) basePrompt = `${basePrompt}\n\n---\nHEARTBEAT.md:\n${heartbeatContent}\n---`;
112
151
  basePrompt = appendCronEventLines(basePrompt, reasons);
152
+ basePrompt += "\nReturn notifications in your final response. Do not send messages, media or voice directly; delivery follows the user notification policy.";
113
153
  const prompt = `${basePrompt}\n\nCurrent time: ${(/* @__PURE__ */ new Date()).toISOString()}`;
114
154
  const ackMax = cfg.ackMaxChars ?? 300;
115
155
  log.debug({
@@ -143,14 +183,21 @@ var HeartbeatService = class {
143
183
  source: "heartbeat"
144
184
  });
145
185
  } catch (error) {
186
+ completeHeartbeatCheck(checkId, "failed", "Agent check failed; see gateway logs");
146
187
  log.error({ err: error }, "Heartbeat: agent call failed");
147
188
  return;
148
189
  }
190
+ if (cfg !== this.runnerConfig || !proactiveChecksAllowed(proactivePreferences(this.deps.getWorkspace()))) {
191
+ completeHeartbeatCheck(checkId, "cancelled", "Checks paused or configuration changed");
192
+ return;
193
+ }
149
194
  if (!reply?.trim()) {
195
+ completeHeartbeatCheck(checkId, "no_change");
150
196
  log.debug({ reasons: reasonSummary }, "Heartbeat: skip (empty model reply)");
151
197
  return;
152
198
  }
153
199
  if (shouldSilence(reply, ackMax)) {
200
+ completeHeartbeatCheck(checkId, "no_change");
154
201
  log.info({
155
202
  ackMax,
156
203
  replyChars: reply.length,
@@ -159,38 +206,8 @@ var HeartbeatService = class {
159
206
  return;
160
207
  }
161
208
  const { stripped } = stripHeartbeatToken(reply);
162
- const finalText = stripped || reply.trim();
163
- if (this.isDuplicate(finalText)) {
164
- log.info({
165
- finalTextChars: finalText.length,
166
- reasons: reasonSummary
167
- }, "Heartbeat: not sent — duplicate within 24h");
168
- return;
169
- }
170
- const target = cfg.target?.trim();
171
- const targetChatId = cfg.targetChatId?.trim();
172
- if (Boolean(target && targetChatId)) {
173
- await this.deps.messageBus.publishOutbound({
174
- channel: target,
175
- chat_id: targetChatId,
176
- content: finalText,
177
- type: "message"
178
- });
179
- log.info({
180
- reasons: reasonSummary,
181
- channel: target,
182
- chatId: targetChatId,
183
- contentChars: finalText.length
184
- }, "Heartbeat: sent — outbound queued");
185
- } else log.info({
186
- reasons: reasonSummary,
187
- finalTextChars: finalText.length
188
- }, "Heartbeat: not sent — no delivery target (set gateway.heartbeat.target + targetChatId)");
189
- this.lastHeartbeatText = finalText;
190
- this.lastHeartbeatAt = Date.now();
191
- }
192
- isDuplicate(text) {
193
- return text.trim() === this.lastHeartbeatText.trim() && Date.now() - this.lastHeartbeatAt < 1440 * 60 * 1e3;
209
+ completeHeartbeatCheck(checkId, "prepared", void 0, stripped || reply.trim(), cfg.target?.trim(), cfg.targetChatId?.trim());
210
+ await this.drainDeliveries();
194
211
  }
195
212
  };
196
213
  //#endregion
@@ -1,11 +1,11 @@
1
1
  import { DISCUSSION_STATUSES } from "../../../../packages/gateway-contract/src/discussions.js";
2
2
  import { hasGatewayScope } from "../../security/gateway-scopes.js";
3
3
  import { DiscussionServiceError } from "../../../discussions/errors.js";
4
- import { convertMeetingAction, meetingActionTasks } from "../../../discussions/action-tasks.js";
5
4
  import { MeetingEditSchema } from "../../../discussions/edits.js";
6
- import { exportDiscussion } from "../../../discussions/export.js";
7
5
  import "../../../discussions/index.js";
8
6
  import { getGatewayPrincipal } from "../../security/gateway-principal.js";
7
+ import { convertMeetingAction, meetingActionTasks } from "../../../discussions/action-tasks.js";
8
+ import { exportDiscussion } from "../../../discussions/export.js";
9
9
  import { createReadStream } from "node:fs";
10
10
  import { stat } from "node:fs/promises";
11
11
  import { Readable } from "node:stream";
@@ -1,12 +1,20 @@
1
1
  import { getGatewayPrincipal } from "../../security/gateway-principal.js";
2
2
  import { ImportError } from "../../../imports/types.js";
3
3
  import { isImportSource } from "../../../imports/sources.js";
4
+ import { ImportSelectionRepository } from "../../../storage/sqlite/import-selection-repository.js";
5
+ import { getLiveInventory, previewInventoryItem, publicInventory, scanProduct } from "../../../imports/inventory.js";
6
+ import { importSelection, listImportSources } from "../../../imports/productImport.js";
4
7
  import { createRuntimeImportService } from "../../../imports/runtime.js";
5
- import { importProduct, listImportSources } from "../../../imports/productImport.js";
6
8
  import { z } from "zod";
7
9
  //#region src/gateway/hono/routes/imports.ts
8
- const requestSchema = z.object({ requestId: z.string().uuid() }).strict();
10
+ const selectionSchema = z.object({
11
+ inventoryId: z.string().uuid(),
12
+ candidateIds: z.array(z.string().uuid()).min(1).max(5e3),
13
+ requestId: z.string().uuid(),
14
+ retryOf: z.string().uuid().optional()
15
+ }).strict();
9
16
  function registerImportRoutes(app, deps) {
17
+ const service = () => createRuntimeImportService("gateway-owner", () => deps.service.agentService.refreshSkillsAfterDiskChange(), (root) => deps.service.agentService.getWorkspaceTrust(root).trusted);
10
18
  const respond = async (c, run) => {
11
19
  try {
12
20
  if (getGatewayPrincipal(c).kind !== "owner") throw new ImportError("owner_required", "Only the Gateway owner can import local capabilities", 403);
@@ -28,17 +36,21 @@ function registerImportRoutes(app, deps) {
28
36
  return c.json({
29
37
  ok: false,
30
38
  code: "import_failed",
31
- error: "Import could not finish. Try again; existing content is preserved."
39
+ error: "Import could not finish. Existing content is preserved."
32
40
  }, 400);
33
41
  }
34
42
  };
35
43
  app.get("/api/imports/sources", (c) => respond(c, () => ({ sources: listImportSources("gateway-owner") })));
36
- app.post("/api/imports/sources/:source/import", deps.strictRateLimitMiddleware, (c) => respond(c, async () => {
44
+ app.post("/api/imports/sources/:source/scan", deps.strictRateLimitMiddleware, (c) => respond(c, async () => {
37
45
  const source = c.req.param("source");
38
46
  if (!isImportSource(source)) throw new ImportError("unsupported_source", "Unsupported source");
39
- const { requestId } = requestSchema.parse(await c.req.json());
40
- return importProduct(createRuntimeImportService("gateway-owner", () => deps.service.agentService.refreshSkillsAfterDiskChange(), (root) => deps.service.agentService.getWorkspaceTrust(root).trusted), source, requestId);
47
+ z.object({}).strict().parse(await c.req.json());
48
+ return scanProduct(service(), source);
41
49
  }));
50
+ app.get("/api/imports/inventories/:id", (c) => respond(c, () => publicInventory(getLiveInventory("gateway-owner", c.req.param("id")))));
51
+ app.get("/api/imports/inventories/:id/items/:itemId/preview", (c) => respond(c, () => previewInventoryItem(service(), "gateway-owner", c.req.param("id"), c.req.param("itemId"))));
52
+ app.post("/api/imports/runs", deps.strictRateLimitMiddleware, (c) => respond(c, async () => importSelection(service(), selectionSchema.parse(await c.req.json()))));
53
+ app.get("/api/imports/runs/:id", (c) => respond(c, () => new ImportSelectionRepository("gateway-owner").get("run", c.req.param("id"))));
42
54
  }
43
55
  //#endregion
44
56
  export { registerImportRoutes };
@@ -33,7 +33,8 @@ function registerProactiveControlRoutes(app, deps) {
33
33
  }));
34
34
  app.get("/api/proactive/overview", (c) => c.json({
35
35
  ok: true,
36
- ...delegationOverview(workspace())
36
+ ...delegationOverview(workspace()),
37
+ heartbeat: deps.service.heartbeatStatus()
37
38
  }));
38
39
  app.post("/api/proactive/delegations", deps.strictRateLimitMiddleware, async (c) => c.json({
39
40
  ok: true,
@@ -1,6 +1,6 @@
1
1
  import { loadConfig } from "../config/loader.js";
2
- import { isRestartEnabled } from "../config/commands.flags.js";
3
2
  import "../config/index.js";
3
+ import { isRestartEnabled } from "../config/commands.flags.js";
4
4
  import { runBootstrapMigrationsSync } from "../migrations/runner.js";
5
5
  import { restartGatewayProcessWithFreshPid } from "./respawn.js";
6
6
  import { acquireGatewayLock } from "./lock.js";
@@ -124,6 +124,7 @@ export declare class GatewayService {
124
124
  private ensureHeartbeatService;
125
125
  private createTaskRunDispatcher;
126
126
  private createNotificationService;
127
+ private readonly taskRunDispatch;
127
128
  dispatchTaskRuns(): void;
128
129
  ensureTaskConversation(taskId: string, options?: {
129
130
  runId?: string;
@@ -255,6 +256,12 @@ export declare class GatewayService {
255
256
  manageable: boolean;
256
257
  order: number;
257
258
  }>;
259
+ heartbeatStatus(): {
260
+ enabled: boolean;
261
+ checksAllowed: boolean;
262
+ nextCheckAt: string;
263
+ recent: import("./heartbeat/check-store.js").HeartbeatCheck[];
264
+ };
258
265
  /**
259
266
  * Request an immediate heartbeat run (coalesced like interval/cron wakes).
260
267
  */
@@ -63,23 +63,24 @@ import { ConnectionRecoveryService } from "../connectors/connection-recovery-ser
63
63
  import { startConnectorLearningCoordinator } from "../connectors/learning-coordinator.js";
64
64
  import { ConnectedSourceChangePublisher } from "../connectors/source-change-publisher.js";
65
65
  import { startConnectorSupervisor } from "../connectors/supervisor.js";
66
- import { DiscussionService } from "../discussions/service.js";
67
- import { DiscussionOrganizer } from "../discussions/organizer.js";
68
- import { DiscussionLiveWorker } from "../discussions/live-worker.js";
69
- import { DiscussionOrganizerWorker } from "../discussions/organizer-worker.js";
70
- import { DiscussionSealer } from "../discussions/sealer.js";
71
- import "../discussions/index.js";
72
66
  import { EndpointToolRuntime } from "../endpoint-tools/runtime.js";
73
67
  import "../endpoint-tools/index.js";
74
68
  import { assertGatewayAuthConfigured, extractToken, resolveGatewayAuth, validateToken } from "./auth.js";
75
69
  import { prepareAutomationAgentSession } from "./automation-agent-session.js";
76
70
  import { stopTailscaleExposure } from "./tailscale-lifecycle.js";
71
+ import { createBackgroundTask } from "../infra/background-task.js";
77
72
  import { buildTaskAgentContext } from "../agent/source-context/task-context.js";
78
73
  import { deliverProactiveCard } from "../proactive/inbox/delivery.js";
79
74
  import { WorkDiscoveryService } from "../work-discovery/service.js";
80
75
  import { buildVoiceMemoryContext } from "../voice/realtime/memory-context.js";
81
76
  import { buildVoicePersonaContext } from "../voice/realtime/persona-context.js";
82
77
  import { voiceMemoryBudget, voicePersonaBudget } from "../voice/realtime/conversation-context.js";
78
+ import { DiscussionService } from "../discussions/service.js";
79
+ import { DiscussionOrganizer } from "../discussions/organizer.js";
80
+ import { DiscussionLiveWorker } from "../discussions/live-worker.js";
81
+ import { DiscussionOrganizerWorker } from "../discussions/organizer-worker.js";
82
+ import { DiscussionSealer } from "../discussions/sealer.js";
83
+ import "../discussions/index.js";
83
84
  import { NotesStore } from "../notes/store.js";
84
85
  import { buildNoteAgentContext } from "../notes/agent-context.js";
85
86
  import { NotesService } from "../notes/service.js";
@@ -665,7 +666,8 @@ var GatewayService = class {
665
666
  agentService: this.ensureAgentService(),
666
667
  messageBus: this.bus,
667
668
  sessionStore: this.sessionIndex.getStore(),
668
- getConfig: () => this.config
669
+ getConfig: () => this.config,
670
+ getWorkspace: () => this.workspacePath
669
671
  });
670
672
  return this.heartbeatService;
671
673
  }
@@ -721,11 +723,21 @@ var GatewayService = class {
721
723
  });
722
724
  return this.notificationService;
723
725
  }
724
- dispatchTaskRuns() {
726
+ taskRunDispatch = createBackgroundTask(async () => {
725
727
  new TaskSignalService().tick();
726
728
  this.dispatchTaskEvents();
727
729
  this.createTaskRunDispatcher().dispatch();
728
- this.createWorkflowRunService().dispatchTaskRuns();
730
+ await this.createWorkflowRunService().dispatchTaskRuns();
731
+ }, (err) => {
732
+ const errorMessage = err instanceof Error ? err.message : String(err);
733
+ log.error({
734
+ err,
735
+ errorMessage,
736
+ phase: "task_run_dispatch"
737
+ }, `Task dispatch failed: ${errorMessage}`);
738
+ });
739
+ dispatchTaskRuns() {
740
+ this.taskRunDispatch();
729
741
  }
730
742
  async ensureTaskConversation(taskId, options = {}) {
731
743
  const task = new TaskRepository().require(taskId);
@@ -1431,6 +1443,9 @@ var GatewayService = class {
1431
1443
  return a.id.localeCompare(b.id);
1432
1444
  });
1433
1445
  }
1446
+ heartbeatStatus() {
1447
+ return this.heartbeatService?.status() ?? null;
1448
+ }
1434
1449
  /**
1435
1450
  * Request an immediate heartbeat run (coalesced like interval/cron wakes).
1436
1451
  */
@@ -1,6 +1,6 @@
1
1
  import { DEFAULT_ACK_MAX_CHARS, HEARTBEAT_OK, NO_REPLY, shouldSilence, stripHeartbeatToken } from "./tokens.js";
2
+ import { appendCronEventLines } from "./event-prompt.js";
2
3
  import { isWithinActiveHours } from "./active-hours.js";
3
4
  import { isHeartbeatContentEmpty } from "./content-check.js";
4
- import { appendCronEventLines } from "./event-prompt.js";
5
5
  import { createHeartbeatWake } from "./wake.js";
6
6
  export { DEFAULT_ACK_MAX_CHARS, HEARTBEAT_OK, NO_REPLY, appendCronEventLines, createHeartbeatWake, isHeartbeatContentEmpty, isWithinActiveHours, shouldSilence, stripHeartbeatToken };
@@ -1,11 +1,12 @@
1
1
  import { type ScanInput } from './scanner.js';
2
- import { type ImportAction, type ImportJob, type ImportScan, type ImportTarget } from './types.js';
2
+ import { type ImportAction, type ImportFile, type ImportJob, type ImportScan, type ImportTarget } from './types.js';
3
3
  export interface ImportServiceOptions {
4
4
  stateDir: string;
5
5
  owner: string;
6
6
  validateTarget?: (target: ImportTarget) => void;
7
- isConnected?: (candidate: ImportScan['candidates'][number]) => boolean;
7
+ isConnected?: (candidate: ImportScan['candidates'][number], workspaceRoot?: string) => boolean;
8
8
  reservedNames?: (target: ImportTarget) => string[];
9
+ isWorkspaceTrusted?: (root: string) => boolean;
9
10
  refreshSkills?: () => void | Promise<void>;
10
11
  }
11
12
  export declare class ImportService {
@@ -13,20 +14,24 @@ export declare class ImportService {
13
14
  private readonly repo;
14
15
  private readonly root;
15
16
  constructor(options: ImportServiceOptions);
16
- scan(input: ScanInput): ImportScan;
17
+ scan(input: ScanInput): Promise<ImportScan>;
17
18
  getScan(id: string): ImportScan;
18
- private storeScan;
19
+ discardUnusedScan(id: string): void;
20
+ storeScan(scan: ImportScan, workspaceRoot?: string): ImportScan;
19
21
  plan(scanId: string, target: ImportTarget, choices: Array<Pick<ImportAction, 'candidateId' | 'operation'> & {
20
22
  name?: string;
21
23
  }>): import("./types.js").ImportPlan;
22
- importSkills(scan: ImportScan, target: ImportTarget): Promise<{
23
- imported: number;
24
- skipped: number;
25
- issues: Array<{
26
- name: string;
27
- reason: string;
28
- }>;
29
- }>;
24
+ describeSkills(scan: ImportScan, target: ImportTarget): {
25
+ candidate: import("./types.js").ImportCandidate;
26
+ status: import("./types.js").InventoryStatus;
27
+ reason: string;
28
+ targetName: string;
29
+ }[];
30
+ readSnapshot(scanId: string, candidateId: string): ImportFile[];
31
+ jobByKey(key: string): ImportJob;
32
+ validateTarget(target: ImportTarget): void;
33
+ reservedNames(target: ImportTarget): string[];
34
+ isWorkspaceTrusted(root: string): boolean;
30
35
  getPlan(id: string): import("./types.js").ImportPlan;
31
36
  apply(planId: string, key: string): ImportJob;
32
37
  private renameSkill;
@@ -30,13 +30,23 @@ var ImportService = class {
30
30
  mode: 448
31
31
  });
32
32
  }
33
- scan(input) {
34
- return this.storeScan(scanLocal(input));
33
+ async scan(input) {
34
+ return this.storeScan(await scanLocal(input), input.projectRoot);
35
35
  }
36
36
  getScan(id) {
37
37
  return this.repo.get("scan", id);
38
38
  }
39
- storeScan(scan) {
39
+ discardUnusedScan(id) {
40
+ this.lock(() => {
41
+ if (this.repo.jobs(-1).some((job) => job.plan.scanId === id)) return;
42
+ rmSync(join(this.root, id), {
43
+ recursive: true,
44
+ force: true
45
+ });
46
+ this.repo.deleteScan(id);
47
+ });
48
+ }
49
+ storeScan(scan, workspaceRoot) {
40
50
  const dir = join(this.root, scan.id);
41
51
  mkdirSync(dir, { mode: 448 });
42
52
  for (const item of scan.candidates) if (item.kind === "skill" && item.compatibility === "compatible") writeTextAtomicSync(join(dir, `${item.id}.json`), JSON.stringify(item.files));
@@ -44,7 +54,7 @@ var ImportService = class {
44
54
  ...scan,
45
55
  candidates: scan.candidates.map((c) => ({
46
56
  ...c,
47
- connected: this.options.isConnected?.(c) ?? false,
57
+ connected: this.options.isConnected?.(c, workspaceRoot) ?? false,
48
58
  files: c.files.map((f) => ({
49
59
  ...f,
50
60
  data: ""
@@ -60,84 +70,71 @@ var ImportService = class {
60
70
  this.repo.save("plan", plan);
61
71
  return plan;
62
72
  }
63
- async importSkills(scan, target) {
73
+ describeSkills(scan, target) {
64
74
  const inventory = targetInventory(target.root);
65
75
  const names = new Set([...this.options.reservedNames?.(target) ?? [], ...inventory.flatMap((i) => [i.directory, i.name])].map((n) => n.toLowerCase()));
66
- const choices = [];
67
- const result = {
68
- imported: 0,
69
- skipped: 0,
70
- issues: []
71
- };
72
76
  const previous = /* @__PURE__ */ new Map();
73
77
  for (const job of this.repo.jobs(-1)) {
74
78
  if (job.plan.target.root !== canonicalTarget(target.root)) continue;
75
79
  const origin = this.getScan(job.plan.scanId);
76
80
  for (const item of job.items.filter((i) => i.status === "active")) {
77
81
  const candidate = origin.candidates.find((c) => c.id === item.action.candidateId);
78
- if (candidate?.source === scan.source && !previous.has(candidate.location)) previous.set(candidate.location, { name: item.action.name });
82
+ if (candidate?.source === scan.source && !previous.has(candidate.location)) previous.set(candidate.location, item.action.name);
79
83
  }
80
84
  }
81
- for (const candidate of scan.candidates.filter((c) => c.kind === "skill")) {
82
- if (candidate.connected) {
83
- result.skipped++;
84
- continue;
85
- }
86
- if (candidate.compatibility !== "compatible") {
87
- result.issues.push({
88
- name: candidate.name,
89
- reason: candidate.findings.join("; ")
90
- });
91
- continue;
92
- }
93
- const prior = previous.get(candidate.location);
94
- const priorTarget = prior && inventory.find((i) => i.directory === prior.name);
95
- if (prior && priorTarget) {
96
- const files = JSON.parse(readFileSync(join(this.root, scan.id, `${candidate.id}.json`), "utf8"));
97
- if (fileHash(this.renameSkill(files, prior.name)) === priorTarget.hash) result.skipped++;
98
- else result.issues.push({
99
- name: candidate.name,
100
- reason: "The previously imported skill has changed. Existing content was kept."
101
- });
102
- continue;
103
- }
104
- if (inventory.find((i) => i.name.toLowerCase() === candidate.name.toLowerCase())?.hash === candidate.hash) {
105
- result.skipped++;
106
- continue;
107
- }
108
- let name = candidate.name;
109
- if (names.has(name.toLowerCase())) name = `${candidate.source}-${candidate.name}`.slice(0, 63);
110
- const files = JSON.parse(readFileSync(join(this.root, scan.id, `${candidate.id}.json`), "utf8"));
111
- const hash = fileHash(this.renameSkill(files, name));
112
- if (inventory.some((i) => i.name === name && i.hash === hash)) {
113
- result.skipped++;
114
- continue;
115
- }
116
- if (names.has(name.toLowerCase())) {
117
- result.issues.push({
118
- name: candidate.name,
119
- reason: "An existing skill was kept. Manage it in Skills to make changes."
120
- });
121
- continue;
85
+ return scan.candidates.filter((c) => c.kind === "skill").map((candidate) => {
86
+ let status = "ready";
87
+ let reason;
88
+ let targetName = candidate.name;
89
+ if (candidate.connected) status = "existing";
90
+ else if (candidate.compatibility !== "compatible") {
91
+ status = "blocked";
92
+ reason = candidate.findings.join("; ");
93
+ } else {
94
+ const prior = previous.get(candidate.location);
95
+ const priorTarget = prior && inventory.find((i) => i.directory === prior);
96
+ const files = this.readSnapshot(scan.id, candidate.id);
97
+ if (priorTarget) {
98
+ targetName = priorTarget.directory;
99
+ status = fileHash(this.renameSkill(files, targetName)) === priorTarget.hash ? "existing" : "blocked";
100
+ if (status === "blocked") reason = "Previously imported content has changed; existing edits will be kept.";
101
+ } else if (inventory.some((i) => i.name.toLowerCase() === candidate.name.toLowerCase() && i.hash === candidate.hash)) status = "existing";
102
+ else if (names.has(targetName.toLowerCase())) {
103
+ targetName = `${candidate.source}-${candidate.name}`.slice(0, 63);
104
+ if (inventory.some((i) => i.name === targetName && i.hash === fileHash(this.renameSkill(files, targetName)))) status = "existing";
105
+ else if (names.has(targetName.toLowerCase())) {
106
+ status = "blocked";
107
+ reason = "Both the original and product-prefixed names are in use.";
108
+ } else status = "conflict";
109
+ }
122
110
  }
123
- names.add(name.toLowerCase());
124
- choices.push({
125
- candidateId: candidate.id,
126
- operation: name === candidate.name ? "create" : "rename",
127
- name
128
- });
129
- }
130
- if (!choices.length) return result;
131
- const plan = this.plan(scan.id, target, choices);
132
- const staged = this.apply(plan.id, plan.id);
133
- const job = await this.activate(staged.id, staged.items.filter((i) => i.status === "staged").map((i) => i.action.candidateId));
134
- for (const item of job.items) if (item.status === "active") result.imported++;
135
- else if (item.status === "skipped") result.skipped++;
136
- else result.issues.push({
137
- name: item.action.name,
138
- reason: item.error ?? "Could not import this skill. Try again."
111
+ if (status === "ready" || status === "conflict") names.add(targetName.toLowerCase());
112
+ return {
113
+ candidate,
114
+ status,
115
+ reason,
116
+ targetName
117
+ };
139
118
  });
140
- return result;
119
+ }
120
+ readSnapshot(scanId, candidateId) {
121
+ const candidate = this.getScan(scanId).candidates.find((c) => c.id === candidateId);
122
+ if (!candidate || candidate.kind !== "skill" || candidate.compatibility !== "compatible") throw new ImportError("not_found", "Snapshot not found", 404);
123
+ const files = JSON.parse(readFileSync(join(this.root, scanId, `${candidateId}.json`), "utf8"));
124
+ if (fileHash(files) !== candidate.hash) throw new ImportError("source_changed", "Snapshot changed", 409);
125
+ return files;
126
+ }
127
+ jobByKey(key) {
128
+ return this.repo.byKey(key);
129
+ }
130
+ validateTarget(target) {
131
+ this.options.validateTarget?.(target);
132
+ }
133
+ reservedNames(target) {
134
+ return this.options.reservedNames?.(target) ?? [];
135
+ }
136
+ isWorkspaceTrusted(root) {
137
+ return this.options.isWorkspaceTrusted?.(root) ?? false;
141
138
  }
142
139
  getPlan(id) {
143
140
  return this.repo.get("plan", id);
@@ -0,0 +1,11 @@
1
+ import type { ImportService } from './importService.js';
2
+ import { type ImportSource, type ImportInventory, type StoredInventory, type ImportCandidate } from './types.js';
3
+ export declare function contextKey(candidate: ImportCandidate): string;
4
+ export declare function existingContext(candidate: ImportCandidate, projectId?: string): import("../knowledge-memory/domain.js").KnowledgeItem;
5
+ export declare function publicInventory(inventory: StoredInventory): ImportInventory;
6
+ export declare function getLiveInventory(owner: string, id: string): StoredInventory;
7
+ export declare function scanProduct(service: ImportService, source: ImportSource, owner?: string): Promise<ImportInventory>;
8
+ export declare function previewInventoryItem(service: ImportService, owner: string, inventoryId: string, itemId: string): {
9
+ text: string;
10
+ truncated: boolean;
11
+ };