@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
@@ -0,0 +1,202 @@
1
+ import { init_paths, resolveSkillsDir } from "../config/paths.js";
2
+ import { listKnowledgeItems } from "../knowledge-memory/repository.js";
3
+ import "../knowledge-memory/index.js";
4
+ import { ProjectService } from "../projects/project-service.js";
5
+ import { resolveWorkspaceSkillsDir } from "../agent/skills/workspace-skills-dir.js";
6
+ import { ImportError } from "./types.js";
7
+ import { digest } from "./files.js";
8
+ import { canonicalTarget } from "./planner.js";
9
+ import { detectImportSources } from "./sources.js";
10
+ import { ImportSelectionRepository } from "../storage/sqlite/import-selection-repository.js";
11
+ import { basename, isAbsolute, parse } from "node:path";
12
+ import { realpathSync, statSync } from "node:fs";
13
+ import { randomUUID } from "node:crypto";
14
+ import { setImmediate } from "node:timers/promises";
15
+ //#region src/imports/inventory.ts
16
+ init_paths();
17
+ function contextKey(candidate) {
18
+ return `product-import:${candidate.source}:${digest(candidate.location)}`;
19
+ }
20
+ function existingContext(candidate, projectId) {
21
+ return listKnowledgeItems({
22
+ canonicalKey: contextKey(candidate),
23
+ scope: projectId ? {
24
+ type: "project",
25
+ id: projectId
26
+ } : { type: "global" },
27
+ statuses: [
28
+ "active",
29
+ "candidate",
30
+ "needs_review",
31
+ "stale",
32
+ "archived",
33
+ "rejected"
34
+ ]
35
+ })[0];
36
+ }
37
+ function publicInventory(inventory) {
38
+ return {
39
+ id: inventory.id,
40
+ source: inventory.source,
41
+ createdAt: inventory.createdAt,
42
+ expiresAt: inventory.expiresAt,
43
+ complete: inventory.complete,
44
+ notices: inventory.notices,
45
+ candidates: inventory.candidates.map(({ id, kind, parentId, name, description, displayPath, scope, status, reason, suggested, targetName }) => ({
46
+ id,
47
+ kind,
48
+ parentId,
49
+ name,
50
+ description,
51
+ displayPath,
52
+ scope,
53
+ status,
54
+ reason,
55
+ suggested,
56
+ targetName
57
+ }))
58
+ };
59
+ }
60
+ function getLiveInventory(owner, id) {
61
+ const inventory = new ImportSelectionRepository(owner).get("inventory", id);
62
+ if (Date.now() > inventory.expiresAt) throw new ImportError("inventory_stale", "This list has expired. Refresh it before importing.", 409);
63
+ return inventory;
64
+ }
65
+ async function scanProduct(service, source, owner = "gateway-owner") {
66
+ if (!detectImportSources().some((s) => s.id === source && s.detected)) throw new ImportError("source_not_found", "App is unavailable on the xopc device", 404);
67
+ const inventory = {
68
+ id: randomUUID(),
69
+ source,
70
+ createdAt: Date.now(),
71
+ expiresAt: Date.now() + 15 * 6e4,
72
+ candidates: [],
73
+ notices: [],
74
+ complete: true,
75
+ scanIds: []
76
+ };
77
+ const budget = {
78
+ bytes: 0,
79
+ files: 0
80
+ };
81
+ const projects = new ProjectService();
82
+ const addScan = (scan, targetRoot, parent) => {
83
+ inventory.scanIds.push(scan.id);
84
+ inventory.notices.push(...scan.diagnostics);
85
+ if (scan.diagnostics.length) inventory.complete = false;
86
+ const relevant = {
87
+ ...scan,
88
+ candidates: scan.candidates.filter((c) => c.scope === (parent ? "project" : "user"))
89
+ };
90
+ const skills = service.describeSkills(relevant, {
91
+ root: targetRoot,
92
+ projectId: parent?.projectId
93
+ });
94
+ for (const candidate of relevant.candidates) {
95
+ if (candidate.kind === "mcp") {
96
+ inventory.notices.push(`${candidate.name}: reconnect this tool in Connections.`);
97
+ continue;
98
+ }
99
+ const item = {
100
+ id: candidate.id,
101
+ kind: candidate.kind === "rule" ? "context" : "skill",
102
+ parentId: parent?.id,
103
+ name: candidate.name,
104
+ description: candidate.description,
105
+ displayPath: candidate.location,
106
+ location: candidate.location,
107
+ scope: candidate.scope,
108
+ status: candidate.compatibility === "compatible" ? "ready" : "blocked",
109
+ suggested: false,
110
+ reason: candidate.findings.join("; ") || void 0,
111
+ scanId: scan.id,
112
+ candidate,
113
+ targetRoot,
114
+ projectId: parent?.projectId
115
+ };
116
+ if (item.kind === "skill") {
117
+ const state = skills.find((s) => s.candidate.id === candidate.id);
118
+ item.status = state.status;
119
+ item.reason = state.reason;
120
+ item.targetName = state.targetName;
121
+ if (parent && !service.isWorkspaceTrusted(parent.location) && item.status !== "existing") {
122
+ item.status = "blocked";
123
+ item.reason = "Trust this project in Projects before importing its skills.";
124
+ }
125
+ item.suggested = item.status === "ready";
126
+ } else if (item.status === "ready") {
127
+ const existing = !parent || parent.projectId ? existingContext(candidate, parent?.projectId) : void 0;
128
+ if (existing) {
129
+ item.status = existing.source.hash === candidate.hash ? "existing" : "blocked";
130
+ item.reason = item.status === "blocked" ? "Previously imported context has changed; existing content will be kept." : void 0;
131
+ }
132
+ }
133
+ inventory.candidates.push(item);
134
+ }
135
+ };
136
+ const scan = await service.scan({
137
+ source,
138
+ budget
139
+ });
140
+ addScan(scan, canonicalTarget(resolveSkillsDir()));
141
+ const seen = /* @__PURE__ */ new Set();
142
+ const paths = scan.projectRoots ?? [];
143
+ const limit = 500;
144
+ if (paths.length > limit) {
145
+ inventory.complete = false;
146
+ inventory.notices.push(`${paths.length - limit} project directories were not scanned (limit: ${limit}).`);
147
+ }
148
+ for (const path of paths.slice(0, limit)) {
149
+ await setImmediate();
150
+ let parent;
151
+ try {
152
+ if (!isAbsolute(path) || parse(path).root === path || !statSync(path).isDirectory()) throw new Error("Unavailable project");
153
+ const location = realpathSync(path);
154
+ if (parse(location).root === location) throw new Error("Invalid project");
155
+ if (seen.has(location)) continue;
156
+ seen.add(location);
157
+ const existing = projects.findByWorkspaceRoot(location);
158
+ parent = {
159
+ id: randomUUID(),
160
+ kind: "project",
161
+ name: existing?.name ?? basename(location),
162
+ description: "",
163
+ displayPath: path,
164
+ location,
165
+ scope: "project",
166
+ status: existing ? "existing" : "ready",
167
+ suggested: false,
168
+ projectId: existing?.id
169
+ };
170
+ inventory.candidates.push(parent);
171
+ addScan(await service.scan({
172
+ source,
173
+ projectRoot: location,
174
+ projectOnly: true,
175
+ budget
176
+ }), canonicalTarget(resolveWorkspaceSkillsDir(location)), parent);
177
+ } catch {
178
+ inventory.complete = false;
179
+ inventory.notices.push(`${path}: some contents could not be scanned; only listed items can be imported.`);
180
+ }
181
+ }
182
+ const score = (item) => inventory.candidates.some((c) => c.parentId === item.id && ["ready", "conflict"].includes(c.status)) ? 0 : item.projectId ? 1 : 2;
183
+ const roots = inventory.candidates.filter((c) => c.kind === "project").sort((a, b) => score(a) - score(b) || a.name.localeCompare(b.name) || a.location.localeCompare(b.location));
184
+ inventory.candidates = [...inventory.candidates.filter((c) => c.scope === "user"), ...roots.flatMap((p) => [p, ...inventory.candidates.filter((c) => c.parentId === p.id)])];
185
+ new ImportSelectionRepository(owner).save("inventory", inventory);
186
+ return publicInventory(inventory);
187
+ }
188
+ function previewInventoryItem(service, owner, inventoryId, itemId) {
189
+ const item = getLiveInventory(owner, inventoryId).candidates.find((c) => c.id === itemId);
190
+ if (!item) throw new ImportError("not_found", "Item not found", 404);
191
+ if (item.status === "blocked") return {
192
+ text: item.reason ?? "",
193
+ truncated: false
194
+ };
195
+ const text = item.kind === "context" ? item.candidate?.content ?? "" : item.kind === "skill" ? Buffer.from(service.readSnapshot(item.scanId, item.id).find((f) => f.path === "SKILL.md").data, "base64").toString("utf8") : item.displayPath;
196
+ return {
197
+ text: text.slice(0, 2e4),
198
+ truncated: text.length > 2e4
199
+ };
200
+ }
201
+ //#endregion
202
+ export { contextKey, existingContext, getLiveInventory, previewInventoryItem, publicInventory, scanProduct };
@@ -1,10 +1,10 @@
1
1
  import type { ImportService } from './importService.js';
2
- import { type ImportSource, type ProductImportResult } from './types.js';
2
+ import { type ImportSelection, type ProductImportResult } from './types.js';
3
3
  export declare function listImportSources(owner: string): {
4
4
  lastImport: ProductImportResult;
5
- id: ImportSource;
5
+ id: import("./types.js").ImportSource;
6
6
  name: string;
7
7
  detected: boolean;
8
8
  }[];
9
- /** One product action. Destination services retain their existing scope and trust policies. */
10
- export declare function importProduct(service: ImportService, source: ImportSource, requestId?: string, owner?: string): Promise<ProductImportResult>;
9
+ export declare function importSelection(service: ImportService, selection: ImportSelection, owner?: string): Promise<ProductImportResult>;
10
+ export declare function recoverSelectionRuns(service: ImportService, owner: string): Promise<void>;
@@ -1,148 +1,241 @@
1
1
  import { resolveStateDir } from "../config/paths-state.js";
2
+ import { runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
2
3
  import { init_paths } from "../config/paths.js";
3
4
  import { ProjectService } from "../projects/project-service.js";
4
5
  import { ImportError } from "./types.js";
6
+ import { digest, fileHash, readRegularFile, readTree } from "./files.js";
5
7
  import { importContextDocument } from "./contextImport.js";
6
- import { ImportRepository } from "../storage/sqlite/import-repository.js";
8
+ import { canonicalTarget, targetInventory } from "./planner.js";
7
9
  import { detectImportSources } from "./sources.js";
8
- import { resolveImportTarget } from "./runtime.js";
9
- import { isAbsolute, join, parse } from "node:path";
10
+ import { ImportSelectionRepository } from "../storage/sqlite/import-selection-repository.js";
11
+ import { existingContext, getLiveInventory } from "./inventory.js";
12
+ import { join } from "node:path";
10
13
  import { mkdirSync, realpathSync, statSync } from "node:fs";
11
- import { randomUUID } from "node:crypto";
14
+ import { setImmediate } from "node:timers/promises";
12
15
  import lockfile from "proper-lockfile";
13
16
  //#region src/imports/productImport.ts
14
17
  init_paths();
15
18
  function listImportSources(owner) {
16
- const runs = new ImportRepository(owner).latestRuns();
19
+ const runs = new ImportSelectionRepository(owner).list("run");
17
20
  return detectImportSources().map((source) => ({
18
21
  ...source,
19
22
  lastImport: runs.find((r) => r.source === source.id)
20
23
  }));
21
24
  }
22
- /** One product action. Destination services retain their existing scope and trust policies. */
23
- async function importProduct(service, source, requestId = randomUUID(), owner = "gateway-owner") {
24
- const root = join(resolveStateDir(), "imports", "product-import");
25
- mkdirSync(root, {
25
+ function stale() {
26
+ throw new ImportError("inventory_stale", "Selected content or its destination changed. Refresh the list before importing.", 409);
27
+ }
28
+ function selectedItems(inventory, ids) {
29
+ if (!ids.length || new Set(ids).size !== ids.length) throw new ImportError("invalid_selection", "Select at least one item; duplicate selections are not allowed");
30
+ return ids.map((id) => {
31
+ const item = inventory.candidates.find((c) => c.id === id);
32
+ if (!item || item.status === "blocked" || item.status === "existing" && item.kind !== "project") throw new ImportError("invalid_selection", "Select available items from this list");
33
+ if (item.parentId && !ids.includes(item.parentId)) throw new ImportError("missing_parent", "Select the project entry along with its contents");
34
+ return item;
35
+ }).sort((a, b) => Number(b.kind === "project") - Number(a.kind === "project"));
36
+ }
37
+ async function preflight(service, inventory, items, retry) {
38
+ const projects = new ProjectService();
39
+ const names = /* @__PURE__ */ new Set();
40
+ for (const item of items) {
41
+ await setImmediate();
42
+ try {
43
+ if (realpathSync(item.location) !== item.location) stale();
44
+ if (item.kind === "project") {
45
+ if (!statSync(item.location).isDirectory()) stale();
46
+ const existing = projects.findByWorkspaceRoot(item.location);
47
+ const expectedId = item.projectId ?? retry?.items.find((i) => i.candidateId === item.id)?.targetId;
48
+ if (existing?.id !== expectedId) stale();
49
+ continue;
50
+ }
51
+ const candidate = item.candidate;
52
+ const parent = item.parentId ? inventory.candidates.find((c) => c.id === item.parentId) : void 0;
53
+ if (item.kind === "context") {
54
+ if (digest(readRegularFile(item.location, 128 * 1024).toString("utf8").trim()) !== candidate.hash) stale();
55
+ const projectId = parent ? projects.findByWorkspaceRoot(parent.location)?.id : void 0;
56
+ if ((!parent || projectId) && existingContext(candidate, projectId)) stale();
57
+ } else {
58
+ if (parent && !service.isWorkspaceTrusted(parent.location)) stale();
59
+ if (fileHash(readTree(item.location)) !== candidate.hash) stale();
60
+ service.readSnapshot(item.scanId, candidate.id);
61
+ if (canonicalTarget(item.targetRoot) !== item.targetRoot) stale();
62
+ const key = `${item.targetRoot}:${item.targetName.toLowerCase()}`;
63
+ if (names.has(key)) stale();
64
+ names.add(key);
65
+ const existing = targetInventory(item.targetRoot);
66
+ const reserved = service.reservedNames({
67
+ root: item.targetRoot,
68
+ projectId: parent?.projectId
69
+ });
70
+ if ([...existing.flatMap((e) => [e.name, e.directory]), ...reserved].some((n) => n.toLowerCase() === item.targetName.toLowerCase())) stale();
71
+ }
72
+ } catch (error) {
73
+ if (error instanceof ImportError && error.code === "inventory_stale") throw error;
74
+ stale();
75
+ }
76
+ }
77
+ }
78
+ function summarize(run) {
79
+ run.skills = run.items.filter((i) => i.kind === "skill" && i.status === "imported").length;
80
+ run.context = run.items.filter((i) => i.kind === "context" && i.status === "imported").length;
81
+ run.projects = run.items.filter((i) => i.kind === "project" && i.status === "imported").length;
82
+ run.skipped = run.items.filter((i) => i.status === "existing").length;
83
+ run.issues = run.items.filter((i) => i.status === "failed").map((i) => ({
84
+ name: i.name,
85
+ reason: i.error
86
+ }));
87
+ }
88
+ async function execute(service, repo, inventory, run) {
89
+ const projects = new ProjectService();
90
+ const persist = () => {
91
+ summarize(run);
92
+ repo.save("run", run);
93
+ };
94
+ for (const entry of run.items.filter((i) => i.status === "pending")) {
95
+ await setImmediate();
96
+ const item = inventory.candidates.find((c) => c.id === entry.candidateId);
97
+ try {
98
+ if (Date.now() - run.createdAt > 720 * 60 * 6e4) throw new ImportError("expired", "Import recovery period expired. Scan again.");
99
+ const parent = item.parentId ? run.items.find((i) => i.candidateId === item.parentId) : void 0;
100
+ if (parent?.status === "failed") throw new ImportError("parent_failed", "The project could not be imported. Retry the project and its contents.");
101
+ if (item.kind === "project") {
102
+ if (realpathSync(item.location) !== item.location || !statSync(item.location).isDirectory()) stale();
103
+ runSqliteWriteTransaction(() => {
104
+ const existing = projects.findByWorkspaceRoot(item.location);
105
+ entry.targetId = (existing ?? projects.create({ workspaceRoot: item.location })).id;
106
+ entry.status = existing ? "existing" : "imported";
107
+ persist();
108
+ });
109
+ } else if (item.kind === "context") runSqliteWriteTransaction(() => {
110
+ if (parent && projects.get(parent.targetId)?.workspaceRoot !== inventory.candidates.find((c) => c.id === item.parentId).location) stale();
111
+ const status = importContextDocument(item.candidate, parent?.targetId);
112
+ if (status === "kept") throw new ImportError("conflict", "Existing context was kept. Refresh before importing.");
113
+ entry.targetId = existingContext(item.candidate, parent?.targetId)?.id;
114
+ entry.status = status === "created" ? "imported" : "existing";
115
+ persist();
116
+ });
117
+ else {
118
+ const key = `${run.id}:${item.id}`;
119
+ let job = service.jobByKey(key);
120
+ if (!job) {
121
+ const plan = service.plan(item.scanId, {
122
+ root: item.targetRoot,
123
+ projectId: parent?.targetId
124
+ }, [{
125
+ candidateId: item.id,
126
+ operation: item.targetName === item.candidate.name ? "create" : "rename",
127
+ name: item.targetName
128
+ }]);
129
+ job = service.apply(plan.id, key);
130
+ }
131
+ entry.jobId = job.id;
132
+ persist();
133
+ job = service.getJob(job.id);
134
+ const pending = job.items.filter((i) => ["staged", "failed"].includes(i.status));
135
+ if (pending.length) try {
136
+ job = await service.activate(job.id, pending.map((i) => i.action.candidateId));
137
+ } catch (error) {
138
+ job = service.getJob(job.id);
139
+ if (!job.items.every((i) => i.status === "active" || i.status === "skipped")) throw error;
140
+ }
141
+ const published = job.items[0];
142
+ if (!["active", "skipped"].includes(published.status)) throw new ImportError("publish_failed", published.error ?? "Could not publish the skill. Retry this item.");
143
+ entry.targetId = join(item.targetRoot, item.targetName);
144
+ entry.status = published.status === "active" ? "imported" : "existing";
145
+ }
146
+ } catch (error) {
147
+ entry.status = "failed";
148
+ delete entry.targetId;
149
+ entry.error = error instanceof ImportError ? error.message : "This item could not be imported. Check access and retry.";
150
+ }
151
+ persist();
152
+ }
153
+ run.status = run.items.some((i) => i.status === "failed") ? "partial" : "completed";
154
+ persist();
155
+ return run;
156
+ }
157
+ async function withImportLock(fn) {
158
+ const path = join(resolveStateDir(), "imports", "selection-lock");
159
+ mkdirSync(path, {
26
160
  recursive: true,
27
161
  mode: 448
28
162
  });
29
- const release = await lockfile.lock(root, {
163
+ const release = await lockfile.lock(path, {
30
164
  realpath: false,
165
+ stale: 6e4,
31
166
  retries: {
32
- retries: 10,
167
+ retries: 15,
33
168
  minTimeout: 100,
34
169
  maxTimeout: 500
35
170
  }
36
171
  });
37
- const repo = new ImportRepository(owner);
38
172
  try {
173
+ return await fn();
174
+ } finally {
175
+ await release();
176
+ }
177
+ }
178
+ async function importSelection(service, selection, owner = "gateway-owner") {
179
+ return withImportLock(async () => {
180
+ const repo = new ImportSelectionRepository(owner);
181
+ const selectionHash = digest({
182
+ inventoryId: selection.inventoryId,
183
+ ids: [...selection.candidateIds].sort(),
184
+ retryOf: selection.retryOf
185
+ });
186
+ let previous;
39
187
  try {
40
- const previous = repo.get("run", requestId);
41
- if (previous.source !== source) throw new ImportError("conflict", "Request belongs to another product", 409);
42
- return previous;
188
+ previous = repo.get("run", selection.requestId);
43
189
  } catch (error) {
44
190
  if (!(error instanceof ImportError) || error.code !== "not_found") throw error;
45
191
  }
46
- if (!detectImportSources().some((s) => s.id === source && s.detected)) throw new ImportError("source_not_found", "This app is no longer available on the xopc device", 404);
47
- const result = {
48
- id: requestId,
49
- source,
192
+ if (previous) {
193
+ if (previous.selectionHash !== selectionHash) throw new ImportError("conflict", "Request ID belongs to a different selection", 409);
194
+ return previous.status === "running" ? execute(service, repo, repo.get("inventory", previous.inventoryId), previous) : previous;
195
+ }
196
+ const inventory = getLiveInventory(owner, selection.inventoryId);
197
+ const items = selectedItems(inventory, selection.candidateIds);
198
+ const retry = selection.retryOf ? repo.get("run", selection.retryOf) : void 0;
199
+ if (retry) {
200
+ if (retry.status !== "partial" || retry.inventoryId !== inventory.id) throw new ImportError("invalid_selection", "Select failed items from the original import");
201
+ const failed = new Set(retry.items.filter((i) => i.status === "failed").map((i) => i.candidateId));
202
+ const parents = new Set(items.filter((i) => failed.has(i.id)).map((i) => i.parentId));
203
+ if (!items.some((i) => failed.has(i.id)) || items.some((i) => !failed.has(i.id) && !parents.has(i.id))) throw new ImportError("invalid_selection", "Retry can only include failed items and their project entries");
204
+ }
205
+ await preflight(service, inventory, items, retry);
206
+ const run = {
207
+ id: selection.requestId,
208
+ inventoryId: inventory.id,
209
+ selectionHash,
210
+ source: inventory.source,
50
211
  createdAt: Date.now(),
212
+ status: "running",
51
213
  skills: 0,
52
214
  context: 0,
53
215
  projects: 0,
54
216
  skipped: 0,
55
- issues: []
56
- };
57
- const budget = {
58
- bytes: 0,
59
- files: 0
60
- };
61
- const scan = service.scan({
62
- source,
63
- budget
64
- });
65
- const saveContext = (items, projectId) => {
66
- for (const item of items.filter((c) => c.kind === "rule" && c.scope === (projectId ? "project" : "user"))) {
67
- if (!item.content || item.compatibility !== "compatible") {
68
- result.issues.push({
69
- name: item.name,
70
- reason: item.findings.join("; ")
71
- });
72
- continue;
73
- }
74
- try {
75
- const status = importContextDocument(item, projectId);
76
- if (status === "created") result.context++;
77
- else if (status === "unchanged") result.skipped++;
78
- else result.issues.push({
79
- name: item.name,
80
- reason: "Existing context was kept. The source document has changed."
81
- });
82
- } catch {
83
- result.issues.push({
84
- name: item.name,
85
- reason: "Unable to save this context document. Try again."
86
- });
87
- }
88
- }
89
- };
90
- const importScope = async (scopeScan, projectId) => {
91
- const scoped = {
92
- ...scopeScan,
93
- candidates: scopeScan.candidates.filter((c) => c.scope === (projectId ? "project" : "user"))
94
- };
95
- result.issues.push(...scopeScan.diagnostics.map((reason) => ({
96
- name: source,
97
- reason
98
- })));
99
- saveContext(scoped.candidates, projectId);
100
- for (const item of scoped.candidates.filter((c) => c.kind === "mcp")) result.issues.push({
217
+ issues: [],
218
+ items: items.map((item) => ({
219
+ candidateId: item.id,
220
+ kind: item.kind,
101
221
  name: item.name,
102
- reason: "Reconnect this tool in Connections; credentials and executable settings are not transferred."
103
- });
104
- try {
105
- const skills = await service.importSkills(scoped, resolveImportTarget(projectId));
106
- result.skills += skills.imported;
107
- result.skipped += skills.skipped;
108
- result.issues.push(...skills.issues);
109
- } catch (error) {
110
- result.issues.push({
111
- name: projectId ? "Project skills" : "Skills",
112
- reason: error instanceof ImportError ? error.message : "Unable to import skills. Try again."
113
- });
114
- }
222
+ status: "pending"
223
+ }))
115
224
  };
116
- await importScope(scan);
117
- const projects = new ProjectService();
118
- const seen = /* @__PURE__ */ new Set();
119
- for (const path of scan.projectRoots ?? []) try {
120
- if (!isAbsolute(path) || parse(path).root === path || !statSync(path).isDirectory()) throw new Error("Invalid project");
121
- const workspaceRoot = realpathSync(path);
122
- if (seen.has(workspaceRoot)) continue;
123
- seen.add(workspaceRoot);
124
- let project = projects.findByWorkspaceRoot(workspaceRoot);
125
- if (!project) {
126
- project = projects.create({ workspaceRoot });
127
- result.projects++;
128
- } else result.skipped++;
129
- await importScope(service.scan({
130
- source,
131
- projectRoot: workspaceRoot,
132
- projectOnly: true,
133
- budget
134
- }), project.id);
135
- } catch {
136
- result.issues.push({
137
- name: path,
138
- reason: "Project unavailable; existing content was kept."
139
- });
225
+ repo.save("run", run);
226
+ return execute(service, repo, inventory, run);
227
+ });
228
+ }
229
+ async function recoverSelectionRuns(service, owner) {
230
+ await withImportLock(async () => {
231
+ const repo = new ImportSelectionRepository(owner);
232
+ for (const run of repo.list("run").filter((r) => r.status === "running")) await execute(service, repo, repo.get("inventory", run.inventoryId), run);
233
+ const referenced = new Set(repo.list("run").map((r) => r.inventoryId));
234
+ for (const inventory of repo.list("inventory")) if (!referenced.has(inventory.id) && inventory.expiresAt < Date.now() || inventory.createdAt < Date.now() - 744 * 60 * 6e4) {
235
+ for (const scanId of inventory.scanIds) service.discardUnusedScan(scanId);
236
+ repo.deleteInventory(inventory.id);
140
237
  }
141
- repo.save("run", result);
142
- return result;
143
- } finally {
144
- await release();
145
- }
238
+ });
146
239
  }
147
240
  //#endregion
148
- export { importProduct, listImportSources };
241
+ export { importSelection, listImportSources, recoverSelectionRuns };
@@ -8,6 +8,7 @@ import { ImportError } from "./types.js";
8
8
  import { ImportRepository } from "../storage/sqlite/import-repository.js";
9
9
  import { canonicalTarget } from "./planner.js";
10
10
  import { ImportService } from "./importService.js";
11
+ import { ImportSelectionRepository } from "../storage/sqlite/import-selection-repository.js";
11
12
  import { existsSync, realpathSync } from "node:fs";
12
13
  //#region src/imports/runtime.ts
13
14
  init_paths();
@@ -25,9 +26,11 @@ function createRuntimeImportService(owner, refreshSkills, checkTrust) {
25
26
  owner,
26
27
  stateDir: resolveStateDir(),
27
28
  refreshSkills,
28
- isConnected(candidate) {
29
+ isWorkspaceTrusted: (root) => checkTrust ? checkTrust(root) : new ProjectTrustStore().get(root) === true,
30
+ isConnected(candidate, sourceWorkspace) {
29
31
  if (!candidate.shared) return false;
30
- const workspaceDir = (candidate.scope === "project" ? new ProjectService().resolveForWorkspacePath(candidate.location)?.project : void 0)?.workspaceRoot;
32
+ const project = candidate.scope === "project" ? new ProjectService().resolveForWorkspacePath(candidate.location)?.project : void 0;
33
+ const workspaceDir = candidate.scope === "project" ? sourceWorkspace ?? project?.workspaceRoot : void 0;
31
34
  return loadSkills({
32
35
  workspaceDir,
33
36
  workspaceTrust: workspaceDir && (checkTrust ? checkTrust(workspaceDir) : new ProjectTrustStore().get(workspaceDir) === true) ? "trusted" : "untrusted"
@@ -51,6 +54,8 @@ function createRuntimeImportService(owner, refreshSkills, checkTrust) {
51
54
  }
52
55
  async function recoverCapabilityImports() {
53
56
  for (const owner of ImportRepository.owners()) await createRuntimeImportService(owner).recoverAndClean();
57
+ const { recoverSelectionRuns } = await import("./productImport.js");
58
+ for (const owner of ImportSelectionRepository.owners()) await recoverSelectionRuns(createRuntimeImportService(owner), owner);
54
59
  }
55
60
  //#endregion
56
61
  export { createRuntimeImportService, recoverCapabilityImports, resolveImportTarget };
@@ -11,4 +11,4 @@ export interface ScanInput {
11
11
  files: number;
12
12
  };
13
13
  }
14
- export declare function scanLocal(input: ScanInput): ImportScan;
14
+ export declare function scanLocal(input: ScanInput): Promise<ImportScan>;
@@ -6,6 +6,7 @@ import { IMPORT_SOURCES, sourceLayout } from "./sources.js";
6
6
  import { basename, join } from "node:path";
7
7
  import { existsSync, readdirSync, realpathSync } from "node:fs";
8
8
  import { randomUUID } from "node:crypto";
9
+ import { setImmediate } from "node:timers/promises";
9
10
  import { parse as parse$1 } from "smol-toml";
10
11
  //#region src/imports/scanner.ts
11
12
  function scanResult(source) {
@@ -31,7 +32,7 @@ function appendMcp(scan, raw, scope, location) {
31
32
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
32
33
  for (const [name, config] of Object.entries(raw)) scan.candidates.push(inspectMcp(base(scan.source, scope, location), name, config));
33
34
  }
34
- function scanLocal(input) {
35
+ async function scanLocal(input) {
35
36
  const scan = scanResult(input.source);
36
37
  const { home, root, layout } = sourceLayout(input.source, input.home, input.root);
37
38
  const scopes = (input.projectOnly ? [] : layout.skills).map((s) => ({
@@ -64,6 +65,7 @@ function scanLocal(input) {
64
65
  continue;
65
66
  }
66
67
  for (const entry of entries) {
68
+ await setImmediate();
67
69
  if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
68
70
  const location = join(scope.path, entry.name);
69
71
  if (!existsSync(join(location, "SKILL.md"))) continue;
@@ -96,9 +98,7 @@ function scanLocal(input) {
96
98
  if (!input.projectOnly || config.scope === "project") appendMcp(scan, parsed[layout.config.mcpKey], config.scope, config.path);
97
99
  const projects = parsed.projects;
98
100
  if (config.scope === "user" && projects && typeof projects === "object" && !Array.isArray(projects)) {
99
- const paths = Object.keys(projects);
100
- scan.projectRoots = paths.slice(0, 100);
101
- if (paths.length > 100) scan.diagnostics.push("Only the first 100 projects were imported.");
101
+ scan.projectRoots = Object.keys(projects);
102
102
  if (input.projectRoot) {
103
103
  const project = projects[input.projectRoot];
104
104
  appendMcp(scan, project?.[layout.config.mcpKey], "project", config.path);