@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.
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{activity-store-CwbIMGLY.js → activity-store-B0BTIk9j.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-DjiOld-S.js → agent-avatar-dicebear-Dc-0QZTQ.js} +1 -1
- package/dist/gateway/static/root/assets/agent-defaults-settings-panel-CcmqK1Sc.js +1 -0
- package/dist/gateway/static/root/assets/agents-B_7PVt7S.js +1 -0
- package/dist/gateway/static/root/assets/api-COG_Tzpv.js +1 -0
- package/dist/gateway/static/root/assets/{api-3lphBVuY.js → api-Coly9ab6.js} +1 -1
- package/dist/gateway/static/root/assets/{app-management-settings-panel-B2nwCXxE.js → app-management-settings-panel-TWBO-msg.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-BXO4Ej88.js → appearance-settings-CpTZ2KuT.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-6qxxKFsK.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-6ZRqNjla.js → archive-plugin-z_u6alob.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-CmPZalcW.js → attachment-load-C2nS04TC.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-Bc3WbK5n.js → automations-page-aIBsw9zR.js} +1 -1
- package/dist/gateway/static/root/assets/automations-workspace-BRQlU4qk.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-OWR6cxpc.js → binary-plugins-CGmXoZnl.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-Ca-tDQXm.js → block-editor-CtvzCZhC.js} +3 -3
- package/dist/gateway/static/root/assets/{browser-automation-inputs-Dxy2_p3U.js → browser-automation-inputs-BOTU1isH.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-automations-page-CibQIxpi.js → browser-automations-page-BwU5V8J0.js} +2 -2
- package/dist/gateway/static/root/assets/browser-settings-page-DDjbDeHQ.js +8 -0
- package/dist/gateway/static/root/assets/capabilities-settings-panel-DM1Bcf1x.js +8 -0
- package/dist/gateway/static/root/assets/channels-settings-Bq36CUxL.js +1 -0
- package/dist/gateway/static/root/assets/{channels-status-swr-Bh-G9mdZ.js → channels-status-swr-Dfi0Desh.js} +1 -1
- package/dist/gateway/static/root/assets/{chat-terminal-dock-lbzbSpV3.js → chat-terminal-dock-B0MvcO6j.js} +2 -2
- package/dist/gateway/static/root/assets/connectors-page-L8JehrxF.js +2 -0
- package/dist/gateway/static/root/assets/{date-picker-tYvbTfZT.js → date-picker-DXOvbHCr.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-B8yo7-7V.js → dependency-picker-BEnC-eS0.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-BUGkGB4-.js → desktop-pet-DtPNvznL.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-DNeVGZNp.js → desktop-pet-settings-CYyjcSn6.js} +1 -1
- package/dist/gateway/static/root/assets/device-pairing-wizard-BPMGvAKE.js +1 -0
- package/dist/gateway/static/root/assets/{extension-debug-page-CiK0QI2p.js → extension-debug-page-H_aPMGXW.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-CirPNHJd.js → extension-page-C1JnpeMt.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-Baua1_Le.js → extension-settings-page-C75kCOqe.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-swr-Co4hHe0i.js → gateway-config-swr-G2lQt7Ak.js} +1 -1
- package/dist/gateway/static/root/assets/gateway-settings-BCF732TU.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-startup-retry-DBPNJG-e.js → gateway-startup-retry-GP31PUrG.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-DTM_kYpE.js → heartbeat-settings-C7fjiowt.js} +1 -1
- package/dist/gateway/static/root/assets/home-page-DZXeXdUL.js +1 -0
- package/dist/gateway/static/root/assets/{image-generation-api-D0rMTumD.js → image-generation-api-CKFhfHSw.js} +1 -1
- package/dist/gateway/static/root/assets/imports-page-GROumhj8.js +1 -0
- package/dist/gateway/static/root/assets/{index-C9aQA4Dr.js → index-ChIrvv3t.js} +47 -47
- package/dist/gateway/static/root/assets/index-R1VNlHjj.css +2 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-CMUaF61L.js → keyboard-shortcuts-settings-zcI822m_.js} +1 -1
- package/dist/gateway/static/root/assets/local-app-workbench-page-CAEKGTAg.js +2 -0
- package/dist/gateway/static/root/assets/{local-apps-page-CHxyT09t.js → local-apps-page-BtgDJ92Q.js} +1 -1
- package/dist/gateway/static/root/assets/{locale-store-DO6JJ1l2.js → locale-store-DlOv7896.js} +1 -1
- package/dist/gateway/static/root/assets/logs-page-DHZ4BEkG.js +2 -0
- package/dist/gateway/static/root/assets/{management-settings-jXbA2S54.js → management-settings-DasZxCyD.js} +1 -1
- package/dist/gateway/static/root/assets/{markdown-view-CKInRsDh.js → markdown-view-6oYdBahT.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-BaPkSvW7.js → media-plugins-CxthbBvk.js} +1 -1
- package/dist/gateway/static/root/assets/{messages-BgZRsCkG.js → messages-Ls1Aaiqd.js} +1 -1
- package/dist/gateway/static/root/assets/note-detail-panel-BcWq3ddw.js +5 -0
- package/dist/gateway/static/root/assets/notes-page-DfZ8QWyv.js +8 -0
- package/dist/gateway/static/root/assets/{page-tabs-CdYemUOu.js → page-tabs-BG39KidE.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-DzgF1yYO.js → preview-open-alternatives-Bevpbq9f.js} +1 -1
- package/dist/gateway/static/root/assets/proactive-page-D2U_xQaS.js +1 -0
- package/dist/gateway/static/root/assets/{product-open-page-iQ-eW6Aj.js → product-open-page-Bi1seZpM.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-CzlGTW4F.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-mQsM-TsP.js +1 -0
- package/dist/gateway/static/root/assets/{registry-api-DMNudT60.js → registry-api-D0ddwBFj.js} +1 -1
- package/dist/gateway/static/root/assets/remote-access-hub-Cb56JcO1.js +1 -0
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-u_wVepf7.js → runtime-tools-settings-panel-Wkru8_My.js} +2 -2
- package/dist/gateway/static/root/assets/{schema-form-YdZj2xdI.js → schema-form-CSMRzj9Y.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-Hj9uFcIO.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-CV4-_EVA.js +1 -0
- package/dist/gateway/static/root/assets/{settings-form-section-Bw6xKOX5.js → settings-form-section-wcAdCkK8.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-CV8kVdnE.js → settings-page-BcQGAnCs.js} +1 -1
- package/dist/gateway/static/root/assets/setup-status-panel-DzxmsaQG.js +1 -0
- package/dist/gateway/static/root/assets/{share-preview-page-BHwjHlrV.js → share-preview-page-DQocUl-1.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-xlJh1ccG.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-wSXJRSxX.js +1 -0
- package/dist/gateway/static/root/assets/{skills-page-DRKdb6O5.js → skills-page-AmqtBJo7.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page.utils-DYfaLtts.js → skills-page.utils-DtlfCgJp.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-CWrWpKV-.js → system-settings-panel-Ct698Nf-.js} +1 -1
- package/dist/gateway/static/root/assets/task-detail-page-sQpwq5-c.js +1 -0
- package/dist/gateway/static/root/assets/{theme-store-CDc7Wqy5.js → theme-store-ng8KJsAT.js} +1 -1
- package/dist/gateway/static/root/assets/{tunnel-api-Bmsy-5Fs.js → tunnel-api-Boajol0M.js} +1 -1
- package/dist/gateway/static/root/assets/{use-autosave-D-FRib85.js → use-autosave-BSyxRHt2.js} +1 -1
- package/dist/gateway/static/root/assets/user-model-page-DIEHnAER.js +2 -0
- package/dist/gateway/static/root/assets/{voice-api-key-field-iQ7EBzII.js → voice-api-key-field-SE3OQI1s.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-config-api-DNMWA2tx.js → voice-config-api-VNuCmdFe.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-BesuhDlH.js → work-discovery-overlay-Dn1vy2oq.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-Du81FoCy.js → workflow-run-setup-panel-BJEfGY-k.js} +2 -2
- package/dist/gateway/static/root/assets/workflows-page-DgRNIyr7.js +6 -0
- package/dist/gateway/static/root/index.html +10 -10
- package/dist/package.js +1 -1
- package/dist/packages/gateway-contract/src/proactive.js +13 -4
- package/dist/src/agent/coding/repository-instructions.js +1 -1
- package/dist/src/agent/coding/workspace-revision.js +1 -1
- package/dist/src/agent/commands/command-isolation.js +1 -1
- package/dist/src/agent/tools/factory.js +7 -3
- package/dist/src/agent/tools/review-workspace.js +1 -1
- package/dist/src/browser/providers/browser-ext-install.js +10 -2
- package/dist/src/cli/command-manifest.js +2 -2
- package/dist/src/cli/commands/agent.js +2 -2
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/cli/commands/gateway/lifecycle.js +2 -2
- package/dist/src/cli/commands/gateway/run-foreground.js +3 -0
- package/dist/src/cli/commands/import.js +54 -6
- package/dist/src/cli/commands/models.js +1 -1
- package/dist/src/cli/commands/onboard.js +1 -1
- package/dist/src/cli/commands/platform.js +1 -1
- package/dist/src/cli/commands/profile.js +1 -1
- package/dist/src/cli/commands/tunnel.js +1 -1
- package/dist/src/cli/commands/update.js +1 -1
- package/dist/src/cli/extension-cli-register.js +1 -1
- package/dist/src/config/index.js +1 -1
- package/dist/src/discussions/action-tasks.js +1 -1
- package/dist/src/discussions/edits.js +1 -1
- package/dist/src/discussions/index.js +1 -1
- package/dist/src/discussions/organizer.js +1 -1
- package/dist/src/discussions/recording.js +1 -1
- package/dist/src/discussions/sealer.js +1 -1
- package/dist/src/discussions/service.js +2 -2
- package/dist/src/endpoint-tools/invocation-service.d.ts +2 -0
- package/dist/src/endpoint-tools/invocation-service.js +48 -19
- package/dist/src/gateway/heartbeat/check-store.d.ts +18 -0
- package/dist/src/gateway/heartbeat/check-store.js +73 -0
- package/dist/src/gateway/heartbeat/service.d.ts +12 -3
- package/dist/src/gateway/heartbeat/service.js +62 -45
- package/dist/src/gateway/hono/routes/discussions.js +2 -2
- package/dist/src/gateway/hono/routes/imports.js +18 -6
- package/dist/src/gateway/hono/routes/proactive-control.js +2 -1
- package/dist/src/gateway/run-loop.js +1 -1
- package/dist/src/gateway/service.d.ts +7 -0
- package/dist/src/gateway/service.js +24 -9
- package/dist/src/heartbeat/index.js +1 -1
- package/dist/src/imports/importService.d.ts +17 -12
- package/dist/src/imports/importService.js +67 -70
- package/dist/src/imports/inventory.d.ts +11 -0
- package/dist/src/imports/inventory.js +202 -0
- package/dist/src/imports/productImport.d.ts +4 -4
- package/dist/src/imports/productImport.js +201 -108
- package/dist/src/imports/runtime.js +7 -2
- package/dist/src/imports/scanner.d.ts +1 -1
- package/dist/src/imports/scanner.js +4 -4
- package/dist/src/imports/types.d.ts +53 -0
- package/dist/src/infra/background-task.d.ts +2 -0
- package/dist/src/infra/background-task.js +25 -0
- package/dist/src/infra/process-diagnostics.d.ts +5 -0
- package/dist/src/infra/process-diagnostics.js +97 -0
- package/dist/src/infra/unhandled-rejections.js +6 -3
- package/dist/src/infra/update-restart.js +1 -1
- package/dist/src/knowledge/ingestion-service.js +1 -0
- package/dist/src/media-shared/http/ssrf-guard.js +1 -1
- package/dist/src/notifications/proactive-policy.js +7 -5
- package/dist/src/proactive/execution/authorization.d.ts +2 -0
- package/dist/src/proactive/execution/authorization.js +10 -1
- package/dist/src/proactive/execution/context.js +1 -0
- package/dist/src/proactive/execution/repository.d.ts +2 -0
- package/dist/src/proactive/execution/repository.js +19 -2
- package/dist/src/proactive/execution/worker.js +14 -4
- package/dist/src/proactive/experience.d.ts +4 -1
- package/dist/src/proactive/experience.js +3 -0
- package/dist/src/proactive/follow-ups.d.ts +5 -0
- package/dist/src/proactive/follow-ups.js +18 -7
- package/dist/src/proactive/inbox/cards.js +2 -1
- package/dist/src/proactive/inbox/delivery.js +6 -7
- package/dist/src/proactive/inbox/digest.d.ts +1 -1
- package/dist/src/proactive/inbox/digest.js +23 -13
- package/dist/src/proactive/inbox/notification-policy.d.ts +1 -1
- package/dist/src/proactive/inbox/notification-policy.js +9 -6
- package/dist/src/proactive/inbox/repository.js +3 -2
- package/dist/src/proactive/inbox/service.d.ts +6 -0
- package/dist/src/proactive/inbox/service.js +18 -0
- package/dist/src/proactive/inbox/types.d.ts +1 -0
- package/dist/src/proactive/policy/presence.d.ts +1 -1
- package/dist/src/proactive/policy/presence.js +7 -5
- package/dist/src/proactive/policy/service.d.ts +8 -5
- package/dist/src/proactive/policy/service.js +12 -12
- package/dist/src/proactive/scenarios/control.d.ts +3 -3
- package/dist/src/proactive/scenarios/control.js +12 -2
- package/dist/src/proactive/source-freshness.d.ts +8 -0
- package/dist/src/proactive/source-freshness.js +27 -0
- package/dist/src/proactive/status.d.ts +2 -0
- package/dist/src/proactive/status.js +21 -0
- package/dist/src/proactive/temporal/worker.js +2 -1
- package/dist/src/realtime/runtime.js +31 -5
- package/dist/src/realtime/writer.js +16 -9
- package/dist/src/storage/sqlite/import-repository.d.ts +2 -3
- package/dist/src/storage/sqlite/import-repository.js +3 -3
- package/dist/src/storage/sqlite/import-selection-repository.d.ts +15 -0
- package/dist/src/storage/sqlite/import-selection-repository.js +28 -0
- package/dist/src/storage/sqlite/knowledge-repository.d.ts +1 -0
- package/dist/src/storage/sqlite/knowledge-repository.js +2 -2
- package/dist/src/storage/sqlite/migrations/175_import_selections.sql +8 -0
- package/dist/src/storage/sqlite/migrations/176_proactive_quality.sql +5 -0
- package/dist/src/storage/sqlite/migrations/177_proactive_check_controls.sql +25 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +3 -3
- package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/work-discovery/candidate-discovery.js +1 -1
- package/dist/src/work-discovery/probe.js +1 -1
- package/package.json +2 -2
- package/dist/gateway/static/root/assets/agent-defaults-settings-panel-D9qP10BK.js +0 -1
- package/dist/gateway/static/root/assets/agents-D-bkOuAl.js +0 -1
- package/dist/gateway/static/root/assets/apps-page-BKwBBwJN.js +0 -1
- package/dist/gateway/static/root/assets/automations-workspace-B2-f9qZu.js +0 -6
- package/dist/gateway/static/root/assets/browser-settings-page-Dtn4rMeq.js +0 -8
- package/dist/gateway/static/root/assets/capabilities-settings-panel-DiwtR9MD.js +0 -8
- package/dist/gateway/static/root/assets/channels-settings-pTdR1q1K.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-BSQ7Hs2w.js +0 -2
- package/dist/gateway/static/root/assets/device-pairing-wizard-CYkT7ecp.js +0 -1
- package/dist/gateway/static/root/assets/gateway-settings-CzpSYWex.js +0 -1
- package/dist/gateway/static/root/assets/home-page-CSSJvdd6.js +0 -1
- package/dist/gateway/static/root/assets/imports-page-XqVVYbAp.js +0 -1
- package/dist/gateway/static/root/assets/index-B_Sg5BKz.css +0 -2
- package/dist/gateway/static/root/assets/local-app-workbench-page-CZSgTYuI.js +0 -2
- package/dist/gateway/static/root/assets/logs-page-z5ktnEzX.js +0 -2
- package/dist/gateway/static/root/assets/note-detail-panel-DSaZfkna.js +0 -5
- package/dist/gateway/static/root/assets/notes-page-CePEyv_C.js +0 -8
- package/dist/gateway/static/root/assets/proactive-page-Cy2Gm2f8.js +0 -1
- package/dist/gateway/static/root/assets/project-detail-page-BliwYpns.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-Dm-BqnQn.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-C_XuQ9XR.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-Drt1aS2G.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-BAUd06_9.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-BHI9e5Tw.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-DOAOjZBQ.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-DU0VQpHj.js +0 -1
- package/dist/gateway/static/root/assets/task-detail-page-DiPgMmBM.js +0 -1
- package/dist/gateway/static/root/assets/user-model-page-DxRaRdA0.js +0 -2
- 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
|
|
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
|
-
|
|
10
|
-
export declare function
|
|
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 {
|
|
8
|
+
import { canonicalTarget, targetInventory } from "./planner.js";
|
|
7
9
|
import { detectImportSources } from "./sources.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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(
|
|
163
|
+
const release = await lockfile.lock(path, {
|
|
30
164
|
realpath: false,
|
|
165
|
+
stale: 6e4,
|
|
31
166
|
retries: {
|
|
32
|
-
retries:
|
|
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
|
-
|
|
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 (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
142
|
-
return result;
|
|
143
|
-
} finally {
|
|
144
|
-
await release();
|
|
145
|
-
}
|
|
238
|
+
});
|
|
146
239
|
}
|
|
147
240
|
//#endregion
|
|
148
|
-
export {
|
|
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
|
-
|
|
29
|
+
isWorkspaceTrusted: (root) => checkTrust ? checkTrust(root) : new ProjectTrustStore().get(root) === true,
|
|
30
|
+
isConnected(candidate, sourceWorkspace) {
|
|
29
31
|
if (!candidate.shared) return false;
|
|
30
|
-
const
|
|
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 };
|
|
@@ -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
|
-
|
|
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);
|